PCI and USB devices in the /sys/devices tree, with uevents and USB hot-plug
PCI devices live at /sys/devices/pci0000:00/<addr> 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
parent
dee3affa3b
commit
8d836c66b2
8 changed files with 526 additions and 67 deletions
|
|
@ -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;
|
||||
|
||||
// "<bus>-<root port>[.<hub port>...]": 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;
|
||||
|
||||
// "<bus>-<root port>[.<hub port>...]": 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];
|
||||
|
|
|
|||
Loading…
Reference in a new issue