board/common: basic support for smbios/dmi based systems in probe

This allows us to gather better product information in /run/system.json
also for x86 based systems.  For Qemu x86_64 systems the 'product-name'
changes from 'VM' to 'Standard PC (i440FX + PIIX, 1996)'.

Also, add new 'product-version' field for, e.g., board revisions, and add
missing 'serial-number' value for systems like Rasperry Pi that inject it
in the device tree.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-11-20 09:34:00 +01:00
parent 3aa22a7c63
commit a53b7dd1ea
@@ -146,6 +146,87 @@ class DTSystem:
flat_devices = [device for sublist in self.infix_devices("vpds") for device in sublist]
return [self.into_vpd(device) for device in flat_devices]
def vendor_name(self):
"""Extract vendor name from devicetree compatible property"""
compatible = self.base.str_array("compatible")
if not compatible or len(compatible) == 0:
return None
# Map of common devicetree vendor prefixes to proper names
vendor_map = {
"raspberrypi": "Raspberry Pi Foundation",
"brcm": "Broadcom Inc.",
"marvell": "Marvell Technology, Inc.",
"fsl": "NXP Semiconductors N.V.",
"nxp": "NXP Semiconductors N.V.",
"ti": "Texas Instruments Inc.",
"qcom": "Qualcomm Inc.",
"rockchip": "Rockchip Electronics Co., Ltd.",
"amlogic": "Amlogic Inc.",
"allwinner": "Allwinner Technology Co., Ltd.",
"mediatek": "MediaTek Inc.",
"st": "STMicroelectronics N.V.",
"xlnx": "Xilinx, Inc.",
"intel": "Intel Corporation",
"amd": "Advanced Micro Devices, Inc.",
"nvidia": "NVIDIA Corporation",
"bananapi": "SinoVoip Co., Ltd.",
"sinovoip": "SinoVoip Co., Ltd.",
"friendlyarm": "FriendlyElec",
"friendlyelec": "FriendlyElec",
"microchip": "Microchip Technology Inc.",
"atmel": "Microchip Technology Inc.",
}
# Get the first (most specific) compatible string
compat = compatible[0]
# Extract vendor prefix (part before comma)
if ',' in compat:
prefix = compat.split(',')[0].lower()
# Return mapped name or capitalized prefix
return vendor_map.get(prefix, prefix.capitalize())
return None
class DMISystem:
BASE = "/sys/class/dmi/id"
def read_dmi(self, attr):
"""Read DMI attribute from /sys/class/dmi/id/"""
path = os.path.join(DMISystem.BASE, attr)
if not os.path.exists(path):
return None
try:
with open(path, 'r', encoding='utf-8') as f:
value = f.read().strip()
return value if value else None
except:
return None
def populate(self, out):
"""Read DMI/SMBIOS data and populate output dictionary"""
vendor = self.read_dmi("sys_vendor")
if vendor:
out["vendor"] = vendor
product_name = self.read_dmi("product_name")
if product_name:
out["product-name"] = product_name
serial = self.read_dmi("product_serial")
if serial:
out["serial-number"] = serial
version = self.read_dmi("product_version")
if version:
out["product-version"] = version
def vpds(self):
"""DMI systems don't have VPD in the traditional sense"""
return []
class QEMUSystem:
BASE = "/sys/firmware/qemu_fw_cfg"
@@ -355,6 +436,9 @@ def probe_qemusystem(out):
if not out[attr]:
out[attr] = default
if os.path.exists(DMISystem.BASE):
DMISystem().populate(out)
if not out["factory-password-hash"] and \
not out["vpd"]["product"]["available"]:
# Virtual instance without VPD emulation, fallback to
@@ -397,6 +481,21 @@ def generic_usb_ports(out):
out["usb-ports"] = [{"name": f"USB{p['num']}", "path": p["path"]} for p in ports]
def probe_dmisystem(out):
"""Probe DMI/SMBIOS based system (x86/AMD64)"""
dmisys = DMISystem()
dmisys.populate(out)
generic_usb_ports(out)
if not out["mac-address"]:
out["mac-address"] = fallback_base_mac()
vpd_inject(out, dmisys.vpds())
return 0
def probe_dtsystem(out):
"""Probe DTS based system, expects a VPD in ONIE PROM format."""
dtsys = DTSystem()
@@ -413,12 +512,24 @@ def probe_dtsystem(out):
out["compatible"] = dtsys.base.str_array("compatible")
# Extract vendor from compatible string if not already set
if not out["vendor"]:
vendor = dtsys.vendor_name()
if vendor:
out["vendor"] = vendor
staticpw = dtsys.infix.str("factory-password-hash")
if not out["factory-password-hash"]:
out["factory-password-hash"] = staticpw
vpd_inject(out, vpds)
# Fallback to devicetree serial-number if VPD doesn't provide one
if not out["serial-number"]:
serial = dtsys.base.str("serial-number")
if serial:
out["serial-number"] = serial
# Fallback to interface MAC if VPD doesn't provide one (e.g., SBCs)
if not out["mac-address"]:
out["mac-address"] = fallback_base_mac()
@@ -430,6 +541,7 @@ def main():
out = {
"vendor": None,
"product-name": None,
"product-version": None,
"part-number": None,
"serial-number": None,
"mac-address": None,
@@ -440,6 +552,8 @@ def main():
if os.path.exists(QEMUSystem.REV):
err = probe_qemusystem(out)
elif os.path.exists(DMISystem.BASE):
err = probe_dmisystem(out)
elif os.path.exists(DTSystem.BASE):
err = probe_dtsystem(out)
else: