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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
Nexoniarz 2026-10-26 17:54:13 +01:00
commit d650ff433b
6 changed files with 461 additions and 42 deletions

View file

@ -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`.

View file

@ -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);

View file

@ -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

View file

@ -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));
}
// "<bus>-<root port>[.<hub port>...]": 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++){

View file

@ -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/<bridge>/.../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 || (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");
return o->n;
}
if (f == PCI_DRIVER && pci_get(idx)->driver) {
puts_(o, "../../../bus/pci/drivers/");
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)

View file

@ -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