SchOS/kernel/dev/dmi.c
Nexoniarz c65bfa3fa3 boot: driver chatter goes to the log; the console clears its margins
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
2026-10-25 01:53:46 +02:00

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);
}