SchOS/kernel/fs/sysfs.c
Nexoniarz 8d836c66b2 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
2026-10-26 15:51:28 +01:00

1550 lines
48 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// /sys, laid out the way Linux lays it out, for the parts that exist here.
//
// /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/ 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
// /sys/firmware/efi/ present when UEFI booted the kernel
// /sys/class/dmi/id/product_name ... the machine, as text
// /sys/class/power_supply/BAT0/ capacity status energy_* ... (ACPI)
// /sys/class/power_supply/AC/ type online
//
// Why Linux's layout rather than one of its own: everything that reads
// hardware information already reads it from these paths. toybox's lspci and
// lsusb walk /sys/bus/*/devices and parse each uevent; acpidump-style tools,
// fastfetch and every "what laptop is this" script read the others. Getting
// the names and formats right makes all of them work at once, and makes a
// report taken here read the same as one taken on Linux on the same machine.
//
// Like /proc, nothing is stored. A directory is listed and a file generated
// from the live tables when asked, so a USB stick appears in /sys the moment
// the hot-plug thread attaches it.
#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/memlayout.h"
#include "arch/mmu.h"
#include "core/spinlock.h"
#include "core/sleeplock.h"
#include "fs/fs.h"
#include "fs/file.h"
#include "core/kmem.h"
#include "core/uaccess.h"
#include "uapi/errno.h"
#include "uapi/stat.h"
#include "dev/pci.h"
#include "dev/usb.h"
#include "core/hwinfo.h"
#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
// nothing else back.
// ---------------------------------------------------------------------------
#define K_DIR 1ULL // a fixed directory; idx is a dirs[] index
#define K_PCI 2ULL // idx a pci_get() index
#define K_USB 3ULL // idx bus << 8 | devnum
#define K_ACPI 4ULL // idx a slot of acpi_table_nth
#define K_MEMMAP 5ULL // idx an entry of e820_nth
#define K_DMIID 6ULL // file a DMI_* field + 1
#define K_DMITAB 7ULL // file 1 the entry point, 2 the table
#define K_PSU 8ULL // idx an acpi_psu_get() index; file a psufiles[] index
#define K_KDEV 9ULL // idx a kdev index; file 0 the directory, KF_* its files
#define K_KSUBV 10ULL // idx a kdev subsystem: /sys/devices/virtual/<subsys>
#define K_KCLASS 11ULL // idx a kdev subsystem: /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_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,
KF_CAPS, KF_EV, KF_KEY, KF_REL, KF_ABS, KF_N };
#define INO(k, idx, f) (((uint64)(k) << 40) | ((uint64)(idx) << 8) | (uint64)(f))
#define I_KIND(i) ((int)((i) >> 40))
#define I_IDX(i) ((int)(((i) >> 8) & 0xFFFFFFFF))
#define I_FILE(i) ((int)((i) & 0xFF))
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_PCIROOT,
D_NDIRS
};
static const struct { const char *name; int parent; } dirs[D_NDIRS] = {
[D_ROOT] = { "", D_ROOT },
[D_BUS] = { "bus", D_ROOT },
[D_PCI] = { "pci", D_BUS },
[D_PCIDEV] = { "devices", D_PCI },
[D_USB] = { "usb", D_BUS },
[D_USBDEV] = { "devices", D_USB },
[D_FW] = { "firmware",D_ROOT },
[D_ACPI] = { "acpi", D_FW },
[D_ACPITAB] = { "tables", D_ACPI },
[D_DMI] = { "dmi", D_FW },
[D_DMITAB] = { "tables", D_DMI },
[D_MEMMAP] = { "memmap", D_FW },
[D_EFI] = { "efi", D_FW },
[D_CLASS] = { "class", D_ROOT },
[D_CLASSDMI] = { "dmi", D_CLASS },
[D_DMIID] = { "id", D_CLASSDMI },
[D_PSU] = { "power_supply", D_CLASS },
[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)
static const char *const pcifiles[] = {
0, "vendor", "device", "class", "revision", "subsystem_vendor",
"subsystem_device", "irq", "resource", "config", "uevent", "boot_vga",
"enable",
};
#define PCI_CONFIG 9
#define PCI_ENABLE 12
#define NPCIFILES ((int)(sizeof pcifiles / sizeof pcifiles[0]))
// Two directories under the display device only: graphics/ and graphics/fb0/.
// Xorg's fbdev driver finds the framebuffer that belongs to a PCI device by
// looking for exactly that path, and libpciaccess picks the primary card by
// 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.
// Failing that, the first display controller. -1 when there is neither.
static int
boot_vga_idx(void)
{
uint64 fb = gfx_fb_phys();
int first = -1;
for (int i = 0; i < pci_count(); i++) {
struct pci_dev *d = pci_get(i);
if (d->class != 0x03)
continue;
if (first < 0)
first = i;
for (int b = 0; fb && b < 6; b++)
if (!d->bar[b].is_io && d->bar[b].base && fb >= d->bar[b].base &&
fb < d->bar[b].base + d->bar[b].size)
return i;
}
return first;
}
static const char *const usbfiles[] = {
0, "idVendor", "idProduct", "bcdDevice", "busnum", "devnum", "speed",
"bDeviceClass", "bDeviceSubClass", "bDeviceProtocol", "bNumConfigurations",
"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" };
// Linux's power_supply attributes. Which of them a battery has depends on
// whether its firmware counts in mWh (energy_*, power_now) or mAh
// (charge_*, current_now); see psu_has().
enum {
PS_TYPE = 1, PS_PRESENT, PS_STATUS, PS_CAPACITY, PS_LEVEL, PS_TECH,
PS_CYCLES, PS_VNOW, PS_VMIN, PS_ENOW, PS_EFULL, PS_EDESIGN, PS_PNOW,
PS_CNOW, PS_CFULL, PS_CDESIGN, PS_INOW, PS_MODEL, PS_MAKER, PS_SERIAL,
PS_UEVENT, PS_ONLINE, NPSUFILES
};
static const char *const psufiles[NPSUFILES] = {
0, "type", "present", "status", "capacity", "capacity_level", "technology",
"cycle_count", "voltage_now", "voltage_min_design", "energy_now",
"energy_full", "energy_full_design", "power_now", "charge_now",
"charge_full", "charge_full_design", "current_now", "model_name",
"manufacturer", "serial_number", "uevent", "online",
};
// ---------------------------------------------------------------------------
// Text
// ---------------------------------------------------------------------------
struct out { char *p; int n, cap; };
static void putc_(struct out *o, char c) { if (o->n < o->cap) o->p[o->n++] = c; }
static void puts_(struct out *o, const char *s) { while (*s) putc_(o, *s++); }
static void
putu(struct out *o, uint64 v)
{
char t[24];
int k = 0;
do { t[k++] = '0' + v % 10; v /= 10; } while (v);
while (k) putc_(o, t[--k]);
}
// `digits` 0 means as few as it takes. Lower case unless `upper`.
static void
putx(struct out *o, uint64 v, int digits, int upper)
{
const char *h = upper ? "0123456789ABCDEF" : "0123456789abcdef";
char t[16];
int k = 0;
do { t[k++] = h[v & 0xF]; v >>= 4; } while (v || k < digits);
while (k) putc_(o, t[--k]);
}
static void
pci_name(struct out *o, const struct pci_dev *d)
{
puts_(o, "0000:");
putx(o, d->bus, 2, 0); putc_(o, ':');
putx(o, d->slot, 2, 0); putc_(o, '.');
putx(o, d->func, 1, 0);
}
static void
memmap_type(struct out *o, uint32 t)
{
// /sys/firmware/memmap's strings, which are e820's as Linux prints them.
switch (t) {
case 1: puts_(o, "System RAM"); break;
case 3: puts_(o, "ACPI Tables"); break;
case 4: puts_(o, "ACPI Non-volatile Storage"); break;
case 5: puts_(o, "Unusable memory"); break;
case 7: puts_(o, "Persistent Memory"); break;
default: puts_(o, "Reserved"); break;
}
}
// ---------------------------------------------------------------------------
// ACPI names: a signature that occurs more than once is numbered from 1, all
// of its instances, as Linux numbers SSDT1, SSDT2.
// ---------------------------------------------------------------------------
static int
acpi_name(int slot, char *name)
{
uint64 pa; uint32 len; char sig[4], s2[4];
if (acpi_table_nth(slot, &pa, &len, sig) != 0)
return -1;
int rank = 0, total = 0;
for (int i = 0, r; (r = acpi_table_nth(i, &pa, &len, s2)) >= 0; i++) {
if (r != 0 || memcmp(sig, s2, 4) != 0)
continue;
if (i < slot)
rank++;
total++;
}
int n = 0;
for (int k = 0; k < 4; k++)
name[n++] = (sig[k] > ' ' && sig[k] < 127 && sig[k] != '/') ? sig[k] : '_';
if (total > 1) {
struct out o = { name + n, 0, 8 };
putu(&o, (uint64)rank + 1);
n += o.n;
}
name[n] = 0;
return 0;
}
// ---------------------------------------------------------------------------
// The namespace: every child of a directory, in order. `cb` returns nonzero
// to stop, which is how both lookup and getdents use the one walk.
// ---------------------------------------------------------------------------
typedef int (*child_fn)(void *ctx, uint64 ino, int isdir, const char *name);
static int
usb_by_idx(int idx, struct usb_info *u)
{
for (int i = 0; usb_info_nth(i, u) == 0; i++)
if (((u->bus << 8) | u->devnum) == idx)
return 0;
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
psu_name(int idx, char *name)
{
struct acpi_psu b;
int nth = 0;
acpi_psu_get(idx, &b);
for (int i = 0; i < idx; i++) {
struct acpi_psu c;
if (acpi_psu_get(i, &c) == 0 && c.battery == b.battery)
nth++;
}
struct out o = { name, 0, 15 };
if (b.battery) { puts_(&o, "BAT"); putu(&o, (uint64)nth); }
else { puts_(&o, "AC"); if (nth) putu(&o, (uint64)nth); }
name[o.n] = 0;
}
static uint32
psu_capacity(const struct acpi_psu *b)
{
if (b->remaining == ACPI_UNKNOWN || b->full == ACPI_UNKNOWN || b->full == 0)
return ACPI_UNKNOWN;
uint64 c = (uint64)b->remaining * 100 / b->full;
return c > 100 ? 100 : (uint32)c;
}
// Whether this supply has the attribute at all. A value the firmware
// reports as unknown is a missing file, not a zero.
static int
psu_has(const struct acpi_psu *b, int f)
{
int mw = b->unit == 0, live = b->present;
if (!b->battery)
return f == PS_TYPE || f == PS_ONLINE || f == PS_UEVENT;
switch (f) {
case PS_TYPE: case PS_PRESENT: case PS_UEVENT: return 1;
case PS_STATUS: case PS_TECH: return live;
case PS_CAPACITY: case PS_LEVEL: return live && psu_capacity(b) != ACPI_UNKNOWN;
case PS_CYCLES: return live && b->cycles != ACPI_UNKNOWN && b->cycles != 0;
case PS_VNOW: return live && b->voltage != ACPI_UNKNOWN;
case PS_VMIN: return live && b->design_voltage != ACPI_UNKNOWN;
case PS_ENOW: return live && mw && b->remaining != ACPI_UNKNOWN;
case PS_EFULL: return live && mw && b->full != ACPI_UNKNOWN;
case PS_EDESIGN: return live && mw && b->design != ACPI_UNKNOWN;
case PS_PNOW: return live && mw && b->rate != ACPI_UNKNOWN;
case PS_CNOW: return live && !mw && b->remaining != ACPI_UNKNOWN;
case PS_CFULL: return live && !mw && b->full != ACPI_UNKNOWN;
case PS_CDESIGN: return live && !mw && b->design != ACPI_UNKNOWN;
case PS_INOW: return live && !mw && b->rate != ACPI_UNKNOWN;
case PS_MODEL: return live && b->model[0];
case PS_MAKER: return live && b->oem[0];
case PS_SERIAL: return live && b->serial[0];
}
return 0;
}
static const char *
psu_status(const struct acpi_psu *b)
{
if (b->state & 2) return "Charging";
if (b->state & 1) return "Discharging";
uint32 c = psu_capacity(b);
return (c != ACPI_UNKNOWN && c >= 98) ? "Full" : "Not charging";
}
static const char *
psu_level(uint32 c)
{
if (c < 5) return "Critical";
if (c < 15) return "Low";
if (c >= 98) return "Full";
if (c > 80) return "High";
return "Normal";
}
static void
gen_psu_value(struct out *o, const struct acpi_psu *b, int f)
{
switch (f) {
case PS_TYPE: puts_(o, b->battery ? "Battery" : "Mains"); break;
case PS_PRESENT: putc_(o, b->present ? '1' : '0'); break;
case PS_ONLINE: putc_(o, b->online ? '1' : '0'); break;
case PS_STATUS: puts_(o, psu_status(b)); break;
case PS_CAPACITY: putu(o, psu_capacity(b)); break;
case PS_LEVEL: puts_(o, psu_level(psu_capacity(b))); break;
case PS_TECH: puts_(o, b->kind[0] == 'L' || b->kind[0] == 'l' ? "Li-ion"
: b->technology == 0 ? "Unknown" : "Li-ion"); break;
case PS_CYCLES: putu(o, b->cycles); break;
// Linux reports micro-units; ACPI's are milli.
case PS_VNOW: putu(o, (uint64)b->voltage * 1000); break;
case PS_VMIN: putu(o, (uint64)b->design_voltage * 1000); break;
case PS_ENOW: case PS_CNOW: putu(o, (uint64)b->remaining * 1000); break;
case PS_EFULL: case PS_CFULL: putu(o, (uint64)b->full * 1000); break;
case PS_EDESIGN: case PS_CDESIGN: putu(o, (uint64)b->design * 1000); break;
case PS_PNOW: case PS_INOW: putu(o, (uint64)b->rate * 1000); break;
case PS_MODEL: puts_(o, b->model); break;
case PS_MAKER: puts_(o, b->oem); break;
case PS_SERIAL: puts_(o, b->serial); break;
}
}
static void
gen_psu(struct out *o, int idx, int f)
{
struct acpi_psu b;
if (acpi_psu_get(idx, &b) < 0)
return;
if (f != PS_UEVENT) {
gen_psu_value(o, &b, f);
putc_(o, '\n');
return;
}
// uevent: every attribute this supply has, as POWER_SUPPLY_<NAME>=value.
char name[16];
psu_name(idx, name);
puts_(o, "POWER_SUPPLY_NAME="); puts_(o, name); putc_(o, '\n');
for (int k = 1; k < NPSUFILES; k++) {
if (k == PS_UEVENT || !psu_has(&b, k))
continue;
puts_(o, "POWER_SUPPLY_");
for (const char *s = psufiles[k]; *s; s++)
putc_(o, *s >= 'a' && *s <= 'z' ? *s - 32 : *s);
putc_(o, '=');
gen_psu_value(o, &b, k);
putc_(o, '\n');
}
}
// ---- kernel-implemented devices (include/dev/kdev.h) ----
static int
kd_subsys_of(const struct kdev *k)
{
for (int s = 0; s < kdev_nsubsys(); s++) {
const char *n = kdev_subsys(s), *m = k->subsys;
while (*n && *n == *m) { n++; m++; }
if (*n == *m)
return s;
}
return -1;
}
static void
kd_devt(const struct kdev *k, char *name)
{
struct out o = { name, 0, 30 };
int maj, min;
kdev_numbers(k, &maj, &min);
putu(&o, (uint64)maj);
putc_(&o, ':');
putu(&o, (uint64)min);
name[o.n] = 0;
}
// Does file f exist in kdev k's directory?
static int
kd_has(const struct kdev *k, int f)
{
switch (f) {
case KF_DEV: return k->devname[0] != 0;
case KF_UEVENT:
case KF_SUBSYSTEM: return 1;
case KF_NAME: case KF_PROPERTIES: case KF_CAPS:
return k->cap != KD_NONE;
}
return 0;
}
static int
kd_children(uint64 dino, child_fn cb, void *ctx)
{
int kind = I_KIND(dino), idx = I_IDX(dino);
char name[32];
struct kdev k;
if (kind == K_KSUBV) {
for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++)
if (kdev_get(i, &k) == 0 && k.parent < 0 && kd_subsys_of(&k) == idx &&
cb(ctx, INO(K_KDEV, i, 0), 1, k.name))
return 1;
} else if (kind == K_KCLASS) {
for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++)
if (kdev_get(i, &k) == 0 && kd_subsys_of(&k) == idx &&
cb(ctx, INO(K_KCLINK, i, 0), 2, k.name))
return 1;
} else if (kind == K_KDEVD) {
for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++)
if (kdev_get(i, &k) == 0 && k.devname[0] && k.block == idx) {
kd_devt(&k, name);
if (cb(ctx, INO(K_KDEVL, i, 0), 2, name))
return 1;
}
} else if (kind == K_KDEV && I_FILE(dino) == KF_DIR) {
if (kdev_get(idx, &k) < 0)
return 0;
for (int f = KF_DEV; f <= KF_CAPS; f++)
if (kd_has(&k, f)) {
static const char *const names[] = { 0, "dev", "uevent", "subsystem",
"name", "properties", "capabilities" };
if (cb(ctx, INO(K_KDEV, idx, f),
f == KF_CAPS ? 1 : f == KF_SUBSYSTEM ? 2 : 0, names[f]))
return 1;
}
for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++) {
struct kdev c;
if (kdev_get(i, &c) == 0 && c.parent == idx &&
cb(ctx, INO(K_KDEV, i, 0), 1, c.name))
return 1;
}
} else if (kind == K_KDEV && I_FILE(dino) == KF_CAPS) {
static const char *const names[] = { "ev", "key", "rel", "abs" };
for (int f = KF_EV; f <= KF_ABS; f++)
if (cb(ctx, INO(K_KDEV, idx, f), 0, names[f - KF_EV]))
return 1;
}
return 0;
}
static int
children(uint64 dino, child_fn cb, void *ctx)
{
char name[64];
int kind = I_KIND(dino), idx = I_IDX(dino);
if (kind == K_DIR) {
for (int d = 1; d < D_NDIRS; d++) {
if (dirs[d].parent != idx)
continue;
if (d == D_EFI && !boot_is_efi())
continue;
if (cb(ctx, INO(K_DIR, d, 0), 1, dirs[d].name))
return 1;
}
switch (idx) {
case D_VIRTUAL:
for (int s = 0; s < kdev_nsubsys(); s++)
if (cb(ctx, INO(K_KSUBV, s, 0), 1, kdev_subsys(s)))
return 1;
break;
case D_DEVT:
if (cb(ctx, INO(K_KDEVD, 0, 0), 1, "char") ||
cb(ctx, INO(K_KDEVD, 1, 0), 1, "block"))
return 1;
break;
case D_CLASS:
for (int s = 0; s < kdev_nsubsys(); s++)
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 (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_USBLINK, (u.bus << 8) | u.devnum, 0), 2, u.path))
return 1;
break;
}
case D_ACPITAB:
for (int i = 0; ; i++) {
uint64 pa; uint32 len; char sig[4];
int r = acpi_table_nth(i, &pa, &len, sig);
if (r < 0)
break;
if (r == 0 && acpi_name(i, name) == 0 &&
cb(ctx, INO(K_ACPI, i, 1), 0, name))
return 1;
}
break;
case D_DMITAB: {
uint64 pa; uint32 len;
if (dmi_entry(&pa, &len) == 0) {
if (cb(ctx, INO(K_DMITAB, 0, 1), 0, "smbios_entry_point")) return 1;
if (cb(ctx, INO(K_DMITAB, 0, 2), 0, "DMI")) return 1;
}
break;
}
case D_MEMMAP: {
struct e820_rec e;
for (int i = 0; e820_nth(i, &e) == 0; i++) {
struct out o = { name, 0, sizeof name - 1 };
putu(&o, (uint64)i);
name[o.n] = 0;
if (cb(ctx, INO(K_MEMMAP, i, 0), 1, name))
return 1;
}
break;
}
case D_PSU:
for (int i = 0; i < acpi_psu_count(); i++) {
psu_name(i, name);
if (cb(ctx, INO(K_PSU, i, 0), 1, name))
return 1;
}
break;
case D_DMIID:
for (int f = 0; f < DMI_NFIELDS; f++)
if (dmi_field(f, name, sizeof name) >= 0 &&
cb(ctx, INO(K_DMIID, 0, f + 1), 0, dmi_field_names[f]))
return 1;
break;
}
return 0;
}
if (kind == K_PCI && I_FILE(dino) == 0) {
for (int f = 1; f < NPCIFILES; f++)
if (cb(ctx, INO(K_PCI, idx, f), 0, pcifiles[f]))
return 1;
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) {
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)
for (int f = 1; f < NPSUFILES; f++)
if (psu_has(&b, f) && cb(ctx, INO(K_PSU, idx, f), 0, psufiles[f]))
return 1;
} else if (kind == K_KSUBV || kind == K_KCLASS || kind == K_KDEVD ||
kind == K_KDEV) {
return kd_children(dino, cb, ctx);
} else if (kind == K_MEMMAP && I_FILE(dino) == 0) {
for (int f = 1; f < 4; f++)
if (cb(ctx, INO(K_MEMMAP, idx, f), 0, memfiles[f]))
return 1;
}
return 0;
}
static uint64
parent_of(uint64 dino)
{
switch (I_KIND(dino)) {
case K_DIR: return INO(K_DIR, dirs[I_IDX(dino)].parent, 0);
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_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);
case K_KCLASS: return INO(K_DIR, D_CLASS, 0);
case K_KDEVD: return INO(K_DIR, D_DEVT, 0);
case K_KDEV: {
struct kdev k;
if (I_FILE(dino) == KF_CAPS)
return INO(K_KDEV, I_IDX(dino), 0);
if (kdev_get(I_IDX(dino), &k) < 0)
return SYSFS_ROOT;
if (k.parent >= 0)
return INO(K_KDEV, k.parent, 0);
return INO(K_KSUBV, kd_subsys_of(&k), 0);
}
}
return SYSFS_ROOT;
}
// ---------------------------------------------------------------------------
// Contents
// ---------------------------------------------------------------------------
// Bytes straight out of memory the firmware owns, or a PCI config space.
// Sets *src for a mapped region; fills `buf` for config space. Returns the
// length, or -1 when this is not a binary file.
static int
binary(uint64 ino, const uchar **src, uchar *buf)
{
uint64 pa; uint32 len; char sig[4];
switch (I_KIND(ino)) {
case K_ACPI:
if (acpi_table_nth(I_IDX(ino), &pa, &len, sig) != 0)
return -1;
break;
case K_DMITAB:
if ((I_FILE(ino) == 1 ? dmi_entry(&pa, &len) : dmi_table(&pa, &len)) != 0)
return -1;
break;
case K_PCI:
if (I_FILE(ino) != PCI_CONFIG || I_IDX(ino) >= pci_count())
return -1;
if (buf) {
struct pci_dev *d = pci_get(I_IDX(ino));
for (int off = 0; off < 256; off += 4) {
uint32 v = pci_read32(d->bus, d->slot, d->func, (uint8)off);
memmove(buf + off, &v, 4);
}
*src = buf;
}
return 256;
default:
return -1;
}
if (src) {
*src = phys_view(pa, len);
if (*src == 0)
return -1;
}
return (int)len;
}
static void
gen_pci(struct out *o, struct pci_dev *d, int f)
{
uint16 svid = 0, sdid = 0;
if ((d->header_type & 0x7F) == 0) {
uint32 ss = pci_read32(d->bus, d->slot, d->func, 0x2C);
svid = ss & 0xFFFF;
sdid = ss >> 16;
}
uint32 cls = ((uint32)d->class << 16) | ((uint32)d->subclass << 8) | d->prog_if;
switch (f) {
case 1: puts_(o, "0x"); putx(o, d->vendor, 4, 0); break;
case 2: puts_(o, "0x"); putx(o, d->device, 4, 0); break;
case 3: puts_(o, "0x"); putx(o, cls, 6, 0); break;
case 4: puts_(o, "0x"); putx(o, d->revision, 2, 0); break;
case 5: puts_(o, "0x"); putx(o, svid, 4, 0); break;
case 6: puts_(o, "0x"); putx(o, sdid, 4, 0); break;
case 7: putu(o, d->irq == 0xFF ? 0 : d->irq); break;
case 11: putc_(o, d == pci_get(boot_vga_idx()) ? '1' : '0'); break;
case PCI_ENABLE: {
// Whether memory or I/O decoding is on: what "enabled" amounts to.
uint32 cmd = pci_read32(d->bus, d->slot, d->func, 0x04);
putc_(o, (cmd & 3) ? '1' : '0');
break;
}
case 8:
// start, end, flags per BAR, then the ROM, as Linux prints them.
for (int b = 0; b < 7; b++) {
uint64 s = 0, e = 0, fl = 0;
if (b < 6 && d->bar[b].base) {
s = d->bar[b].base;
e = s + (d->bar[b].size ? d->bar[b].size - 1 : 0);
fl = d->bar[b].is_io ? 0x101 : 0x200;
if (d->bar[b].prefetch) fl |= 0x2000;
if (d->bar[b].is_64) fl |= 0x100000;
}
puts_(o, "0x"); putx(o, s, 16, 0);
puts_(o, " 0x"); putx(o, e, 16, 0);
puts_(o, " 0x"); putx(o, fl, 16, 0);
putc_(o, '\n');
}
return;
case 10:
if (d->driver) { puts_(o, "DRIVER="); puts_(o, d->driver); putc_(o, '\n'); }
puts_(o, "PCI_CLASS="); putx(o, cls, 4, 1);
puts_(o, "\nPCI_ID="); putx(o, d->vendor, 4, 1); putc_(o, ':');
putx(o, d->device, 4, 1);
puts_(o, "\nPCI_SUBSYS_ID="); putx(o, svid, 4, 1); putc_(o, ':');
putx(o, sdid, 4, 1);
puts_(o, "\nPCI_SLOT_NAME="); pci_name(o, d);
puts_(o, "\nMODALIAS=pci:v"); putx(o, d->vendor, 8, 1);
puts_(o, "d"); putx(o, d->device, 8, 1);
puts_(o, "sv"); putx(o, svid, 8, 1);
puts_(o, "sd"); putx(o, sdid, 8, 1);
puts_(o, "bc"); putx(o, d->class, 2, 1);
puts_(o, "sc"); putx(o, d->subclass, 2, 1);
puts_(o, "i"); putx(o, d->prog_if, 2, 1);
break;
}
putc_(o, '\n');
}
static void
gen_usb(struct out *o, const struct usb_info *u, int f)
{
static const char *const speeds[] = { "unknown", "12", "1.5", "480", "5000" };
switch (f) {
case 1: putx(o, u->vid, 4, 0); break;
case 2: putx(o, u->pid, 4, 0); break;
case 3: putx(o, u->bcd, 4, 0); break;
case 4: putu(o, (uint64)u->bus); break;
case 5: putu(o, (uint64)u->devnum); break;
case 6: puts_(o, u->speed >= 1 && u->speed <= 4 ? speeds[u->speed] : speeds[0]); break;
case 7: putx(o, u->cls, 2, 0); break;
case 8: putx(o, u->subcls, 2, 0); break;
case 9: putx(o, u->proto, 2, 0); break;
case 10: putu(o, u->nconf); break;
case 11:
puts_(o, "DEVTYPE=usb_device\n");
if (u->driver[0]) { puts_(o, "DRIVER="); puts_(o, u->driver); putc_(o, '\n'); }
puts_(o, "PRODUCT="); putx(o, u->vid, 0, 0); putc_(o, '/');
putx(o, u->pid, 0, 0); putc_(o, '/'); putx(o, u->bcd, 0, 0);
puts_(o, "\nTYPE="); putu(o, u->cls); putc_(o, '/');
putu(o, u->subcls); putc_(o, '/'); putu(o, u->proto);
puts_(o, "\nBUSNUM=");
if (u->bus < 100) putc_(o, '0');
if (u->bus < 10) putc_(o, '0');
putu(o, (uint64)u->bus);
puts_(o, "\nDEVNUM=");
if (u->devnum < 100) putc_(o, '0');
if (u->devnum < 10) putc_(o, '0');
putu(o, (uint64)u->devnum);
break;
}
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)
{
struct out o = { buf, 0, 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;
}
case K_PSU:
if (idx >= acpi_psu_count() || f < 1 || f >= NPSUFILES)
return -1;
gen_psu(&o, idx, f);
return o.n;
case K_MEMMAP: {
struct e820_rec e;
if (e820_nth(idx, &e) < 0 || f < 1 || f > 3)
return -1;
if (f == 1) { puts_(&o, "0x"); putx(&o, e.base, 0, 0); }
if (f == 2) { puts_(&o, "0x"); putx(&o, e.base + e.len - 1, 0, 0); }
if (f == 3) memmap_type(&o, e.type);
putc_(&o, '\n');
return o.n;
}
case K_KDEV: {
struct kdev k;
char path[160];
if (kdev_get(idx, &k) < 0 || f < KF_DEV || f >= KF_N || f == KF_CAPS ||
(f != KF_UEVENT && f != KF_SUBSYSTEM && !kd_has(&k, f) &&
!(f >= KF_EV && k.cap != KD_NONE)))
return -1;
switch (f) {
case KF_DEV:
kd_devt(&k, buf);
o.n = (int)strlen(buf);
putc_(&o, '\n');
return o.n;
case KF_UEVENT:
return kdev_uevent_text(idx, buf, cap);
case KF_SUBSYSTEM: {
// ../ once for each directory between here and /sys, then class/<s>.
if (kdev_path(idx, path, sizeof path) < 0)
return -1;
for (const char *p = path; ; p++) {
if (*p == '/' || *p == 0)
puts_(&o, "../");
if (*p == 0)
break;
}
puts_(&o, "class/");
puts_(&o, k.subsys);
return o.n;
}
case KF_NAME:
puts_(&o, k.cap == KD_KEYBOARD ? "SchOS keyboard\n" : "SchOS mouse\n");
return o.n;
case KF_PROPERTIES:
puts_(&o, "0\n");
return o.n;
case KF_EV:
puts_(&o, k.cap == KD_KEYBOARD ? "120013\n" : "17\n");
return o.n;
case KF_KEY:
puts_(&o, k.cap == KD_KEYBOARD
? "ffffffffffffffff ffffffffffffffff ffffffffffffffff fffffffffffffffe\n"
: "70000 0 0 0 0\n");
return o.n;
case KF_REL:
puts_(&o, k.cap == KD_MOUSE ? "903\n" : "0\n");
return o.n;
case KF_ABS:
puts_(&o, "0\n");
return o.n;
}
return -1;
}
case K_KCLINK:
case K_KDEVL: {
char path[160];
if (kdev_path(idx, path, sizeof path) < 0)
return -1;
puts_(&o, "../../");
puts_(&o, path);
return o.n;
}
case K_DMIID: {
int n = dmi_field(f - 1, buf, cap - 1);
if (n < 0)
return -1;
buf[n++] = '\n';
return n;
}
}
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
// ---------------------------------------------------------------------------
static int
exists_dir(uint64 ino)
{
switch (I_KIND(ino)) {
case K_DIR:
return I_IDX(ino) < D_NDIRS && (I_IDX(ino) != D_EFI || boot_is_efi());
case K_PCI:
if (I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0)
return I_IDX(ino) == boot_vga_idx() && gfx_fb_phys() != 0;
return I_IDX(ino) < pci_count();
case K_USB: {
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;
}
case K_PSU:
return I_IDX(ino) < acpi_psu_count();
case K_KSUBV:
case K_KCLASS:
return I_IDX(ino) < kdev_nsubsys();
case K_KDEVD:
return I_IDX(ino) < 2;
case K_KDEV: {
struct kdev k;
if (kdev_get(I_IDX(ino), &k) < 0)
return 0;
return I_FILE(ino) == KF_DIR || (I_FILE(ino) == KF_CAPS && k.cap != KD_NONE);
}
}
return 0;
}
// Symlinks: the class and dev/ entries, and a device's own `subsystem`.
static int
is_link(uint64 ino)
{
int k = I_KIND(ino);
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
sysfs_iload(struct inode *ip)
{
uint64 ino = ip->inum;
char *buf;
ip->valid = 1;
ip->major = ip->minor = 0;
ip->rwmode = 2; // read-only, as procfs marks it
ip->showmode = 1;
ip->atime = ip->mtime = ip->ctime = 0;
ip->size = 0;
ip->nlink = 1;
int subdir = (I_KIND(ino) == K_PCI &&
(I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0)) ||
(I_KIND(ino) == K_KDEV && I_FILE(ino) == KF_CAPS);
if ((I_FILE(ino) == 0 || subdir) && exists_dir(ino)) {
ip->type = T_DIR;
ip->nlink = 2;
return;
}
if (is_link(ino)) {
char lb[200];
int ln = text(ino, lb, sizeof lb);
if (ln >= 0) {
ip->type = T_SYMLINK;
ip->size = (uint64)ln; // readlink(2) trusts this length
ip->mode = 0777;
return;
}
}
int n = binary(ino, 0, 0);
if (n >= 0) {
ip->type = T_FILE;
ip->size = (uint64)n; // real: tools copy tables by length
return;
}
// A text file reports 0, as procfs does and for the same reason: its size
// is whatever it says when it is generated. Generating it here only to
// check that it exists is cheap, and a vanished USB device says no.
if ((buf = kmalloc(512)) != 0) {
n = text(ino, buf, 512);
kmfree(buf);
if (n >= 0) {
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) == 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;
}
}
ip->type = 0;
ip->nlink = 0;
}
static void sysfs_istore(struct inode *ip) { (void)ip; }
static void sysfs_ifree(struct inode *ip) { (void)ip; }
static void sysfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; }
static int
deliver(int user_dst, uint64 dst, const void *src, uint n)
{
if (user_dst)
return copy_to_user(dst, (void *)src, n) < 0 ? -EFAULT : (int)n;
memmove((void *)dst, src, n);
return (int)n;
}
static int
sysfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n)
{
const uchar *src;
uchar *buf;
int len, r;
if (ip->type == T_DIR)
return -EISDIR;
if ((buf = (uchar *)kalloc()) == 0)
return -ENOMEM;
len = binary(ip->inum, &src, buf);
if (len < 0) {
len = text(ip->inum, (char *)buf, PGSIZE);
src = buf;
}
if (len < 0) {
kfree((char *)buf);
return -ENOENT;
}
if (off >= (uint64)len) {
kfree((char *)buf);
return 0;
}
if (off + n > (uint64)len)
n = (uint)((uint64)len - off);
r = deliver(user_dst, dst, src + off, n);
kfree((char *)buf);
return r;
}
// Only `enable`, which libpciaccess writes before it touches a device. The
// kernel's drivers already turned decoding on for every device they use, and
// one nobody drives does not need it -- so the write is accepted and changes
// nothing, which is what it would have done on Linux for a device already on.
static int
sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n)
{
(void)o;
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.
{
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;
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;
kdev_uevent(I_IDX(ip->inum), e ? act : "change");
return (int)n;
}
return -EROFS;
}
struct find { const char *name; uint64 ino; };
static int
find_cb(void *ctx, uint64 ino, int isdir, const char *name)
{
struct find *f = ctx;
(void)isdir;
if (namecmp((char *)name, (char *)f->name) != 0)
return 0;
f->ino = ino;
return 1;
}
static struct inode *
sysfs_dirlookup(struct inode *dp, char *name, uint *poff)
{
struct find f = { name, 0 };
if (poff)
*poff = 0;
if (namecmp(name, ".") == 0)
return iget(dp->dev, dp->inum);
if (namecmp(name, "..") == 0)
return iget(dp->dev, parent_of(dp->inum));
if (!children(dp->inum, find_cb, &f))
return 0;
return iget(dp->dev, f.ino);
}
static int sysfs_dirlink(struct inode *dp, char *name, uint64 inum)
{ (void)dp; (void)name; (void)inum; return -EROFS; }
static int sysfs_dirunlink(struct inode *dp, char *name, uint off)
{ (void)dp; (void)name; (void)off; return -EROFS; }
static int sysfs_dirempty(struct inode *dp) { (void)dp; return 0; }
static struct inode *sysfs_ialloc(uint dev, short type)
{ (void)dev; (void)type; return 0; }
struct linux_dirent64 {
uint64 d_ino;
int64 d_off;
ushort d_reclen;
uchar d_type;
char d_name[];
};
struct list {
char *out;
int cap, used, idx, full;
uint64 want, *off;
};
static int
emit_cb(void *ctx, uint64 ino, int isdir, const char *name)
{
struct list *l = ctx;
if (l->idx++ < (int)l->want)
return 0;
int nlen = (int)strlen(name);
int rec = ((int)sizeof(struct linux_dirent64) + nlen + 1 + 7) & ~7;
if (l->used + rec > l->cap) {
l->full = 1;
return 1;
}
struct linux_dirent64 *de = (struct linux_dirent64 *)(l->out + l->used);
de->d_ino = ino;
de->d_off = l->used + rec;
de->d_reclen = (ushort)rec;
de->d_type = isdir == 2 ? 10 : isdir ? 4 : 8;
memmove(de->d_name, name, nlen);
memset(de->d_name + nlen, 0, rec - (int)sizeof(*de) - nlen);
l->used += rec;
(*l->off)++;
return 0;
}
// *off is an index into the listing, generated fresh each call -- the same
// contract, and the same race with a device vanishing, as /proc.
static int
sysfs_getdents(struct inode *dp, char *out, int cap, uint64 *off)
{
struct list l = { out, cap, 0, 0, 0, *off, off };
if (dp->type != T_DIR)
return -ENOTDIR;
if (emit_cb(&l, dp->inum, 1, ".") || emit_cb(&l, parent_of(dp->inum), 1, ".."))
return l.used;
children(dp->inum, emit_cb, &l);
return l.used;
}
const struct fs_ops sysfs_ops = {
.name = "sysfs",
.iload = sysfs_iload,
.istore = sysfs_istore,
.ifree = sysfs_ifree,
.read = sysfs_read,
.write = sysfs_write,
.truncate = sysfs_truncate,
.dirlookup = sysfs_dirlookup,
.dirlink = sysfs_dirlink,
.dirunlink = sysfs_dirunlink,
.dirempty = sysfs_dirempty,
.ialloc = sysfs_ialloc,
.getdents = sysfs_getdents,
};
// `mount -t sysfs sysfs /sys`. Init does it.
int
sysfs_mount(const char *target)
{
struct inode *dp = namei((char *)target);
int r;
if (dp == 0)
return -ENOENT;
r = mount_add(dp->dev, dp->inum, SYSFS_DEV, SYSFS_ROOT, (char *)target,
"sysfs", "sysfs");
iput(dp);
return r < 0 ? -EBUSY : 0;
}