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
|
|
@ -252,8 +252,8 @@ mounted by init with `mount("sysfs", "/sys", "sysfs", 0, 0)`.
|
|||
|
||||
| path | contents |
|
||||
|------|----------|
|
||||
| `/sys/bus/pci/devices/0000:BB:DD.F/` | `vendor device class revision subsystem_vendor subsystem_device irq resource config uevent` |
|
||||
| `/sys/bus/usb/devices/B-P[.P...]/` | `idVendor idProduct bcdDevice busnum devnum speed bDeviceClass bDeviceSubClass bDeviceProtocol bNumConfigurations 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/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/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 |
|
||||
|
|
@ -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
|
||||
on the uevent netlink socket ([udev.md](udev.md)).
|
||||
|
||||
What is missing, compared with Linux: PCI and USB devices in that tree (the
|
||||
`bus/*/devices` entries are directories rather than links into it),
|
||||
`/sys/class` entries for them, `/sys/block`, and most of what is writable. PCI `config` is
|
||||
PCI and USB devices are in that tree too, under `/sys/devices/pci0000:00`, with
|
||||
the `bus/*/devices` entries as links into it and `add`/`remove` events for USB
|
||||
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
|
||||
no further.
|
||||
|
||||
|
|
|
|||
|
|
@ -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/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/event0/ dev uevent subsystem -> ../../../../../class/input
|
||||
/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
|
||||
readable (`DEVPATH=`, `SUBSYSTEM=`, `MAJOR=`, `MINOR=`, `DEVNAME=`) and
|
||||
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
|
||||
accepted and says nothing yet, because those devices are not in the
|
||||
`/sys/devices` tree and an event's `DEVPATH` has to name something that exists.
|
||||
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.
|
||||
|
||||
**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
|
||||
`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
|
||||
|
||||
- PCI and USB devices in `/sys/devices`, and `add`/`remove` events from the
|
||||
hotplug thread. A USB keyboard works because every keyboard is `event0`, not
|
||||
because udev knew it arrived.
|
||||
- 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.
|
||||
- `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`.
|
||||
|
|
|
|||
|
|
@ -61,3 +61,10 @@ struct usb_info {
|
|||
char driver[16]; // the first interface's, or empty
|
||||
};
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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".
|
||||
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.
|
||||
int kdev_uevent_text(int i, char *buf, int cap);
|
||||
|
|
|
|||
|
|
@ -146,6 +146,13 @@ kdev_path(int i, char *buf, int cap)
|
|||
|
||||
static uint kseq;
|
||||
|
||||
// One counter for every uevent the kernel sends, whoever builds it.
|
||||
uint
|
||||
kdev_seqnum(void)
|
||||
{
|
||||
return ++kseq;
|
||||
}
|
||||
|
||||
static int
|
||||
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;
|
||||
if (action) {
|
||||
n = put(buf, cap, n, "SEQNUM=");
|
||||
n = putnum(buf, cap, n, ++kseq);
|
||||
n = putnum(buf, cap, n, kdev_seqnum());
|
||||
buf[n++] = sep;
|
||||
}
|
||||
if (k.devname[0]) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "fs/file.h"
|
||||
#include "dev/pci.h"
|
||||
#include "dev/usb.h"
|
||||
#include "core/hwinfo.h"
|
||||
#include "xhci.h"
|
||||
|
||||
#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");
|
||||
|
||||
|
||||
// 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
|
||||
// (which unmasks it and sets level/active-low), clear anything the
|
||||
// controller already considers pending so that it drives INTx low, and then
|
||||
|
|
|
|||
|
|
@ -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,17 +255,11 @@ put_num(char *out, int *n, int cap, int v)
|
|||
out[*n] = 0;
|
||||
}
|
||||
|
||||
int
|
||||
usb_info_nth(int idx, struct usb_info *o)
|
||||
// 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)
|
||||
{
|
||||
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);
|
||||
o->bus = 1;
|
||||
for(int b = 0; b < NBUSES; b++)
|
||||
|
|
@ -265,6 +291,20 @@ usb_info_nth(int idx, struct usb_info *o)
|
|||
drv = ifdevs[k].driver;
|
||||
if(drv)
|
||||
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);
|
||||
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];
|
||||
|
|
|
|||
|
|
@ -2,12 +2,21 @@
|
|||
//
|
||||
// /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
|
||||
// resource config uevent boot_vga
|
||||
// enable graphics/fb0/
|
||||
// /sys/bus/usb/devices/1-3.2/ idVendor idProduct bcdDevice busnum
|
||||
// enable graphics/fb0/ subsystem driver
|
||||
// /sys/devices/pci0000:00/0000:00:14.0/usb1/1-3.2/
|
||||
// idVendor idProduct bcdDevice busnum
|
||||
// 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/dmi/tables/DMI the SMBIOS table and entry point
|
||||
// /sys/firmware/memmap/N/ start end type
|
||||
|
|
@ -46,6 +55,7 @@
|
|||
#include "gfx/gfx.h"
|
||||
#include "dev/acpi.h"
|
||||
#include "dev/kdev.h"
|
||||
#include "net/socket.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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_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_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).
|
||||
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 {
|
||||
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_DEVICES, D_VIRTUAL, D_DEVT,
|
||||
D_DEVICES, D_VIRTUAL, D_DEVT, D_PCIROOT,
|
||||
D_NDIRS
|
||||
};
|
||||
|
||||
|
|
@ -104,6 +117,7 @@ static const struct { const char *name; int parent; } dirs[D_NDIRS] = {
|
|||
[D_DEVICES] = { "devices", D_ROOT },
|
||||
[D_VIRTUAL] = { "virtual", D_DEVICES },
|
||||
[D_DEVT] = { "dev", D_ROOT },
|
||||
[D_PCIROOT] = { "pci0000:00", D_DEVICES },
|
||||
};
|
||||
|
||||
#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.
|
||||
#define PCI_GRAPHICS 13
|
||||
#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
|
||||
// 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[] = {
|
||||
0, "idVendor", "idProduct", "bcdDevice", "busnum", "devnum", "speed",
|
||||
"bDeviceClass", "bDeviceSubClass", "bDeviceProtocol", "bNumConfigurations",
|
||||
"uevent",
|
||||
"uevent", "subsystem", "driver",
|
||||
};
|
||||
#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" };
|
||||
|
||||
|
|
@ -272,6 +300,24 @@ 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.
|
||||
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
|
||||
// ACPI lists them -- the names Linux gives most laptops.
|
||||
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)))
|
||||
return 1;
|
||||
break;
|
||||
case D_PCIROOT:
|
||||
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++) {
|
||||
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))
|
||||
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;
|
||||
}
|
||||
break;
|
||||
case D_USBDEV: {
|
||||
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++)
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
|
@ -593,13 +660,41 @@ children(uint64 dino, child_fn cb, void *ctx)
|
|||
if (idx == boot_vga_idx() && gfx_fb_phys() &&
|
||||
cb(ctx, INO(K_PCI, idx, PCI_GRAPHICS), 1, "graphics"))
|
||||
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) {
|
||||
if (cb(ctx, INO(K_PCI, idx, PCI_FB0), 1, "fb0"))
|
||||
return 1;
|
||||
} else if (kind == K_USB && I_FILE(dino) == 0) {
|
||||
for (int f = 1; f < NUSBFILES; f++)
|
||||
if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f]))
|
||||
struct usb_info u;
|
||||
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;
|
||||
}
|
||||
} else if (kind == K_PSU && I_FILE(dino) == 0) {
|
||||
struct acpi_psu b;
|
||||
if (acpi_psu_get(idx, &b) == 0)
|
||||
|
|
@ -625,8 +720,14 @@ 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);
|
||||
return INO(K_DIR, D_PCIDEV, 0);
|
||||
case K_USB: return INO(K_DIR, D_USBDEV, 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);
|
||||
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_PSU: return INO(K_DIR, D_PSU, 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');
|
||||
}
|
||||
|
||||
// --- 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.
|
||||
static int
|
||||
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);
|
||||
|
||||
switch (I_KIND(ino)) {
|
||||
case K_PCILINK:
|
||||
case K_USBLINK:
|
||||
return link_text(ino, &o);
|
||||
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)
|
||||
return -1;
|
||||
gen_pci(&o, pci_get(idx), f);
|
||||
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: {
|
||||
struct usb_info u;
|
||||
if (usb_by_idx(idx, &u) < 0 || f < 1 || f >= NUSBFILES)
|
||||
return -1;
|
||||
if (f == USB_SUBSYS || f == USB_DRIVER)
|
||||
return link_text(ino, &o);
|
||||
gen_usb(&o, &u, f);
|
||||
return o.n;
|
||||
}
|
||||
|
|
@ -895,6 +1135,77 @@ text(uint64 ino, char *buf, int cap)
|
|||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -913,6 +1224,8 @@ exists_dir(uint64 ino)
|
|||
struct usb_info u;
|
||||
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: {
|
||||
struct e820_rec e;
|
||||
return e820_nth(I_IDX(ino), &e) == 0;
|
||||
|
|
@ -939,8 +1252,12 @@ static int
|
|||
is_link(uint64 ino)
|
||||
{
|
||||
int k = I_KIND(ino);
|
||||
return k == K_KCLINK || k == K_KDEVL ||
|
||||
(k == K_KDEV && I_FILE(ino) == KF_SUBSYSTEM);
|
||||
int f = I_FILE(ino);
|
||||
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
|
||||
|
|
@ -991,7 +1308,8 @@ sysfs_iload(struct inode *ip)
|
|||
ip->type = T_FILE;
|
||||
if ((I_KIND(ino) == K_PCI && (I_FILE(ino) == PCI_ENABLE ||
|
||||
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))
|
||||
ip->rwmode = 3;
|
||||
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)
|
||||
return (int)n;
|
||||
// `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;
|
||||
// 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.
|
||||
if (I_KIND(ip->inum) == K_PCI || I_KIND(ip->inum) == K_USB)
|
||||
// said when the device appeared.
|
||||
{
|
||||
int kind = I_KIND(ip->inum), f = I_FILE(ip->inum), idx = I_IDX(ip->inum);
|
||||
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;
|
||||
}
|
||||
if (kind == K_PCI || kind == K_USB)
|
||||
return (int)n;
|
||||
}
|
||||
if (I_KIND(ip->inum) == K_KDEV && I_FILE(ip->inum) == KF_UEVENT) {
|
||||
char act[16];
|
||||
uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1;
|
||||
|
|
|
|||
Loading…
Reference in a new issue