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

@ -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++){