====== std.hardware.usb ====== Der USB-Stack implementiert alle USB-Host-Operationen direkt via Linux ''usbdevfs''-ioctls — kein libusb, keine externen Abhängigkeiten. Alle Transfers (Control, Bulk, Interrupt, Isochronous) sind vollständig in Lyx implementiert. Import-Muster: Jede Sub-Unit wird einzeln importiert, je nach benötigtem Funktionsumfang: import std.hardware.usb_discovery; import std.hardware.usb_control; import std.hardware.usb_bulk; → [[lyx_-_programmiersprache:units:hardware|std.hardware]] · [[lyx_-_programmiersprache:units|Standard Library]] ---- ===== Architektur ===== Der Stack ist in 13 Sub-Units gegliedert, die aufeinander aufbauen: ^ Schicht ^ Sub-Unit ^ Beschreibung ^ | High-Level / Sicherheit | ''usb_endpoint_types'' | Typsichere Endpoint-Wrapper mit Richtungsvalidierung (Bulk OUT/IN, Interrupt IN) | | High-Level / Sicherheit | ''usb_endpoint_bind'' | Endpoint-Registry: Adressen einmalig binden, danach nur per Schlüssel nutzen | | Async-Infrastruktur | ''usb_urb_pool'' | URB-Pool (4 Slots) für überlappende Interrupt-Transfers | | Async-Infrastruktur | ''usb_ifc_mgr'' | Idempotenter Interface-Claim/Release-Manager | | Transfer-Schicht | ''usb_interrupt'' | Asynchrone Interrupt-Transfers: URB allokieren, einreichen, warten | | Transfer-Schicht | ''usb_iso'' | Isochronous Transfers (Audio/Video): ISO-URB mit Paket-Deskriptoren | | Transfer-Schicht | ''usb_bulk'' | Synchrone Bulk-Transfers (OUT/IN) | | Transfer-Schicht | ''usb_control'' | Control-Transfers + Interface-Claim/Release-IOCTLs | | Erkennung | ''usb_discovery'' | Geräteerkennung via ''/dev/bus/usb/'': VID/PID-Scan | | Descriptor-Parsing | ''usb_parse'' | Configuration-Descriptor-Strom → Config/Interface/Endpoint-Baum | | Typen & Accessoren | ''usb_types'' | Descriptor-Konstanten, Lyx-Structs, Accessor-Funktionen | | Hilfstypen | ''usb_util'' | Dirent64-Parser, String-Utilities, Pfad-Builder | | Grundschicht | ''usb_syscalls'' | open/read/write/ioctl/close/lseek/getdents64/poll-Wrapper | ---- ===== usb_syscalls ===== Rohe Linux-Syscall-Wrapper. Alle anderen Sub-Units bauen darauf auf. **Konstanten:** ^ Konstante ^ Wert ^ Bedeutung ^ | ''USB_O_RDONLY'' / ''USB_O_RDWR'' | 0 / 2 | open()-Flags | | ''USB_O_DIRECTORY'' | 65536 | Verzeichnis öffnen | | ''USB_SEEK_SET/CUR/END'' | 0 / 1 / 2 | lseek()-Whence | | ''USB_DT_DIR'' / ''USB_DT_REG'' | 4 / 8 | dirent-Typ | | ''USB_POLLIN'' | 1 | poll()-Event: Daten verfügbar | **Funktionen:** ^ Funktion ^ Parameter ^ Beschreibung ^ | ''usb_open(path, flags)'' | pchar, int64 | Datei oder Verzeichnis öffnen | | ''usb_read(fd, buf, len)'' | int64 × 3 | Bytes lesen | | ''usb_write(fd, buf, len)'' | int64 × 3 | Bytes schreiben | | ''usb_ioctl(fd, request, arg)'' | int64 × 3 | ioctl-Aufruf (usbdevfs) | | ''usb_close(fd)'' | int64 | fd schließen | | ''usb_lseek(fd, offset, whence)'' | int64 × 3 | Leseposition setzen | | ''usb_getdents64(fd, buf, count)'' | int64 × 3 | Verzeichniseinträge lesen | | ''usb_poll(fds_ptr, nfds, timeout_ms)'' | int64 × 3 | Auf fd-Ereignisse warten | | ''usb_pollfd_set(ptr, fd, events)'' | int64 × 3 | pollfd-Struct befüllen | | ''usb_pollfd_revents(ptr)'' | int64 | revents aus pollfd lesen | ---- ===== usb_util ===== Hilfstypen für Verzeichnis-Parsing und Pfad-Operationen. Wird intern von ''usb_discovery'' genutzt. ^ Funktion ^ Beschreibung ^ | ''Dirent64RecLen(entry)'' | Gesamtlänge des Dirent64-Eintrags (für Iteration) | | ''Dirent64Type(entry)'' | Dateityp (USB_DT_REG / USB_DT_DIR) | | ''Dirent64Name(entry)'' | Zeiger auf null-terminierten Dateinamen | | ''Dirent64Ino(entry)'' | Inode-Nummer | | ''UsbIsNumeric(ptr)'' | Gibt 1 wenn String nur Dezimalziffern enthält | | ''UsbParseNum(ptr)'' | Dekodiert Dezimal-String → int64 | | ''UsbBuildPath(base, segment, suffix, buf, buf_size)'' | Pfadsegmente zusammensetzen | | ''UsbStrLen(ptr)'' | Länge eines null-terminierten Strings (int64-Zeiger) | ---- ===== usb_types ===== Alle USB-Descriptor-Typen, Struct-Definitionen und Accessor-Funktionen. **Descriptor-Typ-Konstanten:** ^ Konstante ^ Wert ^ Bedeutung ^ | ''USB_DT_DEVICE'' | 1 | Device Descriptor | | ''USB_DT_CONFIG'' | 2 | Configuration Descriptor | | ''USB_DT_STRING'' | 3 | String Descriptor | | ''USB_DT_INTERFACE'' | 4 | Interface Descriptor | | ''USB_DT_ENDPOINT'' | 5 | Endpoint Descriptor | **Transfer-Typ und Richtung:** ^ Konstante ^ Wert ^ | ''USB_TRANS_CONTROL / ISO / BULK / INTERRUPT'' | 0 / 1 / 2 / 3 | | ''USB_EP_DIR_OUT / USB_EP_DIR_IN'' | 0 / 1 | **Struct-Größen:** ^ Konstante ^ Größe ^ | ''USB_SIZEOF_DEVICE_DESC'' | 18 Bytes (USB 2.0 §9.6.1) | | ''USB_SIZEOF_CONFIG_DESC'' | 9 Bytes | | ''USB_SIZEOF_INTERFACE_DESC'' | 9 Bytes | | ''USB_SIZEOF_ENDPOINT_DESC'' | 7 Bytes | | ''USB_SIZEOF_ENDPOINT'' | 48 Bytes (Lyx-Struct, 6 × int64) | | ''USB_SIZEOF_INTERFACE'' | 56 Bytes (7 × int64) | | ''USB_SIZEOF_DEVICE'' | 72 Bytes (9 × int64) | **Lyx-Typen:** type UsbEndpoint = struct { address: int64; direction: int64; number: int64; transfer_type: int64; max_packet: int64; interval: int64; }; type UsbInterface = struct { interface_number: int64; alternate_setting: int64; class_code: int64; subclass_code: int64; protocol_code: int64; num_endpoints: int64; endpoints: int64; // Zeiger auf alloc'd Array }; type UsbDevice = struct { vendor_id: int64; product_id: int64; device_class: int64; usb_version: int64; bus_num: int64; dev_num: int64; handle: int64; // fd — bleibt offen num_configs: int64; configs: int64; }; **Accessor-Funktionen** (Auswahl): ^ Funktion ^ Beschreibung ^ | ''UsbDevDesc_idVendor(buf)'' | Vendor-ID aus Raw-Buffer | | ''UsbDevDesc_idProduct(buf)'' | Product-ID | | ''UsbDevDesc_bNumConfigurations(buf)'' | Anzahl Konfigurationen | | ''UsbEpDesc_bEndpointAddress(buf)'' | Endpoint-Adresse (Bit 7 = Richtung) | | ''UsbEpDesc_bmAttributes(buf)'' | Transfer-Typ (Bits 1-0) | | ''UsbEpDirection(epAddr)'' | USB_EP_DIR_OUT oder USB_EP_DIR_IN | | ''UsbEpNumber(epAddr)'' | Endpoint-Nummer (Bits 0-3) | | ''UsbEndpointAt(base, i)'' | Zeiger auf UsbEndpoint[i] im Array | | ''UsbInterfaceAt(base, i)'' | Zeiger auf UsbInterface[i] | | ''UsbEndpointFromDesc(ep, epDescBuf)'' | Befüllt UsbEndpoint-Struct aus Raw-Descriptor | ---- ===== usb_parse ===== Liest den vollständigen Descriptor-Strom von einem geöffneten USB-fd und baut daraus einen vollständigen Config/Interface/Endpoint-Baum. ^ Funktion ^ Beschreibung ^ | ''UsbAllocDevice()'' | Allokiert und nullt UsbDevice-Struct (72 Bytes) | | ''UsbParseConfiguration(dev_fd, devicePtr)'' | Parst Descriptor-Strom → Config/Iface/EP-Baum. Rückgabe: 1=Erfolg, 0=Fehler | Hinweis: ''UsbParseConfiguration'' setzt den Dateizeiger via ''lseek'' auf 0 zurück (Kernel-fd ist seekbar). Class-spezifische Descriptoren (HID-Report etc.) werden stillschweigend übersprungen. ---- ===== usb_discovery ===== Scannt ''/dev/bus/usb/'' nach einem Gerät mit gegebener VID/PID. Öffnet das gefundene Gerät und parst automatisch seine Konfiguration. ^ Konstante ^ Bedeutung ^ | ''USB_FIND_OK'' (0) | Gerät gefunden | | ''USB_FIND_NOTFOUND'' (-1) | Nicht gefunden | ^ Funktion ^ Beschreibung ^ | ''UsbFindDevice(vendor, product, devicePtr)'' | Sucht in ''/dev/bus/usb/'' | | ''UsbFindDevicePath(busRoot, vendor, product, devicePtr)'' | Sucht mit konfigurierbarem Bus-Root | Verwendung: import std.hardware.usb_discovery; import std.hardware.usb_parse; var dev: int64 := UsbAllocDevice(); if (UsbFindDevice(0x046D, 0xC52B, dev) == USB_FIND_OK) { // dev.handle ist geöffnet, Konfiguration geparst } ---- ===== usb_control ===== Control-Transfers und Interface-Claim/Release via usbdevfs-ioctls. **IOCTL-Codes:** ^ Konstante ^ Wert ^ Bedeutung ^ | ''USBDEVFS_CONTROL'' | 0xC0185500 | Control-Transfer | | ''USBDEVFS_CLAIMINTERFACE'' | 0x4004550F | Interface beanspruchen | | ''USBDEVFS_RELEASEINTERFACE'' | 0x40045510 | Interface freigeben | **Request-Typen (USB 2.0 §9.4):** ^ Konstante ^ Bedeutung ^ | ''USB_DIR_IN'' / ''USB_DIR_OUT'' | 0x80 / 0x00 — Transfer-Richtung | | ''USB_REQ_GET_DESCRIPTOR'' | 6 | | ''USB_REQ_SET_CONFIGURATION'' | 9 | | ''USB_TIMEOUT_DEFAULT'' / ''USB_TIMEOUT_SHORT'' | 5000 / 1000 ms | **Funktionen:** ^ Funktion ^ Beschreibung ^ | ''UsbCtrlSetRequest(buf, reqType, req, wValue, wIndex)'' | Request-Header (Bytes 0–7) befüllen | | ''UsbCtrlSetTransfer(buf, data, length, timeout)'' | Länge/Timeout/Datenzeiger (Bytes 6–23) | | ''UsbControlTransferRaw(dev_fd, ctrlBuf)'' | Fertigen ctrlBuf via ioctl senden | | ''UsbGetDeviceDescriptor(dev_fd, descBuf)'' | Device Descriptor lesen (18 Bytes) | | ''UsbClaimInterface(dev_fd, interface_num)'' | Interface für exklusiven Zugriff beanspruchen | | ''UsbReleaseInterface(dev_fd, interface_num)'' | Interface freigeben | ---- ===== usb_bulk ===== Synchrone Bulk-Transfers via ''USBDEVFS_BULK'' (0x40185502). ^ Funktion ^ Beschreibung ^ | ''UsbBulkWrite(dev_fd, endpoint, data, length, timeout)'' | Daten an OUT-Endpoint senden (Bit 7 = 0) | | ''UsbBulkRead(dev_fd, endpoint, buffer, length, timeout)'' | Daten von IN-Endpoint empfangen (Bit 7 = 1) | | ''UsbBulkTransfer(dev_fd, ep, data, length, timeout)'' | Niedrig-Level (OUT und IN) | | ''UsbBuildBulkTransfer(buf, ep, data, length, timeout)'' | usbdevfs_bulktransfer-Struct befüllen | Rückgabe: Übertragene Bytes (≥ 0) oder negativer Fehlercode (z.B. ''-ETIMEDOUT'', ''-ENODEV''). ---- ===== usb_interrupt ===== Asynchrone Interrupt-Transfers via URBs (Kernel-interne Puffer). Ablauf: allokieren → befüllen → einreichen → warten → auswerten → erneut einreichen. **IOCTL-Codes:** ^ Konstante ^ Wert ^ Bedeutung ^ | ''USBDEVFS_SUBMITURB'' | 0x4038550A | URB einreichen | | ''USBDEVFS_DISCARDURB'' | 0x0000550B | URB abbrechen | | ''USBDEVFS_REAPURB'' | 0x4008550C | Blockierend warten | | ''USBDEVFS_REAPURBNDELAY'' | 0x4008550D | Nicht-blockierend abholen | **URB-Struct** (56 Bytes, Feld-Offsets als Konstanten ''URB_OFF_*''): ^ Offset ^ Feld ^ Größe ^ | +0 | type (USBDEVFS_URB_TYPE_*) | uint8 | | +1 | endpoint (Bit 7: Richtung) | uint8 | | +4 | status (0 = OK, vom Kernel gesetzt) | int32 | | +16 | buffer | void * (8 Bytes) | | +24 / +28 | buffer_length / actual_length | int32 | | +44 | signr (uint32, 0 = kein Signal) | uint32 | | +48 | usercontext | void * (8 Bytes) | **Funktionen:** ^ Funktion ^ Beschreibung ^ | ''UsbAllocUrb()'' | Allokiert + nullt URB-Struct (Heap, 56 Bytes) | | ''UsbFreeUrb(urbPtr)'' | Gibt URB-Heap frei | | ''UsbInitInterruptRead(urbPtr, endpoint, buffer, length, context)'' | Interrupt-IN-URB befüllen | | ''UsbSubmitUrb(dev_fd, urbPtr)'' | URB beim Kernel einreichen (kehrt sofort zurück) | | ''UsbSubmitInterruptRead(dev_fd, endpoint, buffer, length, context)'' | Allokiert + befüllt + reicht ein | | ''UsbWaitForUrb(dev_fd, timeout_ms)'' | poll + REAPURBNDELAY — gibt URB-Zeiger zurück | | ''UsbDiscardUrb(dev_fd, urbPtr)'' | Laufenden URB abbrechen | | ''UsbUrbStatus(urbPtr)'' | Fehlercode nach Abschluss (0 = OK) | | ''UsbUrbActualLength(urbPtr)'' | Tatsächlich empfangene Bytes | | ''UsbUrbContext(urbPtr)'' | Usercontext-Zeiger (wie beim Submit übergeben) | Verwendung (einfacher HID-Empfang): import std.hardware.usb_interrupt; var buf: int64 := alloc(64); var urb: int64 := UsbSubmitInterruptRead(dev_fd, 0x81, buf, 64, 0); var done: int64 := UsbWaitForUrb(dev_fd, 1000); if (done != 0 && UsbUrbStatus(done) == 0) { var n: int64 := UsbUrbActualLength(done); // buf enthält n Bytes HID-Daten UsbFreeUrb(urb); } ---- ===== usb_endpoint_types ===== Typsichere Endpoint-Wrapper: ''UsbBulkOutEndpoint'' hat **nur** Write-Operationen, ''UsbBulkInEndpoint'' **nur** Read. Falsche Richtung erzeugt einen Sentinel-fd (dev_fd=-1) → alle Ops geben -1. **Typen:** ''UsbBulkOutEndpoint'', ''UsbBulkInEndpoint'', ''UsbInterruptInEndpoint'' ^ Funktion ^ Beschreibung ^ | ''UsbMakeBulkOut(dev_fd, ep, timeout)'' | Erstellt OUT-Wrapper (ep muss Bit 7 = 0 haben) | | ''UsbMakeBulkIn(dev_fd, ep, timeout)'' | Erstellt IN-Wrapper (ep muss Bit 7 = 1 haben) | | ''UsbMakeInterruptIn(dev_fd, ep, bufSize, timeout)'' | Interrupt-IN-Wrapper | | ''UsbBulkOutWrite(ep, data, length)'' | Sendet via OUT-Endpoint | | ''UsbBulkInRead(ep, buffer, length)'' | Empfängt vom IN-Endpoint | | ''UsbInterruptInSubmit(ep, buffer, context)'' | URB einreichen | | ''UsbInterruptInWait(ep, timeout_ms)'' | Auf URB-Abschluss warten | | ''UsbBulkOutValid(ep)'' / ''UsbBulkInValid(ep)'' | 1 wenn Endpoint gültig (kein Sentinel) | ---- ===== usb_endpoint_bind ===== Endpoint-Registry: Adressen einmalig unter einem Schlüssel registrieren, danach nur noch per Schlüssel nutzen — keine Adresse beim Aufruf nötig. ^ Funktion ^ Beschreibung ^ | ''UsbEpRegistryAlloc()'' | Neue Registry anlegen (max. 8 Slots) | | ''UsbEpRegistryFree(reg)'' | Registry freigeben | | ''UsbDefOutEndpoint(reg, key, fd, ep, timeout)'' | OUT-Endpoint unter Schlüssel registrieren | | ''UsbDefInEndpoint(reg, key, fd, ep, timeout)'' | IN-Endpoint registrieren | | ''UsbEpWrite(reg, key, data, len)'' | Senden über registrierten OUT-Endpoint | | ''UsbEpRead(reg, key, buf, len)'' | Empfangen vom registrierten IN-Endpoint | | ''UsbEpAddr(reg, key)'' | EP-Adresse eines Slots lesen | | ''UsbEpIsOut(reg, key)'' | 1 = OUT, 0 = IN, -1 = nicht gefunden | import std.hardware.usb_endpoint_bind; con EP_DATA_OUT: int64 := 1; con EP_DATA_IN: int64 := 2; var reg: int64 := UsbEpRegistryAlloc(); UsbDefOutEndpoint(reg, EP_DATA_OUT, dev_fd, 0x01, 1000); UsbDefInEndpoint(reg, EP_DATA_IN, dev_fd, 0x81, 1000); UsbEpWrite(reg, EP_DATA_OUT, dataPtr, 64); UsbEpRead(reg, EP_DATA_IN, recvBuf, 64); ---- ===== usb_iso ===== Isochronous Transfers für Audio/Video. ISO-URBs enthalten N angehängte Paket-Deskriptoren (''usbdevfs_iso_packet_desc'', je 12 Bytes) direkt hinter dem Basis-URB (Offset 56). ^ Funktion ^ Beschreibung ^ | ''UsbAllocIsoUrb(num_packets)'' | ISO-URB allokieren (56 + n × 12 Bytes) | | ''UsbFreeIsoUrb(urbPtr, num_packets)'' | Freigeben (Größe muss stimmen) | | ''UsbInitIsoRead(urbPtr, endpoint, buffer, packet_size, num_packets)'' | ISO-URB befüllen | | ''UsbSubmitIsoUrb(dev_fd, urbPtr)'' | ISO-URB einreichen | | ''UsbSubmitIsoRead(dev_fd, ep, buf, pkt_size, num_pkts)'' | Allokiert + init + submit | | ''UsbReapIsoUrb(dev_fd)'' | Blockierend warten (USBDEVFS_REAPURB) | | ''UsbReapIsoUrbNd(dev_fd)'' | Nicht-blockierend (USBDEVFS_REAPURBNDELAY) | | ''UsbIsoPacketActualLen(urbPtr, i)'' | Tatsächliche Bytes von Paket i | | ''UsbIsoPacketStatus(urbPtr, i)'' | Status von Paket i (0 = OK) | | ''UsbIsoErrorCount(urbPtr)'' | Gesamt-Fehleranzahl aller Pakete | ---- ===== usb_ifc_mgr ===== Idempotenter Claim/Release-Zustandsautomat: doppeltes Claim ist No-op, Release ohne vorheriges Claim ebenfalls. ^ Funktion ^ Beschreibung ^ | ''UsbIfcMgrAlloc(dev_fd, ifc_num)'' | Neuen Manager anlegen (24 Bytes, unclaimed) | | ''UsbIfcMgrFree(mgrPtr)'' | Manager freigeben | | ''UsbIfcAcquire(mgrPtr)'' | Interface beanspruchen (1 = Erfolg, 0 = Fehler) | | ''UsbIfcRelease(mgrPtr)'' | Interface freigeben (No-op wenn nicht geclaimt) | | ''UsbIfcIsClaimed(mgrPtr)'' | Aktueller Claim-Status (1 oder 0) | ---- ===== usb_urb_pool ===== Pool mit 4 vorbelegten URBs für überlappende Interrupt-Transfers. Während ein URB verarbeitet wird, warten die anderen bereits im Kernel. ''UsbUrbPoolPoll'' gibt den Entry-Index zurück — kein Callback-Mechanismus nötig. **Wichtig:** Bei ''UsbUrbPoolGetStatus(urbp, idx) == -ENODEV'' (Gerät abgesteckt) nicht erneut einreichen. ^ Funktion ^ Beschreibung ^ | ''UsbUrbPoolAlloc(dev_fd, endpoint, bufSize)'' | Pool anlegen (4 Slots, max. 64 Bytes/Slot) | | ''UsbUrbPoolFree(urbp)'' | Pool freigeben | | ''UsbUrbPoolInit(urbp)'' | Alle 4 URBs einreichen, Rückgabe: Anzahl eingereicht | | ''UsbUrbPoolPoll(urbp, timeout_ms)'' | Warten — gibt Entry-Index (0..3) oder -1 zurück | | ''UsbUrbPoolResubmit(urbp, idx)'' | Eintrag erneut einreichen | | ''UsbUrbPoolGetBuffer(urbp, idx)'' | Zeiger auf Datenpuffer von Slot idx | | ''UsbUrbPoolGetActualLen(urbp, idx)'' | Empfangene Bytes nach Poll | | ''UsbUrbPoolGetStatus(urbp, idx)'' | URB-Status (0=OK, -ENODEV=Disconnect) | import std.hardware.usb_urb_pool; var pool: int64 := UsbUrbPoolAlloc(dev_fd, 0x81, 64); UsbUrbPoolInit(pool); while (true) { var idx: int64 := UsbUrbPoolPoll(pool, 2000); if (idx < 0) { break; } if (UsbUrbPoolGetStatus(pool, idx) == 0) { var data: int64 := UsbUrbPoolGetBuffer(pool, idx); var len: int64 := UsbUrbPoolGetActualLen(pool, idx); // data enthält len Bytes UsbUrbPoolResubmit(pool, idx); } } UsbUrbPoolFree(pool); ---- ===== Vollständiges Beispiel: USB-Gerät öffnen und Bulk-Daten übertragen ===== import std.hardware.usb_parse; import std.hardware.usb_discovery; import std.hardware.usb_control; import std.hardware.usb_bulk; fn main(): int64 { var dev: int64 := UsbAllocDevice(); if (UsbFindDevice(0x1234, 0x5678, dev) != USB_FIND_OK) { return -1; } var fd: int64 := peek64(dev + 48); // dev.handle if (UsbClaimInterface(fd, 0) < 0) { return -2; } var txBuf: int64 := alloc(64); poke8(txBuf, 0x01); var sent: int64 := UsbBulkWrite(fd, 0x01, txBuf, 64, 1000); var rxBuf: int64 := alloc(64); var recv: int64 := UsbBulkRead(fd, 0x81, rxBuf, 64, 1000); UsbReleaseInterface(fd, 0); usb_close(fd); free(txBuf, 64); free(rxBuf, 64); return 0; } Letzte Aktualisierung: 2026-06-07