Kernel devices (input, mem, tty, graphics, block) get /sys/devices/virtual entries with dev/uevent/subsystem, /sys/class and /sys/dev links, input capabilities, and writable uevent files that announce on the netlink socket. eudev now runs, processes a full trigger and settles. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
1204 lines
38 KiB
C
1204 lines
38 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/bus/pci/devices/0000:00:1f.2/ vendor device class revision irq
|
|
// subsystem_vendor subsystem_device
|
|
// resource config uevent boot_vga
|
|
// enable graphics/fb0/
|
|
// /sys/bus/usb/devices/1-3.2/ idVendor idProduct bcdDevice busnum
|
|
// devnum speed bDevice* uevent
|
|
// /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"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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/*
|
|
|
|
// 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_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 },
|
|
};
|
|
|
|
#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
|
|
|
|
// 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",
|
|
};
|
|
#define NUSBFILES ((int)(sizeof usbfiles / sizeof usbfiles[0]))
|
|
|
|
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;
|
|
}
|
|
|
|
// 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_PCIDEV:
|
|
for (int i = 0; i < pci_count(); i++) {
|
|
struct out o = { name, 0, sizeof name - 1 };
|
|
pci_name(&o, pci_get(i));
|
|
name[o.n] = 0;
|
|
if (cb(ctx, INO(K_PCI, i, 0), 1, name))
|
|
return 1;
|
|
}
|
|
break;
|
|
case D_USBDEV: {
|
|
struct usb_info u;
|
|
for (int i = 0; usb_info_nth(i, &u) == 0; i++)
|
|
if (cb(ctx, INO(K_USB, (u.bus << 8) | u.devnum, 0), 1, u.path))
|
|
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;
|
|
} else if (kind == K_PCI && I_FILE(dino) == PCI_GRAPHICS) {
|
|
if (cb(ctx, INO(K_PCI, idx, PCI_FB0), 1, "fb0"))
|
|
return 1;
|
|
} else if (kind == K_USB && I_FILE(dino) == 0) {
|
|
for (int f = 1; f < NUSBFILES; f++)
|
|
if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f]))
|
|
return 1;
|
|
} else if (kind == K_PSU && I_FILE(dino) == 0) {
|
|
struct acpi_psu b;
|
|
if (acpi_psu_get(idx, &b) == 0)
|
|
for (int f = 1; f < NPSUFILES; f++)
|
|
if (psu_has(&b, f) && cb(ctx, INO(K_PSU, idx, f), 0, psufiles[f]))
|
|
return 1;
|
|
} else if (kind == K_KSUBV || kind == K_KCLASS || kind == K_KDEVD ||
|
|
kind == K_KDEV) {
|
|
return kd_children(dino, cb, ctx);
|
|
} else if (kind == K_MEMMAP && I_FILE(dino) == 0) {
|
|
for (int f = 1; f < 4; f++)
|
|
if (cb(ctx, INO(K_MEMMAP, idx, f), 0, memfiles[f]))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint64
|
|
parent_of(uint64 dino)
|
|
{
|
|
switch (I_KIND(dino)) {
|
|
case K_DIR: return INO(K_DIR, dirs[I_IDX(dino)].parent, 0);
|
|
case K_PCI:
|
|
if (I_FILE(dino) == PCI_GRAPHICS) return INO(K_PCI, I_IDX(dino), 0);
|
|
if (I_FILE(dino) == PCI_FB0) return INO(K_PCI, I_IDX(dino), PCI_GRAPHICS);
|
|
return INO(K_DIR, D_PCIDEV, 0);
|
|
case K_USB: 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 11:
|
|
puts_(o, "DEVTYPE=usb_device\n");
|
|
if (u->driver[0]) { puts_(o, "DRIVER="); puts_(o, u->driver); putc_(o, '\n'); }
|
|
puts_(o, "PRODUCT="); putx(o, u->vid, 0, 0); putc_(o, '/');
|
|
putx(o, u->pid, 0, 0); putc_(o, '/'); putx(o, u->bcd, 0, 0);
|
|
puts_(o, "\nTYPE="); putu(o, u->cls); putc_(o, '/');
|
|
putu(o, u->subcls); putc_(o, '/'); putu(o, u->proto);
|
|
puts_(o, "\nBUSNUM=");
|
|
if (u->bus < 100) putc_(o, '0');
|
|
if (u->bus < 10) putc_(o, '0');
|
|
putu(o, (uint64)u->bus);
|
|
puts_(o, "\nDEVNUM=");
|
|
if (u->devnum < 100) putc_(o, '0');
|
|
if (u->devnum < 10) putc_(o, '0');
|
|
putu(o, (uint64)u->devnum);
|
|
break;
|
|
}
|
|
putc_(o, '\n');
|
|
}
|
|
|
|
// 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_PCI:
|
|
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_USB: {
|
|
struct usb_info u;
|
|
if (usb_by_idx(idx, &u) < 0 || f < 1 || f >= NUSBFILES)
|
|
return -1;
|
|
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;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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_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);
|
|
return k == K_KCLINK || k == K_KDEVL ||
|
|
(k == K_KDEV && I_FILE(ino) == KF_SUBSYSTEM);
|
|
}
|
|
|
|
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) == NUSBFILES - 1) ||
|
|
(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. Only the kernel's own devices are announced;
|
|
// a PCI or USB device's uevent accepts the write and says nothing, because
|
|
// their /sys paths are not the /devices tree an event's DEVPATH must name.
|
|
if (I_KIND(ip->inum) == K_PCI || I_KIND(ip->inum) == K_USB)
|
|
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;
|
|
}
|