Das Geräte-Verzeichnis ist die Ablage für das, was beim Hochfahren über die Hardware bekannt wird: Hersteller, Kennungen, Takte, gefundene Einstellungen. Es ist bewusst formlos gehalten — ein Eintrag hat eine Art, einen Namen und vier Zahlen; was die Zahlen bedeuten, entscheidet die Art.
→ Lyx OS · Treibermodell · sysinfo · lspci · lsdrv · Syscall-ABI · Architektur · Kernel-Interna
Beim Hochfahren stellt jedes Subsystem fest, was es vorfindet: der PCI-Scan die Funktionen, ACPI die I²C-Geräte, der Plattencode die Laufwerke, CPUID die Kerne und den Takt. Diese Angaben landeten bisher nur im Boot-Protokoll — und das rollt weg, sobald der Desktop startet. Wer später wissen wollte, mit welcher DesignWare-Version der I²C-Bus läuft oder wie viele Blöcke die NVMe hat, hatte keine Quelle mehr; dmesg zeigt Text, keine Zahlen.
Das Verzeichnis löst genau dieses Problem: einmal beim Erkennen abgelegt, im Betrieb abrufbar.
Der Entwurf ist absichtlich anspruchslos. Es gibt keine Geräteklassenhierarchie, keine Treiberbindung, keine Baumstruktur — nur eine flache Liste. Damit kann jedes Subsystem etwas ablegen, ohne dass das Verzeichnis alle Gerätetypen kennen muss. Ein neuer Bus braucht eine neue Art-Nummer und sonst nichts.
/dev, kein sysfs und keine Treiberverwaltung. Es kann nichts öffnen, nichts binden, nichts einstellen. Es ist ein Fundbericht: was beim Hochfahren gesehen wurde und mit welchen Zahlen. Für „welcher Treiber gehört dazu„ ist lsdrv zuständig, für den rohen PCI-Bestand lspci.
Im Kernel, in der Unit kernel/devinfo.lyx. Die Tabelle ist eine einzelne Speicherseite, die beim ersten Eintrag angelegt und genullt wird:
| Größe | Wert | Bedeutung |
|---|---|---|
DI_MAX | 64 | mehr Einträge nimmt das Verzeichnis nicht auf |
DI_SZ | 48 | Byte je Eintrag: kind(8) + Name(8) + v0…v3(4 × 8) |
| gesamt | 3072 Byte | eine mmap-Anforderung, beim ersten DevInfoAdd |
Zwei Modulvariablen halten den Zustand: g_di (Adresse der Tabelle, 0 = noch nicht angelegt) und g_di_n (Zahl der belegten Einträge).
Daraus folgen drei Eigenschaften, die man kennen muss:
DevInfoAdd, aber kein Entfernen und kein Ändern. Ein Eintrag, der einmal drinsteht, bleibt bis zum Neustart unverändert stehen.Hier steckt die wichtigste Einzelheit dieser Seite: die Anordnung im Kernel und die herauskopierte Anordnung sind nicht dieselbe.
| Versatz | In der Tabelle (DI_SZ = 48) | Im Ausgabepuffer von sys_devinfo_get |
|---|---|---|
| +0 | kind | kind |
| +8 | Name (8 Byte) | v0 |
| +16 | v0 | v1 |
| +24 | v1 | v2 |
| +32 | v2 | v3 |
| +40 | v3 | Name (8 Byte) |
DevInfoCopy schreibt die Zahlen zusammenhängend nach vorn und hängt den Namen hinten an. Wer aus Ring 3 liest, benutzt also die rechte Spalte: kind bei +0, die vier Zahlen bei +8/+16/+24/+32, der Name ab +40. Ein Puffer von 48 Byte reicht; sysinfo nimmt 64 und ist damit auf der sicheren Seite.
Acht Byte, nicht null-terminiert im Sinne von „mindestens ein Nullbyte“ — sondern hinter dem Ende mit Nullen aufgefüllt. Beim Lesen hört man am ersten Nullbyte auf.
Warum das ausdrücklich dasteht. Vorher kopierteDevInfoAddstur acht Byte aus dem übergebenen Zeiger. Bei einem kürzeren Namen las das in das nächste Stringliteral hinein, und die Anzeige zeigteatasatastattata. Aufgefallen ist es erst, als mit den Plattennamen zum ersten Mal Namen unter acht Zeichen vorkamen — vorher war jeder Name zufällig lang genug.
Ein reiner Bereichsfilter (nur Zeichen 32…126 ausgeben) hätte den Fehler verdeckt, nicht behoben: die Reste sind gültige Zeichen. Deshalb prüft die Leseseite auf das Nullbyte, nicht auf Druckbarkeit.
| Nr | Konstante | Name | v0 | v1 | v2 | v3 |
|---|---|---|---|---|---|---|
| 1 | DEV_KIND_I2C_CTRL | ACPI-Name | MMIO-Basis | Länge | IRQ | DesignWare-Version |
| 2 | DEV_KIND_I2C_HID | ACPI-Name | Adresse | Hersteller | Produkt | Taktstufe |
| 3 | DEV_KIND_USB_CTRL | — | BDF | Ports | Slots | MSI-Art |
| 4 | DEV_KIND_USB_DEV | — | Slot | Klasse | Protokoll | Controller |
| 5 | DEV_KIND_CPU | — | Kerne | TSC-Takte je µs | Familie | Modell |
| 6 | DEV_KIND_DISK | Bus-Name | Platten-Nummer | Blöcke | Blockgröße | Anschluss |
| 7 | DEV_KIND_GPIO_INT | ACPI-Name | Pin | Geräteadresse | — | — |
| 8 | DEV_KIND_MEM | — | Seiten gesamt | Seiten frei | Seitengröße | — |
| 9 | DEV_KIND_HOST | CPUID-Signatur | 1 = virtuell | — | — | — |
| 10 | DEV_KIND_AUDIO | Treibername | BDF | Klasse<<8 | Unterklasse | — | — |
Zwei Anmerkungen dazu:
DEV_KIND_USB_CTRL (3) ist vergeben, wird aber von niemandem gefüllt. Eingetragen werden nur die USB-Geräte (Art 4). Die Art steht bereit für den Tag, an dem der Controller selbst hineingehört.DEV_KIND_HOST die CPUID-Signatur, nicht einen Gerätenamen — bei einer VM steht dort der Hypervisor, auf echter Hardware ist das Feld leer. Die Signatur kommt aus CPUID-Leaf 0x40000000.In dieser Reihenfolge beim Hochfahren:
| Quelle | Was hineingeht |
|---|---|
kernel/kernel.lyx nach XhciInit() | jedes gefundene USB-Gerät (Art 4) |
kernel/disk.lyx → DiskRegisterInfo() | Platten (0–7, Name aus dem Bus: ata/sata/nvme/virtio), RAM-Disks (ram), USB-Speicher (usb) |
kernel/kernel.lyx, Tongerät | HD Audio bzw. AC97 — nur, was das PCI-Verzeichnis ohnehin weiß |
kernel/kernel.lyx, CPU/Speicher/Umgebung | Kerne und Takt, Seitenzahlen, echte Hardware oder VM |
kernel/kernel.lyx, ACPI-Durchlauf | I²C-Controller, I²C-HID-Geräte und deren GPIO-Interrupt |
import devinfo;
// Eintragen — der Rückgabewert ist der Index, -1 wenn die Tabelle voll ist.
DevInfoAdd(DEV_KIND_DISK, "nvme"c as int64, disk_id, bloecke, blockgroesse, port);
var n: int64 := DevInfoCount(); // Zahl der Einträge
var art: int64 := DevInfoKind(3); // Art des vierten Eintrags
DevInfoCopy(3, ziel); // 48 Byte nach `ziel` schreiben
| Funktion | Rückgabe |
|---|---|
DevInfoAdd(kind, name, v0, v1, v2, v3) | Index des neuen Eintrags, -1 wenn DI_MAX erreicht ist |
DevInfoCount() | Zahl der belegten Einträge |
DevInfoCopy(i, out) | true/false — schreibt in der Ausgabe-Anordnung (siehe oben) |
DevInfoKind(i) | Art des Eintrags, 0 bei ungültigem Index |
name ist ein Zeiger auf bis zu acht Zeichen und darf 0 sein.
Drei Syscalls, ohne Rechteprüfung — die Auskunft ist lesend und verändert nichts:
| Nr | Aufruf | Wirkung |
|---|---|---|
| 251 | sys_devinfo_count() | Zahl der Einträge |
| 252 | sys_devinfo_get(idx, out_uva) | einen Eintrag nach out_uva schreiben; 0 = gut, -1 = Index oder Adresse ungültig |
| 253 | sys_cpu_ident(out_uva) | 16 Byte Hersteller, dann 64 Byte Modellbezeichnung — direkt aus CPUID, nicht aus dem Verzeichnis |
Beide Dispatcher kennen die drei Nummern (handle_r3_syscall und der Scheduler-Pfad), sie sind also aus jedem Ring-3-Programm erreichbar. Die Adresse wird über VmmPhysFromUserVirt übersetzt; ein Puffer, der nicht abgebildet ist, führt zu -1 statt zu einem Schreibzugriff ins Leere.
import bsys;
con SYS_DEVINFO_COUNT: int64 := 251;
con SYS_DEVINFO_GET: int64 := 252;
con KIND_DISK: int64 := 6;
@capabilities([])
fn main(): int64 {
Init();
var n: int64 := Sys1(SYS_DEVINFO_COUNT, 0);
if (n <= 0) { PutsLn("Verzeichnis leer"); return 1; }
var rec: int64 := mmap(0, 64, 3, 34, -1, 0);
var i: int64 := 0;
while (i < n) {
if (Sys2(SYS_DEVINFO_GET, i, rec) == 0) {
if (peek64(rec) == KIND_DISK) {
Puts("Platte "); PutInt(peek64(rec + 8)); // v0 = Nummer
Puts(" Bloecke "); PutInt(peek64(rec + 16)); // v1
Puts(" a "); PutInt(peek64(rec + 24)); // v2 = Blockgroesse
Puts(" Byte ");
var k: int64 := 0; // Name ab +40
while (k < 8 && peek8(rec + 40 + k) != 0) {
PutCh(peek8(rec + 40 + k)); k := k + 1;
}
PutCh(10);
}
}
i := i + 1;
}
return 0;
}
Sys1/Sys2 sind die allgemeinen Syscall-Wrapper aus bsys — für Nummern, für die es keinen benannten Wrapper gibt. Sie schreiben Nummer und Argumente in den r3_sc_block und lösen aus; ein rohes SYSCALL aus einem Werkzeug würde den Ring-3-Stapel beschädigen.
DevInfoAdd liefert -1 und legt nichts an. Ein Rechner mit vielen USB-Geräten kann die Tabelle füllen — die späteren Funde fehlen dann stillschweigend in der Anzeige.DEV_KIND_MEM trägt PmmTotalCount() und PmmFreeCount() zum Zeitpunkt der Eintragung. Der Wert „frei„ altert also ab der ersten Sekunde. Wer den aktuellen Stand braucht, nimmt sys_mem_stats (203) — das ist die Quelle, aus der top liest.v1 bei einer Platte sind Blöcke, nicht Byte. Die Übersetzung in etwas Lesbares macht das anzeigende Programm.sysinfo trägt @capabilities([fs.read]) — und braucht davon für das Verzeichnis selbst nichts; die Nummern 251–253 sind ungeprüft, weil sie nur lesen.| Quelle | Was sie kennt | Werkzeug |
|---|---|---|
| Geräte-Verzeichnis | was beim Hochfahren gefunden wurde, als Zahlen — quer über alle Busse | sysinfo |
PCI-Verzeichnis (kernel/pci.lyx) | jede PCI-Funktion mit Fundstelle, Klasse, Anbieter | lspci |
Treiber-Manifeste (/lib/drivers) | welcher Treiber für welchen Schlüssel zuständig wäre | lsdrv, lyxdevd |
| Prozess-/Speicherzähler | der laufende Betrieb, nicht der Fund | top, ps |
Die vier überschneiden sich absichtlich nicht: das Verzeichnis sagt was da ist, das PCI-Verzeichnis wo es steckt, die Manifeste wer es bedienen soll, und die Zähler was gerade passiert.
Letzte Aktualisierung: 2026-08-27 — Seite neu angelegt; aus kernel/devinfo.lyx, kernel/kernel.lyx, kernel/disk.lyx, kernel/ring3.lyx und bin/sysinfo.lyx erhoben.