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:
Nexoniarz 2026-10-23 12:54:11 +02:00
commit eab13241a0
25 changed files with 2520 additions and 65 deletions

View file

@ -15,6 +15,7 @@
#include "spinlock.h"
#include "dma.h"
#include "usb.h"
#include "hwinfo.h"
// A staging buffer for control transfers.
//
@ -167,6 +168,108 @@ usb_offer(const struct usb_driver *drv, struct usb_dev *dev)
}
// ---------------------------------------------------------------------------
// What is plugged in, for /sys/bus/usb/devices.
//
// The controller owns each usb_dev; this only remembers which ones are
// attached, from usb_attach until usb_detach, and copies their identity out
// under the lock so a reader never follows a pointer the controller may be
// about to reuse. A bus number is handed to each controller the first time one
// of its devices appears, in that order, as Linux numbers usb1, usb2.
#define NPRESENT 64
#define NBUSES 8
static struct spinlock present_lock;
static int present_init;
static struct usb_dev *present[NPRESENT];
static void *buses[NBUSES];
static void
present_add(struct usb_dev *dev)
{
if(!present_init){
initlock(&present_lock, "usbpresent");
present_init = 1;
}
acquire(&present_lock);
for(int i = 0; i < NPRESENT; i++)
if(present[i] == 0){ present[i] = dev; break; }
for(int b = 0; b < NBUSES; b++){
if(buses[b] == dev->hc) break;
if(buses[b] == 0){ buses[b] = dev->hc; break; }
}
release(&present_lock);
}
static void
present_del(struct usb_dev *dev)
{
if(!present_init)
return;
acquire(&present_lock);
for(int i = 0; i < NPRESENT; i++)
if(present[i] == dev) present[i] = 0;
release(&present_lock);
}
static void
put_num(char *out, int *n, int cap, int v)
{
char t[12];
int k = 0;
do { t[k++] = '0' + v % 10; v /= 10; } while(v);
while(k && *n < cap - 1) out[(*n)++] = t[--k];
out[*n] = 0;
}
int
usb_info_nth(int idx, struct usb_info *o)
{
if(!present_init)
return -1;
acquire(&present_lock);
int seen = 0;
for(int i = 0; i < NPRESENT; i++){
struct usb_dev *d = present[i];
if(d == 0 || seen++ != idx)
continue;
memset(o, 0, sizeof *o);
o->bus = 1;
for(int b = 0; b < NBUSES; b++)
if(buses[b] == d->hc){ o->bus = b + 1; break; }
o->devnum = d->slot;
o->speed = d->speed;
o->vid = d->desc.idVendor;
o->pid = d->desc.idProduct;
o->bcd = d->desc.bcdDevice;
o->cls = d->desc.bDeviceClass;
o->subcls = d->desc.bDeviceSubClass;
o->proto = d->desc.bDeviceProtocol;
o->nconf = d->desc.bNumConfigurations;
// "<bus>-<root port>[.<hub port>...]": the route string holds one
// downstream port per tier below the root, a nibble each.
int n = 0, cap = (int)sizeof o->path;
put_num(o->path, &n, cap, o->bus);
o->path[n++] = '-';
put_num(o->path, &n, cap, d->port);
for(int t = 0; t < d->tier && n < cap - 3; t++){
o->path[n++] = '.';
put_num(o->path, &n, cap, (int)((d->route >> (4 * t)) & 0xF));
}
const char *drv = d->driver;
for(int k = 0; !drv && k < NIFDEV; k++)
if(ifdevs[k].in_use && ifdevs[k].base == d)
drv = ifdevs[k].driver;
if(drv)
safestrcpy(o->driver, drv, sizeof o->driver);
release(&present_lock);
return 0;
}
release(&present_lock);
return -1;
}
void
usb_attach(struct usb_dev *dev)
@ -177,6 +280,7 @@ usb_attach(struct usb_dev *dev)
if(buf == 0)
return;
present_add(dev);
// Nine bytes first, because wTotalLength is in them and there is no other
// way to know how much to ask for.
@ -260,6 +364,7 @@ usb_attach(struct usb_dev *dev)
void
usb_detach(struct usb_dev *dev)
{
present_del(dev);
for(int i = 0; i < NIFDEV; i++){
struct usb_dev *d = &ifdevs[i];
if(!d->in_use || d->base != dev)