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

@ -104,6 +104,7 @@ good chance it is in there.
| [testing.md](guides/testing.md) | The harnesses, and what each one actually proves. |
| [layout.md](guides/layout.md) | Repository map, and how to add a program or a port. |
| [adding-a-driver.md](guides/adding-a-driver.md) | Writing one, binding it, publishing its class vtable. |
| [hardware-report.md](guides/hardware-report.md) | `hwreport`: describing a machine onto a USB stick, for whoever fixes it. |
## branding/ — the marks

View file

@ -52,8 +52,11 @@ and `entry.S` so the two cannot disagree.
| `+0x000` | VBE-shaped mode info: LFB address, stride, width, height, depth |
| `+0x100` | Verbose-boot flag |
| `+0x101` | Boot medium (`1` = CD; `0` = not stated) |
| `+0x200` | E820 entry count, then up to 40 × 24-byte entries |
| `+0x200` | E820 entry count |
| `+0x600` | ACPI RSDP physical address, or 0 |
| `+0x638` | SMBIOS entry point physical address, or 0 |
| `+0x640` | `"EFI!"` when the UEFI loader booted the kernel |
| `+0x1000` | the E820 entries, up to 256 × 24 bytes |
Two of these exist because of specific failures worth knowing about.
@ -65,8 +68,14 @@ interrupt routing — and the machine boots to a prompt on polled serial output
and then ignores every keystroke, which looks like a broken console rather than
a missing table.
**The E820 cap is 40 entries, not 32.** Real UEFI firmware produces far more
descriptors than QEMU's, and the space was already there.
**The E820 entries have an area of their own**, room for 256. They used to sit
between the count and the RSDP, which held 40, and a laptop's UEFI map is 60 to
150 descriptors. The firmware lists RAM above 4 GB last, so that was what fell
off: a 16 GB machine came up with 2 or 3 GB of it.
**The SMBIOS entry point is passed the same way as the RSDP**, from the
configuration table, preferring the 64-bit `_SM3_` one. It names the machine in
the boot log's `DMI:` line and in `/sys/class/dmi/id`.
### Choosing a display, on a machine that has more than one
@ -174,13 +183,14 @@ schos: memory map larger than the loader's buffer (status 0x8000000000000005)
QEMU's own map needs 5664 bytes — 118 descriptors — so the real number on
hardware is larger still, and 256 KB is 5461 descriptors.)
**A second limit is nearby and is only a warning.** `SCHBOOT_E820_MAX` is 40
entries, fixed by the hand-off block's layout. `build_e820()` merges adjacent
regions of the same type, so 40 survives a long map — but if it ever truncates,
what gets dropped is the *high* end (the map arrives sorted, and `phystop` is
capped below 4 GB regardless). It now says so on screen as well as on the
serial line, because by that point `print()` goes to a port a laptop does not
have.
**A second limit is nearby and is only a warning.** `SCHBOOT_E820_MAX` is 256
entries. `build_e820()` sorts the descriptors by address first (the
specification does not promise an ordered map) and then merges neighbours of
the same type, so a long map fits easily. If one ever truncates, it says so on
screen as well as on the serial line, because by that point `print()` goes to a
port a laptop does not have. The kernel keeps at most 32 usable regions; past
that, the smallest give way to larger ones, so what is lost is crumbs rather
than whatever was listed last.
### Two signals for a machine that cannot speak

View file

@ -119,8 +119,8 @@ during path resolution. `/proc` is mounted by init:
mount("proc", "/proc", "proc", 0, 0);
```
The filesystem types are `proc`, `devtmpfs`, `schfs` and `vfat` (also accepted
as `msdos` and `fat`). Unmounting a FAT volume while anything on it is open, or
The filesystem types are `proc`, `devtmpfs`, `sysfs`, `schfs` and `vfat` (also
accepted as `msdos` and `fat`). Unmounting a FAT volume while anything on it is open, or
is some process's working directory, fails with `EBUSY`.
## procfs
@ -222,6 +222,46 @@ selftest, `t` text specimen, `n` the netdev ARP selftest.
---
## sysfs
`/sys`, in Linux's layout for the parts that exist here. `kernel/fs/sysfs.c`,
mounted by init with `mount("sysfs", "/sys", "sysfs", 0, 0)`.
| path | contents |
|------|----------|
| `/sys/bus/pci/devices/0000:BB:DD.F/` | `vendor device class revision subsystem_vendor subsystem_device irq resource config uevent` |
| `/sys/bus/usb/devices/B-P[.P...]/` | `idVendor idProduct bcdDevice busnum devnum speed bDeviceClass bDeviceSubClass bDeviceProtocol bNumConfigurations uevent` |
| `/sys/firmware/acpi/tables/` | each ACPI table, raw; a repeated signature is numbered from 1 (`SSDT1`, `SSDT2`) |
| `/sys/firmware/dmi/tables/` | `smbios_entry_point` and `DMI`, raw |
| `/sys/firmware/memmap/N/` | `start end type`, the firmware's map as the loader handed it over |
| `/sys/firmware/efi/` | present when UEFI booted the kernel; empty |
| `/sys/class/dmi/id/` | `sys_vendor product_name product_version board_name bios_version`, and the rest Linux has, except serials and the UUID |
**Linux's layout because everything that reads hardware reads it from there.**
toybox's `lspci` and `lsusb` walk `/sys/bus/*/devices` and parse each `uevent`
(`DRIVER=`, `PCI_ID=`, `PRODUCT=`, `BUSNUM=`), so with these files they work
unmodified, with names from `/usr/share/hwdata/pci.ids` and `usb.ids`. Formats
follow Linux to the character: `0x8086` in `vendor`, `%04x` in `idVendor`,
`PCI_CLASS` in upper case.
Like `/proc`, nothing is stored: a listing comes from the live PCI list, the
USB core's table of attached devices, the ACPI root table and the SMBIOS table
at the moment it is read, so a stick shows up in `/sys/bus/usb/devices` as soon
as it is attached. Text files report size 0, as `/proc`'s do; binary ones (the
tables, `config`) report their real length, since tools copy them by it.
What is missing, compared with Linux: `/sys/devices` and its symlinks (the
`bus/*/devices` entries are directories rather than links into it),
`/sys/class` beyond `dmi`, `/sys/block`, and anything writable. PCI `config` is
256 bytes, because config space is read through port `0xCF8`, which reaches
no further.
The machine's identity comes from SMBIOS (`kernel/dev/dmi.c`): the UEFI loader
hands over the entry point from the firmware's configuration table, and the
kernel falls back to scanning `0xF0000`-`0xFFFFF`. The boot log carries Linux's
line for it, `DMI: <vendor> <product>/<board>, BIOS <version> <date>`.
## Errno, and why `return -1` is a bug
A syscall that fails returns a negative errno. `-1` is therefore **`-EPERM`**,

View file

@ -0,0 +1,59 @@
# Reporting a machine
When SchOS half-works on a machine (no network, a dead touchpad, a disk it
cannot see), the fastest fix starts with a description of that machine.
`hwreport` writes one onto a USB stick that any other computer can read.
```
hwreport # onto a mounted FAT volume, or the first USB stick that mounts
hwreport /mnt # into a directory of your choosing
```
1. Plug in a FAT32 USB stick. Nearly every stick comes formatted that way.
2. Run `hwreport`. It mounts the stick itself if nothing is mounted, writes a
folder named `hwreport-YYYYMMDD-HHMMSS`, syncs, and unmounts again.
3. Pull the stick out once it says so, and send the folder from another
computer. Zip it first if your mail or chat client prefers one file.
It also prints `summary.txt` on the screen. On a machine where even the USB
port is not working, a photo of that screen is the next best thing.
## What is in the folder
| file | what it is |
|------|------------|
| `summary.txt` | machine, firmware, CPU, memory, and every PCI and USB device with the driver that claimed it, or `-- no driver` |
| `dmesg.txt` | the kernel log from this boot (and earlier ones, if a crash left them in memory) |
| `lspci.txt` | each PCI device the way `lspci -nnk -xxx` shows it: IDs, subsystem, driver, IRQ, the first 256 bytes of config space |
| `lsusb.txt` | each USB device and its `uevent` |
| `dmi.txt` | the SMBIOS identity: vendor, model, board, BIOS version |
| `memmap.txt` | the firmware's memory map |
| `acpi/*.dat` | every ACPI table, byte for byte; `iasl -d DSDT.dat` disassembles one |
| `acpi/tables.txt` | the table list with lengths and OEM IDs |
| `proc/*.txt` | `/proc` files: interrupts, devices, partitions, mounts and more |
The file names and formats are Linux's, so a report taken here can be compared
with `lspci`, `acpidump` and `dmesg` output taken under Linux on the same
machine.
## What is left out
- **Serial numbers and the system UUID.** The kernel does not publish them.
- **The MSDM and SLIC ACPI tables.** They hold the machine's Windows licence
key. `acpi/tables.txt` lists them as left out.
Nothing else in the report identifies its owner. Read `summary.txt` before
sending it if you want to be sure.
## Where the information comes from
All of it comes from `/sys`, laid out the way Linux lays it out (see
[filesystem.md](../architecture/filesystem.md#sysfs)), plus `/proc`. The same
files are there to read by hand:
```
cat /sys/class/dmi/id/product_name
ls /sys/firmware/acpi/tables
lspci -nnk
lsusb
```