USB mit Lyx

Die Lyx USB-Bibliothek nutzt den Linux usbdevfs-Treiber (dev/bus/usb/) — kein libusb, keine externen Abhängigkeiten. Die 13 Units decken die gesamte USB-Stack-Breite ab: von rohen Syscalls über Descriptor-Parsing bis hin zu typsicheren Endpoint-Wrappern und URB-Pools für überlappende I/O.

Guides · std.hardware.usb Referenz


Architektur

usb_syscalls      — open/read/write/ioctl auf /dev/bus/usb/
usb_util          — Pfad-Hilfsfunktionen, String/Num-Helfer
  └─ usb_discovery  — Gerätesuche via /dev/bus/usb/, UsbFindDevice
usb_types         — Descriptor-Strukturkonstanten
  └─ usb_parse      — Configuration-Parsing (Device/Config/Interface/Endpoint)
usb_control       — Control-Transfers, Interface-Claim/Release
usb_bulk          — Bulk-Transfers (IN/OUT)
usb_interrupt     — Interrupt-Transfers (asynchron, URB-basiert)
usb_iso           — Isochronous-Transfers (Audio/Video)
usb_endpoint_types — Typsichere Endpoint-Wrapper (BulkIn/BulkOut/InterruptIn)
usb_endpoint_bind  — Endpoint-Registry (numerischer Schlüssel)
usb_ifc_mgr       — Idempotenter Interface-Claim/Release-Manager
usb_urb_pool      — URB-Pool (4 URBs, überlappende I/O)

Alle Units basieren auf dem Linux-Kernel-Interface:

  • USBDEVFS_CONTROL — Control-Transfer via ioctl
  • USBDEVFS_BULK — Bulk-Transfer via ioctl
  • USBDEVFS_SUBMITURB / USBDEVFS_REAPURB — Async-URB für Interrupt/ISO

Typischer Workflow

1. Gerät finden

import std.hardware.usb_discovery;
import std.hardware.usb_types;
import std.alloc;

fn main(): int64 {
  // Gerät mit Vendor-ID 0x046D (Logitech) und Product-ID 0xC52B suchen:
  var dev: int64 := alloc(USB_SIZEOF_DEVICE);
  var rc: int64 := UsbFindDevice(0x046D, 0xC52B, dev);
  if rc < 0 {
    free(dev, USB_SIZEOF_DEVICE);
    return -1;
  }
  // dev enthält jetzt den Gerätepfad und Basisinformationen
  // ...
  free(dev, USB_SIZEOF_DEVICE);
  return 0;
}

Alternativ manueller Pfad: /dev/bus/usb/001/003 (Bus 1, Device 3).

2. Gerät öffnen und Deskriptoren lesen

import std.hardware.usb_syscalls;
import std.hardware.usb_control;
import std.hardware.usb_parse;
import std.alloc;

var dev_fd: int64 := usb_open("/dev/bus/usb/001/003"c, USB_O_RDWR);
if dev_fd < 0 { return -1; }

// Device-Descriptor lesen:
var descBuf: int64 := alloc(USB_SIZEOF_DEVICE_DESC);
UsbGetDeviceDescriptor(dev_fd, descBuf);
var vendor:  int64 := UsbDevDesc_idVendor(descBuf);
var product: int64 := UsbDevDesc_idProduct(descBuf);
free(descBuf, USB_SIZEOF_DEVICE_DESC);

// Configuration-Descriptor parsen (alle Interfaces + Endpoints):
var devBuf: int64 := alloc(USB_SIZEOF_DEVICE);
UsbParseConfiguration(dev_fd, devBuf);

3. Interface beanspruchen

import std.hardware.usb_control;
import std.hardware.usb_ifc_mgr;

// Einfach direkt (ohne Manager):
UsbClaimInterface(dev_fd, 0);   // Interface 0

// Oder mit idempotent-sicherem Manager:
var mgr: int64 := UsbIfcMgrAlloc(dev_fd, 0);
UsbIfcAcquire(mgr);    // Claim — doppelter Aufruf ist ein No-op
// ... Arbeit ...
UsbIfcRelease(mgr);    // Release
UsbIfcMgrFree(mgr);


Transfer-Typen

Bulk-Transfer

Für Massendatentransfer (USB-Sticks, Drucker, serielle Adapter).

import std.hardware.usb_bulk;
import std.alloc;

// Daten senden (OUT, Endpoint 0x01):
var data: pchar := "Hello USB"c;
var rc: int64 := UsbBulkWrite(dev_fd, 0x01, data as int64, 9, USB_TIMEOUT_DEFAULT);

// Daten empfangen (IN, Endpoint 0x81):
var buf: int64 := alloc(512);
var read: int64 := UsbBulkRead(dev_fd, 0x81, buf, 512, USB_TIMEOUT_DEFAULT);
free(buf, 512);

Typsichere Variante über usb_endpoint_types:

import std.hardware.usb_endpoint_types;

var out_ep: UsbBulkOutEndpoint := UsbMakeBulkOut(dev_fd, 0x01, USB_TIMEOUT_DEFAULT);
var in_ep:  UsbBulkInEndpoint  := UsbMakeBulkIn(dev_fd, 0x81, USB_TIMEOUT_DEFAULT);

if UsbBulkOutValid(out_ep) {
  UsbBulkOutWrite(out_ep, data, dataLen);
}
if UsbBulkInValid(in_ep) {
  UsbBulkInRead(in_ep, buf, bufLen);
}

Control-Transfer

Für Konfigurations-Requests an das Gerät (Device/Interface/Endpoint).

import std.hardware.usb_control;
import std.alloc;

var ctrlBuf: int64 := alloc(USB_CTRL_STRUCT_SIZE);
// GET_DESCRIPTOR für Interface 0:
UsbCtrlSetRequest(ctrlBuf,
  USB_DIR_IN | USB_RECIP_INTERFACE,   // bmRequestType
  USB_REQ_GET_DESCRIPTOR,             // bRequest
  (USB_DT_INTERFACE << 8),            // wValue
  0,                                  // wIndex = Interface 0
  9                                   // wLength
);
var respBuf: int64 := alloc(9);
UsbCtrlSetTransfer(ctrlBuf, respBuf, 9, USB_TIMEOUT_DEFAULT);
var rc: int64 := UsbControlTransferRaw(dev_fd, ctrlBuf);
free(respBuf, 9);
free(ctrlBuf, USB_CTRL_STRUCT_SIZE);

Interrupt-Transfer (asynchron)

Für HID-Geräte (Maus, Tastatur, Gamepad) und andere periodische Daten.

import std.hardware.usb_interrupt;
import std.alloc;

// URB anlegen und einreichen:
var urbPtr: int64 := UsbAllocUrb();
var buf: int64 := alloc(64);
UsbInitInterruptRead(urbPtr, 0x81, buf, 64);
UsbSubmitUrb(dev_fd, urbPtr);

// Auf Antwort warten (500 ms):
var reapedUrb: int64 := UsbWaitForUrb(dev_fd, 500);
if reapedUrb != 0 {
  var actualLen: int64 := UsbUrbActualLength(reapedUrb);
  // ... buf[0..actualLen-1] auswerten ...
}
UsbFreeUrb(urbPtr);
free(buf, 64);

Für kontinuierlichen Empfang → URB-Pool verwenden:

import std.hardware.usb_urb_pool;

// Pool mit 4 URBs, je 64 Bytes, Endpoint 0x81:
var pool: int64 := UsbUrbPoolAlloc(dev_fd, 0x81, 64);
UsbUrbPoolInit(pool);   // alle 4 URBs einreichen

// Poll-Schleife:
var idx: int64 := UsbUrbPoolPoll(pool, 100);   // 100 ms Timeout
if idx >= 0 {
  var buf: int64 := UsbUrbPoolGetBuffer(pool, idx);
  var len: int64 := UsbUrbPoolGetActualLen(pool, idx);
  // ... Daten verarbeiten ...
  UsbUrbPoolResubmit(pool, idx);   // URB für nächsten Transfer wiederverwenden
}
UsbUrbPoolFree(pool);

Isochronous-Transfer (Audio/Video)

Für USB-Audio, Webcams und andere zeitkritische Streams.

import std.hardware.usb_iso;
import std.alloc;

// ISO-URB mit 8 Paketen à 192 Bytes (USB-Audio 48kHz, 32bit):
var urbPtr: int64 := UsbAllocIsoUrb(8);
var buf: int64 := alloc(8 * 192);
UsbInitIsoRead(urbPtr, 0x83, buf, 8, 192);
UsbSubmitIsoUrb(dev_fd, urbPtr);

// Antwort holen:
var reaped: int64 := UsbReapIsoUrb(dev_fd);
if reaped != 0 {
  var errors: int64 := UsbIsoErrorCount(reaped);
  var i: int64 := 0;
  while i < 8 {
    var pkLen:  int64 := UsbIsoPacketActualLen(reaped, i);
    var pkStat: int64 := UsbIsoPacketStatus(reaped, i);
    // ... Paket i auswerten ...
    i := i + 1;
  }
}
UsbFreeIsoUrb(urbPtr, 8);
free(buf, 8 * 192);


Wichtige Konstanten

Descriptor-Typen (usb_types):

Konstante Wert Bedeutung
USB_DT_DEVICE 1 Device Descriptor
USB_DT_CONFIG 2 Configuration Descriptor
USB_DT_INTERFACE 4 Interface Descriptor
USB_DT_ENDPOINT 5 Endpoint Descriptor

Transfer-Richtung und Typ:

Konstante Wert Bedeutung
USB_DIR_OUT 0x00 Host → Device
USB_DIR_IN 0x80 Device → Host
USB_TRANS_CONTROL 0 Control
USB_TRANS_ISO 1 Isochronous
USB_TRANS_BULK 2 Bulk
USB_TRANS_INTERRUPT 3 Interrupt

Timeouts:

Konstante Wert Verwendung
USB_TIMEOUT_DEFAULT 5000 ms Standard
USB_TIMEOUT_SHORT 1000 ms Schnelle Geräte

Transfer-Typ Auswahl

Anwendungsfall Transfer-Typ Unit
USB-Stick, Drucker, serieller Adapter Bulk usb_bulk
Maus, Tastatur, Gamepad (HID) Interrupt usb_interrupt
USB-Audio, Webcam Isochronous usb_iso
Gerätekonfiguration, Descriptor lesen Control usb_control
Kontinuierlicher HID-Empfang Interrupt + URB-Pool usb_urb_pool

Letzte Aktualisierung: 2026-06-08