Lyx OS – Treibermodell: was unterstützt wird und wie
Diese Seite beantwortet zwei Fragen: welche Controller, Busse und Geräteklassen Lyx OS heute bedient — und womit man einen eigenen Treiber schreibt.
→ Lyx OS · Architektur · Geräte-Verzeichnis · lsdrv · lspci · XHCI-Treiber · Syscall-ABI
1. Zwei Stufen, ein Modell
| Stufe | Wo der Treiber läuft | Wofür |
|---|---|---|
| Tier-0 | in Ring 0, in den Kernel einkompiliert | boot- und latenzkritisch: Platten, Netzkarte, USB-Host, I²C-Bus |
| Tier-1 | in Ring 3, als gewöhnliches Programm | alles Übrige — heute die HID-Treiber usbhid und i2chid |
Tier-0 heißt nicht „fest verdrahtet„: Die Treiber tragen sich in eine Tabelle ein (kernel/drv.lyx), statt sich über if-Ketten in disk.lyx, vfs.lyx und kernel.lyx zu verteilen. Ein neuer Blocktreiber ist damit ein Registrierungsaufruf, keine Änderung an drei Stellen.
Die Zuordnung läuft über eine Frage an den Treiber — „gehört dir diese Gerätenummer?“ — statt über fest verdrahtete Nummernbereiche. Welche Geräte eine Maschine hat und in welcher Reihenfolge, darf den Ablauf nicht bestimmen.
2. Was heute bedient wird
Massenspeicher
| Bus | Kennung | Unit | Anmerkung |
|---|---|---|---|
| ATA (PIO) | DISK_BUS_ATA = 0 | kernel/ata.lyx | der Rückfallweg, funktioniert überall |
| SATA / AHCI | DISK_BUS_AHCI = 1 | kernel/ata.lyx | je Anschluss eigene Strukturen; die Gerätenummer ist nicht der AHCI-Port-Index |
| NVMe | DISK_BUS_NVME = 2 | kernel/nvme.lyx | Blockgröße kommt aus dem Namensraum (4Kn möglich), nicht fest 512 |
| virtio-blk | DISK_BUS_VIRTIO = 3 | kernel/virtio_blk.lyx | der vierte Bus, für Virtualisierung |
| RAM-Disk | Nummern 8–11 | kernel/ramdisk.lyx | → RAM-Disk |
| USB-Massenspeicher | Nummer 12 | kernel/xhci.lyx | Bulk-Only Transport + SCSI über den xHCI-Treiber |
Acht Plattenplätze (0–7) für physische Laufwerke, vier RAM-Disks, ein USB-Speicher. Die Registrierung als Blocktreiber erfolgt für ata, ramdisk und usb; NVMe und virtio-blk hängen im Plattenverwalter am Bus-Feld.
Netz
| Karte | Kennung | Unit | Wo sie typisch steckt |
|---|---|---|---|
| virtio-net | PCI\VEN_1AF4&DEV_1000 | kernel/virtio_net.lyx | KVM/QEMU — wird zuerst geprüft, weil dort am schnellsten |
| Intel E1000 | PCI\VEN_8086&DEV_100E | kernel/e1000.lyx | QEMU-Vorgabe |
| AMD PCnet | PCI\VEN_1022&DEV_2000 | kernel/pcnet.lyx | VirtualBox |
| Realtek RTL8139 | PCI\VEN_10EC&DEV_8139 | kernel/rtl8139.lyx | verbreitete Emulation |
Darüber liegt kernel/nic.lyx als eine Schnittstelle: der Netzstapel (net.lyx) kennt keine Karte mehr. Vorher rief er E1000Send/E1000Recv unmittelbar auf — damit hatte Lyx OS in VirtualBox schlicht kein Netz.
e1000.
USB
| Was | Unit | Stand |
|---|---|---|
| xHCI-Host-Controller | kernel/xhci.lyx | Enumerierung und Datentransfer; ein Klassentreiber bedient alle (PCI\CC_0C0330) |
| USB-Massenspeicher | kernel/xhci.lyx | Bulk-Only Transport mit SCSI, zwei Bulk-Endpoints je Gerät |
| USB-HID | bin/usbhid.lyx | Tier-1, läuft vollständig in Ring 3 |
I²C
| Was | Unit | Stand |
|---|---|---|
| DesignWare-I²C-Controller | kernel/i2c.lyx, kernel/i2c_dw.lyx | über ACPI gefunden; MMIO, IRQ und Version stehen im Geräte-Verzeichnis |
| I²C-HID (Trackpad) | bin/i2chid.lyx | Tier-1: der Kernel stellt nur den Bus (248) und die ACPI-Liste (249/250) |
Ton
| Gerät | Kennung | Unit | Ladeart |
|---|---|---|---|
| Intel HD Audio | PCI\CC_040300 | kernel/hda.lyx | bei Bedarf — erst beim ersten Tonaufruf |
| AC97 | PCI\CC_040100 | kernel/ac97.lyx | bei Bedarf |
Der Grund für „bei Bedarf„ steht im Manifest: AC97 und HDA fassen beim Hochfahren PCI, Anschlüsse und DMA an — und genau das hat auf echter Hardware Eingabe und Platte gestört. Ins Geräte-Verzeichnis kommt deshalb nur, was steckt; die Auswahl fällt beim ersten Ton.
Eingabe
| Weg | Wo |
|---|---|
| PS/2-Tastatur | kernel/keyboard.lyx — der Grundweg, der immer da ist |
| USB-HID | bin/usbhid.lyx (Tier-1) meldet Tasten und Maus über 246/247 an den Kernel |
| I²C-HID | bin/i2chid.lyx (Tier-1), gleicher Rückweg |
3. Treiber-Manifeste
Jeder Treiber hat eine Datei unter /lib/drivers (im Baum: drivers/*.mf). Sie sagt, wofür er zuständig ist und wann er geladen wird:
# Manifest: USB-Controller (xHCI)
name xhci
treiber eingebaut
laden sofort
abi 1
match PCI\CC_0C0330
| Feld | Bedeutung |
|---|---|
name | Treibername, wie ihn lsdrv anzeigt |
treiber | eingebaut = Tier-0 im Kernel |
laden | sofort, beimStart (der Plattenverwalter fragt ihn im Hochfahren) oder beiBedarf |
abi | Fassung der Treiberschnittstelle |
match | Schlüssel, auf den er passt — mehrfach erlaubt (i2c.mf hat zwei) |
Die Schlüssel und ihre Reihenfolge
Geprüft wird vom genauesten zum allgemeinsten; der erste Treffer gewinnt. So kommt ein auf das Modell zugeschnittener Treiber vor einem, der nur die Chipfamilie kennt:
| Stufe | Form | Beispiel |
|---|---|---|
| 0 | PCI\VEN_xxxx&DEV_xxxx&SUBSYS_xxxxxxxx&REV_xx | PCI\VEN_8086&DEV_1E31&SUBSYS_00000000&REV_04 |
| 1 | PCI\VEN_xxxx&DEV_xxxx | PCI\VEN_1AF4&DEV_1001 |
| 2 | PCI\CC_xxxxxx (Klasse, Unterklasse, Schnittstelle) | PCI\CC_0C0330 |
| 3 | PCI\CC_xxxx (nur Klasse und Unterklasse) | PCI\CC_0C80 |
lsdrv hält die Manifeste gegen die tatsächlich gefundenen Geräte und zeigt IST gegen SOLL; lyxdevd bindet danach wirklich.
4. Was ein Tier-1-Treiber vom Kernel bekommt
Ein Ring-3-Treiber braucht genau zwei Dinge, um überhaupt anzufangen: seinen MMIO-Bereich im eigenen Adressraum und Zugriff auf die PCI-Konfiguration. Alles Weitere kommt in Stufen dazu:
| Nr | Aufruf | Paket | Wofür |
|---|---|---|---|
| 230 | sys_map_device(phys, size) | DRV-3 | Geräteregister in den eigenen Adressraum abbilden |
| 232 | sys_pci_cfg_read(bdf, reg) | DRV-3 | PCI-Konfiguration lesen |
| 233 | sys_pci_cfg_write(bdf, reg, val) | DRV-3 | PCI-Konfiguration schreiben (Gerät einschalten) |
| 234 | sys_dma_alloc(size) | DRV-4 | DMA-Puffer anfordern, gibt eine Kennung |
| 235 | sys_dma_addr(id) | DRV-4 | die abgebildete Adresse des Puffers |
| 236 | sys_dma_program(id, mmio, offset, width) | DRV-4 | der Kernel trägt die physische Adresse ins Geräteregister ein |
| 237 | sys_notify_create() | DRV-5 | Benachrichtigungsschlange anlegen |
| 238 | sys_notify_post(id) | DRV-5 | melden |
| 239 | sys_notify_wait(id) | DRV-5 | blockierend warten |
| 240 | sys_irq_bind(bdf, notify_id) | DRV-5 | Interrupt des Geräts auf die Schlange legen |
| 241 / 242 | sys_hid_claim(slot, notify_id) / sys_hid_release(slot) | DRV-6 | ein HID-Gerät übernehmen und wieder freigeben |
| 243 / 244 | sys_usb_dev_count() / sys_usb_dev_info(idx, out) | DRV-6 | die enumerierten USB-Geräte durchgehen |
| 245 | sys_hid_claim_dma(slot, notify_id, dma_id) | DRV-6 | dasselbe mit eigenem DMA-Puffer |
| 246 / 247 | sys_input_key(ascii) / sys_input_mouse(dx, dy, btn) | DRV-6 | Eingaben an den Kernel zurückmelden |
| 248 | sys_i2c_xfer(bus, wbuf, lens, rbuf) | I²C | Übertragung auf dem I²C-Bus |
| 249 / 250 | sys_i2c_hid_info(idx, out) / sys_i2c_hid_count() | I²C | die von ACPI genannten Geräte |
Die Entscheidung hinter DRV-4
Der Treiber bekommt nie eine rohe physische Adresse. Er sagt „DMA in meinen Puffer der Größe N“, bekommt eine Kennung und den Puffer in seinen Adressraum abgebildet — die Adresse ins Geräteregister trägt der Kernel ein, nach Bereichsprüfung. Damit ist die naheliegendste Fußangel („beliebiger physischer Schreibzugriff„) schon in Fassung 1 weg.
5. Interrupts statt Abfragen
Bis 2026-08 fragten die Treiber ihre Geräte ab: USB-HID mit 100 Hz aus dem vsync-Pfad, der Netzempfang in jeder Warteschleife. Das kostet Rechenzeit, meldet spät — und hängt daran, dass jemand die Abfrage überhaupt ruft. Genau daran war USB-HID im Desktop schon einmal tot, weil vsync_wait niemand mehr rief.
Die Verteilung selbst steht in kernel/irq.lyx (Vektor → Lyx-Handler, Rahmen drv_irq_common für 0x50–0x5F mit LAPIC-EOI und drv_pic_common für 32–47 mit PIC-EOI) — siehe Kernel-Interna.
USB-HID (xhci.lyx)
MsiBindVector(bdf, vektor)ergänztmsi.lyxum eine Bindung mit freier Vektorwahl.MsiBindvergibt fest0x60+notify_id— gedacht für Ring-3-Treiber; ein Treiber im Kernel will seinen eigenen Vektor aus der Verteilung.XhciIrqAttach(idx, vektor)trägt erst den Handler ein, bindet dann MSI/MSI-X und schaltet zuletzt den Interrupter scharf. Andersherum könnte der erste Interrupt eintreffen, bevor jemand zuständig ist. QEMU (–usbhid): Vektor 0x50 (MSI-X).- Den Ring einmal leeren, nachdem scharf geschaltet wurde. Ein Interrupt entsteht beim Übergang auf „es liegt etwas an“ — lag beim Umschalten schon ein Ereignis im Ring, kommt danach nie einer. Derselbe Aufruf holt zugleich die ausstehenden Transfers nach, denn das Nachladen (Doorbell) passiert in derselben Schleife wie das Abarbeiten.
- Kein fristbasierter Rückfall. „Kam in N Durchläufen kein Interrupt, dann zurück zur Abfrage„ schaltete immer zurück: im Leerlauf ist „kein Interrupt“ der Normalfall — die Frist maß nicht den Fehler, sondern die Ruhe. Stattdessen
XhciTick(): ohne Interruptbetrieb die alte Abfrage, mit Interrupt nur noch ~1×/s als Sicherheitsnetz, damit ein verlorenes Ereignis nicht dauerhaft hängen bleibt.
Netzempfang (nic.lyx / e1000)
- Befund zuerst: der e1000 maskierte seine Interrupts nicht, sondern schaltete sie ein. Der Code schrieb
0xFFFFFFFFnach0x00D0mit dem Kommentar „IMC: alle Interrupts maskieren„ — beim 82540EM ist0x00D0aber IMS (Maske setzen), IMC liegt auf0x00D8. Folgenlos, solange kein Handler gebunden war, an drei Stellen korrigiert. NicIrqAttach(vektor)richtet den Empfangs-Interrupt ein, mit zwei Wegen: MSI bevorzugt (kein Routing, keine geteilte Leitung) und INTx als Rückfall — QEMUs 82540EM hat gar keine MSI-Capability, meldet sich aber über eine klassische Leitung (Konfigurationsraum0x3C); Vektor = 32 + Leitung, eingehängt über op117. Belegt in QEMU: Leitung 11 → Vektor 43.- Der Handler verarbeitet nichts — er quittiert (ICR lesen, read-to-clear) und zählt. Das Auslesen bleibt in den Warteschleifen, die den Inhalt ohnehin deuten müssen; den Protokollstapel in den Interruptkontext zu ziehen wäre viel Risiko für wenig Gewinn.
- Freigeschaltet werden nur die Empfangsursachen (RXT0, RXO). Sendequittungen und Verbindungswechsel kosten je einen Austritt aus dem Gastmodus, ohne gebraucht zu werden.
Platte: gemessen, nicht umgebaut
Der geplante Umbau der AHCI/NVMe-Fertigmeldung auf Interrupt unterblieb — die Messung davor fiel eindeutig aus: disk Wartezeit bis hier: 0 ms in 0 Zugriffen. Der QEMU-Boot liest überhaupt nicht von der Platte; Kernel, Initrd, Schriften und Hintergrund kommen aus der Initrd im Arbeitsspeicher.
Auch bei Zugriffen wäre der Gewinn klein: NVMe wartet in cq_wait auf ein Phase-Bit in der per DMA gefüllten Completion Queue (Rechenzeit, aber keine teuren Gerätezugriffe), AHCI pollt PxCI per MMIO aus dem Bootloader-Assembler. Und der ganze Pfad (DiskRead → fat32 → vfs → Syscall) ist synchron: der Wartende kann nicht schlafen. Ein Interrupt, der eine Marke setzt, ersetzt die Warteschleife nicht, er ändert nur ihr Ziel. Der Gewinn entstünde erst mit asynchronem Disk-I/O.
Die Messung selbst bleibt (zwei TSC-Lesungen je Zugriff) und beantwortet die Frage künftig sofort — auch dort, wo tatsächlich von Platte gelesen wird. Ausgegeben wird sie nur, wenn es Zugriffe gab.
Offen: der Nachweis, dass die Zähler unter Last steigen (Netz), und dass die Zeichen aus dem USB-Interruptpfad im Terminal ankommen — im Prüflauf war der Desktop nicht oben. Anbindung und Stabilität sind belegt (20 s ohne Interrupt-Sturm, PIC-Maske zeigt genau Leitung 9 (SCI) und 11 (Netz) offen), die Wirksamkeit noch nicht.
6. Sicherheitsmodus und die Grenzen, die bleiben
| Sicherheitsmodus | Wirkung |
|---|---|
| aus (Vorgabe) | eine Rechtemaske von 0 heißt unbeschränkt. Normale ELF-Programme tragen keine, können also auf Geräte zugreifen — der Entwicklungsstand |
| an | ohne Manifest kein Gerätezugriff. Nur Programme mit ausdrücklicher Rechtemaske — also LBF-Dateien, deren Manifest sie trägt — dürfen MMIO abbilden oder PCI anfassen |
Der Kernel meldet den Stand beim Hochfahren: drv Sicherheitsmodus: AN — Geraetezugriff nur mit Manifest (LBF).
Was das Modell ausdrücklich nicht behauptet. Die Quelltexte benennen die Grenzen selbst, und die Doku übernimmt sie unverändert:
* Das Manifest ist eine Selbstauskunft. Es sagt, was ein Treiber anfassen will, und der Kernel hält ihn daran fest — es beweist nicht, dass der Treiber vertrauenswürdig ist. Dafür braucht es Signaturen (Fassung 2).
* Tier-1 ist heute ausschließlich eigener Code. Eine Isolationsgarantie nach außen wird nicht behauptet.
* Der Schutz beisys_map_deviceverhindert nur das Grobe: Bereiche, die sicher kein Gerätespeicher sind (Kernel-Abbild und Bump-Arena unterhalb 160 MB), werden abgelehnt; höchstens 16 MB je Aufruf. Eine echte Grenze zieht erst die IOMMU (Fassung 2).
* Deskriptorketten bleiben offen. Geräte, die vom Treiber geschriebene Ketten im Speicher ablaufen (NVMe-PRP-Listen, Scatter-Gather-Ringe), folgen Zeigern, die der Treiber selbst gesetzt hat. Auch diese Lücke schließt erst die IOMMU.
7. Womit man nachsieht
| Frage | Werkzeug |
|---|---|
| Was steckt überhaupt im Rechner? | lspci (jede PCI-Funktion), sysinfo (was beim Hochfahren gefunden wurde) |
| Welcher Treiber ist zuständig — und hat er sich gemeldet? | lsdrv (IST gegen SOLL) |
| Geräte tatsächlich binden | lyxdevd |
| Welche Platten gibt es? | diskinfo |
| Netzschnittstelle | ifconfig |
| Was hat die Hardware beim Lauf gemeldet? | hwreport — schreibt Prozessor, Speicher, Grafik, Geräte-Verzeichnis und das vollständige Boot-Protokoll in eine Datei (Vorgabe /hwreport.txt) |
Letzte Aktualisierung: 2026-08-29 — Abschnitt 5 „Interrupts statt Abfragen“ ergänzt (WP-DRV-3, USB-HID über MSI-X, Netz-RX über INTx, e1000-Registerfehler, Disk-Messung); erhoben aus kernel/irq.lyx, kernel/acpi_pm.lyx, kernel/xhci.lyx und kernel/nic.lyx.
Vorherige Aktualisierung: 2026-08-27 — Seite neu angelegt; aus drivers/*.mf, kernel/drv.lyx, kernel/disk.lyx, kernel/nic.lyx, kernel/ring3.lyx (DRV-3…DRV-6) und kernel/xhci.lyx erhoben.
