From d26a21dc4529f81b116ff5160ecc7052dc5fe6d7 Mon Sep 17 00:00:00 2001 From: Lucas Holt Date: Thu, 24 Sep 2026 08:42:31 -0400 Subject: [PATCH] linsysfs: expose SMBIOS DMI identification Add /sys/class/dmi/id files backed by SMBIOS kernel environment values so Linux applications can use sysfs instead of falling back to /dev/mem. Add ATF coverage for file contents, permissions, and absent SMBIOS keys. AI-Assisted-by: Codex (GPT-5) Signed-off-by: Lucas Holt --- sys/compat/linsysfs/linsysfs.c | 56 ++++++++++++++++++++++- tests/sys/fs/Makefile | 1 + tests/sys/fs/linsysfs/Makefile | 7 +++ tests/sys/fs/linsysfs/dmi_id_test.sh | 67 ++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 tests/sys/fs/linsysfs/Makefile create mode 100644 tests/sys/fs/linsysfs/dmi_id_test.sh diff --git a/sys/compat/linsysfs/linsysfs.c b/sys/compat/linsysfs/linsysfs.c index 50ff63c4249..b8fe16738fd 100644 --- a/sys/compat/linsysfs/linsysfs.c +++ b/sys/compat/linsysfs/linsysfs.c @@ -57,7 +57,22 @@ struct scsi_host_queue { char *name; }; -TAILQ_HEAD(,scsi_host_queue) scsi_host_q; +struct linsysfs_dmi_field { + const char *name; + const char *kenv; +}; + +static const struct linsysfs_dmi_field linsysfs_dmi_fields[] = { + { "sys_vendor", "smbios.system.maker" }, + { "product_name", "smbios.system.product" }, + { "board_vendor", "smbios.planar.maker" }, + { "board_name", "smbios.planar.product" }, + { "bios_vendor", "smbios.bios.vendor" }, + { "bios_version", "smbios.bios.version" }, + { "chassis_vendor", "smbios.chassis.maker" }, +}; + +TAILQ_HEAD(scsi_host_list, scsi_host_queue) scsi_host_q; static int host_number = 0; @@ -207,6 +222,44 @@ linsysfs_listnics(struct pfs_node *dir) NULL, NULL, NULL, PFS_RD); } +static int +linsysfs_dmi_field(PFS_FILL_ARGS) +{ + const struct linsysfs_dmi_field *field; + char *value; + + field = pn->pn_data; + value = kern_getenv(field->kenv); + if (value == NULL) + return (ENOENT); + sbuf_printf(sb, "%s\n", value); + freeenv(value); + return (0); +} + +static void +linsysfs_listdmi(struct pfs_node *class) +{ + struct pfs_node *dmi, *id, *node; + size_t i; + + dmi = pfs_create_dir(class, "dmi", NULL, NULL, NULL, 0); + id = pfs_create_dir(dmi, "id", NULL, NULL, NULL, 0); + for (i = 0; i < nitems(linsysfs_dmi_fields); i++) { + char *value; + + value = kern_getenv(linsysfs_dmi_fields[i].kenv); + if (value == NULL) + continue; + freeenv(value); + node = pfs_create_file(id, linsysfs_dmi_fields[i].name, + &linsysfs_dmi_field, NULL, NULL, NULL, PFS_RD); + if (node != NULL) + node->pn_data = __DECONST(void *, + &linsysfs_dmi_fields[i]); + } +} + /* * Filler function for proc_name */ @@ -672,6 +725,7 @@ linsysfs_init(PFS_INIT_ARGS) linsysfs_listcpus(cpu); linsysfs_listnics(net); + linsysfs_listdmi(class); /* /sys/kernel */ kernel = pfs_create_dir(root, "kernel", NULL, NULL, NULL, 0); diff --git a/tests/sys/fs/Makefile b/tests/sys/fs/Makefile index 24954bde472..146315a641f 100644 --- a/tests/sys/fs/Makefile +++ b/tests/sys/fs/Makefile @@ -13,6 +13,7 @@ TESTSRC= ${SRCTOP}/contrib/netbsd-tests/fs .if ${COMPILER_FEATURES:Mc++14} && ${MK_GOOGLETEST} != "no" TESTS_SUBDIRS+= fusefs .endif +TESTS_SUBDIRS+= linsysfs TESTS_SUBDIRS+= tmpfs ${PACKAGE}FILES+= h_funcs.subr diff --git a/tests/sys/fs/linsysfs/Makefile b/tests/sys/fs/linsysfs/Makefile new file mode 100644 index 00000000000..f59c0c66bf7 --- /dev/null +++ b/tests/sys/fs/linsysfs/Makefile @@ -0,0 +1,7 @@ +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/sys/fs/linsysfs + +ATF_TESTS_SH+= dmi_id_test + +.include diff --git a/tests/sys/fs/linsysfs/dmi_id_test.sh b/tests/sys/fs/linsysfs/dmi_id_test.sh new file mode 100644 index 00000000000..c34a814bf9b --- /dev/null +++ b/tests/sys/fs/linsysfs/dmi_id_test.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +atf_test_case dmi_id cleanup +dmi_id_head() +{ + atf_set "descr" "Verify Linux-compatible DMI identification files" + atf_set "require.user" "root" +} + +dmi_id_body() +{ + atf_require_prog kenv + atf_require_prog kldload + atf_require_prog kldstat + atf_require_prog mount + atf_require_prog stat + atf_require_prog umount + + if ! kldstat -q -m linsysfs; then + atf_check -s exit:0 kldload linsysfs + touch linsysfs_loaded + fi + + mkdir mnt + atf_check -s exit:0 mount -t linsysfs linsysfs "$(pwd)/mnt" + touch linsysfs_mounted + + found=0 + while read -r name key; do + if value=$(kenv "${key}" 2>/dev/null); then + found=1 + atf_check -s exit:0 -o inline:"${value}\n" \ + cat "mnt/class/dmi/id/${name}" + atf_check -s exit:0 -o inline:"444\n" \ + stat -f '%Lp' "mnt/class/dmi/id/${name}" + elif [ -e "mnt/class/dmi/id/${name}" ]; then + atf_fail "${name} exists without ${key}" + fi + done <<-EOF + sys_vendor smbios.system.maker + product_name smbios.system.product + board_vendor smbios.planar.maker + board_name smbios.planar.product + bios_vendor smbios.bios.vendor + bios_version smbios.bios.version + chassis_vendor smbios.chassis.maker + EOF + + if [ "${found}" -eq 0 ]; then + atf_skip "no SMBIOS identification values are available" + fi +} + +dmi_id_cleanup() +{ + if [ -f linsysfs_mounted ]; then + umount "$(pwd)/mnt" >/dev/null 2>&1 || true + fi + if [ -f linsysfs_loaded ]; then + kldunload linsysfs >/dev/null 2>&1 || true + fi +} + +atf_init_test_cases() +{ + atf_add_test_case dmi_id +}