SchOS/kernel/dev/dmi.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
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
2026-10-23 15:05:12 +02:00

285 lines
7.6 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) {
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);
}