Inhaltsverzeichnis

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;

std.hardware · 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