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:
Nexoniarz 2026-10-26 15:51:28 +01:00
commit 8d836c66b2
8 changed files with 526 additions and 67 deletions

View file

@ -252,8 +252,8 @@ mounted by init with `mount("sysfs", "/sys", "sysfs", 0, 0)`.
| path | contents | | path | contents |
|------|----------| |------|----------|
| `/sys/bus/pci/devices/0000:BB:DD.F/` | `vendor device class revision subsystem_vendor subsystem_device irq resource config uevent` | | `/sys/devices/pci0000:00/0000:BB:DD.F/` (and `/sys/bus/pci/devices/` links to it) | `vendor device class revision subsystem_vendor subsystem_device irq resource config uevent subsystem driver` |
| `/sys/bus/usb/devices/B-P[.P...]/` | `idVendor idProduct bcdDevice busnum devnum speed bDeviceClass bDeviceSubClass bDeviceProtocol bNumConfigurations uevent` | | `/sys/devices/pci0000:00/<xHCI>/usbB/B-P[.P...]/` (and `/sys/bus/usb/devices/` links) | `idVendor idProduct bcdDevice busnum devnum speed bDeviceClass bDeviceSubClass bDeviceProtocol bNumConfigurations uevent subsystem driver` |
| `/sys/firmware/acpi/tables/` | each ACPI table, raw; a repeated signature is numbered from 1 (`SSDT1`, `SSDT2`) | | `/sys/firmware/acpi/tables/` | each ACPI table, raw; a repeated signature is numbered from 1 (`SSDT1`, `SSDT2`) |
| `/sys/firmware/dmi/tables/` | `smbios_entry_point` and `DMI`, raw | | `/sys/firmware/dmi/tables/` | `smbios_entry_point` and `DMI`, raw |
| `/sys/firmware/memmap/N/` | `start end type`, the firmware's map as the loader handed it over | | `/sys/firmware/memmap/N/` | `start end type`, the firmware's map as the loader handed it over |
@ -281,9 +281,12 @@ link, `/sys/class/<class>/<name>` and `/sys/dev/{char,block}/MAJ:MIN` as symlink
into it, and input capabilities; `uevent` is writable, and a write is announced into it, and input capabilities; `uevent` is writable, and a write is announced
on the uevent netlink socket ([udev.md](udev.md)). on the uevent netlink socket ([udev.md](udev.md)).
What is missing, compared with Linux: PCI and USB devices in that tree (the PCI and USB devices are in that tree too, under `/sys/devices/pci0000:00`, with
`bus/*/devices` entries are directories rather than links into it), the `bus/*/devices` entries as links into it and `add`/`remove` events for USB
`/sys/class` entries for them, `/sys/block`, and most of what is writable. PCI `config` is hot-plug ([udev.md](udev.md)).
What is missing, compared with Linux: PCI bridges as parents, USB interfaces,
`/sys/class` entries for most devices, `/sys/block`, and most of what is writable. PCI `config` is
256 bytes, because config space is read through port `0xCF8`, which reaches 256 bytes, because config space is read through port `0xCF8`, which reaches
no further. no further.

View file

@ -22,6 +22,10 @@ internal ones: `/dev/tty1` is `4:1` to a Linux program (`linux_tty_rdev` in
## `/sys`: `kernel/fs/sysfs.c` ## `/sys`: `kernel/fs/sysfs.c`
``` ```
/sys/devices/pci0000:00/0000:00:1f.2/ vendor device class ... uevent subsystem -> ../../../bus/pci driver
/sys/devices/pci0000:00/0000:00:14.0/usb1/1-3 idVendor idProduct ... uevent subsystem -> ../../../../../bus/usb
/sys/bus/pci/devices/0000:00:1f.2 -> ../../../devices/pci0000:00/0000:00:1f.2
/sys/bus/usb/devices/1-3 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-3
/sys/devices/virtual/input/input0/ name capabilities/{ev,key,rel,abs} properties /sys/devices/virtual/input/input0/ name capabilities/{ev,key,rel,abs} properties
/sys/devices/virtual/input/input0/event0/ dev uevent subsystem -> ../../../../../class/input /sys/devices/virtual/input/input0/event0/ dev uevent subsystem -> ../../../../../class/input
/sys/class/input/event0 -> ../../devices/virtual/input/input0/event0 /sys/class/input/event0 -> ../../devices/virtual/input/input0/event0
@ -31,9 +35,22 @@ internal ones: `/dev/tty1` is `4:1` to a Linux program (`linux_tty_rdev` in
Symlinks are real (`T_SYMLINK` inodes whose target is generated). `uevent` is Symlinks are real (`T_SYMLINK` inodes whose target is generated). `uevent` is
readable (`DEVPATH=`, `SUBSYSTEM=`, `MAJOR=`, `MINOR=`, `DEVNAME=`) and readable (`DEVPATH=`, `SUBSYSTEM=`, `MAJOR=`, `MINOR=`, `DEVNAME=`) and
writable: `echo add > uevent` — which is what `udevadm trigger` does — makes the writable: `echo add > uevent` — which is what `udevadm trigger` does — makes the
kernel announce the device again. A write to a PCI or USB device's `uevent` is kernel announce the device again. PCI and USB devices announce the same way:
accepted and says nothing yet, because those devices are not in the `MODALIAS`, `PCI_ID`, `PCI_CLASS`, `PCI_SLOT_NAME` for a PCI device, `PRODUCT`,
`/sys/devices` tree and an event's `DEVPATH` has to name something that exists. `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.
**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
up. With `udevadm monitor`, plugging in a mouse prints the kernel event and
then the same event after udev's rules have run. PCI has no hot-plug here.
The input capability files are what `input_id` reads to decide The input capability files are what `input_id` reads to decide
`ID_INPUT_KEYBOARD` or `ID_INPUT_MOUSE`. SchOS has two input devices whatever `ID_INPUT_KEYBOARD` or `ID_INPUT_MOUSE`. SchOS has two input devices whatever
@ -65,9 +82,9 @@ and regular files in `/dev` are refused.
## What is not there ## What is not there
- PCI and USB devices in `/sys/devices`, and `add`/`remove` events from the - USB interfaces (`1-3:1.0`), `/dev/bus/usb` nodes, and PCI bridges as parents.
hotplug thread. A USB keyboard works because every keyboard is `event0`, not A USB keyboard works because every keyboard is `event0`; udev now knows the
because udev knew it arrived. device arrived but not which interface became the keyboard.
- `hwdb` (`/etc/udev/hwdb.bin`): `udevadm hwdb --update` has not been run in the - `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 image, so `60-input-id` logs "returned non-zero" for the hwdb import. The
properties that matter come from `input_id`. properties that matter come from `input_id`.

View file

@ -61,3 +61,10 @@ struct usb_info {
char driver[16]; // the first interface's, or empty char driver[16]; // the first interface's, or empty
}; };
int usb_info_nth(int i, struct usb_info *out); int usb_info_nth(int i, struct usb_info *out);
// How many USB buses have shown a device so far; bus numbers run 1..this.
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);

View file

@ -40,5 +40,7 @@ int kdev_path(int i, char *buf, int cap);
// Announce a device on the uevent socket: action is "add", "remove", "change". // Announce a device on the uevent socket: action is "add", "remove", "change".
void kdev_uevent(int i, const char *action); void kdev_uevent(int i, const char *action);
// The next SEQNUM: events from every source (kdev, PCI, USB) share one count.
uint kdev_seqnum(void);
// What reading a device's `uevent` file says. // What reading a device's `uevent` file says.
int kdev_uevent_text(int i, char *buf, int cap); int kdev_uevent_text(int i, char *buf, int cap);

View file

@ -146,6 +146,13 @@ kdev_path(int i, char *buf, int cap)
static uint kseq; static uint kseq;
// One counter for every uevent the kernel sends, whoever builds it.
uint
kdev_seqnum(void)
{
return ++kseq;
}
static int static int
put(char *b, int cap, int n, const char *s) put(char *b, int cap, int n, const char *s)
{ {
@ -190,7 +197,7 @@ vars(int i, char *buf, int cap, const char *action, int nl)
buf[n++] = sep; buf[n++] = sep;
if (action) { if (action) {
n = put(buf, cap, n, "SEQNUM="); n = put(buf, cap, n, "SEQNUM=");
n = putnum(buf, cap, n, ++kseq); n = putnum(buf, cap, n, kdev_seqnum());
buf[n++] = sep; buf[n++] = sep;
} }
if (k.devname[0]) { if (k.devname[0]) {

View file

@ -32,6 +32,7 @@
#include "fs/file.h" #include "fs/file.h"
#include "dev/pci.h" #include "dev/pci.h"
#include "dev/usb.h" #include "dev/usb.h"
#include "core/hwinfo.h"
#include "xhci.h" #include "xhci.h"
#define RING_TRBS 64 // TRBs per ring, including the link #define RING_TRBS 64 // TRBs per ring, including the link
@ -1673,6 +1674,11 @@ xhci_probe(struct pci_dev *d)
kinfo("xhci: no devices attached\n"); kinfo("xhci: no devices attached\n");
// 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);
// Now, and only now, turn interrupts on. The order is: claim the line // Now, and only now, turn interrupts on. The order is: claim the line
// (which unmasks it and sets level/active-low), clear anything the // (which unmasks it and sets level/active-low), clear anything the
// controller already considers pending so that it drives INTx low, and then // controller already considers pending so that it drives INTx low, and then

View file

@ -185,20 +185,52 @@ static int present_init;
static struct usb_dev *present[NPRESENT]; static struct usb_dev *present[NPRESENT];
static void *buses[NBUSES]; 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 static void
present_add(struct usb_dev *dev) present_init_once(void)
{ {
if(!present_init){ if(!present_init){
initlock(&present_lock, "usbpresent"); initlock(&present_lock, "usbpresent");
present_init = 1; 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); acquire(&present_lock);
for(int i = 0; i < NPRESENT; i++) for(int i = 0; i < NPRESENT; i++)
if(present[i] == 0){ present[i] = dev; break; } if(present[i] == 0){ present[i] = dev; break; }
for(int b = 0; b < NBUSES; b++){ bus_number(dev->hc);
if(buses[b] == dev->hc) break;
if(buses[b] == 0){ buses[b] = dev->hc; break; }
}
release(&present_lock); release(&present_lock);
} }
@ -223,17 +255,11 @@ put_num(char *out, int *n, int cap, int v)
out[*n] = 0; out[*n] = 0;
} }
int // Copy `d`'s identity out. Caller holds present_lock, or is the one thread
usb_info_nth(int idx, struct usb_info *o) // that owns `d` (attach and detach).
static void
fill_info(struct usb_dev *d, struct usb_info *o)
{ {
if(!present_init)
return -1;
acquire(&present_lock);
int seen = 0;
for(int i = 0; i < NPRESENT; i++){
struct usb_dev *d = present[i];
if(d == 0 || seen++ != idx)
continue;
memset(o, 0, sizeof *o); memset(o, 0, sizeof *o);
o->bus = 1; o->bus = 1;
for(int b = 0; b < NBUSES; b++) for(int b = 0; b < NBUSES; b++)
@ -265,6 +291,20 @@ usb_info_nth(int idx, struct usb_info *o)
drv = ifdevs[k].driver; drv = ifdevs[k].driver;
if(drv) if(drv)
safestrcpy(o->driver, drv, sizeof o->driver); safestrcpy(o->driver, drv, sizeof o->driver);
}
int
usb_info_nth(int idx, struct usb_info *o)
{
if(!present_init)
return -1;
acquire(&present_lock);
int seen = 0;
for(int i = 0; i < NPRESENT; i++){
struct usb_dev *d = present[i];
if(d == 0 || seen++ != idx)
continue;
fill_info(d, o);
release(&present_lock); release(&present_lock);
return 0; return 0;
} }
@ -272,6 +312,35 @@ usb_info_nth(int idx, struct usb_info *o)
return -1; 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 void
usb_attach(struct usb_dev *dev) usb_attach(struct usb_dev *dev)
{ {
@ -282,6 +351,7 @@ usb_attach(struct usb_dev *dev)
if(buf == 0) if(buf == 0)
return; return;
present_add(dev); present_add(dev);
announce(dev, "add");
// Nine bytes first, because wTotalLength is in them and there is no other // Nine bytes first, because wTotalLength is in them and there is no other
// way to know how much to ask for. // way to know how much to ask for.
@ -365,6 +435,7 @@ usb_attach(struct usb_dev *dev)
void void
usb_detach(struct usb_dev *dev) usb_detach(struct usb_dev *dev)
{ {
announce(dev, "remove");
present_del(dev); present_del(dev);
for(int i = 0; i < NIFDEV; i++){ for(int i = 0; i < NIFDEV; i++){
struct usb_dev *d = &ifdevs[i]; struct usb_dev *d = &ifdevs[i];

View file

@ -2,12 +2,21 @@
// //
// /sys, laid out the way Linux lays it out, for the parts that exist here. // /sys, laid out the way Linux lays it out, for the parts that exist here.
// //
// /sys/bus/pci/devices/0000:00:1f.2/ vendor device class revision irq // /sys/devices/pci0000:00/0000:00:1f.2/ vendor device class revision irq
// subsystem_vendor subsystem_device // subsystem_vendor subsystem_device
// resource config uevent boot_vga // resource config uevent boot_vga
// enable graphics/fb0/ // enable graphics/fb0/ subsystem driver
// /sys/bus/usb/devices/1-3.2/ idVendor idProduct bcdDevice busnum // /sys/devices/pci0000:00/0000:00:14.0/usb1/1-3.2/
// idVendor idProduct bcdDevice busnum
// devnum speed bDevice* uevent // devnum speed bDevice* uevent
// subsystem driver
// /sys/bus/pci/devices/<addr> symlinks to the above, as on Linux
// /sys/bus/usb/devices/<name>
//
// The real directories live under /sys/devices because that is what udev
// resolves a device to, and what an event's DEVPATH has to name. USB devices
// sit under the xHCI controller that carries their bus, flat below its usbN
// root hub: a hub's children are siblings of the hub here, not nested in it.
// /sys/firmware/acpi/tables/DSDT the tables, byte for byte // /sys/firmware/acpi/tables/DSDT the tables, byte for byte
// /sys/firmware/dmi/tables/DMI the SMBIOS table and entry point // /sys/firmware/dmi/tables/DMI the SMBIOS table and entry point
// /sys/firmware/memmap/N/ start end type // /sys/firmware/memmap/N/ start end type
@ -46,6 +55,7 @@
#include "gfx/gfx.h" #include "gfx/gfx.h"
#include "dev/acpi.h" #include "dev/acpi.h"
#include "dev/kdev.h" #include "dev/kdev.h"
#include "net/socket.h"
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Inode numbers: kind, index, file -- the whole identity, since iget gives // Inode numbers: kind, index, file -- the whole identity, since iget gives
@ -66,6 +76,9 @@
#define K_KCLINK 12ULL // idx a kdev: the symlink in /sys/class/<subsys> #define K_KCLINK 12ULL // idx a kdev: the symlink in /sys/class/<subsys>
#define K_KDEVD 13ULL // idx 0 char, 1 block: /sys/dev/char, /sys/dev/block #define K_KDEVD 13ULL // idx 0 char, 1 block: /sys/dev/char, /sys/dev/block
#define K_KDEVL 14ULL // idx a kdev: the "MAJ:MIN" symlink in /sys/dev/* #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_USBROOT 17ULL // idx a bus number: the root hub, /sys/devices/pci.../usbN
// Files in a kdev directory (K_KDEV). // Files in a kdev directory (K_KDEV).
enum { KF_DIR, KF_DEV, KF_UEVENT, KF_SUBSYSTEM, KF_NAME, KF_PROPERTIES, enum { KF_DIR, KF_DEV, KF_UEVENT, KF_SUBSYSTEM, KF_NAME, KF_PROPERTIES,
@ -79,7 +92,7 @@ enum { KF_DIR, KF_DEV, KF_UEVENT, KF_SUBSYSTEM, KF_NAME, KF_PROPERTIES,
enum { enum {
D_ROOT, D_BUS, D_PCI, D_PCIDEV, D_USB, D_USBDEV, D_FW, D_ACPI, D_ACPITAB, D_ROOT, D_BUS, D_PCI, D_PCIDEV, D_USB, D_USBDEV, D_FW, D_ACPI, D_ACPITAB,
D_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_PSU, D_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_PSU,
D_DEVICES, D_VIRTUAL, D_DEVT, D_DEVICES, D_VIRTUAL, D_DEVT, D_PCIROOT,
D_NDIRS D_NDIRS
}; };
@ -104,6 +117,7 @@ static const struct { const char *name; int parent; } dirs[D_NDIRS] = {
[D_DEVICES] = { "devices", D_ROOT }, [D_DEVICES] = { "devices", D_ROOT },
[D_VIRTUAL] = { "virtual", D_DEVICES }, [D_VIRTUAL] = { "virtual", D_DEVICES },
[D_DEVT] = { "dev", D_ROOT }, [D_DEVT] = { "dev", D_ROOT },
[D_PCIROOT] = { "pci0000:00", D_DEVICES },
}; };
#define SYSFS_ROOT INO(K_DIR, D_ROOT, 0) #define SYSFS_ROOT INO(K_DIR, D_ROOT, 0)
@ -123,6 +137,8 @@ static const char *const pcifiles[] = {
// boot_vga. Empty, because the path existing is the whole answer. // boot_vga. Empty, because the path existing is the whole answer.
#define PCI_GRAPHICS 13 #define PCI_GRAPHICS 13
#define PCI_FB0 14 #define PCI_FB0 14
#define PCI_SUBSYS 15 // symlink: ../../../bus/pci
#define PCI_DRIVER 16 // symlink, only while a driver is bound
// The display device: the one whose memory the framebuffer is in, which is // The display device: the one whose memory the framebuffer is in, which is
// what boot_vga means on Linux -- the card firmware brought up and drew on. // what boot_vga means on Linux -- the card firmware brought up and drew on.
@ -150,9 +166,21 @@ boot_vga_idx(void)
static const char *const usbfiles[] = { static const char *const usbfiles[] = {
0, "idVendor", "idProduct", "bcdDevice", "busnum", "devnum", "speed", 0, "idVendor", "idProduct", "bcdDevice", "busnum", "devnum", "speed",
"bDeviceClass", "bDeviceSubClass", "bDeviceProtocol", "bNumConfigurations", "bDeviceClass", "bDeviceSubClass", "bDeviceProtocol", "bNumConfigurations",
"uevent", "uevent", "subsystem", "driver",
}; };
#define NUSBFILES ((int)(sizeof usbfiles / sizeof usbfiles[0])) #define NUSBFILES ((int)(sizeof usbfiles / sizeof usbfiles[0]))
#define USB_UEVENT 11
#define USB_SUBSYS 12 // symlink
#define USB_DRIVER 13 // symlink, only while a driver is bound
// 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",
};
#define NHUBFILES ((int)(sizeof hubfiles / sizeof hubfiles[0]))
#define HUB_UEVENT 1
#define HUB_SUBSYS 2
static const char *const memfiles[] = { 0, "start", "end", "type" }; static const char *const memfiles[] = { 0, "start", "end", "type" };
@ -272,6 +300,24 @@ usb_by_idx(int idx, struct usb_info *u)
return -1; 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.
static int
usb_hc_pci(int bus)
{
int n = 0;
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)
return i;
}
return -1;
}
// BAT0, BAT1 for batteries and AC, AC1 for adapters, numbered in the order // BAT0, BAT1 for batteries and AC, AC1 for adapters, numbered in the order
// ACPI lists them -- the names Linux gives most laptops. // ACPI lists them -- the names Linux gives most laptops.
static void static void
@ -523,19 +569,40 @@ children(uint64 dino, child_fn cb, void *ctx)
if (cb(ctx, INO(K_KCLASS, s, 0), 1, kdev_subsys(s))) if (cb(ctx, INO(K_KCLASS, s, 0), 1, kdev_subsys(s)))
return 1; return 1;
break; break;
case D_PCIROOT:
case D_PCIDEV: case D_PCIDEV:
// The devices themselves live in the first; the second holds the
// links udev and lspci walk to reach them.
for (int i = 0; i < pci_count(); i++) { for (int i = 0; i < pci_count(); i++) {
struct out o = { name, 0, sizeof name - 1 }; struct out o = { name, 0, sizeof name - 1 };
pci_name(&o, pci_get(i)); pci_name(&o, pci_get(i));
name[o.n] = 0; name[o.n] = 0;
if (cb(ctx, INO(K_PCI, i, 0), 1, name)) if (idx == D_PCIDEV ? cb(ctx, INO(K_PCILINK, i, 0), 2, name)
: cb(ctx, INO(K_PCI, i, 0), 1, name))
return 1;
}
// A bus whose controller we cannot place hangs off the root instead.
if (idx == D_PCIROOT)
for (int b = 1; b <= usb_nbuses(); b++)
if (usb_hc_pci(b) < 0) {
struct out o = { name, 0, sizeof name - 1 };
puts_(&o, "usb"); putu(&o, (uint64)b);
name[o.n] = 0;
if (cb(ctx, INO(K_USBROOT, b, 0), 1, name))
return 1; return 1;
} }
break; break;
case D_USBDEV: { case D_USBDEV: {
struct usb_info u; struct usb_info u;
for (int b = 1; b <= usb_nbuses(); b++) {
struct out o = { name, 0, sizeof name - 1 };
puts_(&o, "usb"); putu(&o, (uint64)b);
name[o.n] = 0;
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++)
if (cb(ctx, INO(K_USB, (u.bus << 8) | u.devnum, 0), 1, u.path)) if (cb(ctx, INO(K_USBLINK, (u.bus << 8) | u.devnum, 0), 2, u.path))
return 1; return 1;
break; break;
} }
@ -593,13 +660,41 @@ children(uint64 dino, child_fn cb, void *ctx)
if (idx == boot_vga_idx() && gfx_fb_phys() && if (idx == boot_vga_idx() && gfx_fb_phys() &&
cb(ctx, INO(K_PCI, idx, PCI_GRAPHICS), 1, "graphics")) cb(ctx, INO(K_PCI, idx, PCI_GRAPHICS), 1, "graphics"))
return 1; return 1;
if (cb(ctx, INO(K_PCI, idx, PCI_SUBSYS), 2, "subsystem"))
return 1;
if (idx < pci_count() && pci_get(idx)->driver &&
cb(ctx, INO(K_PCI, idx, PCI_DRIVER), 2, "driver"))
return 1;
for (int b = 1; b <= usb_nbuses(); b++)
if (usb_hc_pci(b) == idx) {
char un[12] = "usb";
struct out o = { un + 3, 0, 8 };
putu(&o, (uint64)b);
un[3 + o.n] = 0;
if (cb(ctx, INO(K_USBROOT, b, 0), 1, un))
return 1;
}
} else if (kind == K_USBROOT && I_FILE(dino) == 0) {
struct usb_info u;
for (int f = 1; f < NHUBFILES; f++)
if (cb(ctx, INO(K_USBROOT, idx, f), f == HUB_SUBSYS ? 2 : 0, hubfiles[f]))
return 1;
for (int i = 0; usb_info_nth(i, &u) == 0; i++)
if (u.bus == idx &&
cb(ctx, INO(K_USB, (u.bus << 8) | u.devnum, 0), 1, u.path))
return 1;
} else if (kind == K_PCI && I_FILE(dino) == PCI_GRAPHICS) { } else if (kind == K_PCI && I_FILE(dino) == PCI_GRAPHICS) {
if (cb(ctx, INO(K_PCI, idx, PCI_FB0), 1, "fb0")) if (cb(ctx, INO(K_PCI, idx, PCI_FB0), 1, "fb0"))
return 1; return 1;
} else if (kind == K_USB && I_FILE(dino) == 0) { } else if (kind == K_USB && I_FILE(dino) == 0) {
for (int f = 1; f < NUSBFILES; f++) struct usb_info u;
if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f])) int have = usb_by_idx(idx, &u) == 0;
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]))
return 1; return 1;
}
} else if (kind == K_PSU && I_FILE(dino) == 0) { } else if (kind == K_PSU && I_FILE(dino) == 0) {
struct acpi_psu b; struct acpi_psu b;
if (acpi_psu_get(idx, &b) == 0) if (acpi_psu_get(idx, &b) == 0)
@ -625,8 +720,14 @@ parent_of(uint64 dino)
case K_PCI: case K_PCI:
if (I_FILE(dino) == PCI_GRAPHICS) return INO(K_PCI, I_IDX(dino), 0); 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_FILE(dino) == PCI_FB0) return INO(K_PCI, I_IDX(dino), PCI_GRAPHICS);
return INO(K_DIR, D_PCIDEV, 0); return INO(K_DIR, D_PCIROOT, 0);
case K_USB: return INO(K_DIR, D_USBDEV, 0); case K_PCILINK: return INO(K_DIR, D_PCIDEV, 0);
case K_USBLINK: return INO(K_DIR, D_USBDEV, 0);
case K_USBROOT: {
int hc = usb_hc_pci(I_IDX(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_MEMMAP: return INO(K_DIR, D_MEMMAP, 0); case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0);
case K_PSU: return INO(K_DIR, D_PSU, 0); case K_PSU: return INO(K_DIR, D_PSU, 0);
case K_KSUBV: return INO(K_DIR, D_VIRTUAL, 0); case K_KSUBV: return INO(K_DIR, D_VIRTUAL, 0);
@ -788,6 +889,131 @@ gen_usb(struct out *o, const struct usb_info *u, int f)
putc_(o, '\n'); 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.
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));
putc_(o, '/');
depth++;
}
puts_(o, "usb");
putu(o, (uint64)bus);
if (path) {
putc_(o, '/');
puts_(o, path);
depth++;
}
return depth;
}
static void
updirs(struct out *o, int n)
{
while (n-- > 0)
puts_(o, "../");
}
// The target of a symlink for the PCI or USB entry `ino`, or -1.
static int
link_text(uint64 ino, struct out *o)
{
int idx = I_IDX(ino), f = I_FILE(ino);
struct usb_info u;
switch (I_KIND(ino)) {
case K_PCILINK:
if (idx >= pci_count())
return -1;
puts_(o, "../../../devices/pci0000:00/");
pci_name(o, pci_get(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);
return o->n;
}
return -1;
case K_USBLINK:
if ((idx & 0xFF) == 0) {
if (idx >> 8 < 1 || idx >> 8 > usb_nbuses())
return -1;
puts_(o, "../../../");
usb_dir(o, idx >> 8, 0);
return o->n;
}
if (usb_by_idx(idx, &u) < 0)
return -1;
puts_(o, "../../../");
usb_dir(o, u.bus, u.path);
return o->n;
case K_USB:
if (usb_by_idx(idx, &u) < 0)
return -1;
if (f == USB_SUBSYS) {
struct out d = { o->p + o->n, 0, 0 }; // measure only
updirs(o, usb_dir(&d, u.bus, u.path));
puts_(o, "bus/usb");
return o->n;
}
if (f == USB_DRIVER && u.driver[0]) {
struct out d = { o->p + o->n, 0, 0 };
updirs(o, usb_dir(&d, u.bus, u.path));
puts_(o, "bus/usb/drivers/");
puts_(o, u.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 };
updirs(o, usb_dir(&d, idx, 0));
puts_(o, "bus/usb");
return o->n;
}
return -1;
}
return -1;
}
static void
gen_hub(struct out *o, int bus, int f)
{
switch (f) {
case 1:
puts_(o, "DEVTYPE=usb_device\nPRODUCT=1d6b/3/");
putx(o, 0x0601, 4, 0);
puts_(o, "\nTYPE=9/0/1\nBUSNUM=");
if (bus < 100) putc_(o, '0');
if (bus < 10) putc_(o, '0');
putu(o, (uint64)bus);
puts_(o, "\nDEVNUM=001");
break;
case 3: puts_(o, "1d6b"); break;
case 4: puts_(o, "0003"); break;
case 5: putu(o, (uint64)bus); break;
case 6: putu(o, 1); break;
case 7: puts_(o, "09"); break;
case 8: puts_(o, "5000"); break;
}
putc_(o, '\n');
}
// A text file, whole. Returns its length, or -1 if there is no such file. // A text file, whole. Returns its length, or -1 if there is no such file.
static int static int
text(uint64 ino, char *buf, int cap) text(uint64 ino, char *buf, int cap)
@ -796,15 +1022,29 @@ text(uint64 ino, char *buf, int cap)
int idx = I_IDX(ino), f = I_FILE(ino); int idx = I_IDX(ino), f = I_FILE(ino);
switch (I_KIND(ino)) { switch (I_KIND(ino)) {
case K_PCILINK:
case K_USBLINK:
return link_text(ino, &o);
case K_PCI: case K_PCI:
if (f == PCI_SUBSYS || f == PCI_DRIVER)
return link_text(ino, &o);
if (idx >= pci_count() || f < 1 || f >= NPCIFILES || f == PCI_CONFIG) if (idx >= pci_count() || f < 1 || f >= NPCIFILES || f == PCI_CONFIG)
return -1; return -1;
gen_pci(&o, pci_get(idx), f); gen_pci(&o, pci_get(idx), f);
return o.n; return o.n;
case K_USBROOT:
if (idx < 1 || idx > usb_nbuses() || f < 1 || f >= NHUBFILES)
return -1;
if (f == HUB_SUBSYS)
return link_text(ino, &o);
gen_hub(&o, idx, f);
return o.n;
case K_USB: { case K_USB: {
struct usb_info u; struct usb_info u;
if (usb_by_idx(idx, &u) < 0 || f < 1 || f >= NUSBFILES) if (usb_by_idx(idx, &u) < 0 || f < 1 || f >= NUSBFILES)
return -1; return -1;
if (f == USB_SUBSYS || f == USB_DRIVER)
return link_text(ino, &o);
gen_usb(&o, &u, f); gen_usb(&o, &u, f);
return o.n; return o.n;
} }
@ -895,6 +1135,77 @@ text(uint64 ino, char *buf, int cap)
return -1; return -1;
} }
// ---------------------------------------------------------------------------
// Announcing devices
//
// A uevent is "action@/devpath", then NUL-separated KEY=value pairs: the
// four the kernel always sends, then whatever the device's own `uevent` file
// says. The same text serves a hot-plug event and the answer to
// `echo add > uevent`, so udev sees one thing however it learned of a device.
// ---------------------------------------------------------------------------
static void
announce(const char *action, const char *devpath, const char *subsys,
const char *vars, int vlen)
{
char msg[700];
struct out o = { msg, 0, (int)sizeof msg - 1 };
puts_(&o, action); puts_(&o, "@/"); puts_(&o, devpath); putc_(&o, 0);
puts_(&o, "ACTION="); puts_(&o, action); putc_(&o, 0);
puts_(&o, "DEVPATH=/"); puts_(&o, devpath); putc_(&o, 0);
puts_(&o, "SUBSYSTEM="); puts_(&o, subsys); putc_(&o, 0);
puts_(&o, "SEQNUM="); putu(&o, kdev_seqnum()); putc_(&o, 0);
for (int i = 0; i < vlen; i++)
putc_(&o, vars[i] == '\n' ? 0 : vars[i]);
uevent_send(UEVENT_GROUP_KERNEL, msg, o.n, 0);
}
void
sysfs_usb_uevent(const struct usb_info *u, const char *action)
{
char path[96], vars[300];
struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 };
usb_dir(&p, u->bus, u->path);
path[p.n] = 0;
gen_usb(&v, u, USB_UEVENT);
announce(action, path, "usb", vars, v.n);
}
static void
pci_uevent(int idx, const char *action)
{
char path[64], 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));
path[p.n] = 0;
gen_pci(&v, pci_get(idx), 10);
announce(action, path, "pci", vars, v.n);
}
static void
hub_uevent(int bus, const char *action)
{
char path[96], vars[200];
struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 };
usb_dir(&p, bus, 0);
path[p.n] = 0;
gen_hub(&v, bus, HUB_UEVENT);
announce(action, path, "usb", vars, v.n);
}
void
sysfs_usb_root_uevent(int bus, const char *action)
{
hub_uevent(bus, action);
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// fs_ops // fs_ops
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -913,6 +1224,8 @@ exists_dir(uint64 ino)
struct usb_info u; struct usb_info u;
return usb_by_idx(I_IDX(ino), &u) == 0; return usb_by_idx(I_IDX(ino), &u) == 0;
} }
case K_USBROOT:
return I_IDX(ino) >= 1 && I_IDX(ino) <= usb_nbuses();
case K_MEMMAP: { case K_MEMMAP: {
struct e820_rec e; struct e820_rec e;
return e820_nth(I_IDX(ino), &e) == 0; return e820_nth(I_IDX(ino), &e) == 0;
@ -939,8 +1252,12 @@ static int
is_link(uint64 ino) is_link(uint64 ino)
{ {
int k = I_KIND(ino); int k = I_KIND(ino);
return k == K_KCLINK || k == K_KDEVL || int f = I_FILE(ino);
(k == K_KDEV && I_FILE(ino) == KF_SUBSYSTEM); return k == K_KCLINK || k == K_KDEVL || k == K_PCILINK || k == K_USBLINK ||
(k == K_KDEV && f == KF_SUBSYSTEM) ||
(k == K_PCI && (f == PCI_SUBSYS || f == PCI_DRIVER)) ||
(k == K_USB && (f == USB_SUBSYS || f == USB_DRIVER)) ||
(k == K_USBROOT && f == HUB_SUBSYS);
} }
static void static void
@ -991,7 +1308,8 @@ sysfs_iload(struct inode *ip)
ip->type = T_FILE; ip->type = T_FILE;
if ((I_KIND(ino) == K_PCI && (I_FILE(ino) == PCI_ENABLE || if ((I_KIND(ino) == K_PCI && (I_FILE(ino) == PCI_ENABLE ||
I_FILE(ino) == 10 /* uevent */)) || I_FILE(ino) == 10 /* uevent */)) ||
(I_KIND(ino) == K_USB && I_FILE(ino) == NUSBFILES - 1) || (I_KIND(ino) == K_USB && I_FILE(ino) == USB_UEVENT) ||
(I_KIND(ino) == K_USBROOT && I_FILE(ino) == HUB_UEVENT) ||
(I_KIND(ino) == K_KDEV && I_FILE(ino) == KF_UEVENT)) (I_KIND(ino) == K_KDEV && I_FILE(ino) == KF_UEVENT))
ip->rwmode = 3; ip->rwmode = 3;
return; return;
@ -1057,11 +1375,39 @@ sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n)
if (I_KIND(ip->inum) == K_PCI && I_FILE(ip->inum) == PCI_ENABLE) if (I_KIND(ip->inum) == K_PCI && I_FILE(ip->inum) == PCI_ENABLE)
return (int)n; return (int)n;
// `echo add > uevent`: udevadm trigger asks the kernel to say again what it // `echo add > uevent`: udevadm trigger asks the kernel to say again what it
// said when the device appeared. Only the kernel's own devices are announced; // said when the device appeared.
// a PCI or USB device's uevent accepts the write and says nothing, because {
// their /sys paths are not the /devices tree an event's DEVPATH must name. int kind = I_KIND(ip->inum), f = I_FILE(ip->inum), idx = I_IDX(ip->inum);
if (I_KIND(ip->inum) == K_PCI || I_KIND(ip->inum) == K_USB) int pcie = kind == K_PCI && f == 10;
int usbe = kind == K_USB && f == USB_UEVENT;
int hube = kind == K_USBROOT && f == HUB_UEVENT;
if (pcie || usbe || hube) {
char act[16];
uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1;
if (u) {
if (copy_from_user(act, s, k) < 0)
return -EFAULT;
} else {
memmove(act, (void *)s, k);
}
uint e = 0;
while (e < k && act[e] != '\n' && act[e] != ' ')
e++;
act[e] = 0;
const char *a = e ? act : "change";
struct usb_info ui;
if (pcie)
pci_uevent(idx, a);
else if (usbe && usb_by_idx(idx, &ui) == 0)
sysfs_usb_uevent(&ui, a);
else if (hube)
hub_uevent(idx, a);
return (int)n; return (int)n;
}
if (kind == K_PCI || kind == K_USB)
return (int)n;
}
if (I_KIND(ip->inum) == K_KDEV && I_FILE(ip->inum) == KF_UEVENT) { if (I_KIND(ip->inum) == K_KDEV && I_FILE(ip->inum) == KF_UEVENT) {
char act[16]; char act[16];
uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1; uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1;