Xorg on a framebuffer -- the fbdev video driver, the evdev input driver, libpciaccess -- asks the kernel a fixed set of questions before it draws anything, and gives up on the first wrong answer. These are those answers. evdev: EVIOCGVERSION, GID, GNAME, GPHYS, GUNIQ, GPROP, GBIT, GKEY, GABS, GREP and the accepted setters. Xorg and libinput decide what a device *is* from the capability bitmaps; /dev/input/eventN answered only EVIOCGRAB, which reads as no device at all. Each device's bitmaps are its defaults plus whatever it has reported, so a USB tablet makes the pointer absolute. fbdev: FBIOPUT_VSCREENINFO succeeds for the mode already set, as on Linux -- the fbdev driver sets it every time it starts -- and still refuses any other. FBIOPAN_DISPLAY to the origin; FBIOBLANK. The console: KDSETMODE(KD_GRAPHICS) stops it drawing on a terminal, KDSKBMODE(K_OFF) stops keys reaching its tty, and VT_PROCESS now does the switch handshake: leaving the terminal sends relsig and waits for VT_RELDISP, arriving sends acqsig. A terminal whose owner has died goes back to the kernel whole, so a crashed X cannot leave the console dead. /sys: boot_vga, a writable enable, and graphics/fb0 under the display device, which is how the fbdev driver pairs a card with /dev/fb0. ports/linux-headers: <linux/*.h> into musl's prefix, from sabotage- linux's kernel-headers 4.19.88-2. Its checksum is recorded from the first download; upstream publishes none. cmd/xready makes each call and checks the answer, including the switch handshake on tty2 and an owner that dies holding it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
777 lines
23 KiB
C
777 lines
23 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
|
|
//
|
|
// 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"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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 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_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 },
|
|
};
|
|
|
|
#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" };
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
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_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_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_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);
|
|
}
|
|
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_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_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;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
if ((I_FILE(ino) == 0 || subdir) && exists_dir(ino)) {
|
|
ip->type = T_DIR;
|
|
ip->nlink = 2;
|
|
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)
|
|
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)u; (void)s; (void)o;
|
|
if (I_KIND(ip->inum) == K_PCI && I_FILE(ip->inum) == PCI_ENABLE)
|
|
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 ? 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;
|
|
}
|