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.

 
Die erste gefundene Karte gewinnt, und welche es war, steht im Protokoll. Mehrere Karten gleichzeitig wären ein eigenes Arbeitspaket: dafür bräuchte der Stapel einen Schnittstellenbegriff, den er heute nicht hat. Stille Auswahl wäre hier der teuerste Fall — deshalb wird sie ausgegeben. Eine MAC setzen kann derzeit nur 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änzt msi.lyx um eine Bindung mit freier Vektorwahl. MsiBind vergibt fest 0x60+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 0xFFFFFFFF nach 0x00D0 mit dem Kommentar „IMC: alle Interrupts maskieren„ — beim 82540EM ist 0x00D0 aber IMS (Maske setzen), IMC liegt auf 0x00D8. 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 (Konfigurationsraum 0x3C); 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 bei sys_map_device verhindert 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.