// 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/ symlinks to the above, as on Linux // /sys/bus/usb/devices/ // // 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/ #define K_KCLASS 11ULL // idx a kdev subsystem: /sys/class/ #define K_KCLINK 12ULL // idx a kdev: the symlink in /sys/class/ #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_USBIF 18ULL // idx devidx << 8 | interface number: 1-5:1.0 #define K_USBIFLN 19ULL // the same, as the symlink in /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", // What lsusb and libusb read from sysfs rather than from the device. "version", "bMaxPacketSize0", "bNumInterfaces", "bConfigurationValue", "bmAttributes", "bMaxPower", "configuration", "maxchild", "devpath", "rx_lanes", "tx_lanes", }; #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", "version", "bMaxPacketSize0", "bNumInterfaces", "bConfigurationValue", "bmAttributes", "bMaxPower", "configuration", "maxchild", "devpath", "rx_lanes", "tx_lanes", "bDeviceSubClass", "bDeviceProtocol", "bcdDevice", }; #define NHUBFILES ((int)(sizeof hubfiles / sizeof hubfiles[0])) #define HUB_UEVENT 1 #define HUB_SUBSYS 2 // A USB interface: 1-5:1.0, a directory of its own below its device. static const char *const ifiles[] = { 0, "uevent", "subsystem", "driver", "bInterfaceClass", "bInterfaceSubClass", "bInterfaceProtocol", "bInterfaceNumber", "bNumEndpoints", "bAlternateSetting", }; #define NIFILES ((int)(sizeof ifiles / sizeof ifiles[0])) #define IF_UEVENT 1 #define IF_SUBSYS 2 #define IF_DRIVER 3 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; } // Interface number `num` of the device `idx` (bus << 8 | devnum). static int usb_if_by_num(int idx, int num, struct usb_if_info *ii) { for (int n = 0; usb_if_nth(idx >> 8, idx & 0xFF, n, ii) == 0; n++) if (ii->num == num) return 0; return -1; } // "1-5:1.0" static void usb_if_name(struct out *o, const struct usb_info *u, int num) { puts_(o, u->path); puts_(o, ":1."); putu(o, (uint64)num); } // The PCI device a USB bus is: the controller recorded its own address when it // came up (usb_bus_add). -1 if there is no such controller. static int usb_hc_pci(int bus) { int pb, ps, pf; if (usb_bus_pci(bus, &pb, &ps, &pf) < 0) return -1; for (int i = 0; i < pci_count(); i++) { struct pci_dev *d = pci_get(i); if (d->bus == pb && d->slot == ps && d->func == pf) return i; } return -1; } // The PCI-to-PCI bridge a device sits behind: the one whose secondary.. // subordinate bus range holds the device's bus, the innermost if several do. // -1 for a device on the root bus. The bridges' own windows are read from // config space each time; there are a handful of devices and this runs when a // directory is listed. static int pci_parent(int idx) { struct pci_dev *d = pci_get(idx); int best = -1, bestsec = -1; for (int i = 0; i < pci_count(); i++) { struct pci_dev *b = pci_get(i); if (i == idx || (b->header_type & 0x7F) != 1) continue; uint32 w = pci_read32(b->bus, b->slot, b->func, 0x18); int sec = (w >> 8) & 0xFF, sub = (w >> 16) & 0xFF; if (sec == 0 || d->bus < sec || d->bus > sub || sec <= b->bus) continue; if (sec > bestsec) { best = i; bestsec = sec; } } return best; } // 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_=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) : pci_parent(i) < 0 && 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++) { struct usb_if_info ii; if (cb(ctx, INO(K_USBLINK, (u.bus << 8) | u.devnum, 0), 2, u.path)) return 1; for (int n = 0; usb_if_nth(u.bus, u.devnum, n, &ii) == 0; n++) { struct out o = { name, 0, sizeof name - 1 }; usb_if_name(&o, &u, ii.num); name[o.n] = 0; if (cb(ctx, INO(K_USBIFLN, (((u.bus << 8) | u.devnum) << 8) | ii.num, 0), 2, name)) 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; for (int i = 0; i < pci_count(); i++) if (i != idx && pci_parent(i) == idx) { 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)) 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 || f == USB_DRIVER ? 2 : 0, usbfiles[f])) return 1; } for (int n = 0; have; n++) { struct usb_if_info ii; if (usb_if_nth(u.bus, u.devnum, n, &ii) < 0) break; struct out o = { name, 0, sizeof name - 1 }; usb_if_name(&o, &u, ii.num); name[o.n] = 0; if (cb(ctx, INO(K_USBIF, (idx << 8) | ii.num, 0), 1, name)) return 1; } } else if (kind == K_USBIF && I_FILE(dino) == 0) { struct usb_if_info ii; int have = usb_if_by_num(idx >> 8, idx & 0xFF, &ii) == 0; for (int f = 1; have && f < NIFILES; f++) { if (f == IF_DRIVER && !ii.driver[0]) continue; if (cb(ctx, INO(K_USBIF, idx, f), f == IF_SUBSYS || f == IF_DRIVER ? 2 : 0, ifiles[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); if (I_IDX(dino) < pci_count() && pci_parent(I_IDX(dino)) >= 0) return INO(K_PCI, pci_parent(I_IDX(dino)), 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_USBIF: return INO(K_USB, I_IDX(dino) >> 8, 0); case K_USBIFLN: return INO(K_DIR, D_USBDEV, 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 14: // version: " 2.00", as Linux pads it putc_(o, (u->bcdusb >> 8) > 9 ? '1' : ' '); putx(o, (u->bcdusb >> 8) > 9 ? (u->bcdusb >> 8) - 10 : (u->bcdusb >> 8), 1, 0); putc_(o, '.'); putx(o, u->bcdusb & 0xFF, 2, 0); break; case 15: putu(o, u->maxp0); break; case 16: putc_(o, ' '); putu(o, u->nif); break; case 17: putu(o, u->confval); break; case 18: putx(o, u->attrs, 2, 0); break; case 19: putu(o, u->maxpower); puts_(o, "mA"); break; case 20: break; // configuration: no string descriptor read case 21: putu(o, 0); break; // maxchild: a hub's port count is not recorded case 22: { // devpath: the port chain, "5" or "5.2" const char *q = u->path; while (*q && *q != '-') q++; puts_(o, *q ? q + 1 : q); break; } case 23: case 24: putu(o, 1); 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); puts_(o, "\nMODALIAS=usb:v"); putx(o, u->vid, 4, 1); puts_(o, "p"); putx(o, u->pid, 4, 1); puts_(o, "d"); putx(o, u->bcd, 4, 1); puts_(o, "dc"); putx(o, u->cls, 2, 1); puts_(o, "dsc"); putx(o, u->subcls, 2, 1); puts_(o, "dp"); putx(o, u->proto, 2, 1); puts_(o, "ic*isc*ip*in*"); break; } putc_(o, '\n'); } // An interface's own uevent: what udev needs to load a driver by alias and to // tell a keyboard from a hub. static void gen_usbif(struct out *o, const struct usb_info *u, const struct usb_if_info *ii, int f) { switch (f) { case IF_UEVENT: puts_(o, "DEVTYPE=usb_interface\n"); if (ii->driver[0]) { puts_(o, "DRIVER="); puts_(o, ii->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, "\nINTERFACE="); putu(o, ii->cls); putc_(o, '/'); putu(o, ii->subcls); putc_(o, '/'); putu(o, ii->proto); puts_(o, "\nMODALIAS=usb:v"); putx(o, u->vid, 4, 1); puts_(o, "p"); putx(o, u->pid, 4, 1); puts_(o, "d"); putx(o, u->bcd, 4, 1); puts_(o, "dc"); putx(o, u->cls, 2, 1); puts_(o, "dsc"); putx(o, u->subcls, 2, 1); puts_(o, "dp"); putx(o, u->proto, 2, 1); puts_(o, "ic"); putx(o, ii->cls, 2, 1); puts_(o, "isc"); putx(o, ii->subcls, 2, 1); puts_(o, "ip"); putx(o, ii->proto, 2, 1); puts_(o, "in"); putx(o, ii->num, 2, 1); break; case 4: putx(o, ii->cls, 2, 0); break; case 5: putx(o, ii->subcls, 2, 0); break; case 6: putx(o, ii->proto, 2, 0); break; case 7: putx(o, ii->num, 2, 0); break; case 8: putx(o, ii->nendp, 2, 0); break; case 9: putu(o, 0); 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. // "devices/pci0000:00//.../0000:00:1f.2": a PCI device's directory, // below its bridges. Returns the depth below /sys, for the ../ of a link. static int pci_dir(struct out *o, int idx) { int par = pci_parent(idx), depth; if (par < 0) { puts_(o, "devices/pci0000:00/"); depth = 2; } else { depth = pci_dir(o, par); putc_(o, '/'); } pci_name(o, pci_get(idx)); return depth + 1; } static int usb_dir(struct out *o, int bus, const char *path) { int hc = usb_hc_pci(bus), depth = 3; if (hc >= 0) { depth = pci_dir(o, hc) + 1; putc_(o, '/'); } else { puts_(o, "devices/pci0000:00/"); } 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, "../../../"); pci_dir(o, idx); return o->n; case K_PCI: if (idx >= pci_count()) return -1; if (f == PCI_SUBSYS || (f == PCI_DRIVER && pci_get(idx)->driver)) { struct out d = { o->p + o->n, 0, 0 }; // measure only updirs(o, pci_dir(&d, idx)); if (f == PCI_SUBSYS) { puts_(o, "bus/pci"); } else { 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_USBIFLN: { struct usb_if_info ii; if (usb_by_idx(idx >> 8, &u) < 0 || usb_if_by_num(idx >> 8, idx & 0xFF, &ii) < 0) return -1; puts_(o, "../../../"); usb_dir(o, u.bus, u.path); putc_(o, '/'); usb_if_name(o, &u, ii.num); return o->n; } case K_USBIF: { struct usb_if_info ii; if (usb_by_idx(idx >> 8, &u) < 0 || usb_if_by_num(idx >> 8, idx & 0xFF, &ii) < 0) return -1; if (f == IF_SUBSYS || (f == IF_DRIVER && ii.driver[0])) { struct out d = { o->p + o->n, 0, 0 }; updirs(o, usb_dir(&d, u.bus, u.path) + 1); if (f == IF_SUBSYS) { puts_(o, "bus/usb"); } else { puts_(o, "bus/usb/drivers/"); puts_(o, ii.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; case 9: puts_(o, " 3.00"); break; case 10: puts_(o, "9"); break; case 11: puts_(o, " 1"); break; case 12: puts_(o, "1"); break; case 13: puts_(o, "e0"); break; case 14: puts_(o, "0mA"); break; case 15: break; case 16: puts_(o, "0"); break; case 17: puts_(o, "0"); break; case 18: case 19: puts_(o, "1"); break; case 20: puts_(o, "00"); break; case 21: puts_(o, "03"); break; case 22: puts_(o, "0601"); 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_USBIFLN: return link_text(ino, &o); case K_USBIF: { struct usb_info u; struct usb_if_info ii; if (usb_by_idx(idx >> 8, &u) < 0 || usb_if_by_num(idx >> 8, idx & 0xFF, &ii) < 0 || f < 1 || f >= NIFILES) return -1; if (f == IF_SUBSYS || f == IF_DRIVER) return link_text(ino, &o); gen_usbif(&o, &u, &ii, 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/. 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); } void sysfs_usb_if_uevent(const struct usb_info *u, const struct usb_if_info *ii, const char *action) { char path[128], vars[400]; struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; usb_dir(&p, u->bus, u->path); putc_(&p, '/'); usb_if_name(&p, u, ii->num); path[p.n] = 0; gen_usbif(&v, u, ii, IF_UEVENT); announce(action, path, "usb", vars, v.n); } static void pci_uevent(int idx, const char *action) { char path[128], vars[400]; struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; if (idx >= pci_count()) return; pci_dir(&p, 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_USBIF: { struct usb_if_info ii; return usb_if_by_num(I_IDX(ino) >> 8, I_IDX(ino) & 0xFF, &ii) == 0; } 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_USBIFLN || (k == K_USBIF && (f == IF_SUBSYS || f == IF_DRIVER)) || (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_USBIF && I_FILE(ino) == IF_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; int ife = kind == K_USBIF && f == IF_UEVENT; if (pcie || usbe || hube || ife) { 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); else if (ife) { struct usb_if_info ii; if (usb_by_idx(idx >> 8, &ui) == 0 && usb_if_by_num(idx >> 8, idx & 0xFF, &ii) == 0) sysfs_usb_if_uevent(&ui, &ii, 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; }