From d650ff433b191747b2950ece8ee3e6b8e78ee8f9 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Mon, 26 Oct 2026 17:54:13 +0100 Subject: [PATCH] udev: PCI bridges as parents, USB interfaces, the controller's own address, and the attributes lsusb reads PCI devices now nest under the bridge they sit behind (from each bridge's secondary..subordinate window), so a controller behind a root port is where Linux would put it. USB interfaces (1-5:1.0) are directories with class, number, a driver link and a uevent with INTERFACE= and a MODALIAS, announced after the drivers have been offered the device and removed before it. The xHCI driver tells the USB core its PCI address when it comes up, replacing the guess that bus N was the Nth controller. Devices and root hubs carry version, bMaxPacketSize0, bNumInterfaces, bmAttributes and the rest; lsusb -t from Alpine's usbutils now draws the tree. Checked on q35 with an xHCI behind a pcie-root-port and a USB keyboard on each controller. Plain lsusb still fails: libusb wants usbfs, and refuses a kernel that calls itself 0.1.0. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao --- docs/architecture/udev.md | 33 +++- include/core/hwinfo.h | 21 ++- kernel/drivers/bus/xhci/xhci.c | 2 +- kernel/drivers/usb/core/usb.c | 119 +++++++++++- kernel/fs/sysfs.c | 325 ++++++++++++++++++++++++++++++--- ports/alpine/world | 3 + 6 files changed, 461 insertions(+), 42 deletions(-) diff --git a/docs/architecture/udev.md b/docs/architecture/udev.md index 4b871e1..de24757 100644 --- a/docs/architecture/udev.md +++ b/docs/architecture/udev.md @@ -39,13 +39,22 @@ 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. +**PCI devices** are nested under the PCI-to-PCI bridge they sit behind: a +device's parent is the bridge whose secondary..subordinate bus range holds its +bus (the innermost if several do), read from the bridge's config space, so an +xHCI behind a root port is `pci0000:00/0000:00:04.0/0000:01:00.0`. **USB +devices** sit under the xHCI controller that carries their bus, below that +bus's root hub `usbN`; the controller tells the USB core its own PCI address +when it comes up, so the link is recorded, not guessed. A hub's children are +its siblings, not its children. + +**USB interfaces** (`1-5:1.0`) are directories below their device, with +`bInterfaceClass`, `bInterfaceNumber`, a `driver` link when one is bound, and a +`uevent` carrying `DEVTYPE=usb_interface`, `INTERFACE=` and a `MODALIAS=usb:...` +that names the interface's class. They are announced after the drivers have +been offered the device, so the event says which interface was taken, and +removed before the device. Devices also carry what `lsusb -t` and libusb read: +`version`, `bMaxPacketSize0`, `bNumInterfaces`, `bmAttributes`, `bMaxPower`. **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 @@ -108,9 +117,13 @@ Getting there found two kernel bugs, both in descriptor passing: ## What is not there -- 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. +- `/dev/bus/usb` nodes (usbfs). `lsusb -t` works because it reads sysfs; plain + `lsusb` goes through libusb, which also refuses to start while `uname -r` + says `0.1.0` (it wants Linux 2.6.32 or newer) and wants usbfs to open devices. +- A hub's port count (`maxchild` reads 0), and string descriptors + (`manufacturer`, `product`, `serial`). +- A USB keyboard works because every keyboard is `event0`; udev knows the + interface arrived but the input device does not hang below it. - `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 e93893d..e90a052 100644 --- a/include/core/hwinfo.h +++ b/include/core/hwinfo.h @@ -57,6 +57,10 @@ struct usb_info { uint16 vid, pid, bcd; uint8 cls, subcls, proto; // the device descriptor's uint8 nconf; + uint16 bcdusb; // the version the device speaks + uint8 maxp0; // bMaxPacketSize0 + uint8 nif, confval, attrs; // the configuration descriptor's + uint16 maxpower; // in mA char path[24]; // Linux's name: "1-3", "2-1.4" char driver[16]; // the first interface's, or empty }; @@ -66,5 +70,18 @@ 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); +// A USB controller is up, at this PCI address; number its bus and announce its +// root hub. +void usb_bus_add(void *hc, int pci_bus, int pci_slot, int pci_func); +// The PCI address of the controller behind bus number `bus`; 0, or -1 if none. +int usb_bus_pci(int bus, int *pci_bus, int *pci_slot, int *pci_func); + +// One interface of a configured USB device, in the order the configuration +// descriptor lists them (alternate setting 0 only). +struct usb_if_info { + uint8 num, cls, subcls, proto, nendp; + char driver[16]; // what is bound to it, or empty +}; +int usb_if_nth(int bus, int devnum, int n, struct usb_if_info *out); +void sysfs_usb_if_uevent(const struct usb_info *u, const struct usb_if_info *i, + const char *action); diff --git a/kernel/drivers/bus/xhci/xhci.c b/kernel/drivers/bus/xhci/xhci.c index bfe06e5..b0aff15 100644 --- a/kernel/drivers/bus/xhci/xhci.c +++ b/kernel/drivers/bus/xhci/xhci.c @@ -1677,7 +1677,7 @@ xhci_probe(struct pci_dev *d) // 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); + usb_bus_add(x, d->bus, d->slot, d->func); // Now, and only now, turn interrupts on. The order is: claim the line // (which unmasks it and sets level/active-low), clear anything the diff --git a/kernel/drivers/usb/core/usb.c b/kernel/drivers/usb/core/usb.c index e3d5476..517cb29 100644 --- a/kernel/drivers/usb/core/usb.c +++ b/kernel/drivers/usb/core/usb.c @@ -184,6 +184,7 @@ static struct spinlock present_lock; static int present_init; static struct usb_dev *present[NPRESENT]; static void *buses[NBUSES]; +static uint8 bus_pci[NBUSES][3]; // the controller's PCI bus, slot, function // 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. @@ -211,18 +212,103 @@ present_init_once(void) // 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) +usb_bus_add(void *hc, int pci_bus, int pci_slot, int pci_func) { int n; present_init_once(); acquire(&present_lock); n = bus_number(hc); + if(n){ + bus_pci[n - 1][0] = (uint8)pci_bus; + bus_pci[n - 1][1] = (uint8)pci_slot; + bus_pci[n - 1][2] = (uint8)pci_func; + } release(&present_lock); if(n) sysfs_usb_root_uevent(n, "add"); } +int +usb_bus_pci(int bus, int *pb, int *ps, int *pf) +{ + int r = -1; + + if(!present_init || bus < 1 || bus > NBUSES) + return -1; + acquire(&present_lock); + if(buses[bus - 1]){ + *pb = bus_pci[bus - 1][0]; + *ps = bus_pci[bus - 1][1]; + *pf = bus_pci[bus - 1][2]; + r = 0; + } + release(&present_lock); + return r; +} + +// The nth interface (alternate setting 0) in `dev`'s configuration descriptor. +// Returns 0, or -1 if there are fewer. The driver is whatever is bound to an +// interface of that number, on the device itself or on one of its interface +// slots. +static int +if_desc(struct usb_dev *dev, int nth, struct usb_if_info *o) +{ + int off = 0, count = 0; + + while(off + 2 <= dev->conflen){ + uint8 len = dev->conf[off]; + uint8 type = dev->conf[off + 1]; + + if(len < 2 || off + len > dev->conflen) + break; + if(type == USB_DT_INTERFACE && len >= (int)sizeof(struct usb_interface_desc)){ + struct usb_interface_desc *id = (struct usb_interface_desc *)&dev->conf[off]; + if(id->bAlternateSetting == 0 && count++ == nth){ + memset(o, 0, sizeof *o); + o->num = id->bInterfaceNumber; + o->cls = id->bInterfaceClass; + o->subcls = id->bInterfaceSubClass; + o->proto = id->bInterfaceProtocol; + o->nendp = id->bNumEndpoints; + if(dev->driver && dev->iface.bInterfaceNumber == o->num) + safestrcpy(o->driver, dev->driver, sizeof o->driver); + for(int k = 0; !o->driver[0] && k < NIFDEV; k++) + if(ifdevs[k].in_use && ifdevs[k].base == dev && ifdevs[k].driver && + ifdevs[k].iface.bInterfaceNumber == o->num) + safestrcpy(o->driver, ifdevs[k].driver, sizeof o->driver); + return 0; + } + } + off += len; + } + return -1; +} + +int +usb_if_nth(int bus, int devnum, int n, struct usb_if_info *o) +{ + int r = -1; + + if(!present_init) + return -1; + acquire(&present_lock); + for(int i = 0; i < NPRESENT; i++){ + struct usb_dev *d = present[i]; + if(d == 0 || d->slot != devnum) + continue; + int b = 1; + for(int k = 0; k < NBUSES; k++) + if(buses[k] == d->hc){ b = k + 1; break; } + if(b != bus) + continue; + r = if_desc(d, n, o); + break; + } + release(&present_lock); + return r; +} + static void present_add(struct usb_dev *dev) { @@ -273,6 +359,15 @@ fill_info(struct usb_dev *d, struct usb_info *o) o->subcls = d->desc.bDeviceSubClass; o->proto = d->desc.bDeviceProtocol; o->nconf = d->desc.bNumConfigurations; + o->bcdusb = d->desc.bcdUSB; + o->maxp0 = d->desc.bMaxPacketSize0; + if(d->conflen >= 9){ + o->nif = d->conf[4]; + o->confval = d->conf[5]; + o->attrs = d->conf[7]; + // bMaxPower counts 2 mA below SuperSpeed and 8 mA from it. + o->maxpower = (uint16)(d->conf[8] * (d->speed >= 4 ? 8 : 2)); + } // "-[....]": the route string holds one // downstream port per tier below the root, a nibble each. @@ -326,6 +421,8 @@ usb_nbuses(void) return n; } +static void announce_ifaces(struct usb_dev *dev, const char *action); + // 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. @@ -428,6 +525,25 @@ usb_attach(struct usb_dev *dev) d->in_use = 0; // nothing will ever use it; give it back } } + + // Every interface, once all of them have been offered to the drivers, so the + // event says which one was taken. + announce_ifaces(dev, "add"); +} + +static void +announce_ifaces(struct usb_dev *dev, const char *action) +{ + struct usb_info u; + struct usb_if_info ii; + + if(!present_init) + return; + acquire(&present_lock); + fill_info(dev, &u); + release(&present_lock); + for(int n = 0; if_desc(dev, n, &ii) == 0; n++) + sysfs_usb_if_uevent(&u, &ii, action); } // Tell every driver bound to any interface of `dev` that it is gone, and give @@ -435,6 +551,7 @@ usb_attach(struct usb_dev *dev) void usb_detach(struct usb_dev *dev) { + announce_ifaces(dev, "remove"); announce(dev, "remove"); present_del(dev); for(int i = 0; i < NIFDEV; i++){ diff --git a/kernel/fs/sysfs.c b/kernel/fs/sysfs.c index 639d8c7..d72a070 100644 --- a/kernel/fs/sysfs.c +++ b/kernel/fs/sysfs.c @@ -78,6 +78,8 @@ #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_USBIF 18ULL // idx devidx << 8 | interface number: 1-5:1.0 +#define K_USBIFLN 19ULL // the same, as the symlink in /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). @@ -167,6 +169,10 @@ static const char *const usbfiles[] = { 0, "idVendor", "idProduct", "bcdDevice", "busnum", "devnum", "speed", "bDeviceClass", "bDeviceSubClass", "bDeviceProtocol", "bNumConfigurations", "uevent", "subsystem", "driver", + // What lsusb and libusb read from sysfs rather than from the device. + "version", "bMaxPacketSize0", "bNumInterfaces", "bConfigurationValue", + "bmAttributes", "bMaxPower", "configuration", "maxchild", "devpath", + "rx_lanes", "tx_lanes", }; #define NUSBFILES ((int)(sizeof usbfiles / sizeof usbfiles[0])) #define USB_UEVENT 11 @@ -176,12 +182,25 @@ static const char *const usbfiles[] = { // 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", + "bDeviceClass", "speed", "version", "bMaxPacketSize0", "bNumInterfaces", + "bConfigurationValue", "bmAttributes", "bMaxPower", "configuration", + "maxchild", "devpath", "rx_lanes", "tx_lanes", "bDeviceSubClass", + "bDeviceProtocol", "bcdDevice", }; #define NHUBFILES ((int)(sizeof hubfiles / sizeof hubfiles[0])) #define HUB_UEVENT 1 #define HUB_SUBSYS 2 +// A USB interface: 1-5:1.0, a directory of its own below its device. +static const char *const ifiles[] = { + 0, "uevent", "subsystem", "driver", "bInterfaceClass", "bInterfaceSubClass", + "bInterfaceProtocol", "bInterfaceNumber", "bNumEndpoints", "bAlternateSetting", +}; +#define NIFILES ((int)(sizeof ifiles / sizeof ifiles[0])) +#define IF_UEVENT 1 +#define IF_SUBSYS 2 +#define IF_DRIVER 3 + static const char *const memfiles[] = { 0, "start", "end", "type" }; // Linux's power_supply attributes. Which of them a battery has depends on @@ -300,24 +319,69 @@ 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. +// Interface number `num` of the device `idx` (bus << 8 | devnum). +static int +usb_if_by_num(int idx, int num, struct usb_if_info *ii) +{ + for (int n = 0; usb_if_nth(idx >> 8, idx & 0xFF, n, ii) == 0; n++) + if (ii->num == num) + return 0; + return -1; +} + +// "1-5:1.0" +static void +usb_if_name(struct out *o, const struct usb_info *u, int num) +{ + puts_(o, u->path); + puts_(o, ":1."); + putu(o, (uint64)num); +} + +// The PCI device a USB bus is: the controller recorded its own address when it +// came up (usb_bus_add). -1 if there is no such controller. static int usb_hc_pci(int bus) { - int n = 0; + int pb, ps, pf; + + if (usb_bus_pci(bus, &pb, &ps, &pf) < 0) + return -1; 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) + if (d->bus == pb && d->slot == ps && d->func == pf) return i; } return -1; } +// The PCI-to-PCI bridge a device sits behind: the one whose secondary.. +// subordinate bus range holds the device's bus, the innermost if several do. +// -1 for a device on the root bus. The bridges' own windows are read from +// config space each time; there are a handful of devices and this runs when a +// directory is listed. +static int +pci_parent(int idx) +{ + struct pci_dev *d = pci_get(idx); + int best = -1, bestsec = -1; + + for (int i = 0; i < pci_count(); i++) { + struct pci_dev *b = pci_get(i); + if (i == idx || (b->header_type & 0x7F) != 1) + continue; + uint32 w = pci_read32(b->bus, b->slot, b->func, 0x18); + int sec = (w >> 8) & 0xFF, sub = (w >> 16) & 0xFF; + if (sec == 0 || d->bus < sec || d->bus > sub || sec <= b->bus) + continue; + if (sec > bestsec) { + best = i; + bestsec = sec; + } + } + return best; +} + // BAT0, BAT1 for batteries and AC, AC1 for adapters, numbered in the order // ACPI lists them -- the names Linux gives most laptops. static void @@ -578,7 +642,8 @@ children(uint64 dino, child_fn cb, void *ctx) pci_name(&o, pci_get(i)); name[o.n] = 0; if (idx == D_PCIDEV ? cb(ctx, INO(K_PCILINK, i, 0), 2, name) - : cb(ctx, INO(K_PCI, i, 0), 1, name)) + : pci_parent(i) < 0 && + cb(ctx, INO(K_PCI, i, 0), 1, name)) return 1; } // A bus whose controller we cannot place hangs off the root instead. @@ -601,9 +666,19 @@ children(uint64 dino, child_fn cb, void *ctx) if (cb(ctx, INO(K_USBLINK, b << 8, 0), 2, name)) return 1; } - for (int i = 0; usb_info_nth(i, &u) == 0; i++) + for (int i = 0; usb_info_nth(i, &u) == 0; i++) { + struct usb_if_info ii; if (cb(ctx, INO(K_USBLINK, (u.bus << 8) | u.devnum, 0), 2, u.path)) return 1; + for (int n = 0; usb_if_nth(u.bus, u.devnum, n, &ii) == 0; n++) { + struct out o = { name, 0, sizeof name - 1 }; + usb_if_name(&o, &u, ii.num); + name[o.n] = 0; + if (cb(ctx, INO(K_USBIFLN, (((u.bus << 8) | u.devnum) << 8) | ii.num, 0), + 2, name)) + return 1; + } + } break; } case D_ACPITAB: @@ -662,6 +737,14 @@ children(uint64 dino, child_fn cb, void *ctx) return 1; if (cb(ctx, INO(K_PCI, idx, PCI_SUBSYS), 2, "subsystem")) return 1; + for (int i = 0; i < pci_count(); i++) + if (i != idx && pci_parent(i) == idx) { + 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)) + return 1; + } if (idx < pci_count() && pci_get(idx)->driver && cb(ctx, INO(K_PCI, idx, PCI_DRIVER), 2, "driver")) return 1; @@ -692,7 +775,28 @@ children(uint64 dino, child_fn cb, void *ctx) 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])) + if (cb(ctx, INO(K_USB, idx, f), f == USB_SUBSYS || f == USB_DRIVER ? 2 : 0, + usbfiles[f])) + return 1; + } + for (int n = 0; have; n++) { + struct usb_if_info ii; + if (usb_if_nth(u.bus, u.devnum, n, &ii) < 0) + break; + struct out o = { name, 0, sizeof name - 1 }; + usb_if_name(&o, &u, ii.num); + name[o.n] = 0; + if (cb(ctx, INO(K_USBIF, (idx << 8) | ii.num, 0), 1, name)) + return 1; + } + } else if (kind == K_USBIF && I_FILE(dino) == 0) { + struct usb_if_info ii; + int have = usb_if_by_num(idx >> 8, idx & 0xFF, &ii) == 0; + for (int f = 1; have && f < NIFILES; f++) { + if (f == IF_DRIVER && !ii.driver[0]) + continue; + if (cb(ctx, INO(K_USBIF, idx, f), f == IF_SUBSYS || f == IF_DRIVER ? 2 : 0, + ifiles[f])) return 1; } } else if (kind == K_PSU && I_FILE(dino) == 0) { @@ -720,6 +824,8 @@ 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); + if (I_IDX(dino) < pci_count() && pci_parent(I_IDX(dino)) >= 0) + return INO(K_PCI, pci_parent(I_IDX(dino)), 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); @@ -728,6 +834,8 @@ parent_of(uint64 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_USBIF: return INO(K_USB, I_IDX(dino) >> 8, 0); + case K_USBIFLN: return INO(K_DIR, D_USBDEV, 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); @@ -869,6 +977,24 @@ gen_usb(struct out *o, const struct usb_info *u, int f) case 8: putx(o, u->subcls, 2, 0); break; case 9: putx(o, u->proto, 2, 0); break; case 10: putu(o, u->nconf); break; + case 14: // version: " 2.00", as Linux pads it + putc_(o, (u->bcdusb >> 8) > 9 ? '1' : ' '); + putx(o, (u->bcdusb >> 8) > 9 ? (u->bcdusb >> 8) - 10 : (u->bcdusb >> 8), 1, 0); + putc_(o, '.'); putx(o, u->bcdusb & 0xFF, 2, 0); break; + case 15: putu(o, u->maxp0); break; + case 16: putc_(o, ' '); putu(o, u->nif); break; + case 17: putu(o, u->confval); break; + case 18: putx(o, u->attrs, 2, 0); break; + case 19: putu(o, u->maxpower); puts_(o, "mA"); break; + case 20: break; // configuration: no string descriptor read + case 21: putu(o, 0); break; // maxchild: a hub's port count is not recorded + case 22: { // devpath: the port chain, "5" or "5.2" + const char *q = u->path; + while (*q && *q != '-') q++; + puts_(o, *q ? q + 1 : q); + break; + } + case 23: case 24: putu(o, 1); break; case 11: puts_(o, "DEVTYPE=usb_device\n"); if (u->driver[0]) { puts_(o, "DRIVER="); puts_(o, u->driver); putc_(o, '\n'); } @@ -884,26 +1010,87 @@ gen_usb(struct out *o, const struct usb_info *u, int f) if (u->devnum < 100) putc_(o, '0'); if (u->devnum < 10) putc_(o, '0'); putu(o, (uint64)u->devnum); + puts_(o, "\nMODALIAS=usb:v"); putx(o, u->vid, 4, 1); + puts_(o, "p"); putx(o, u->pid, 4, 1); + puts_(o, "d"); putx(o, u->bcd, 4, 1); + puts_(o, "dc"); putx(o, u->cls, 2, 1); + puts_(o, "dsc"); putx(o, u->subcls, 2, 1); + puts_(o, "dp"); putx(o, u->proto, 2, 1); + puts_(o, "ic*isc*ip*in*"); break; } putc_(o, '\n'); } +// An interface's own uevent: what udev needs to load a driver by alias and to +// tell a keyboard from a hub. +static void +gen_usbif(struct out *o, const struct usb_info *u, const struct usb_if_info *ii, int f) +{ + switch (f) { + case IF_UEVENT: + puts_(o, "DEVTYPE=usb_interface\n"); + if (ii->driver[0]) { puts_(o, "DRIVER="); puts_(o, ii->driver); putc_(o, '\n'); } + puts_(o, "PRODUCT="); putx(o, u->vid, 0, 0); putc_(o, '/'); + putx(o, u->pid, 0, 0); putc_(o, '/'); putx(o, u->bcd, 0, 0); + puts_(o, "\nTYPE="); putu(o, u->cls); putc_(o, '/'); + putu(o, u->subcls); putc_(o, '/'); putu(o, u->proto); + puts_(o, "\nINTERFACE="); putu(o, ii->cls); putc_(o, '/'); + putu(o, ii->subcls); putc_(o, '/'); putu(o, ii->proto); + puts_(o, "\nMODALIAS=usb:v"); putx(o, u->vid, 4, 1); + puts_(o, "p"); putx(o, u->pid, 4, 1); + puts_(o, "d"); putx(o, u->bcd, 4, 1); + puts_(o, "dc"); putx(o, u->cls, 2, 1); + puts_(o, "dsc"); putx(o, u->subcls, 2, 1); + puts_(o, "dp"); putx(o, u->proto, 2, 1); + puts_(o, "ic"); putx(o, ii->cls, 2, 1); + puts_(o, "isc"); putx(o, ii->subcls, 2, 1); + puts_(o, "ip"); putx(o, ii->proto, 2, 1); + puts_(o, "in"); putx(o, ii->num, 2, 1); + break; + case 4: putx(o, ii->cls, 2, 0); break; + case 5: putx(o, ii->subcls, 2, 0); break; + case 6: putx(o, ii->proto, 2, 0); break; + case 7: putx(o, ii->num, 2, 0); break; + case 8: putx(o, ii->nendp, 2, 0); break; + case 9: putu(o, 0); break; + } + 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. +// "devices/pci0000:00//.../0000:00:1f.2": a PCI device's directory, +// below its bridges. Returns the depth below /sys, for the ../ of a link. +static int +pci_dir(struct out *o, int idx) +{ + int par = pci_parent(idx), depth; + + if (par < 0) { + puts_(o, "devices/pci0000:00/"); + depth = 2; + } else { + depth = pci_dir(o, par); + putc_(o, '/'); + } + pci_name(o, pci_get(idx)); + return depth + 1; +} + 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)); + depth = pci_dir(o, hc) + 1; putc_(o, '/'); - depth++; + } else { + puts_(o, "devices/pci0000:00/"); } puts_(o, "usb"); putu(o, (uint64)bus); @@ -933,19 +1120,21 @@ link_text(uint64 ino, struct out *o) case K_PCILINK: if (idx >= pci_count()) return -1; - puts_(o, "../../../devices/pci0000:00/"); - pci_name(o, pci_get(idx)); + puts_(o, "../../../"); + pci_dir(o, 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); + if (f == PCI_SUBSYS || (f == PCI_DRIVER && pci_get(idx)->driver)) { + struct out d = { o->p + o->n, 0, 0 }; // measure only + updirs(o, pci_dir(&d, idx)); + if (f == PCI_SUBSYS) { + puts_(o, "bus/pci"); + } else { + puts_(o, "bus/pci/drivers/"); + puts_(o, pci_get(idx)->driver); + } return o->n; } return -1; @@ -979,6 +1168,33 @@ link_text(uint64 ino, struct out *o) return o->n; } return -1; + case K_USBIFLN: { + struct usb_if_info ii; + if (usb_by_idx(idx >> 8, &u) < 0 || usb_if_by_num(idx >> 8, idx & 0xFF, &ii) < 0) + return -1; + puts_(o, "../../../"); + usb_dir(o, u.bus, u.path); + putc_(o, '/'); + usb_if_name(o, &u, ii.num); + return o->n; + } + case K_USBIF: { + struct usb_if_info ii; + if (usb_by_idx(idx >> 8, &u) < 0 || usb_if_by_num(idx >> 8, idx & 0xFF, &ii) < 0) + return -1; + if (f == IF_SUBSYS || (f == IF_DRIVER && ii.driver[0])) { + struct out d = { o->p + o->n, 0, 0 }; + updirs(o, usb_dir(&d, u.bus, u.path) + 1); + if (f == IF_SUBSYS) { + puts_(o, "bus/usb"); + } else { + puts_(o, "bus/usb/drivers/"); + puts_(o, ii.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 }; @@ -1010,6 +1226,19 @@ gen_hub(struct out *o, int bus, int f) case 6: putu(o, 1); break; case 7: puts_(o, "09"); break; case 8: puts_(o, "5000"); break; + case 9: puts_(o, " 3.00"); break; + case 10: puts_(o, "9"); break; + case 11: puts_(o, " 1"); break; + case 12: puts_(o, "1"); break; + case 13: puts_(o, "e0"); break; + case 14: puts_(o, "0mA"); break; + case 15: break; + case 16: puts_(o, "0"); break; + case 17: puts_(o, "0"); break; + case 18: case 19: puts_(o, "1"); break; + case 20: puts_(o, "00"); break; + case 21: puts_(o, "03"); break; + case 22: puts_(o, "0601"); break; } putc_(o, '\n'); } @@ -1032,6 +1261,19 @@ text(uint64 ino, char *buf, int cap) return -1; gen_pci(&o, pci_get(idx), f); return o.n; + case K_USBIFLN: + return link_text(ino, &o); + case K_USBIF: { + struct usb_info u; + struct usb_if_info ii; + if (usb_by_idx(idx >> 8, &u) < 0 || usb_if_by_num(idx >> 8, idx & 0xFF, &ii) < 0 || + f < 1 || f >= NIFILES) + return -1; + if (f == IF_SUBSYS || f == IF_DRIVER) + return link_text(ino, &o); + gen_usbif(&o, &u, &ii, f); + return o.n; + } case K_USBROOT: if (idx < 1 || idx > usb_nbuses() || f < 1 || f >= NHUBFILES) return -1; @@ -1173,16 +1415,30 @@ sysfs_usb_uevent(const struct usb_info *u, const char *action) announce(action, path, "usb", vars, v.n); } +void +sysfs_usb_if_uevent(const struct usb_info *u, const struct usb_if_info *ii, + const char *action) +{ + char path[128], vars[400]; + struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; + + usb_dir(&p, u->bus, u->path); + putc_(&p, '/'); + usb_if_name(&p, u, ii->num); + path[p.n] = 0; + gen_usbif(&v, u, ii, IF_UEVENT); + announce(action, path, "usb", vars, v.n); +} + static void pci_uevent(int idx, const char *action) { - char path[64], vars[400]; + char path[128], 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)); + pci_dir(&p, idx); path[p.n] = 0; gen_pci(&v, pci_get(idx), 10); announce(action, path, "pci", vars, v.n); @@ -1226,6 +1482,10 @@ exists_dir(uint64 ino) } case K_USBROOT: return I_IDX(ino) >= 1 && I_IDX(ino) <= usb_nbuses(); + case K_USBIF: { + struct usb_if_info ii; + return usb_if_by_num(I_IDX(ino) >> 8, I_IDX(ino) & 0xFF, &ii) == 0; + } case K_MEMMAP: { struct e820_rec e; return e820_nth(I_IDX(ino), &e) == 0; @@ -1254,6 +1514,8 @@ is_link(uint64 ino) int k = I_KIND(ino); int f = I_FILE(ino); return k == K_KCLINK || k == K_KDEVL || k == K_PCILINK || k == K_USBLINK || + k == K_USBIFLN || + (k == K_USBIF && (f == IF_SUBSYS || f == IF_DRIVER)) || (k == K_KDEV && f == KF_SUBSYSTEM) || (k == K_PCI && (f == PCI_SUBSYS || f == PCI_DRIVER)) || (k == K_USB && (f == USB_SUBSYS || f == USB_DRIVER)) || @@ -1310,6 +1572,7 @@ sysfs_iload(struct inode *ip) I_FILE(ino) == 10 /* uevent */)) || (I_KIND(ino) == K_USB && I_FILE(ino) == USB_UEVENT) || (I_KIND(ino) == K_USBROOT && I_FILE(ino) == HUB_UEVENT) || + (I_KIND(ino) == K_USBIF && I_FILE(ino) == IF_UEVENT) || (I_KIND(ino) == K_KDEV && I_FILE(ino) == KF_UEVENT)) ip->rwmode = 3; return; @@ -1381,8 +1644,9 @@ sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) int pcie = kind == K_PCI && f == 10; int usbe = kind == K_USB && f == USB_UEVENT; int hube = kind == K_USBROOT && f == HUB_UEVENT; + int ife = kind == K_USBIF && f == IF_UEVENT; - if (pcie || usbe || hube) { + if (pcie || usbe || hube || ife) { char act[16]; uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1; if (u) { @@ -1403,6 +1667,11 @@ sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) sysfs_usb_uevent(&ui, a); else if (hube) hub_uevent(idx, a); + else if (ife) { + struct usb_if_info ii; + if (usb_by_idx(idx >> 8, &ui) == 0 && usb_if_by_num(idx >> 8, idx & 0xFF, &ii) == 0) + sysfs_usb_if_uevent(&ui, &ii, a); + } return (int)n; } if (kind == K_PCI || kind == K_USB) diff --git a/ports/alpine/world b/ports/alpine/world index 214f187..ac0ebd2 100644 --- a/ports/alpine/world +++ b/ports/alpine/world @@ -78,3 +78,6 @@ seatd seatd-launch # lsusb that reads /sys/bus/usb/devices, which busybox's does not. usbutils + +# usb.ids, which lsusb reads to name devices. +hwdata-usb