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