hwreport: describe a machine onto a USB stick, from a Linux-style /sys
hwreport writes one folder to the first read-write FAT mount, or mounts the first USB stick that takes vfat itself and unmounts it afterwards: summary.txt (machine, firmware, CPU, memory, every PCI and USB device with its driver or "-- no driver"), dmesg.txt, lspci.txt in the shape of lspci -nnk -xxx, lsusb.txt, dmi.txt, memmap.txt, the ACPI tables as raw .dat files iasl -d reads, and copies of /proc. Serials, the UUID and the MSDM/SLIC tables (a Windows licence key) are left out. It reads /sys, which is new: sysfs at Linux's paths and in Linux's formats -- /sys/bus/pci/devices with uevent, config and resource, /sys/bus/usb/devices, /sys/firmware/acpi/tables, dmi/tables, memmap and efi, /sys/class/dmi/id. toybox's lspci and lsusb work unmodified against it. The USB core now keeps a table of attached devices for it. The machine's name comes from SMBIOS (kernel/dev/dmi.c). The UEFI loader hands over the entry point from the configuration table, the kernel falls back to the 0xF0000 scan, and the log gets Linux's "DMI:" line. Two bugs found on the way: - The loader handed over at most 40 memory-map entries, in firmware order. A laptop's UEFI map is 60-150 descriptors with RAM above 4 GB listed last, so that is what was dropped. The entries now have their own area at 0x31000 with room for 256, are sorted before merging, and the kernel's 32 usable regions keep the largest when there are more. - MemTotal and sysinfo's totalram were PHYSTOP, an address, which counts the PCI hole: a 6 GB machine reported 8. Now the usable RAM, summed. usb.ids moves to /usr/share/hwdata, where lsusb looks for it, and pci.ids joins it (`make pci-ids`, BSD-3-Clause like usb.ids). Checked under QEMU q35: the report written to a FAT32 stick, fsck.fat clean, DSDT.dat disassembled by iasl on the host, lspci -k and lsusb showing names, 6 GB of RAM seen as 6137 MB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
edc4e0537d
commit
eab13241a0
25 changed files with 2520 additions and 65 deletions
|
|
@ -283,6 +283,7 @@ extern const struct fs_ops fat_ops;
|
|||
// Defined in fs/procfs.c -- the synthetic /proc.
|
||||
extern const struct fs_ops procfs_ops;
|
||||
extern const struct fs_ops devfs_ops;
|
||||
extern const struct fs_ops sysfs_ops;
|
||||
|
||||
// Is this filesystem read-only?
|
||||
//
|
||||
|
|
@ -305,6 +306,7 @@ ops_for(uint dev)
|
|||
if (SCHFS_IS_ANY_DEV(dev)) return &schfs_ops;
|
||||
if (dev == PROCFS_DEV) return &procfs_ops;
|
||||
if (dev == DEVFS_DEV) return &devfs_ops;
|
||||
if (dev == SYSFS_DEV) return &sysfs_ops;
|
||||
if (FAT_IS_DEV(dev)) return &fat_ops;
|
||||
return &v1_ops;
|
||||
}
|
||||
|
|
|
|||
712
kernel/fs/sysfs.c
Normal file
712
kernel/fs/sysfs.c
Normal file
|
|
@ -0,0 +1,712 @@
|
|||
// 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
|
||||
// /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 "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "kmem.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
#include "stat.h"
|
||||
#include "pci.h"
|
||||
#include "usb.h"
|
||||
#include "hwinfo.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",
|
||||
};
|
||||
#define PCI_CONFIG 9
|
||||
#define NPCIFILES ((int)(sizeof pcifiles / sizeof pcifiles[0]))
|
||||
|
||||
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;
|
||||
} 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: 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 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:
|
||||
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;
|
||||
|
||||
if (I_FILE(ino) == 0 && 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;
|
||||
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;
|
||||
}
|
||||
|
||||
static int
|
||||
sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n)
|
||||
{ (void)ip; (void)u; (void)s; (void)o; (void)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;
|
||||
}
|
||||
Loading…
Reference in a new issue