PCI devices now nest under the bridge they sit behind (from each bridge's secondary..subordinate window), so a controller behind a root port is where Linux would put it. USB interfaces (1-5:1.0) are directories with class, number, a driver link and a uevent with INTERFACE= and a MODALIAS, announced after the drivers have been offered the device and removed before it. The xHCI driver tells the USB core its PCI address when it comes up, replacing the guess that bus N was the Nth controller. Devices and root hubs carry version, bMaxPacketSize0, bNumInterfaces, bmAttributes and the rest; lsusb -t from Alpine's usbutils now draws the tree. Checked on q35 with an xHCI behind a pcie-root-port and a USB keyboard on each controller. Plain lsusb still fails: libusb wants usbfs, and refuses a kernel that calls itself 0.1.0. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
1819 lines
57 KiB
C
1819 lines
57 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_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_<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)
|
|
: 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/<bridge>/.../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/<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);
|
|
}
|
|
|
|
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;
|
|
}
|