SchOS/cmd/hwreport/main.c
Nexoniarz eab13241a0 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
2026-10-23 12:54:11 +02:00

938 lines
28 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// hwreport -- write down what this machine is, onto something you can carry.
//
// hwreport onto the first FAT volume mounted read-write, or the
// first USB stick it can mount, or /root
// hwreport <dir> into <dir>
//
// It exists for the machines that do not work. A laptop SchOS half-supports
// has no network driver more often than not, and photos of a screen are a poor
// way to carry a boot log and the tables that explain it. So this collects
// what anybody debugging the port would ask for first -- the log, the PCI and
// USB devices and what claimed each, the ACPI tables, the memory map, the
// machine's name -- into one folder on a stick that opens on any other system.
//
// The layout is Linux's where Linux has one: the ACPI tables are the raw .dat
// files `iasl -d` disassembles, and lspci.txt holds what `lspci -nnk -xxx`
// would, so a report from here can be compared line for line with one taken
// on Linux on the same machine.
//
// What it leaves out on purpose: serial numbers and the system UUID (the
// kernel does not publish them), and the MSDM and SLIC tables, which hold a
// Windows licence key. A report is meant to be handed to somebody.
#include "types.h"
#include "user.h"
#include "fcntl.h"
#include "posix.h"
#include "stat.h"
// ---------------------------------------------------------------------------
// Small things a libc would have
// ---------------------------------------------------------------------------
static int
slen(const char *s)
{
int n = 0;
while (s[n]) n++;
return n;
}
static int
seq(const char *a, const char *b)
{
while (*a && *a == *b) { a++; b++; }
return *a == *b;
}
static int
sprefix(const char *s, const char *p)
{
while (*p && *s == *p) { s++; p++; }
return *p == 0;
}
static void
scpy(char *d, const char *s, int cap)
{
int n = 0;
while (s[n] && n < cap - 1) { d[n] = s[n]; n++; }
d[n] = 0;
}
// An output buffer, flushed to a file in one write -- each write to FAT is a
// whole operation with its own flush, so fewer is much faster.
struct buf { char *p; int n, cap; };
static char textmem[512 * 1024];
static struct buf T = { textmem, 0, sizeof textmem };
static void bc(struct buf *b, char c) { if (b->n < b->cap) b->p[b->n++] = c; }
static void bs(struct buf *b, const char *s) { while (*s) bc(b, *s++); }
static void bsn(struct buf *b, const char *s, int n) { while (n-- > 0) bc(b, *s++); }
static void
bu(struct buf *b, unsigned long long v)
{
char t[24];
int k = 0;
do { t[k++] = '0' + v % 10; v /= 10; } while (v);
while (k) bc(b, t[--k]);
}
static void
bx(struct buf *b, unsigned long long v, int digits)
{
static const char h[] = "0123456789abcdef";
char t[16];
int k = 0;
do { t[k++] = h[v & 0xF]; v >>= 4; } while (v || k < digits);
while (k) bc(b, t[--k]);
}
static void
bpad(struct buf *b, const char *s, int w)
{
int n = slen(s);
bs(b, s);
while (n++ < w) bc(b, ' ');
}
static void
say(const char *s)
{
write(1, (void *)s, slen(s));
}
static void
saybuf(struct buf *b)
{
write(1, b->p, b->n);
b->n = 0;
}
// ---------------------------------------------------------------------------
// Files
// ---------------------------------------------------------------------------
static char iobuf[64 * 1024];
// The whole of a small file into `out`, terminated. Length, or -1.
static int
slurp(const char *path, char *out, int cap)
{
int fd = open((char *)path, O_RDONLY), n = 0, r;
if (fd < 0)
return -1;
while (n < cap - 1 && (r = read(fd, out + n, cap - 1 - n)) > 0)
n += r;
close(fd);
out[n] = 0;
return n;
}
// One line of a file, trailing newline dropped; "" when it is missing.
static char *
line(const char *path)
{
static char l[8][256];
static int k;
char *s = l[k++ & 7];
if (slurp(path, s, 256) < 0)
s[0] = 0;
for (int i = 0; s[i]; i++)
if (s[i] == '\n') { s[i] = 0; break; }
return s;
}
static char dir[512];
static int nfiles, nfail;
static void
path_in(char *out, const char *name)
{
int n = 0;
for (const char *p = dir; *p && n < 500; p++) out[n++] = *p;
out[n++] = '/';
for (const char *p = name; *p && n < 510; p++) out[n++] = *p;
out[n] = 0;
}
static int
put_file(const char *name, const char *data, int len)
{
char p[520];
path_in(p, name);
int fd = open(p, O_WRONLY | O_CREATE | O_TRUNC);
if (fd < 0) {
nfail++;
return -1;
}
int done = 0;
while (done < len) {
int r = write(fd, (void *)(data + done), len - done);
if (r <= 0)
break;
done += r;
}
close(fd);
if (done != len) {
nfail++;
return -1;
}
nfiles++;
return 0;
}
static void
put_text(const char *name)
{
put_file(name, T.p, T.n);
T.n = 0;
}
// Stream `src` to `name`, for files of any size.
static long
copy_to(const char *src, const char *name)
{
char p[520];
int in = open((char *)src, O_RDONLY);
if (in < 0)
return -1;
path_in(p, name);
int fd = open(p, O_WRONLY | O_CREATE | O_TRUNC);
if (fd < 0) {
close(in);
nfail++;
return -1;
}
long total = 0;
int r;
while ((r = read(in, iobuf, sizeof iobuf)) > 0) {
if (write(fd, iobuf, r) != r) {
total = -1;
break;
}
total += r;
}
close(in);
close(fd);
if (total < 0)
nfail++;
else
nfiles++;
return total;
}
// Every entry of a directory but the dots, sorted, into names[].
#define MAXNAMES 256
static char names[MAXNAMES][64];
static int
list(const char *path)
{
static char dbuf[4096];
int fd = open((char *)path, O_RDONLY | O_DIRECTORY), n = 0, r;
if (fd < 0)
return 0;
while ((r = getdents64(fd, dbuf, sizeof dbuf)) > 0)
for (int off = 0; off < r; ) {
struct dirent64 *d = (struct dirent64 *)(dbuf + off);
off += d->d_reclen;
if (seq(d->d_name, ".") || seq(d->d_name, "..") || n >= MAXNAMES)
continue;
scpy(names[n++], d->d_name, 64);
}
close(fd);
for (int i = 1; i < n; i++) /* insertion sort; n is small */
for (int j = i; j > 0; j--) {
const char *a = names[j - 1], *b = names[j];
while (*a && *a == *b) { a++; b++; }
if ((unsigned char)*a <= (unsigned char)*b)
break;
char t[64];
scpy(t, names[j], 64);
scpy(names[j], names[j - 1], 64);
scpy(names[j - 1], t, 64);
}
return n;
}
static void
join(char *out, const char *a, const char *b)
{
int n = 0;
while (*a && n < 250) out[n++] = *a++;
out[n++] = '/';
while (*b && n < 510) out[n++] = *b++;
out[n] = 0;
}
// ---------------------------------------------------------------------------
// Where to write
// ---------------------------------------------------------------------------
static int mounted_here;
static const char *mnt = "/mnt/hwreport";
// The first read-write FAT mount in /proc/mounts.
static int
find_fat_mount(char *out, int cap)
{
static char m[4096];
if (slurp("/proc/mounts", m, sizeof m) < 0)
return -1;
for (char *l = m; *l; ) {
char *f[4] = { 0 };
int nf = 0;
char *e = l;
while (*e && *e != '\n') e++;
char save = *e;
*e = 0;
for (char *p = l; *p && nf < 4; ) {
while (*p == ' ') *p++ = 0;
if (!*p) break;
f[nf++] = p;
while (*p && *p != ' ') p++;
}
if (nf == 4 && seq(f[2], "vfat") && !sprefix(f[3], "ro")) {
scpy(out, f[1], cap);
return 0;
}
l = save ? e + 1 : e;
}
return -1;
}
// Mount the first USB disk or partition that takes FAT. Partitions first,
// since a stick almost always has one and its whole-disk node is the table.
static int
mount_usb(char *out, int cap)
{
int n = list("/dev");
mkdir("/mnt");
mkdir((char *)mnt);
for (int pass = 0; pass < 2; pass++)
for (int i = 0; i < n; i++) {
const char *nm = names[i];
if (!sprefix(nm, "usb"))
continue;
int haspart = 0;
for (const char *p = nm + 3; *p; p++)
if (*p == 'p') haspart = 1;
if (haspart != (pass == 0))
continue;
char dev[80];
join(dev, "/dev", nm);
if (mount(dev, mnt, "vfat", 0, 0) == 0) {
say("hwreport: mounted ");
say(dev);
say(" on ");
say(mnt);
say("\n");
mounted_here = 1;
scpy(out, mnt, cap);
return 0;
}
}
return -1;
}
// "hwreport-20261023-142501", or "hwreport-N" when the clock is not set.
static void
make_dir(const char *base)
{
struct timespec ts;
struct buf b = { dir, 0, sizeof dir - 1 };
bs(&b, base);
if (b.n > 1 || base[0] != '/')
bc(&b, '/');
bs(&b, "hwreport-");
if (clock_gettime(CLOCK_REALTIME, &ts) == 0 && ts.tv_sec > 946684800) {
// Days to a civil date: Howard Hinnant's algorithm.
long long s = ts.tv_sec, days = s / 86400, rem = s % 86400;
long long z = days + 719468, era = z / 146097;
long long doe = z - era * 146097;
long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
long long y = yoe + era * 400, doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
long long mp = (5 * doy + 2) / 153, d = doy - (153 * mp + 2) / 5 + 1;
long long mo = mp < 10 ? mp + 3 : mp - 9;
if (mo <= 2) y++;
bu(&b, (unsigned long long)y);
if (mo < 10) bc(&b, '0');
bu(&b, (unsigned long long)mo);
if (d < 10) bc(&b, '0');
bu(&b, (unsigned long long)d);
bc(&b, '-');
long long hh = rem / 3600, mm = rem / 60 % 60, ss = rem % 60;
if (hh < 10) bc(&b, '0');
bu(&b, (unsigned long long)hh);
if (mm < 10) bc(&b, '0');
bu(&b, (unsigned long long)mm);
if (ss < 10) bc(&b, '0');
bu(&b, (unsigned long long)ss);
dir[b.n] = 0;
if (mkdir(dir) == 0)
return;
bc(&b, '-');
}
int keep = b.n;
for (int i = 1; i < 1000; i++) {
b.n = keep;
bu(&b, (unsigned long long)i);
dir[b.n] = 0;
if (mkdir(dir) == 0)
return;
}
dir[0] = 0;
}
// ---------------------------------------------------------------------------
// The report
// ---------------------------------------------------------------------------
static const char *
chassis_name(int t)
{
static const char *const n[] = {
0, "Other", "Unknown", "Desktop", "Low Profile Desktop", "Pizza Box",
"Mini Tower", "Tower", "Portable", "Laptop", "Notebook", "Hand Held",
"Docking Station", "All in One", "Sub Notebook", "Space-saving",
"Lunch Box", "Main Server Chassis", "Expansion Chassis", "SubChassis",
"Bus Expansion Chassis", "Peripheral Chassis", "RAID Chassis",
"Rack Mount Chassis", "Sealed-case PC", "Multi-system chassis",
"Compact PCI", "AdvancedTCA", "Blade", "Blade Enclosure", "Tablet",
"Convertible", "Detachable", "IoT Gateway", "Embedded PC", "Mini PC",
"Stick PC",
};
return t > 0 && t < (int)(sizeof n / sizeof n[0]) ? n[t] : "?";
}
static int
atoi_(const char *s)
{
int v = 0;
while (*s >= '0' && *s <= '9') v = v * 10 + (*s++ - '0');
return v;
}
static unsigned
hexv(const char *s)
{
unsigned v = 0;
if (s[0] == '0' && s[1] == 'x') s += 2;
for (;; s++) {
int c = *s;
if (c >= '0' && c <= '9') v = v * 16 + (unsigned)(c - '0');
else if (c >= 'a' && c <= 'f') v = v * 16 + (unsigned)(c - 'a' + 10);
else if (c >= 'A' && c <= 'F') v = v * 16 + (unsigned)(c - 'A' + 10);
else break;
}
return v;
}
// The value of KEY= in a uevent, or "".
static const char *
uevent_get(const char *ev, const char *key, char *out, int cap)
{
int kl = slen(key);
out[0] = 0;
for (const char *l = ev; *l; ) {
if (sprefix(l, key) && l[kl] == '=') {
int n = 0;
for (const char *p = l + kl + 1; *p && *p != '\n' && n < cap - 1; p++)
out[n++] = *p;
out[n] = 0;
break;
}
while (*l && *l != '\n') l++;
if (*l) l++;
}
return out;
}
static struct buf S = { 0, 0, 0 }; // summary.txt, built alongside
static char summem[64 * 1024];
static void
dmi_section(void)
{
static const char *const f[] = {
"sys_vendor", "product_name", "product_version", "product_family",
"product_sku", "board_vendor", "board_name", "board_version",
"bios_vendor", "bios_version", "bios_date", "bios_release",
"chassis_vendor", "chassis_type", "chassis_version",
};
char p[128];
for (int i = 0; i < (int)(sizeof f / sizeof f[0]); i++) {
join(p, "/sys/class/dmi/id", f[i]);
bpad(&T, f[i], 17);
bs(&T, line(p));
bc(&T, '\n');
}
put_text("dmi.txt");
char a[256], b[256];
scpy(a, line("/sys/class/dmi/id/sys_vendor"), 256);
scpy(b, line("/sys/class/dmi/id/product_name"), 256);
bs(&S, "Machine: ");
bs(&S, a[0] || b[0] ? a : "(no SMBIOS table)");
bc(&S, ' ');
bs(&S, b);
const char *v = line("/sys/class/dmi/id/product_version");
if (v[0]) { bs(&S, " / "); bs(&S, v); }
const char *fam = line("/sys/class/dmi/id/product_family");
if (fam[0]) { bs(&S, " ("); bs(&S, fam); bc(&S, ')'); }
bs(&S, "\nBoard: ");
bs(&S, line("/sys/class/dmi/id/board_vendor")); bc(&S, ' ');
bs(&S, line("/sys/class/dmi/id/board_name")); bc(&S, ' ');
bs(&S, line("/sys/class/dmi/id/board_version"));
bs(&S, "\nFirmware: ");
bs(&S, line("/sys/class/dmi/id/bios_vendor")); bc(&S, ' ');
bs(&S, line("/sys/class/dmi/id/bios_version")); bc(&S, ' ');
bs(&S, line("/sys/class/dmi/id/bios_date"));
{
struct stat st;
bs(&S, stat("/sys/firmware/efi", &st) == 0 ? ", booted by UEFI" :
", booted by BIOS");
}
const char *ct = line("/sys/class/dmi/id/chassis_type");
if (ct[0]) {
bs(&S, "\nChassis: ");
bs(&S, chassis_name(atoi_(ct)));
bs(&S, " (type ");
bs(&S, ct);
bc(&S, ')');
}
bc(&S, '\n');
}
static void
cpu_mem_section(void)
{
static char ci[16 * 1024];
int ncpu = 0;
char model[128] = "";
if (slurp("/proc/cpuinfo", ci, sizeof ci) >= 0)
for (char *l = ci; *l; ) {
if (sprefix(l, "processor"))
ncpu++;
if (!model[0] && sprefix(l, "model name")) {
char *p = l;
while (*p && *p != ':') p++;
if (*p) p++;
while (*p == ' ') p++;
int n = 0;
while (p[n] && p[n] != '\n' && n < 127) { model[n] = p[n]; n++; }
model[n] = 0;
}
while (*l && *l != '\n') l++;
if (*l) l++;
}
bs(&S, "CPU: ");
bs(&S, model[0] ? model : "?");
bs(&S, ", ");
bu(&S, (unsigned long long)ncpu);
bs(&S, ncpu == 1 ? " processor running\n" : " processors running\n");
bs(&S, "Memory: ");
char *mt = line("/proc/meminfo");
while (*mt && *mt != ':') mt++;
if (*mt) mt++;
while (*mt == ' ') mt++;
bs(&S, mt);
bs(&S, " usable\nSchOS: ");
bs(&S, line("/proc/version"));
bc(&S, '\n');
}
static const char *
pci_class_name(unsigned cls)
{
switch (cls >> 8) {
case 0x0100: return "SCSI"; case 0x0101: return "IDE";
case 0x0104: return "RAID"; case 0x0106: return "SATA";
case 0x0108: return "NVMe"; case 0x0200: return "Ethernet";
case 0x0280: return "Network (Wi-Fi?)"; case 0x0300: return "VGA";
case 0x0302: return "3D controller"; case 0x0380: return "Display";
case 0x0401: return "Audio"; case 0x0403: return "HD Audio";
case 0x0480: return "Multimedia"; case 0x0600: return "Host bridge";
case 0x0601: return "ISA bridge"; case 0x0604: return "PCI bridge";
case 0x0680: return "Bridge"; case 0x0805: return "SD host";
case 0x0880: return "System peripheral";case 0x0c03: return "USB";
case 0x0c05: return "SMBus"; case 0x0c80: return "Serial bus";
case 0x0d00: return "IrDA"; case 0x0d11: return "Bluetooth";
case 0x1180: return "Signal processing";case 0x0c09: return "CANbus";
case 0x0c10: return "Thunderbolt"; case 0x0780: return "Communication";
case 0x0700: return "Serial"; case 0x0c00: return "FireWire";
}
switch (cls >> 16) {
case 0x01: return "Storage"; case 0x02: return "Network";
case 0x03: return "Display"; case 0x04: return "Multimedia";
case 0x06: return "Bridge"; case 0x08: return "System";
case 0x0c: return "Serial bus";
}
return "Other";
}
static void
pci_section(void)
{
static char ev[1024], cfg[256];
char p[256], q[256], drv[32], id[32];
int n = list("/sys/bus/pci/devices"), nodrv = 0;
struct buf L = { 0, 0, 0 };
static char lmem[160 * 1024];
L.p = lmem; L.cap = sizeof lmem;
// Copy the names: list() is reused below.
static char devs[MAXNAMES][64];
for (int i = 0; i < n; i++)
scpy(devs[i], names[i], 64);
bs(&S, "\nPCI devices (");
bu(&S, (unsigned long long)n);
bs(&S, "):\n");
for (int i = 0; i < n; i++) {
join(p, "/sys/bus/pci/devices", devs[i]);
join(q, p, "uevent");
if (slurp(q, ev, sizeof ev) < 0)
continue;
join(q, p, "class");
unsigned cls = hexv(line(q));
join(q, p, "revision");
unsigned rev = hexv(line(q));
uevent_get(ev, "DRIVER", drv, sizeof drv);
uevent_get(ev, "PCI_ID", id, sizeof id);
for (char *c = id; *c; c++)
if (*c >= 'A' && *c <= 'F') *c += 'a' - 'A';
char sub[32];
uevent_get(ev, "PCI_SUBSYS_ID", sub, sizeof sub);
for (char *c = sub; *c; c++)
if (*c >= 'A' && *c <= 'F') *c += 'a' - 'A';
// lspci -nnk shape.
const char *bdf = devs[i] + 5;
bs(&L, bdf);
bc(&L, ' ');
bs(&L, pci_class_name(cls));
bs(&L, " [");
bx(&L, cls >> 8, 4);
bs(&L, "]: [");
bs(&L, id);
bc(&L, ']');
if (rev) { bs(&L, " (rev "); bx(&L, rev, 2); bc(&L, ')'); }
bs(&L, "\n\tSubsystem: [");
bs(&L, sub);
bs(&L, "]\n");
if (drv[0]) { bs(&L, "\tKernel driver in use: "); bs(&L, drv); bc(&L, '\n'); }
join(q, p, "irq");
bs(&L, "\tIRQ: ");
bs(&L, line(q));
bc(&L, '\n');
join(q, p, "config");
int cl = 0, fd = open(q, O_RDONLY);
if (fd >= 0) { cl = read(fd, cfg, 256); close(fd); }
for (int o = 0; o < cl; o += 16) {
bx(&L, (unsigned)o, 2);
bc(&L, ':');
for (int k = 0; k < 16 && o + k < cl; k++) {
bc(&L, ' ');
bx(&L, (unsigned char)cfg[o + k], 2);
}
bc(&L, '\n');
}
bc(&L, '\n');
bs(&S, " ");
bs(&S, bdf);
bc(&S, ' ');
bs(&S, id);
bc(&S, ' ');
bpad(&S, pci_class_name(cls), 18);
if (drv[0]) {
bs(&S, drv);
} else {
bs(&S, "-- no driver");
nodrv++;
}
bc(&S, '\n');
}
if (n == 0)
bs(&S, " (none found; is /sys mounted?)\n");
else if (nodrv) {
bs(&S, " ");
bu(&S, (unsigned long long)nodrv);
bs(&S, " without a driver.\n");
}
put_file("lspci.txt", L.p, L.n);
}
static void
usb_section(void)
{
static char ev[1024];
char p[256], q[256], drv[32], prod[32];
int n = list("/sys/bus/usb/devices");
static char devs[MAXNAMES][64];
for (int i = 0; i < n; i++)
scpy(devs[i], names[i], 64);
bs(&S, "\nUSB devices (");
bu(&S, (unsigned long long)n);
bs(&S, "):\n");
for (int i = 0; i < n; i++) {
join(p, "/sys/bus/usb/devices", devs[i]);
join(q, p, "uevent");
if (slurp(q, ev, sizeof ev) < 0)
continue;
uevent_get(ev, "DRIVER", drv, sizeof drv);
uevent_get(ev, "PRODUCT", prod, sizeof prod);
join(q, p, "idVendor");
const char *vid = line(q);
join(q, p, "idProduct");
const char *pid = line(q);
join(q, p, "speed");
const char *spd = line(q);
bs(&T, devs[i]);
bs(&T, ": ID ");
bs(&T, vid); bc(&T, ':'); bs(&T, pid);
bs(&T, ", ");
bs(&T, spd);
bs(&T, " Mb/s, driver ");
bs(&T, drv[0] ? drv : "(none)");
bs(&T, "\n");
bsn(&T, ev, slen(ev));
bc(&T, '\n');
bs(&S, " ");
bpad(&S, devs[i], 8);
bs(&S, vid); bc(&S, ':'); bs(&S, pid);
bs(&S, " ");
bpad(&S, spd, 5);
bs(&S, " Mb/s ");
bs(&S, drv[0] ? drv : "-- no driver");
bc(&S, '\n');
}
put_text("lsusb.txt");
}
static void
memmap_section(void)
{
char p[256], q[256];
int n = list("/sys/firmware/memmap");
static char ent[MAXNAMES][64];
unsigned long long ram = 0;
for (int i = 0; i < n; i++)
scpy(ent[i], names[i], 64);
// Numerically, not as the names sort.
for (int want = 0; want < n; want++) {
char nm[16];
struct buf b = { nm, 0, 15 };
bu(&b, (unsigned long long)want);
nm[b.n] = 0;
join(p, "/sys/firmware/memmap", nm);
join(q, p, "start");
char s[64];
scpy(s, line(q), 64);
join(q, p, "end");
char e[64];
scpy(e, line(q), 64);
join(q, p, "type");
const char *t = line(q);
bpad(&T, s, 20);
bpad(&T, e, 20);
bs(&T, t);
bc(&T, '\n');
if (seq(t, "System RAM")) {
unsigned long long a = 0, z = 0;
for (const char *c = s + 2; *c; c++)
a = a * 16 + (unsigned long long)(*c <= '9' ? *c - '0' : (*c | 32) - 'a' + 10);
for (const char *c = e + 2; *c; c++)
z = z * 16 + (unsigned long long)(*c <= '9' ? *c - '0' : (*c | 32) - 'a' + 10);
ram += z - a + 1;
}
}
put_text("memmap.txt");
bs(&S, "Firmware memory map: ");
bu(&S, (unsigned long long)n);
bs(&S, " entries, ");
bu(&S, ram >> 20);
bs(&S, " MB of RAM\n");
}
static void
acpi_section(void)
{
char p[256], q[256];
static unsigned char hdr[36];
int n = list("/sys/firmware/acpi/tables"), skipped = 0, copied = 0;
static char tabs[MAXNAMES][64];
for (int i = 0; i < n; i++)
scpy(tabs[i], names[i], 64);
mkdir((path_in(p, "acpi"), p));
bs(&T, "name length rev oem oem table oem rev\n");
for (int i = 0; i < n; i++) {
join(p, "/sys/firmware/acpi/tables", tabs[i]);
int fd = open(p, O_RDONLY), h = 0;
if (fd >= 0) { h = read(fd, hdr, 36); close(fd); }
// A Windows licence key, which belongs to the machine's owner.
if (sprefix(tabs[i], "MSDM") || sprefix(tabs[i], "SLIC")) {
bpad(&T, tabs[i], 9);
bs(&T, "(left out: holds a Windows licence key)\n");
skipped++;
continue;
}
if (h >= 36) {
unsigned len = hdr[4] | hdr[5] << 8 | hdr[6] << 16 | (unsigned)hdr[7] << 24;
bpad(&T, tabs[i], 9);
char num[16];
struct buf b = { num, 0, 15 };
bu(&b, len);
num[b.n] = 0;
bpad(&T, num, 8);
bu(&T, hdr[8]);
bs(&T, hdr[8] < 10 ? " " : " ");
bsn(&T, (char *)hdr + 10, 6);
bs(&T, " ");
bsn(&T, (char *)hdr + 16, 8);
bs(&T, " 0x");
bx(&T, hdr[24] | hdr[25] << 8 | hdr[26] << 16 | (unsigned)hdr[27] << 24, 8);
bc(&T, '\n');
} else {
bpad(&T, tabs[i], 9);
bs(&T, "(no header)\n");
}
char dst[80];
struct buf b = { dst, 0, 70 };
bs(&b, "acpi/");
bs(&b, tabs[i]);
bs(&b, ".dat");
dst[b.n] = 0;
(void)q;
if (copy_to(p, dst) >= 0)
copied++;
}
put_text("acpi/tables.txt");
bs(&S, "ACPI tables: ");
bu(&S, (unsigned long long)copied);
bs(&S, " saved in acpi/");
if (skipped) {
bs(&S, " (");
bu(&S, (unsigned long long)skipped);
bs(&S, " left out: Windows licence key)");
}
if (n == 0)
bs(&S, " -- none found, so no ACPI at all");
bc(&S, '\n');
}
static void
proc_copies(void)
{
static const char *const f[] = {
"cpuinfo", "meminfo", "interrupts", "devices", "partitions", "mounts",
"pci", "netif", "version", "bootmode", "filesystems", "fsstat", "stat",
};
char src[64], dst[64];
mkdir((path_in(dst, "proc"), dst));
for (int i = 0; i < (int)(sizeof f / sizeof f[0]); i++) {
struct buf a = { src, 0, 60 }, b = { dst, 0, 60 };
bs(&a, "/proc/"); bs(&a, f[i]); src[a.n] = 0;
bs(&b, "proc/"); bs(&b, f[i]); bs(&b, ".txt"); dst[b.n] = 0;
copy_to(src, dst);
}
bs(&S, "\nDisks and partitions:\n");
static char pt[4096];
if (slurp("/proc/partitions", pt, sizeof pt) > 0)
bs(&S, pt);
}
int
main(int argc, char **argv)
{
char base[256];
S.p = summem;
S.cap = sizeof summem;
if (argc > 1 && (seq(argv[1], "-h") || seq(argv[1], "--help"))) {
say("usage: hwreport [dir]\n"
"Writes a folder describing this machine: the boot log, PCI and USB\n"
"devices, ACPI tables, memory map and firmware identity. With no dir,\n"
"onto a mounted FAT volume or the first USB stick that mounts.\n");
exit();
}
if (argc > 1)
scpy(base, argv[1], sizeof base);
else if (find_fat_mount(base, sizeof base) != 0 &&
mount_usb(base, sizeof base) != 0) {
say("hwreport: no FAT volume mounted and no USB stick would mount;\n"
" writing to /root instead. Plug a FAT32 stick in and run\n"
" it again, or give a directory: hwreport /mnt\n");
scpy(base, "/root", sizeof base);
}
make_dir(base);
if (!dir[0]) {
say("hwreport: cannot create a folder in ");
say(base);
say("\n");
exit();
}
say("hwreport: writing ");
say(dir);
say("\n");
bs(&S, "SchOS hardware report\n=====================\n\n");
dmi_section();
cpu_mem_section();
memmap_section();
acpi_section();
pci_section();
usb_section();
proc_copies();
long kl = copy_to("/proc/kmsg", "dmesg.txt");
bs(&S, "\nFiles: summary.txt, dmesg.txt (the boot log");
if (kl >= 0) { bs(&S, ", "); bu(&S, (unsigned long long)kl); bs(&S, " bytes"); }
bs(&S, "), dmi.txt, lspci.txt, lsusb.txt,\n"
"memmap.txt, acpi/*.dat (iasl -d disassembles them), proc/*.txt\n");
put_file("summary.txt", S.p, S.n);
// On the screen too, for a machine whose stick is about to be pulled.
saybuf(&S);
sync();
if (mounted_here) {
if (umount2(mnt, 0) == 0)
say("\nhwreport: unmounted; the stick can be pulled out.\n");
else
say("\nhwreport: written and synced, but the unmount failed.\n");
}
struct buf r = { iobuf, 0, 200 };
bs(&r, "hwreport: ");
bu(&r, (unsigned long long)nfiles);
bs(&r, " files in ");
bs(&r, dir);
if (nfail) {
bs(&r, ", ");
bu(&r, (unsigned long long)nfail);
bs(&r, " could not be written");
}
bs(&r, "\n");
saybuf(&r);
exit();
}