From 8d836c66b271d12774c9b226d48b818bb960abd7 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Mon, 26 Oct 2026 15:51:28 +0100 Subject: [PATCH] PCI and USB devices in the /sys/devices tree, with uevents and USB hot-plug PCI devices live at /sys/devices/pci0000:00/ and USB devices under the xHCI controller that carries their bus, below that bus's root hub usbN; /sys/bus/pci/devices and /sys/bus/usb/devices are symlinks into it, and every device has its subsystem and driver links, which is how udev learns SUBSYSTEM and DRIVER. Writing to a device's uevent now announces it with MODALIAS, PCI_ID, PRODUCT and the rest, so udevadm trigger covers them, and the USB core sends add when a device is attached and remove as it goes. The root hub is announced when the controller comes up, before anything is plugged in. Checked under QEMU: lspci and udevadm info through the links, udevadm monitor showing the kernel and udev add event for device_add usb-kbd/usb-mouse and the remove event for device_del, and a triggered add for usb1 and 1-5. Known limits: the bus-to-controller mapping is a convention (bus N is the Nth xHCI in PCI order); PCI bridges are not parents; USB interfaces and /dev/bus/usb nodes are absent; busybox's lsusb reads /dev/bus/usb and prints nothing. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao --- docs/architecture/filesystem.md | 13 +- docs/architecture/udev.md | 29 ++- include/core/hwinfo.h | 7 + include/dev/kdev.h | 2 + kernel/dev/kdev.c | 9 +- kernel/drivers/bus/xhci/xhci.c | 6 + kernel/drivers/usb/core/usb.c | 143 +++++++++--- kernel/fs/sysfs.c | 384 ++++++++++++++++++++++++++++++-- 8 files changed, 526 insertions(+), 67 deletions(-) diff --git a/docs/architecture/filesystem.md b/docs/architecture/filesystem.md index c60488e..c5ee274 100644 --- a/docs/architecture/filesystem.md +++ b/docs/architecture/filesystem.md @@ -252,8 +252,8 @@ mounted by init with `mount("sysfs", "/sys", "sysfs", 0, 0)`. | path | contents | |------|----------| -| `/sys/bus/pci/devices/0000:BB:DD.F/` | `vendor device class revision subsystem_vendor subsystem_device irq resource config uevent` | -| `/sys/bus/usb/devices/B-P[.P...]/` | `idVendor idProduct bcdDevice busnum devnum speed bDeviceClass bDeviceSubClass bDeviceProtocol bNumConfigurations uevent` | +| `/sys/devices/pci0000:00/0000:BB:DD.F/` (and `/sys/bus/pci/devices/` links to it) | `vendor device class revision subsystem_vendor subsystem_device irq resource config uevent subsystem driver` | +| `/sys/devices/pci0000:00//usbB/B-P[.P...]/` (and `/sys/bus/usb/devices/` links) | `idVendor idProduct bcdDevice busnum devnum speed bDeviceClass bDeviceSubClass bDeviceProtocol bNumConfigurations uevent subsystem driver` | | `/sys/firmware/acpi/tables/` | each ACPI table, raw; a repeated signature is numbered from 1 (`SSDT1`, `SSDT2`) | | `/sys/firmware/dmi/tables/` | `smbios_entry_point` and `DMI`, raw | | `/sys/firmware/memmap/N/` | `start end type`, the firmware's map as the loader handed it over | @@ -281,9 +281,12 @@ link, `/sys/class//` and `/sys/dev/{char,block}/MAJ:MIN` as symlink into it, and input capabilities; `uevent` is writable, and a write is announced on the uevent netlink socket ([udev.md](udev.md)). -What is missing, compared with Linux: PCI and USB devices in that tree (the -`bus/*/devices` entries are directories rather than links into it), -`/sys/class` entries for them, `/sys/block`, and most of what is writable. PCI `config` is +PCI and USB devices are in that tree too, under `/sys/devices/pci0000:00`, with +the `bus/*/devices` entries as links into it and `add`/`remove` events for USB +hot-plug ([udev.md](udev.md)). + +What is missing, compared with Linux: PCI bridges as parents, USB interfaces, +`/sys/class` entries for most devices, `/sys/block`, and most of what is writable. PCI `config` is 256 bytes, because config space is read through port `0xCF8`, which reaches no further. diff --git a/docs/architecture/udev.md b/docs/architecture/udev.md index ddba380..0a4b3c1 100644 --- a/docs/architecture/udev.md +++ b/docs/architecture/udev.md @@ -22,6 +22,10 @@ internal ones: `/dev/tty1` is `4:1` to a Linux program (`linux_tty_rdev` in ## `/sys`: `kernel/fs/sysfs.c` ``` +/sys/devices/pci0000:00/0000:00:1f.2/ vendor device class ... uevent subsystem -> ../../../bus/pci driver +/sys/devices/pci0000:00/0000:00:14.0/usb1/1-3 idVendor idProduct ... uevent subsystem -> ../../../../../bus/usb +/sys/bus/pci/devices/0000:00:1f.2 -> ../../../devices/pci0000:00/0000:00:1f.2 +/sys/bus/usb/devices/1-3 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-3 /sys/devices/virtual/input/input0/ name capabilities/{ev,key,rel,abs} properties /sys/devices/virtual/input/input0/event0/ dev uevent subsystem -> ../../../../../class/input /sys/class/input/event0 -> ../../devices/virtual/input/input0/event0 @@ -31,9 +35,22 @@ internal ones: `/dev/tty1` is `4:1` to a Linux program (`linux_tty_rdev` in Symlinks are real (`T_SYMLINK` inodes whose target is generated). `uevent` is readable (`DEVPATH=`, `SUBSYSTEM=`, `MAJOR=`, `MINOR=`, `DEVNAME=`) and writable: `echo add > uevent` — which is what `udevadm trigger` does — makes the -kernel announce the device again. A write to a PCI or USB device's `uevent` is -accepted and says nothing yet, because those devices are not in the -`/sys/devices` tree and an event's `DEVPATH` has to name something that exists. +kernel announce the device again. PCI and USB devices announce the same way: +`MODALIAS`, `PCI_ID`, `PCI_CLASS`, `PCI_SLOT_NAME` for a PCI device, `PRODUCT`, +`TYPE`, `BUSNUM`, `DEVNUM` for a USB one. + +**PCI devices** are flat under `pci0000:00` however the bridges are wired: the +kernel keeps no bridge tree, so a device behind a bridge is not nested inside +it. **USB devices** sit under the xHCI controller that carries their bus, below +that bus's root hub `usbN`; a hub's children are its siblings, not its +children. The bus-to-controller mapping is a convention (bus N is the Nth xHCI +in PCI order), since the USB core numbers a bus without recording which +controller it came from. + +**Hot-plug is real**: the USB core announces `add` when a device is attached +and `remove` as it detaches, and `usbN` is announced when its controller comes +up. With `udevadm monitor`, plugging in a mouse prints the kernel event and +then the same event after udev's rules have run. PCI has no hot-plug here. The input capability files are what `input_id` reads to decide `ID_INPUT_KEYBOARD` or `ID_INPUT_MOUSE`. SchOS has two input devices whatever @@ -65,9 +82,9 @@ and regular files in `/dev` are refused. ## What is not there -- PCI and USB devices in `/sys/devices`, and `add`/`remove` events from the - hotplug thread. A USB keyboard works because every keyboard is `event0`, not - because udev knew it arrived. +- USB interfaces (`1-3:1.0`), `/dev/bus/usb` nodes, and PCI bridges as parents. + A USB keyboard works because every keyboard is `event0`; udev now knows the + device arrived but not which interface became the keyboard. - `hwdb` (`/etc/udev/hwdb.bin`): `udevadm hwdb --update` has not been run in the image, so `60-input-id` logs "returned non-zero" for the hwdb import. The properties that matter come from `input_id`. diff --git a/include/core/hwinfo.h b/include/core/hwinfo.h index 2024376..e93893d 100644 --- a/include/core/hwinfo.h +++ b/include/core/hwinfo.h @@ -61,3 +61,10 @@ struct usb_info { char driver[16]; // the first interface's, or empty }; int usb_info_nth(int i, struct usb_info *out); +// How many USB buses have shown a device so far; bus numbers run 1..this. +int usb_nbuses(void); +// Tell /sys and udev a USB device appeared ("add") or is about to go ("remove"). +void sysfs_usb_uevent(const struct usb_info *u, const char *action); +void sysfs_usb_root_uevent(int bus, const char *action); +// A USB controller is up; number its bus and announce its root hub. +void usb_bus_add(void *hc); diff --git a/include/dev/kdev.h b/include/dev/kdev.h index 6cedaaa..99e052f 100644 --- a/include/dev/kdev.h +++ b/include/dev/kdev.h @@ -40,5 +40,7 @@ int kdev_path(int i, char *buf, int cap); // Announce a device on the uevent socket: action is "add", "remove", "change". void kdev_uevent(int i, const char *action); +// The next SEQNUM: events from every source (kdev, PCI, USB) share one count. +uint kdev_seqnum(void); // What reading a device's `uevent` file says. int kdev_uevent_text(int i, char *buf, int cap); diff --git a/kernel/dev/kdev.c b/kernel/dev/kdev.c index 70d3471..a130091 100644 --- a/kernel/dev/kdev.c +++ b/kernel/dev/kdev.c @@ -146,6 +146,13 @@ kdev_path(int i, char *buf, int cap) static uint kseq; +// One counter for every uevent the kernel sends, whoever builds it. +uint +kdev_seqnum(void) +{ + return ++kseq; +} + static int put(char *b, int cap, int n, const char *s) { @@ -190,7 +197,7 @@ vars(int i, char *buf, int cap, const char *action, int nl) buf[n++] = sep; if (action) { n = put(buf, cap, n, "SEQNUM="); - n = putnum(buf, cap, n, ++kseq); + n = putnum(buf, cap, n, kdev_seqnum()); buf[n++] = sep; } if (k.devname[0]) { diff --git a/kernel/drivers/bus/xhci/xhci.c b/kernel/drivers/bus/xhci/xhci.c index 8e9879f..bfe06e5 100644 --- a/kernel/drivers/bus/xhci/xhci.c +++ b/kernel/drivers/bus/xhci/xhci.c @@ -32,6 +32,7 @@ #include "fs/file.h" #include "dev/pci.h" #include "dev/usb.h" +#include "core/hwinfo.h" #include "xhci.h" #define RING_TRBS 64 // TRBs per ring, including the link @@ -1673,6 +1674,11 @@ xhci_probe(struct pci_dev *d) kinfo("xhci: no devices attached\n"); + // Number the bus and announce its root hub before the first port can be + // scanned, so `usbN` is in /sys, and udev knows of it, ahead of anything + // plugged into it. + usb_bus_add(x); + // Now, and only now, turn interrupts on. The order is: claim the line // (which unmasks it and sets level/active-low), clear anything the // controller already considers pending so that it drives INTx low, and then diff --git a/kernel/drivers/usb/core/usb.c b/kernel/drivers/usb/core/usb.c index 4edfc1e..e3d5476 100644 --- a/kernel/drivers/usb/core/usb.c +++ b/kernel/drivers/usb/core/usb.c @@ -185,20 +185,52 @@ static int present_init; static struct usb_dev *present[NPRESENT]; static void *buses[NBUSES]; +// Give a controller its bus number if it has none. Caller holds present_lock. +// Returns the number (1-based), or 0 if there is no room. +static int +bus_number(void *hc) +{ + for(int b = 0; b < NBUSES; b++){ + if(buses[b] == hc) return b + 1; + if(buses[b] == 0){ buses[b] = hc; return b + 1; } + } + return 0; +} + static void -present_add(struct usb_dev *dev) +present_init_once(void) { if(!present_init){ initlock(&present_lock, "usbpresent"); present_init = 1; } +} + +// A controller is up: number its bus now and announce its root hub, so usbN +// is in /sys before anything is plugged into it. The loader of the driver that +// calls this runs once per controller, in PCI order -- which is what ties bus +// N to the Nth controller in sysfs.c. +void +usb_bus_add(void *hc) +{ + int n; + + present_init_once(); + acquire(&present_lock); + n = bus_number(hc); + release(&present_lock); + if(n) + sysfs_usb_root_uevent(n, "add"); +} + +static void +present_add(struct usb_dev *dev) +{ + present_init_once(); acquire(&present_lock); for(int i = 0; i < NPRESENT; i++) if(present[i] == 0){ present[i] = dev; break; } - for(int b = 0; b < NBUSES; b++){ - if(buses[b] == dev->hc) break; - if(buses[b] == 0){ buses[b] = dev->hc; break; } - } + bus_number(dev->hc); release(&present_lock); } @@ -223,6 +255,44 @@ put_num(char *out, int *n, int cap, int v) out[*n] = 0; } +// Copy `d`'s identity out. Caller holds present_lock, or is the one thread +// that owns `d` (attach and detach). +static void +fill_info(struct usb_dev *d, struct usb_info *o) +{ + memset(o, 0, sizeof *o); + o->bus = 1; + for(int b = 0; b < NBUSES; b++) + if(buses[b] == d->hc){ o->bus = b + 1; break; } + o->devnum = d->slot; + o->speed = d->speed; + o->vid = d->desc.idVendor; + o->pid = d->desc.idProduct; + o->bcd = d->desc.bcdDevice; + o->cls = d->desc.bDeviceClass; + o->subcls = d->desc.bDeviceSubClass; + o->proto = d->desc.bDeviceProtocol; + o->nconf = d->desc.bNumConfigurations; + + // "-[....]": the route string holds one + // downstream port per tier below the root, a nibble each. + int n = 0, cap = (int)sizeof o->path; + put_num(o->path, &n, cap, o->bus); + o->path[n++] = '-'; + put_num(o->path, &n, cap, d->port); + for(int t = 0; t < d->tier && n < cap - 3; t++){ + o->path[n++] = '.'; + put_num(o->path, &n, cap, (int)((d->route >> (4 * t)) & 0xF)); + } + + const char *drv = d->driver; + for(int k = 0; !drv && k < NIFDEV; k++) + if(ifdevs[k].in_use && ifdevs[k].base == d) + drv = ifdevs[k].driver; + if(drv) + safestrcpy(o->driver, drv, sizeof o->driver); +} + int usb_info_nth(int idx, struct usb_info *o) { @@ -234,37 +304,7 @@ usb_info_nth(int idx, struct usb_info *o) struct usb_dev *d = present[i]; if(d == 0 || seen++ != idx) continue; - memset(o, 0, sizeof *o); - o->bus = 1; - for(int b = 0; b < NBUSES; b++) - if(buses[b] == d->hc){ o->bus = b + 1; break; } - o->devnum = d->slot; - o->speed = d->speed; - o->vid = d->desc.idVendor; - o->pid = d->desc.idProduct; - o->bcd = d->desc.bcdDevice; - o->cls = d->desc.bDeviceClass; - o->subcls = d->desc.bDeviceSubClass; - o->proto = d->desc.bDeviceProtocol; - o->nconf = d->desc.bNumConfigurations; - - // "-[....]": the route string holds one - // downstream port per tier below the root, a nibble each. - int n = 0, cap = (int)sizeof o->path; - put_num(o->path, &n, cap, o->bus); - o->path[n++] = '-'; - put_num(o->path, &n, cap, d->port); - for(int t = 0; t < d->tier && n < cap - 3; t++){ - o->path[n++] = '.'; - put_num(o->path, &n, cap, (int)((d->route >> (4 * t)) & 0xF)); - } - - const char *drv = d->driver; - for(int k = 0; !drv && k < NIFDEV; k++) - if(ifdevs[k].in_use && ifdevs[k].base == d) - drv = ifdevs[k].driver; - if(drv) - safestrcpy(o->driver, drv, sizeof o->driver); + fill_info(d, o); release(&present_lock); return 0; } @@ -272,6 +312,35 @@ usb_info_nth(int idx, struct usb_info *o) return -1; } +int +usb_nbuses(void) +{ + int n = 0; + if(!present_init) + return 0; + acquire(&present_lock); + for(int b = 0; b < NBUSES; b++) + if(buses[b]) + n++; + release(&present_lock); + return n; +} + +// Announce `dev` to udev. Attach says "add" once the device is in the table, +// so a rule that opens its /sys directory finds it; detach says "remove" +// first, while the directory still exists. +static void +announce(struct usb_dev *dev, const char *action) +{ + struct usb_info u; + if(!present_init) + return; + acquire(&present_lock); + fill_info(dev, &u); + release(&present_lock); + sysfs_usb_uevent(&u, action); +} + void usb_attach(struct usb_dev *dev) { @@ -282,6 +351,7 @@ usb_attach(struct usb_dev *dev) if(buf == 0) return; present_add(dev); + announce(dev, "add"); // Nine bytes first, because wTotalLength is in them and there is no other // way to know how much to ask for. @@ -365,6 +435,7 @@ usb_attach(struct usb_dev *dev) void usb_detach(struct usb_dev *dev) { + announce(dev, "remove"); present_del(dev); for(int i = 0; i < NIFDEV; i++){ struct usb_dev *d = &ifdevs[i]; diff --git a/kernel/fs/sysfs.c b/kernel/fs/sysfs.c index 796d6a4..639d8c7 100644 --- a/kernel/fs/sysfs.c +++ b/kernel/fs/sysfs.c @@ -2,12 +2,21 @@ // // /sys, laid out the way Linux lays it out, for the parts that exist here. // -// /sys/bus/pci/devices/0000:00:1f.2/ vendor device class revision irq +// /sys/devices/pci0000:00/0000:00:1f.2/ vendor device class revision irq // subsystem_vendor subsystem_device // resource config uevent boot_vga -// enable graphics/fb0/ -// /sys/bus/usb/devices/1-3.2/ idVendor idProduct bcdDevice busnum +// enable graphics/fb0/ subsystem driver +// /sys/devices/pci0000:00/0000:00:14.0/usb1/1-3.2/ +// idVendor idProduct bcdDevice busnum // devnum speed bDevice* uevent +// subsystem driver +// /sys/bus/pci/devices/ symlinks to the above, as on Linux +// /sys/bus/usb/devices/ +// +// The real directories live under /sys/devices because that is what udev +// resolves a device to, and what an event's DEVPATH has to name. USB devices +// sit under the xHCI controller that carries their bus, flat below its usbN +// root hub: a hub's children are siblings of the hub here, not nested in it. // /sys/firmware/acpi/tables/DSDT the tables, byte for byte // /sys/firmware/dmi/tables/DMI the SMBIOS table and entry point // /sys/firmware/memmap/N/ start end type @@ -46,6 +55,7 @@ #include "gfx/gfx.h" #include "dev/acpi.h" #include "dev/kdev.h" +#include "net/socket.h" // --------------------------------------------------------------------------- // Inode numbers: kind, index, file -- the whole identity, since iget gives @@ -66,6 +76,9 @@ #define K_KCLINK 12ULL // idx a kdev: the symlink in /sys/class/ #define K_KDEVD 13ULL // idx 0 char, 1 block: /sys/dev/char, /sys/dev/block #define K_KDEVL 14ULL // idx a kdev: the "MAJ:MIN" symlink in /sys/dev/* +#define K_PCILINK 15ULL // idx a pci_get() index: the symlink in /sys/bus/pci/devices +#define K_USBLINK 16ULL // idx bus << 8 | devnum (0: the root hub): /sys/bus/usb/devices +#define K_USBROOT 17ULL // idx a bus number: the root hub, /sys/devices/pci.../usbN // Files in a kdev directory (K_KDEV). enum { KF_DIR, KF_DEV, KF_UEVENT, KF_SUBSYSTEM, KF_NAME, KF_PROPERTIES, @@ -79,7 +92,7 @@ enum { KF_DIR, KF_DEV, KF_UEVENT, KF_SUBSYSTEM, KF_NAME, KF_PROPERTIES, enum { D_ROOT, D_BUS, D_PCI, D_PCIDEV, D_USB, D_USBDEV, D_FW, D_ACPI, D_ACPITAB, D_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_PSU, - D_DEVICES, D_VIRTUAL, D_DEVT, + D_DEVICES, D_VIRTUAL, D_DEVT, D_PCIROOT, D_NDIRS }; @@ -104,6 +117,7 @@ static const struct { const char *name; int parent; } dirs[D_NDIRS] = { [D_DEVICES] = { "devices", D_ROOT }, [D_VIRTUAL] = { "virtual", D_DEVICES }, [D_DEVT] = { "dev", D_ROOT }, + [D_PCIROOT] = { "pci0000:00", D_DEVICES }, }; #define SYSFS_ROOT INO(K_DIR, D_ROOT, 0) @@ -123,6 +137,8 @@ static const char *const pcifiles[] = { // boot_vga. Empty, because the path existing is the whole answer. #define PCI_GRAPHICS 13 #define PCI_FB0 14 +#define PCI_SUBSYS 15 // symlink: ../../../bus/pci +#define PCI_DRIVER 16 // symlink, only while a driver is bound // The display device: the one whose memory the framebuffer is in, which is // what boot_vga means on Linux -- the card firmware brought up and drew on. @@ -150,9 +166,21 @@ boot_vga_idx(void) static const char *const usbfiles[] = { 0, "idVendor", "idProduct", "bcdDevice", "busnum", "devnum", "speed", "bDeviceClass", "bDeviceSubClass", "bDeviceProtocol", "bNumConfigurations", - "uevent", + "uevent", "subsystem", "driver", }; #define NUSBFILES ((int)(sizeof usbfiles / sizeof usbfiles[0])) +#define USB_UEVENT 11 +#define USB_SUBSYS 12 // symlink +#define USB_DRIVER 13 // symlink, only while a driver is bound + +// A root hub, usbN: the little that udev and lsusb read. +static const char *const hubfiles[] = { + 0, "uevent", "subsystem", "idVendor", "idProduct", "busnum", "devnum", + "bDeviceClass", "speed", +}; +#define NHUBFILES ((int)(sizeof hubfiles / sizeof hubfiles[0])) +#define HUB_UEVENT 1 +#define HUB_SUBSYS 2 static const char *const memfiles[] = { 0, "start", "end", "type" }; @@ -272,6 +300,24 @@ usb_by_idx(int idx, struct usb_info *u) return -1; } +// The PCI device a USB bus is: the Nth xHCI controller in PCI order, N being +// the bus number. The USB core numbers a bus when its first device appears and +// does not record which controller it came from, so this is a convention that +// holds as long as every xHCI controller has a driver -- true here, where one +// driver takes them all. -1 if there is no such controller. +static int +usb_hc_pci(int bus) +{ + int n = 0; + for (int i = 0; i < pci_count(); i++) { + struct pci_dev *d = pci_get(i); + if (d->class == 0x0C && d->subclass == 0x03 && d->prog_if == 0x30 && + ++n == bus) + return i; + } + return -1; +} + // BAT0, BAT1 for batteries and AC, AC1 for adapters, numbered in the order // ACPI lists them -- the names Linux gives most laptops. static void @@ -523,19 +569,40 @@ children(uint64 dino, child_fn cb, void *ctx) if (cb(ctx, INO(K_KCLASS, s, 0), 1, kdev_subsys(s))) return 1; break; + case D_PCIROOT: case D_PCIDEV: + // The devices themselves live in the first; the second holds the + // links udev and lspci walk to reach them. for (int i = 0; i < pci_count(); i++) { struct out o = { name, 0, sizeof name - 1 }; pci_name(&o, pci_get(i)); name[o.n] = 0; - if (cb(ctx, INO(K_PCI, i, 0), 1, name)) + if (idx == D_PCIDEV ? cb(ctx, INO(K_PCILINK, i, 0), 2, name) + : cb(ctx, INO(K_PCI, i, 0), 1, name)) return 1; } + // A bus whose controller we cannot place hangs off the root instead. + if (idx == D_PCIROOT) + for (int b = 1; b <= usb_nbuses(); b++) + if (usb_hc_pci(b) < 0) { + struct out o = { name, 0, sizeof name - 1 }; + puts_(&o, "usb"); putu(&o, (uint64)b); + name[o.n] = 0; + if (cb(ctx, INO(K_USBROOT, b, 0), 1, name)) + return 1; + } break; case D_USBDEV: { struct usb_info u; + for (int b = 1; b <= usb_nbuses(); b++) { + struct out o = { name, 0, sizeof name - 1 }; + puts_(&o, "usb"); putu(&o, (uint64)b); + name[o.n] = 0; + if (cb(ctx, INO(K_USBLINK, b << 8, 0), 2, name)) + return 1; + } for (int i = 0; usb_info_nth(i, &u) == 0; i++) - if (cb(ctx, INO(K_USB, (u.bus << 8) | u.devnum, 0), 1, u.path)) + if (cb(ctx, INO(K_USBLINK, (u.bus << 8) | u.devnum, 0), 2, u.path)) return 1; break; } @@ -593,13 +660,41 @@ children(uint64 dino, child_fn cb, void *ctx) if (idx == boot_vga_idx() && gfx_fb_phys() && cb(ctx, INO(K_PCI, idx, PCI_GRAPHICS), 1, "graphics")) return 1; + if (cb(ctx, INO(K_PCI, idx, PCI_SUBSYS), 2, "subsystem")) + return 1; + if (idx < pci_count() && pci_get(idx)->driver && + cb(ctx, INO(K_PCI, idx, PCI_DRIVER), 2, "driver")) + return 1; + for (int b = 1; b <= usb_nbuses(); b++) + if (usb_hc_pci(b) == idx) { + char un[12] = "usb"; + struct out o = { un + 3, 0, 8 }; + putu(&o, (uint64)b); + un[3 + o.n] = 0; + if (cb(ctx, INO(K_USBROOT, b, 0), 1, un)) + return 1; + } + } else if (kind == K_USBROOT && I_FILE(dino) == 0) { + struct usb_info u; + for (int f = 1; f < NHUBFILES; f++) + if (cb(ctx, INO(K_USBROOT, idx, f), f == HUB_SUBSYS ? 2 : 0, hubfiles[f])) + return 1; + for (int i = 0; usb_info_nth(i, &u) == 0; i++) + if (u.bus == idx && + cb(ctx, INO(K_USB, (u.bus << 8) | u.devnum, 0), 1, u.path)) + return 1; } else if (kind == K_PCI && I_FILE(dino) == PCI_GRAPHICS) { if (cb(ctx, INO(K_PCI, idx, PCI_FB0), 1, "fb0")) return 1; } else if (kind == K_USB && I_FILE(dino) == 0) { - for (int f = 1; f < NUSBFILES; f++) - if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f])) + struct usb_info u; + int have = usb_by_idx(idx, &u) == 0; + for (int f = 1; f < NUSBFILES; f++) { + if (f == USB_DRIVER && !(have && u.driver[0])) + continue; + if (cb(ctx, INO(K_USB, idx, f), f >= USB_SUBSYS ? 2 : 0, usbfiles[f])) return 1; + } } else if (kind == K_PSU && I_FILE(dino) == 0) { struct acpi_psu b; if (acpi_psu_get(idx, &b) == 0) @@ -625,8 +720,14 @@ parent_of(uint64 dino) case K_PCI: if (I_FILE(dino) == PCI_GRAPHICS) return INO(K_PCI, I_IDX(dino), 0); if (I_FILE(dino) == PCI_FB0) return INO(K_PCI, I_IDX(dino), PCI_GRAPHICS); - return INO(K_DIR, D_PCIDEV, 0); - case K_USB: return INO(K_DIR, D_USBDEV, 0); + return INO(K_DIR, D_PCIROOT, 0); + case K_PCILINK: return INO(K_DIR, D_PCIDEV, 0); + case K_USBLINK: return INO(K_DIR, D_USBDEV, 0); + case K_USBROOT: { + int hc = usb_hc_pci(I_IDX(dino)); + return hc >= 0 ? INO(K_PCI, hc, 0) : INO(K_DIR, D_PCIROOT, 0); + } + case K_USB: return INO(K_USBROOT, I_IDX(dino) >> 8, 0); case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0); case K_PSU: return INO(K_DIR, D_PSU, 0); case K_KSUBV: return INO(K_DIR, D_VIRTUAL, 0); @@ -788,6 +889,131 @@ gen_usb(struct out *o, const struct usb_info *u, int f) putc_(o, '\n'); } +// --- where a device is, and how to say so ----------------------------------- + +// "devices/pci0000:00/0000:00:14.0/usb1": the directory of a root hub; with +// `path`, the device below it. No leading slash. Returns how many directories +// lie between it and /sys, which is how many "../" a link inside it needs. +static int +usb_dir(struct out *o, int bus, const char *path) +{ + int hc = usb_hc_pci(bus), depth = 3; + + puts_(o, "devices/pci0000:00/"); + if (hc >= 0) { + pci_name(o, pci_get(hc)); + putc_(o, '/'); + depth++; + } + puts_(o, "usb"); + putu(o, (uint64)bus); + if (path) { + putc_(o, '/'); + puts_(o, path); + depth++; + } + return depth; +} + +static void +updirs(struct out *o, int n) +{ + while (n-- > 0) + puts_(o, "../"); +} + +// The target of a symlink for the PCI or USB entry `ino`, or -1. +static int +link_text(uint64 ino, struct out *o) +{ + int idx = I_IDX(ino), f = I_FILE(ino); + struct usb_info u; + + switch (I_KIND(ino)) { + case K_PCILINK: + if (idx >= pci_count()) + return -1; + puts_(o, "../../../devices/pci0000:00/"); + pci_name(o, pci_get(idx)); + return o->n; + case K_PCI: + if (idx >= pci_count()) + return -1; + if (f == PCI_SUBSYS) { + puts_(o, "../../../bus/pci"); + return o->n; + } + if (f == PCI_DRIVER && pci_get(idx)->driver) { + puts_(o, "../../../bus/pci/drivers/"); + puts_(o, pci_get(idx)->driver); + return o->n; + } + return -1; + case K_USBLINK: + if ((idx & 0xFF) == 0) { + if (idx >> 8 < 1 || idx >> 8 > usb_nbuses()) + return -1; + puts_(o, "../../../"); + usb_dir(o, idx >> 8, 0); + return o->n; + } + if (usb_by_idx(idx, &u) < 0) + return -1; + puts_(o, "../../../"); + usb_dir(o, u.bus, u.path); + return o->n; + case K_USB: + if (usb_by_idx(idx, &u) < 0) + return -1; + if (f == USB_SUBSYS) { + struct out d = { o->p + o->n, 0, 0 }; // measure only + updirs(o, usb_dir(&d, u.bus, u.path)); + puts_(o, "bus/usb"); + return o->n; + } + if (f == USB_DRIVER && u.driver[0]) { + struct out d = { o->p + o->n, 0, 0 }; + updirs(o, usb_dir(&d, u.bus, u.path)); + puts_(o, "bus/usb/drivers/"); + puts_(o, u.driver); + return o->n; + } + return -1; + case K_USBROOT: + if (f == HUB_SUBSYS && idx >= 1 && idx <= usb_nbuses()) { + struct out d = { o->p + o->n, 0, 0 }; + updirs(o, usb_dir(&d, idx, 0)); + puts_(o, "bus/usb"); + return o->n; + } + return -1; + } + return -1; +} + +static void +gen_hub(struct out *o, int bus, int f) +{ + switch (f) { + case 1: + puts_(o, "DEVTYPE=usb_device\nPRODUCT=1d6b/3/"); + putx(o, 0x0601, 4, 0); + puts_(o, "\nTYPE=9/0/1\nBUSNUM="); + if (bus < 100) putc_(o, '0'); + if (bus < 10) putc_(o, '0'); + putu(o, (uint64)bus); + puts_(o, "\nDEVNUM=001"); + break; + case 3: puts_(o, "1d6b"); break; + case 4: puts_(o, "0003"); break; + case 5: putu(o, (uint64)bus); break; + case 6: putu(o, 1); break; + case 7: puts_(o, "09"); break; + case 8: puts_(o, "5000"); break; + } + putc_(o, '\n'); +} + // A text file, whole. Returns its length, or -1 if there is no such file. static int text(uint64 ino, char *buf, int cap) @@ -796,15 +1022,29 @@ text(uint64 ino, char *buf, int cap) int idx = I_IDX(ino), f = I_FILE(ino); switch (I_KIND(ino)) { + case K_PCILINK: + case K_USBLINK: + return link_text(ino, &o); case K_PCI: + if (f == PCI_SUBSYS || f == PCI_DRIVER) + return link_text(ino, &o); if (idx >= pci_count() || f < 1 || f >= NPCIFILES || f == PCI_CONFIG) return -1; gen_pci(&o, pci_get(idx), f); return o.n; + case K_USBROOT: + if (idx < 1 || idx > usb_nbuses() || f < 1 || f >= NHUBFILES) + return -1; + if (f == HUB_SUBSYS) + return link_text(ino, &o); + gen_hub(&o, idx, f); + return o.n; case K_USB: { struct usb_info u; if (usb_by_idx(idx, &u) < 0 || f < 1 || f >= NUSBFILES) return -1; + if (f == USB_SUBSYS || f == USB_DRIVER) + return link_text(ino, &o); gen_usb(&o, &u, f); return o.n; } @@ -895,6 +1135,77 @@ text(uint64 ino, char *buf, int cap) return -1; } +// --------------------------------------------------------------------------- +// Announcing devices +// +// A uevent is "action@/devpath", then NUL-separated KEY=value pairs: the +// four the kernel always sends, then whatever the device's own `uevent` file +// says. The same text serves a hot-plug event and the answer to +// `echo add > uevent`, so udev sees one thing however it learned of a device. +// --------------------------------------------------------------------------- + +static void +announce(const char *action, const char *devpath, const char *subsys, + const char *vars, int vlen) +{ + char msg[700]; + struct out o = { msg, 0, (int)sizeof msg - 1 }; + + puts_(&o, action); puts_(&o, "@/"); puts_(&o, devpath); putc_(&o, 0); + puts_(&o, "ACTION="); puts_(&o, action); putc_(&o, 0); + puts_(&o, "DEVPATH=/"); puts_(&o, devpath); putc_(&o, 0); + puts_(&o, "SUBSYSTEM="); puts_(&o, subsys); putc_(&o, 0); + puts_(&o, "SEQNUM="); putu(&o, kdev_seqnum()); putc_(&o, 0); + for (int i = 0; i < vlen; i++) + putc_(&o, vars[i] == '\n' ? 0 : vars[i]); + uevent_send(UEVENT_GROUP_KERNEL, msg, o.n, 0); +} + +void +sysfs_usb_uevent(const struct usb_info *u, const char *action) +{ + char path[96], vars[300]; + struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; + + usb_dir(&p, u->bus, u->path); + path[p.n] = 0; + gen_usb(&v, u, USB_UEVENT); + announce(action, path, "usb", vars, v.n); +} + +static void +pci_uevent(int idx, const char *action) +{ + char path[64], vars[400]; + struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; + + if (idx >= pci_count()) + return; + puts_(&p, "devices/pci0000:00/"); + pci_name(&p, pci_get(idx)); + path[p.n] = 0; + gen_pci(&v, pci_get(idx), 10); + announce(action, path, "pci", vars, v.n); +} + +static void +hub_uevent(int bus, const char *action) +{ + char path[96], vars[200]; + struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; + + usb_dir(&p, bus, 0); + path[p.n] = 0; + gen_hub(&v, bus, HUB_UEVENT); + announce(action, path, "usb", vars, v.n); +} + +void +sysfs_usb_root_uevent(int bus, const char *action) +{ + hub_uevent(bus, action); +} + // --------------------------------------------------------------------------- // fs_ops // --------------------------------------------------------------------------- @@ -913,6 +1224,8 @@ exists_dir(uint64 ino) struct usb_info u; return usb_by_idx(I_IDX(ino), &u) == 0; } + case K_USBROOT: + return I_IDX(ino) >= 1 && I_IDX(ino) <= usb_nbuses(); case K_MEMMAP: { struct e820_rec e; return e820_nth(I_IDX(ino), &e) == 0; @@ -939,8 +1252,12 @@ static int is_link(uint64 ino) { int k = I_KIND(ino); - return k == K_KCLINK || k == K_KDEVL || - (k == K_KDEV && I_FILE(ino) == KF_SUBSYSTEM); + int f = I_FILE(ino); + return k == K_KCLINK || k == K_KDEVL || k == K_PCILINK || k == K_USBLINK || + (k == K_KDEV && f == KF_SUBSYSTEM) || + (k == K_PCI && (f == PCI_SUBSYS || f == PCI_DRIVER)) || + (k == K_USB && (f == USB_SUBSYS || f == USB_DRIVER)) || + (k == K_USBROOT && f == HUB_SUBSYS); } static void @@ -991,7 +1308,8 @@ sysfs_iload(struct inode *ip) ip->type = T_FILE; if ((I_KIND(ino) == K_PCI && (I_FILE(ino) == PCI_ENABLE || I_FILE(ino) == 10 /* uevent */)) || - (I_KIND(ino) == K_USB && I_FILE(ino) == NUSBFILES - 1) || + (I_KIND(ino) == K_USB && I_FILE(ino) == USB_UEVENT) || + (I_KIND(ino) == K_USBROOT && I_FILE(ino) == HUB_UEVENT) || (I_KIND(ino) == K_KDEV && I_FILE(ino) == KF_UEVENT)) ip->rwmode = 3; return; @@ -1057,11 +1375,39 @@ sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) if (I_KIND(ip->inum) == K_PCI && I_FILE(ip->inum) == PCI_ENABLE) return (int)n; // `echo add > uevent`: udevadm trigger asks the kernel to say again what it - // said when the device appeared. Only the kernel's own devices are announced; - // a PCI or USB device's uevent accepts the write and says nothing, because - // their /sys paths are not the /devices tree an event's DEVPATH must name. - if (I_KIND(ip->inum) == K_PCI || I_KIND(ip->inum) == K_USB) - return (int)n; + // said when the device appeared. + { + int kind = I_KIND(ip->inum), f = I_FILE(ip->inum), idx = I_IDX(ip->inum); + int pcie = kind == K_PCI && f == 10; + int usbe = kind == K_USB && f == USB_UEVENT; + int hube = kind == K_USBROOT && f == HUB_UEVENT; + + if (pcie || usbe || hube) { + char act[16]; + uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1; + if (u) { + if (copy_from_user(act, s, k) < 0) + return -EFAULT; + } else { + memmove(act, (void *)s, k); + } + uint e = 0; + while (e < k && act[e] != '\n' && act[e] != ' ') + e++; + act[e] = 0; + const char *a = e ? act : "change"; + struct usb_info ui; + if (pcie) + pci_uevent(idx, a); + else if (usbe && usb_by_idx(idx, &ui) == 0) + sysfs_usb_uevent(&ui, a); + else if (hube) + hub_uevent(idx, a); + return (int)n; + } + if (kind == K_PCI || kind == K_USB) + return (int)n; + } if (I_KIND(ip->inum) == K_KDEV && I_FILE(ip->inum) == KF_UEVENT) { char act[16]; uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1;