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
247 lines
6.3 KiB
C
247 lines
6.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// See include/dev/kdev.h.
|
|
|
|
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/blkdev.h"
|
|
#include "dev/kdev.h"
|
|
#include "dev/pty.h"
|
|
#include "net/socket.h"
|
|
|
|
static const char *const subsys[] = {
|
|
"input", "mem", "tty", "graphics", "block",
|
|
};
|
|
#define NSUBSYS ((int)(sizeof subsys / sizeof subsys[0]))
|
|
|
|
#define NO_PARENT (-1)
|
|
|
|
// Majors and minors are the ones /dev reports, devsw indices and all -- the
|
|
// devnodes table in kernel/fs/devfs.c. udev compares what it reads here with
|
|
// what stat() says about the node, so these have to be the same numbers.
|
|
static const struct kdev table[] = {
|
|
{ "input", "input0", "", 0, 0, 0, NO_PARENT, KD_KEYBOARD },
|
|
{ "input", "event0", "input/event0", 7, 0, 0, 0, KD_NONE },
|
|
{ "input", "input1", "", 0, 0, 0, NO_PARENT, KD_MOUSE },
|
|
{ "input", "event1", "input/event1", 7, 1, 0, 2, KD_NONE },
|
|
|
|
{ "mem", "null", "null", 1, 2, 0, NO_PARENT, KD_NONE },
|
|
{ "mem", "zero", "zero", 1, 5, 0, NO_PARENT, KD_NONE },
|
|
{ "mem", "full", "full", 1, 7, 0, NO_PARENT, KD_NONE },
|
|
{ "mem", "random", "random", 1, 8, 0, NO_PARENT, KD_NONE },
|
|
{ "mem", "urandom", "urandom", 1, 9, 0, NO_PARENT, KD_NONE },
|
|
|
|
{ "tty", "console", "console", 1, 1, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "tty", "tty", 1, 10, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "tty0", "tty0", 1, 0, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "tty1", "tty1", 1, 11, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "tty2", "tty2", 1, 12, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "tty3", "tty3", 1, 13, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "tty4", "tty4", 1, 14, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "tty5", "tty5", 1, 15, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "tty6", "tty6", 1, 16, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "tty7", "tty7", 1, 17, 0, NO_PARENT, KD_NONE },
|
|
{ "tty", "ptmx", "ptmx", PTMX_MAJOR, 0, 0, NO_PARENT, KD_NONE },
|
|
|
|
{ "graphics", "fb0", "fb0", 2, 0, 0, NO_PARENT, KD_NONE },
|
|
};
|
|
#define NTABLE ((int)(sizeof table / sizeof table[0]))
|
|
|
|
int kdev_nsubsys(void) { return NSUBSYS; }
|
|
const char *kdev_subsys(int s) { return s >= 0 && s < NSUBSYS ? subsys[s] : 0; }
|
|
|
|
int
|
|
kdev_nth(int n)
|
|
{
|
|
if (n < NTABLE)
|
|
return n < 0 ? -1 : n;
|
|
n -= NTABLE;
|
|
for (int k = 0, b; (b = blkdev_nth(k)) >= 0; k++)
|
|
if (n-- == 0)
|
|
return KDEV_BLK0 + b;
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
copy(char *d, const char *s, int cap)
|
|
{
|
|
int i = 0;
|
|
while (s[i] && i < cap - 1) { d[i] = s[i]; i++; }
|
|
d[i] = 0;
|
|
}
|
|
|
|
int
|
|
kdev_get(int i, struct kdev *k)
|
|
{
|
|
if (i >= 0 && i < NTABLE) {
|
|
*k = table[i];
|
|
return 0;
|
|
}
|
|
if (i >= KDEV_BLK0 && blkdev_present((uint)(i - KDEV_BLK0))) {
|
|
memset(k, 0, sizeof(*k));
|
|
copy(k->subsys, "block", sizeof k->subsys);
|
|
copy(k->name, blkdev_name((uint)(i - KDEV_BLK0)), sizeof k->name);
|
|
copy(k->devname, k->name, sizeof k->devname);
|
|
k->major = 8;
|
|
k->minor = (short)(i - KDEV_BLK0);
|
|
k->block = 1;
|
|
k->parent = NO_PARENT;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
kdev_numbers(const struct kdev *k, int *maj, int *min)
|
|
{
|
|
if (k->block) {
|
|
*maj = k->major;
|
|
*min = k->minor;
|
|
return;
|
|
}
|
|
dev_stat_numbers(k->major, k->minor, maj, min);
|
|
}
|
|
|
|
int
|
|
kdev_path(int i, char *buf, int cap)
|
|
{
|
|
struct kdev k;
|
|
char tmp[160];
|
|
int n = 0;
|
|
|
|
if (kdev_get(i, &k) < 0)
|
|
return -1;
|
|
// The top ancestor names the subsystem directory; below it, one directory
|
|
// per level.
|
|
int chain[4], depth = 0;
|
|
for (int c = i; c >= 0 && depth < 4; ) {
|
|
struct kdev ck;
|
|
if (kdev_get(c, &ck) < 0)
|
|
break;
|
|
chain[depth++] = c;
|
|
c = ck.parent;
|
|
}
|
|
copy(tmp, "devices/virtual/", sizeof tmp);
|
|
n = (int)strlen(tmp);
|
|
{
|
|
struct kdev top;
|
|
kdev_get(chain[depth - 1], &top);
|
|
copy(tmp + n, top.subsys, (int)sizeof tmp - n);
|
|
n += (int)strlen(tmp + n);
|
|
}
|
|
for (int d = depth - 1; d >= 0 && n < (int)sizeof tmp - 2; d--) {
|
|
struct kdev ck;
|
|
kdev_get(chain[d], &ck);
|
|
tmp[n++] = '/';
|
|
copy(tmp + n, ck.name, (int)sizeof tmp - n);
|
|
n += (int)strlen(tmp + n);
|
|
}
|
|
copy(buf, tmp, cap);
|
|
return (int)strlen(buf);
|
|
}
|
|
|
|
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)
|
|
{
|
|
while (*s && n < cap - 1)
|
|
b[n++] = *s++;
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
putnum(char *b, int cap, int n, uint v)
|
|
{
|
|
char t[12];
|
|
int k = 0;
|
|
do { t[k++] = '0' + v % 10; v /= 10; } while (v);
|
|
while (k && n < cap - 1)
|
|
b[n++] = t[--k];
|
|
return n;
|
|
}
|
|
|
|
// KEY=value pairs, NUL separated, as the kernel sends them; `nl` swaps the
|
|
// NULs for newlines, which is what the uevent file shows.
|
|
static int
|
|
vars(int i, char *buf, int cap, const char *action, int nl)
|
|
{
|
|
struct kdev k;
|
|
char path[160];
|
|
int n = 0;
|
|
char sep = nl ? '\n' : 0;
|
|
|
|
if (kdev_get(i, &k) < 0 || kdev_path(i, path, sizeof path) < 0)
|
|
return -1;
|
|
if (action) {
|
|
n = put(buf, cap, n, "ACTION=");
|
|
n = put(buf, cap, n, action);
|
|
buf[n++] = sep;
|
|
}
|
|
n = put(buf, cap, n, "DEVPATH=/");
|
|
n = put(buf, cap, n, path);
|
|
buf[n++] = sep;
|
|
n = put(buf, cap, n, "SUBSYSTEM=");
|
|
n = put(buf, cap, n, k.subsys);
|
|
buf[n++] = sep;
|
|
if (action) {
|
|
n = put(buf, cap, n, "SEQNUM=");
|
|
n = putnum(buf, cap, n, kdev_seqnum());
|
|
buf[n++] = sep;
|
|
}
|
|
if (k.devname[0]) {
|
|
int maj, min;
|
|
kdev_numbers(&k, &maj, &min);
|
|
n = put(buf, cap, n, "MAJOR=");
|
|
n = putnum(buf, cap, n, (uint)maj);
|
|
buf[n++] = sep;
|
|
n = put(buf, cap, n, "MINOR=");
|
|
n = putnum(buf, cap, n, (uint)min);
|
|
buf[n++] = sep;
|
|
n = put(buf, cap, n, "DEVNAME=");
|
|
n = put(buf, cap, n, k.devname);
|
|
buf[n++] = sep;
|
|
}
|
|
if (k.block) {
|
|
n = put(buf, cap, n, "DEVTYPE=disk");
|
|
buf[n++] = sep;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int
|
|
kdev_uevent_text(int i, char *buf, int cap)
|
|
{
|
|
return vars(i, buf, cap, 0, 1);
|
|
}
|
|
|
|
void
|
|
kdev_uevent(int i, const char *action)
|
|
{
|
|
char msg[512];
|
|
char path[160];
|
|
int n = 0;
|
|
|
|
if (kdev_path(i, path, sizeof path) < 0)
|
|
return;
|
|
n = put(msg, sizeof msg, n, action);
|
|
msg[n++] = '@';
|
|
n = put(msg, sizeof msg, n, "/");
|
|
n = put(msg, sizeof msg, n, path);
|
|
msg[n++] = 0;
|
|
int v = vars(i, msg + n, (int)sizeof msg - n, action, 0);
|
|
if (v < 0)
|
|
return;
|
|
uevent_send(UEVENT_GROUP_KERNEL, msg, n + v, 0);
|
|
}
|