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