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:
parent
edc4e0537d
commit
eab13241a0
25 changed files with 2520 additions and 65 deletions
285
kernel/dev/dmi.c
Normal file
285
kernel/dev/dmi.c
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
// SMBIOS: what the firmware says this machine is.
|
||||
//
|
||||
// Vendor, model, board and firmware version live nowhere else. ACPI describes
|
||||
// the hardware's wiring and PCI its parts, but "Lenovo 20XW / ThinkPad X1
|
||||
// Carbon Gen 9, BIOS N32ET75W" is only in this table, and it is the first line
|
||||
// anybody asks for about a machine that will not boot. Linux prints it as the
|
||||
// "DMI:" line of its log and publishes the fields in /sys/class/dmi/id; this
|
||||
// does both, under the same names.
|
||||
//
|
||||
// Written from the DMTF's SMBIOS specification (DSP0134). The entry point is
|
||||
// handed over by the UEFI loader from the configuration table, or found by the
|
||||
// BIOS-era scan of 0xF0000-0xFFFFF when it was not.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "memlayout.h"
|
||||
#include "schboot.h"
|
||||
#include "hwinfo.h"
|
||||
|
||||
static int tried;
|
||||
static uint64 ep_pa; static uint32 ep_len;
|
||||
static uint64 tab_pa; static uint32 tab_len;
|
||||
static const uint8 *tab; // mapped, or 0
|
||||
|
||||
const char *const dmi_field_names[DMI_NFIELDS] = {
|
||||
[DMI_BIOS_VENDOR] = "bios_vendor",
|
||||
[DMI_BIOS_VERSION] = "bios_version",
|
||||
[DMI_BIOS_DATE] = "bios_date",
|
||||
[DMI_BIOS_RELEASE] = "bios_release",
|
||||
[DMI_SYS_VENDOR] = "sys_vendor",
|
||||
[DMI_PRODUCT_NAME] = "product_name",
|
||||
[DMI_PRODUCT_VERSION] = "product_version",
|
||||
[DMI_PRODUCT_SKU] = "product_sku",
|
||||
[DMI_PRODUCT_FAMILY] = "product_family",
|
||||
[DMI_BOARD_VENDOR] = "board_vendor",
|
||||
[DMI_BOARD_NAME] = "board_name",
|
||||
[DMI_BOARD_VERSION] = "board_version",
|
||||
[DMI_CHASSIS_VENDOR] = "chassis_vendor",
|
||||
[DMI_CHASSIS_TYPE] = "chassis_type",
|
||||
[DMI_CHASSIS_VERSION] = "chassis_version",
|
||||
};
|
||||
|
||||
void *
|
||||
phys_view(uint64 pa, uint64 len)
|
||||
{
|
||||
if (pa + len <= 4ULL * 1024 * 1024 * 1024)
|
||||
return P2V(pa);
|
||||
return ioremap(pa, len);
|
||||
}
|
||||
|
||||
int
|
||||
boot_is_efi(void)
|
||||
{
|
||||
return *(uint32 *)P2V(SCHBOOT_EFI_FLAG) == SCHBOOT_EFI_MAGIC;
|
||||
}
|
||||
|
||||
static int
|
||||
sum_ok(const uint8 *p, uint32 n)
|
||||
{
|
||||
uint8 s = 0;
|
||||
while (n--)
|
||||
s += *p++;
|
||||
return s == 0;
|
||||
}
|
||||
|
||||
// One entry point at `pa`, either kind. Sets the table on success.
|
||||
static int
|
||||
try_entry(uint64 pa)
|
||||
{
|
||||
const uint8 *e = phys_view(pa, 32);
|
||||
if (e == 0)
|
||||
return 0;
|
||||
|
||||
if (memcmp(e, "_SM3_", 5) == 0) {
|
||||
uint32 n = e[6];
|
||||
if (n < 24 || n > 32 || !sum_ok(e, n))
|
||||
return 0;
|
||||
ep_len = n;
|
||||
tab_len = *(uint32 *)(e + 0x0C); // the most it can be
|
||||
tab_pa = *(uint64 *)(e + 0x10);
|
||||
} else if (memcmp(e, "_SM_", 4) == 0) {
|
||||
uint32 n = e[5];
|
||||
if (n < 0x1F || n > 32 || !sum_ok(e, n) || memcmp(e + 0x10, "_DMI_", 5))
|
||||
return 0;
|
||||
ep_len = n;
|
||||
tab_len = *(uint16 *)(e + 0x16);
|
||||
tab_pa = *(uint32 *)(e + 0x18);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
// A table that large is not one; a firmware bug, and walking it would be
|
||||
// walking whatever memory follows.
|
||||
if (tab_pa == 0 || tab_len == 0 || tab_len > 1024 * 1024)
|
||||
return 0;
|
||||
ep_pa = pa;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
find(void)
|
||||
{
|
||||
if (tried)
|
||||
return;
|
||||
tried = 1;
|
||||
|
||||
uint64 handed = *(uint64 *)P2V(SCHBOOT_SMBIOS_ADDR);
|
||||
int ok = boot_is_efi() && handed && try_entry(handed);
|
||||
|
||||
// The BIOS convention: 16-byte aligned somewhere in the last 64 KB below
|
||||
// 1 MB. The 64-bit kind first, as Linux prefers it.
|
||||
for (int pass = 0; !ok && pass < 2; pass++)
|
||||
for (uint64 a = 0xF0000; !ok && a < 0x100000; a += 16) {
|
||||
const char *p = (const char *)P2V(a);
|
||||
if (memcmp(p, pass == 0 ? "_SM3_" : "_SM_", pass == 0 ? 5 : 4) == 0)
|
||||
ok = try_entry(a);
|
||||
}
|
||||
if (!ok) {
|
||||
ep_pa = tab_pa = 0;
|
||||
return;
|
||||
}
|
||||
tab = phys_view(tab_pa, tab_len);
|
||||
}
|
||||
|
||||
int
|
||||
dmi_entry(uint64 *pa, uint32 *len)
|
||||
{
|
||||
find();
|
||||
if (ep_pa == 0)
|
||||
return -1;
|
||||
*pa = ep_pa;
|
||||
*len = ep_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The real length: up to and including the end-of-table structure, which for
|
||||
// SMBIOS 3 is less than the maximum the entry point gave.
|
||||
int
|
||||
dmi_table(uint64 *pa, uint32 *len)
|
||||
{
|
||||
find();
|
||||
if (tab == 0)
|
||||
return -1;
|
||||
uint32 off = 0;
|
||||
while (off + 4 <= tab_len) {
|
||||
uint8 type = tab[off], flen = tab[off + 1];
|
||||
if (flen < 4)
|
||||
break;
|
||||
uint32 next = off + flen;
|
||||
while (next + 1 < tab_len && (tab[next] || tab[next + 1]))
|
||||
next++;
|
||||
next += 2;
|
||||
off = next > tab_len ? tab_len : next;
|
||||
if (type == 127)
|
||||
break;
|
||||
}
|
||||
*pa = tab_pa;
|
||||
*len = off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The first structure of `type`, and its length. Strings follow it.
|
||||
static const uint8 *
|
||||
find_struct(int type, uint8 *flen)
|
||||
{
|
||||
uint32 off = 0;
|
||||
while (tab && off + 4 <= tab_len) {
|
||||
const uint8 *s = tab + off;
|
||||
if (s[1] < 4)
|
||||
return 0;
|
||||
if (s[0] == type) {
|
||||
*flen = s[1];
|
||||
return s;
|
||||
}
|
||||
if (s[0] == 127)
|
||||
return 0;
|
||||
uint32 next = off + s[1];
|
||||
while (next + 1 < tab_len && (tab[next] || tab[next + 1]))
|
||||
next++;
|
||||
off = next + 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// String number `idx` of a structure, trimmed as Linux trims it.
|
||||
static int
|
||||
get_str(const uint8 *s, uint8 flen, int field, char *out, int cap)
|
||||
{
|
||||
if (field >= flen)
|
||||
return -1;
|
||||
int idx = s[field];
|
||||
if (idx == 0)
|
||||
return -1;
|
||||
const char *p = (const char *)s + flen;
|
||||
const char *end = (const char *)tab + tab_len;
|
||||
while (--idx > 0 && p < end) {
|
||||
while (p < end && *p)
|
||||
p++;
|
||||
p++;
|
||||
if (p < end && *p == 0) // the double NUL: there is no such string
|
||||
return -1;
|
||||
}
|
||||
int n = 0;
|
||||
while (p < end && *p == ' ')
|
||||
p++;
|
||||
while (p < end && *p && n < cap - 1)
|
||||
out[n++] = *p++;
|
||||
while (n > 0 && out[n - 1] == ' ')
|
||||
n--;
|
||||
out[n] = 0;
|
||||
return n ? n : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
put_u(char *out, int cap, uint v)
|
||||
{
|
||||
char t[12];
|
||||
int k = 0, n = 0;
|
||||
do { t[k++] = '0' + v % 10; v /= 10; } while (v);
|
||||
while (k && n < cap - 1)
|
||||
out[n++] = t[--k];
|
||||
out[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
dmi_field(int f, char *out, int cap)
|
||||
{
|
||||
static const struct { uint8 type, off; } at[DMI_NFIELDS] = {
|
||||
[DMI_BIOS_VENDOR] = { 0, 4 }, [DMI_BIOS_VERSION] = { 0, 5 },
|
||||
[DMI_BIOS_DATE] = { 0, 8 },
|
||||
[DMI_SYS_VENDOR] = { 1, 4 }, [DMI_PRODUCT_NAME] = { 1, 5 },
|
||||
[DMI_PRODUCT_VERSION] = { 1, 6 }, [DMI_PRODUCT_SKU] = { 1, 0x19 },
|
||||
[DMI_PRODUCT_FAMILY] = { 1, 0x1A },
|
||||
[DMI_BOARD_VENDOR] = { 2, 4 }, [DMI_BOARD_NAME] = { 2, 5 },
|
||||
[DMI_BOARD_VERSION] = { 2, 6 },
|
||||
[DMI_CHASSIS_VENDOR] = { 3, 4 }, [DMI_CHASSIS_VERSION] = { 3, 6 },
|
||||
};
|
||||
uint8 flen;
|
||||
const uint8 *s;
|
||||
|
||||
if (f < 0 || f >= DMI_NFIELDS || cap < 2)
|
||||
return -1;
|
||||
find();
|
||||
if (tab == 0)
|
||||
return -1;
|
||||
|
||||
if (f == DMI_BIOS_RELEASE) {
|
||||
if ((s = find_struct(0, &flen)) == 0 || flen < 0x16 ||
|
||||
s[0x14] == 0xFF || s[0x15] == 0xFF)
|
||||
return -1;
|
||||
int n = put_u(out, cap, s[0x14]);
|
||||
if (n < cap - 1) out[n++] = '.';
|
||||
return n + put_u(out + n, cap - n, s[0x15]);
|
||||
}
|
||||
if (f == DMI_CHASSIS_TYPE) {
|
||||
if ((s = find_struct(3, &flen)) == 0 || flen < 6)
|
||||
return -1;
|
||||
return put_u(out, cap, s[5] & 0x7F);
|
||||
}
|
||||
if ((s = find_struct(at[f].type, &flen)) == 0)
|
||||
return -1;
|
||||
return get_str(s, flen, at[f].off, out, cap);
|
||||
}
|
||||
|
||||
// Linux's boot line, word for word in shape, because it is the line people
|
||||
// search for: "DMI: LENOVO 20XW/20XW, BIOS N32ET75W (1.51 ) 09/05/2023".
|
||||
void
|
||||
dmi_init(void)
|
||||
{
|
||||
char v[64], p[64], b[64], bv[64], bd[32];
|
||||
|
||||
find();
|
||||
if (tab == 0) {
|
||||
cprintf("DMI: not present or invalid.\n");
|
||||
return;
|
||||
}
|
||||
if (dmi_field(DMI_SYS_VENDOR, v, sizeof v) < 0) v[0] = 0;
|
||||
if (dmi_field(DMI_PRODUCT_NAME, p, sizeof p) < 0) p[0] = 0;
|
||||
if (dmi_field(DMI_BOARD_NAME, b, sizeof b) < 0) b[0] = 0;
|
||||
if (dmi_field(DMI_BIOS_VERSION, bv, sizeof bv) < 0) bv[0] = 0;
|
||||
if (dmi_field(DMI_BIOS_DATE, bd, sizeof bd) < 0) bd[0] = 0;
|
||||
cprintf("DMI: %s %s/%s, BIOS %s %s\n", v, p, b, bv, bd);
|
||||
}
|
||||
Loading…
Reference in a new issue