Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
938 lines
28 KiB
C
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 "core/types.h"
|
|
#include "uapi/user.h"
|
|
#include "uapi/fcntl.h"
|
|
#include "uapi/posix.h"
|
|
#include "uapi/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();
|
|
}
|