ACPI, DMI, SMP, xHCI, USB, I2C, NVMe, r8169 and ramdisk progress lines are kinfo now: in dmesg and on a verbose boot, off the screen of a working machine. Failures stay cprintf. vt_repaint also paints the strip right of the last column and below the last row, which kept the bottom of the X desktop after Ctrl+Alt+F-key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
285 lines
7.5 KiB
C
285 lines
7.5 KiB
C
// 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 "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "arch/memlayout.h"
|
|
#include "arch/schboot.h"
|
|
#include "core/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) {
|
|
kinfo("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;
|
|
kinfo("DMI: %s %s/%s, BIOS %s %s\n", v, p, b, bv, bd);
|
|
}
|