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

@ -1674,23 +1674,32 @@ static UINT32
build_e820(const UINT8 *mapbuf, UINTN mapsize, UINTN descsize,
struct e820entry *out)
{
UINT32 nent = 0;
// Sorted by address first. The specification does not promise an ordered
// map, firmware that hands one over unsorted exists, and merging only
// finds neighbours that sit next to each other in the list.
static struct e820entry raw[1024];
UINT32 nraw = 0, nent = 0;
for (UINTN off = 0; off + descsize <= mapsize && nent < E820_MAXENTRIES;
for (UINTN off = 0; off + descsize <= mapsize && nraw < 1024;
off += descsize) {
EFI_MEMORY_DESCRIPTOR *d = (EFI_MEMORY_DESCRIPTOR *)(mapbuf + off);
UINT32 type = efi_type_to_e820(d->Type);
struct e820entry e = { d->PhysicalStart, d->NumberOfPages * 0x1000,
efi_type_to_e820(d->Type), 1 };
UINT32 i = nraw++;
while (i > 0 && raw[i - 1].base > e.base) {
raw[i] = raw[i - 1];
i--;
}
raw[i] = e;
}
if (nent > 0 && out[nent-1].type == type &&
out[nent-1].base + out[nent-1].length == d->PhysicalStart) {
out[nent-1].length += d->NumberOfPages * 0x1000;
for (UINT32 i = 0; i < nraw && nent < E820_MAXENTRIES; i++) {
if (nent > 0 && out[nent-1].type == raw[i].type &&
out[nent-1].base + out[nent-1].length == raw[i].base) {
out[nent-1].length += raw[i].length;
continue;
}
out[nent].base = d->PhysicalStart;
out[nent].length = d->NumberOfPages * 0x1000;
out[nent].type = type;
out[nent].acpi_ext = 1;
nent++;
out[nent++] = raw[i];
}
return nent;
}
@ -1724,6 +1733,26 @@ find_rsdp(void)
return fallback;
}
// The SMBIOS entry point, the same way: 3.0's 64-bit one first, since a
// table above 4 GB is out of reach of the 2.x one.
static UINT64
find_smbios(void)
{
EFI_GUID sm3 = SMBIOS3_TABLE_GUID;
EFI_GUID sm2 = SMBIOS_TABLE_GUID;
EFI_CONFIGURATION_TABLE *t =
(EFI_CONFIGURATION_TABLE *)ST->ConfigurationTable;
UINT64 fallback = 0;
for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) {
if (guid_eq(&t[i].VendorGuid, &sm3))
return (UINT64)(UINTN)t[i].VendorTable;
if (guid_eq(&t[i].VendorGuid, &sm2))
fallback = (UINT64)(UINTN)t[i].VendorTable;
}
return fallback;
}
EFI_STATUS
EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
@ -1769,6 +1798,7 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
// of it, for the same reason the e820 map is.
UINT64 rsdp = find_rsdp();
vprint(L"schos: acpi rsdp "); vprint_hex(rsdp); vprint(L"\r\n");
UINT64 smbios = find_smbios();
// The memory map, and then out. GetMemoryMap must be the last thing before
// ExitBootServices: the key it returns is invalidated by any allocation,
@ -1813,6 +1843,21 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
static struct e820entry e820[E820_MAXENTRIES];
UINT32 nent = build_e820(mapbuf, mapsize, descsize, e820);
// The hand-off block is written after ExitBootServices into memory nobody
// allocated, which is safe only while the firmware keeps nothing there
// that outlives it. Say so if it does, since the alternative is a kernel
// that dies in a way nothing points back at.
for (UINT32 i = 0; i < nent; i++)
if (e820[i].type != E820_USABLE &&
e820[i].base < SCHBOOT_E820_MAP + SCHBOOT_E820_MAX * 24 &&
e820[i].base + e820[i].length > SCHBOOT_BASE) {
print(L"schos: WARNING: firmware memory overlaps the hand-off "
L"block at 0x30000\r\n");
ui_progress_label("Firmware memory at 0x30000 -- may not boot");
BS->Stall(2000000);
break;
}
// Truncation is silent data loss: phystop comes from the largest usable
// region, so dropping the tail of the map can leave the kernel believing a
// 16 GB machine has a fraction of that. Say so while there is still a
@ -1863,6 +1908,8 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
copy((void *)SCHBOOT_E820_MAP, e820, nent * sizeof e820[0]);
*(UINT32 *)SCHBOOT_E820_COUNT = nent;
*(UINT64 *)SCHBOOT_RSDP_ADDR = rsdp;
*(UINT64 *)SCHBOOT_SMBIOS_ADDR = smbios;
*(UINT32 *)SCHBOOT_EFI_FLAG = SCHBOOT_EFI_MAGIC;
*(UINT8 *)SCHBOOT_VERBOSE_FLAG = (UINT8)VERBOSE_BOOT;
*(UINT8 *)SCHBOOT_DEBUG_FLAG = (UINT8)DEBUG_BOOT;