diff --git a/sys/compat/linsysfs/linsysfs.c b/sys/compat/linsysfs/linsysfs.c index b8fe16738fd..2b523c601d8 100644 --- a/sys/compat/linsysfs/linsysfs.c +++ b/sys/compat/linsysfs/linsysfs.c @@ -38,9 +38,13 @@ #include #include #include +#include #include #include +#if defined(__amd64__) || defined(__i386__) +#include +#endif #include #include @@ -260,6 +264,98 @@ linsysfs_listdmi(struct pfs_node *class) } } +#if defined(__amd64__) || defined(__i386__) +static int +linsysfs_smbios_entry_point_attr(PFS_ATTR_ARGS) +{ + const void *data; + size_t length; + int error; + + error = smbios_get_entry_point(&data, &length); + if (error != 0) + return (error); + vap->va_mode = 0400; + vap->va_bytes = vap->va_size = length; + return (0); +} + +static int +linsysfs_smbios_table_attr(PFS_ATTR_ARGS) +{ + const void *data; + size_t length; + int error; + + error = smbios_get_structure_table(&data, &length); + if (error != 0) + return (error); + vap->va_mode = 0400; + vap->va_bytes = vap->va_size = length; + return (0); +} + +static int +linsysfs_smbios_read(struct uio *uio, const void *data, size_t length) +{ + size_t count, offset; + + if (uio->uio_offset < 0 || uio->uio_resid < 0) + return (EINVAL); + if ((uintmax_t)uio->uio_offset >= length) + return (0); + offset = (size_t)uio->uio_offset; + count = MIN((size_t)uio->uio_resid, length - offset); + return (uiomove(__DECONST(uint8_t *, data) + offset, count, uio)); +} + +static int +linsysfs_smbios_entry_point(PFS_FILL_ARGS) +{ + const void *data; + size_t length; + int error; + + error = smbios_get_entry_point(&data, &length); + if (error != 0) + return (error); + return (linsysfs_smbios_read(uio, data, length)); +} + +static int +linsysfs_smbios_table(PFS_FILL_ARGS) +{ + const void *data; + size_t length; + int error; + + error = smbios_get_structure_table(&data, &length); + if (error != 0) + return (error); + return (linsysfs_smbios_read(uio, data, length)); +} + +static void +linsysfs_listsmbios(struct pfs_node *root) +{ + const void *data; + struct pfs_node *dmi, *firmware, *tables; + size_t length; + + if (smbios_get_entry_point(&data, &length) != 0 || length == 0 || + smbios_get_structure_table(&data, &length) != 0 || length == 0) + return; + firmware = pfs_create_dir(root, "firmware", NULL, NULL, NULL, 0); + dmi = pfs_create_dir(firmware, "dmi", NULL, NULL, NULL, 0); + tables = pfs_create_dir(dmi, "tables", NULL, NULL, NULL, 0); + pfs_create_file(tables, "smbios_entry_point", + &linsysfs_smbios_entry_point, &linsysfs_smbios_entry_point_attr, + NULL, NULL, PFS_RD | PFS_RAWRD); + pfs_create_file(tables, "DMI", &linsysfs_smbios_table, + &linsysfs_smbios_table_attr, NULL, NULL, PFS_RD | PFS_RAWRD); +} +#endif + /* * Filler function for proc_name */ @@ -726,6 +822,9 @@ linsysfs_init(PFS_INIT_ARGS) linsysfs_listcpus(cpu); linsysfs_listnics(net); linsysfs_listdmi(class); +#if defined(__amd64__) || defined(__i386__) + linsysfs_listsmbios(root); +#endif /* /sys/kernel */ kernel = pfs_create_dir(root, "kernel", NULL, NULL, NULL, 0); @@ -759,3 +858,6 @@ MODULE_DEPEND(linsysfs, linux_common, 1, 1, 1); #else MODULE_DEPEND(linsysfs, linux, 1, 1, 1); #endif +#if defined(__amd64__) || defined(__i386__) +MODULE_DEPEND(linsysfs, smbios, 1, 1, 1); +#endif diff --git a/sys/dev/smbios/smbios.c b/sys/dev/smbios/smbios.c index 34cd5049d02..f1d3d85c735 100644 --- a/sys/dev/smbios/smbios.c +++ b/sys/dev/smbios/smbios.c @@ -59,13 +59,17 @@ struct smbios_softc { device_t dev; struct resource * res; int rid; - - struct smbios_eps * eps; }; -#define RES2EPS(res) ((struct smbios_eps *)rman_get_virtual(res)) +#define RES2EPS(res) ((uint8_t *)rman_get_virtual(res)) +#define SMBIOS_EPS_MAX_SIZE 32 +#define SMBIOS_MAX_TABLE_SIZE (16 * 1024 * 1024) static devclass_t smbios_devclass; +static void *smbios_eps_data; +static size_t smbios_eps_len; +static void *smbios_table_data; +static size_t smbios_table_len; static void smbios_identify (driver_t *, device_t); static int smbios_probe (device_t); @@ -73,27 +77,34 @@ static int smbios_attach (device_t); static int smbios_detach (device_t); static int smbios_modevent (module_t, int, void *); -static int smbios_cksum (struct smbios_eps *); +static int smbios_cksum(const void *, size_t); +static int smbios_validate_eps(const uint8_t *, size_t *); static void smbios_identify (driver_t *driver, device_t parent) { #ifdef ARCH_MAY_USE_EFI - struct uuid efi_smbios = EFI_TABLE_SMBIOS; + struct uuid efi_smbios; void *addr_efi; #endif - struct smbios_eps *eps; + uint8_t *eps; + size_t length, resource_length; device_t child; vm_paddr_t addr = 0; - int length; int rid; if (!device_is_alive(parent)) return; #ifdef ARCH_MAY_USE_EFI + efi_smbios = (struct uuid)EFI_TABLE_SMBIOS3; if (!efi_get_table(&efi_smbios, &addr_efi)) addr = (vm_paddr_t)addr_efi; + else { + efi_smbios = (struct uuid)EFI_TABLE_SMBIOS; + if (!efi_get_table(&efi_smbios, &addr_efi)) + addr = (vm_paddr_t)addr_efi; + } #endif #if defined(__amd64__) || defined(__i386__) @@ -103,28 +114,22 @@ smbios_identify (driver_t *driver, device_t parent) #endif if (addr != 0) { - eps = pmap_mapbios(addr, 0x1f); + eps = pmap_mapbios(addr, SMBIOS_EPS_MAX_SIZE); rid = 0; - length = eps->length; - - if (length != 0x1f) { - u_int8_t major, minor; - - major = eps->major_version; - minor = eps->minor_version; - - /* SMBIOS v2.1 implementation might use 0x1e. */ - if (length == 0x1e && major == 2 && minor == 1) - length = 0x1f; - else - return; + if (smbios_validate_eps(eps, &length) != 0) { + pmap_unmapbios((vm_offset_t)eps, SMBIOS_EPS_MAX_SIZE); + return; } + resource_length = length; + if (memcmp(eps, SMBIOS_SIG, 4) == 0 && length == 0x1e) + resource_length = sizeof(struct smbios_eps); child = BUS_ADD_CHILD(parent, 5, "smbios", -1); device_set_driver(child, driver); - bus_set_resource(child, SYS_RES_MEMORY, rid, addr, length); + bus_set_resource(child, SYS_RES_MEMORY, rid, addr, + resource_length); device_set_desc(child, "System Management BIOS"); - pmap_unmapbios((vm_offset_t)eps, 0x1f); + pmap_unmapbios((vm_offset_t)eps, SMBIOS_EPS_MAX_SIZE); } return; @@ -134,6 +139,7 @@ static int smbios_probe (device_t dev) { struct resource *res; + size_t length; int rid; int error; @@ -146,7 +152,8 @@ smbios_probe (device_t dev) goto bad; } - if (smbios_cksum(RES2EPS(res))) { + if (smbios_validate_eps(RES2EPS(res), &length) != 0 || + length > rman_get_size(res)) { device_printf(dev, "SMBIOS checksum failed.\n"); error = ENXIO; goto bad; @@ -162,9 +169,16 @@ static int smbios_attach (device_t dev) { struct smbios_softc *sc; + struct smbios3_eps *eps3; + struct smbios_eps *eps; + void *eps_data, *table, *table_data; + vm_paddr_t table_addr; + size_t eps_len, table_len; int error; sc = device_get_softc(dev); + eps_data = NULL; + table_data = NULL; error = 0; sc->dev = dev; @@ -176,20 +190,60 @@ smbios_attach (device_t dev) error = ENOMEM; goto bad; } - sc->eps = RES2EPS(sc->res); - - device_printf(dev, "Version: %u.%u", - sc->eps->major_version, sc->eps->minor_version); - if (bcd2bin(sc->eps->BCD_revision)) - printf(", BCD Revision: %u.%u", - bcd2bin(sc->eps->BCD_revision >> 4), - bcd2bin(sc->eps->BCD_revision & 0x0f)); - printf("\n"); + if (smbios_validate_eps(RES2EPS(sc->res), &eps_len) != 0) { + error = ENXIO; + goto bad; + } + eps_data = malloc(eps_len, M_DEVBUF, M_WAITOK); + memcpy(eps_data, RES2EPS(sc->res), eps_len); + + if (memcmp(eps_data, SMBIOS3_SIG, 5) == 0) { + eps3 = eps_data; + table_addr = eps3->structure_table_address; + table_len = eps3->structure_table_length; + device_printf(dev, "Version: %u.%u, Doc Revision: %u\n", + eps3->major_version, eps3->minor_version, + eps3->doc_revision); + } else { + eps = eps_data; + table_addr = eps->structure_table_address; + table_len = eps->structure_table_length; + device_printf(dev, "Version: %u.%u", + eps->major_version, eps->minor_version); + if (bcd2bin(eps->BCD_revision)) + printf(", BCD Revision: %u.%u", + bcd2bin(eps->BCD_revision >> 4), + bcd2bin(eps->BCD_revision & 0x0f)); + printf("\n"); + } + if (table_len == 0 || table_len > SMBIOS_MAX_TABLE_SIZE || + table_addr > UINT64_MAX - (table_len - 1)) { + device_printf(dev, "Invalid SMBIOS table range.\n"); + error = EINVAL; + goto bad; + } + table = pmap_mapbios(table_addr, table_len); + if (table == NULL) { + error = ENOMEM; + goto bad; + } + table_data = malloc(table_len, M_DEVBUF, M_WAITOK); + memcpy(table_data, table, table_len); + pmap_unmapbios((vm_offset_t)table, table_len); + smbios_eps_data = eps_data; + smbios_eps_len = eps_len; + smbios_table_data = table_data; + smbios_table_len = table_len; return (0); bad: + if (table_data != NULL) + free(table_data, M_DEVBUF); + if (eps_data != NULL) + free(eps_data, M_DEVBUF); if (sc->res) bus_release_resource(dev, SYS_RES_MEMORY, sc->rid, sc->res); + sc->res = NULL; return (error); } @@ -225,6 +279,14 @@ smbios_modevent (mod, what, arg) device_delete_child(device_get_parent(devs[i]), devs[i]); } free(devs, M_TEMP); + if (smbios_table_data != NULL) + free(smbios_table_data, M_DEVBUF); + if (smbios_eps_data != NULL) + free(smbios_eps_data, M_DEVBUF); + smbios_table_data = NULL; + smbios_eps_data = NULL; + smbios_table_len = 0; + smbios_eps_len = 0; break; default: break; @@ -254,18 +316,69 @@ MODULE_DEPEND(smbios, efirt, 1, 1, 1); #endif MODULE_VERSION(smbios, 1); +int +smbios_get_entry_point(const void **data, size_t *length) +{ + if (data == NULL || length == NULL) + return (EINVAL); + if (smbios_eps_data == NULL) + return (ENXIO); + *data = smbios_eps_data; + *length = smbios_eps_len; + return (0); +} + +int +smbios_get_structure_table(const void **data, size_t *length) +{ + if (data == NULL || length == NULL) + return (EINVAL); + if (smbios_table_data == NULL) + return (ENXIO); + *data = smbios_table_data; + *length = smbios_table_len; + return (0); +} + static int -smbios_cksum (struct smbios_eps *e) +smbios_cksum(const void *data, size_t length) { - u_int8_t *ptr; + const u_int8_t *ptr; u_int8_t cksum; - int i; + size_t i; - ptr = (u_int8_t *)e; + ptr = data; cksum = 0; - for (i = 0; i < e->length; i++) { + for (i = 0; i < length; i++) cksum += ptr[i]; - } return (cksum); } + +static int +smbios_validate_eps(const uint8_t *eps, size_t *length) +{ + const struct smbios3_eps *eps3; + const struct smbios_eps *eps2; + size_t len; + + if (memcmp(eps, SMBIOS3_SIG, 5) == 0) { + eps3 = (const struct smbios3_eps *)eps; + len = eps3->length; + if (len < sizeof(*eps3) || len > SMBIOS_EPS_MAX_SIZE || + smbios_cksum(eps, len) != 0) + return (EINVAL); + } else if (memcmp(eps, SMBIOS_SIG, 4) == 0) { + eps2 = (const struct smbios_eps *)eps; + len = eps2->length; + if (len < 0x1e || len > SMBIOS_EPS_MAX_SIZE || + memcmp(eps2->intermediate_anchor_string, "_DMI_", 5) != 0 || + smbios_cksum(eps, len) != 0 || + smbios_cksum(eps2->intermediate_anchor_string, 0x0f) != 0) + return (EINVAL); + } else { + return (EINVAL); + } + *length = len; + return (0); +} diff --git a/sys/dev/smbios/smbios.h b/sys/dev/smbios/smbios.h index 71d603d982b..a9c2ad73d37 100644 --- a/sys/dev/smbios/smbios.h +++ b/sys/dev/smbios/smbios.h @@ -38,6 +38,7 @@ #define SMBIOS_OFF 0 #define SMBIOS_LEN 4 #define SMBIOS_SIG "_SM_" +#define SMBIOS3_SIG "_SM3_" struct smbios_eps { uint8_t anchor_string[4]; /* '_SM_' */ @@ -56,6 +57,19 @@ struct smbios_eps { uint8_t BCD_revision; } __packed; +struct smbios3_eps { + uint8_t anchor_string[5]; + uint8_t checksum; + uint8_t length; + uint8_t major_version; + uint8_t minor_version; + uint8_t doc_revision; + uint8_t entry_point_revision; + uint8_t reserved; + uint32_t structure_table_length; + uint64_t structure_table_address; +} __packed; + struct smbios_structure_header { uint8_t type; uint8_t length; @@ -64,6 +78,11 @@ struct smbios_structure_header { typedef void (*smbios_callback_t)(struct smbios_structure_header *, void *); +#ifdef _KERNEL +int smbios_get_entry_point(const void **, size_t *); +int smbios_get_structure_table(const void **, size_t *); +#endif + static inline void smbios_walk_table(uint8_t *p, int entries, smbios_callback_t cb, void *arg) { diff --git a/sys/modules/bios/smbios/Makefile b/sys/modules/bios/smbios/Makefile index ca1e4b04815..cbe965c490b 100644 --- a/sys/modules/bios/smbios/Makefile +++ b/sys/modules/bios/smbios/Makefile @@ -6,4 +6,7 @@ KMOD= smbios SRCS= smbios.c \ bus_if.h device_if.h +EXPORT_SYMS= smbios_get_entry_point \ + smbios_get_structure_table + .include diff --git a/tests/sys/fs/linsysfs/Makefile b/tests/sys/fs/linsysfs/Makefile index f59c0c66bf7..315eba405f9 100644 --- a/tests/sys/fs/linsysfs/Makefile +++ b/tests/sys/fs/linsysfs/Makefile @@ -3,5 +3,6 @@ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/fs/linsysfs ATF_TESTS_SH+= dmi_id_test +ATF_TESTS_SH+= raw_dmi_test .include diff --git a/tests/sys/fs/linsysfs/raw_dmi_test.sh b/tests/sys/fs/linsysfs/raw_dmi_test.sh new file mode 100644 index 00000000000..2dd2c1dcef3 --- /dev/null +++ b/tests/sys/fs/linsysfs/raw_dmi_test.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +atf_test_case raw_dmi cleanup +raw_dmi_head() +{ + atf_set "descr" "Verify Linux-compatible raw SMBIOS table files" + atf_set "require.user" "root" +} + +raw_dmi_body() +{ + atf_require_prog awk + atf_require_prog cmp + atf_require_prog dd + atf_require_prog kldload + atf_require_prog kldstat + atf_require_prog mount + atf_require_prog od + 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 + + eps="mnt/firmware/dmi/tables/smbios_entry_point" + dmi="mnt/firmware/dmi/tables/DMI" + if [ ! -e "${eps}" ] && [ ! -e "${dmi}" ]; then + atf_skip "no raw SMBIOS tables are available" + fi + atf_check -s exit:0 -o inline:"400\n" stat -f '%Lp' "${eps}" + atf_check -s exit:0 -o inline:"400\n" stat -f '%Lp' "${dmi}" + atf_check -s exit:0 -o match:'^[1-9][0-9]*$' stat -f '%z' "${eps}" + atf_check -s exit:0 -o match:'^[1-9][0-9]*$' stat -f '%z' "${dmi}" + + atf_check -s exit:0 -e ignore dd if="${eps}" of=eps bs=1 + atf_check -s exit:0 -e ignore dd if="${dmi}" of=dmi bs=1 + if [ ! -s eps ] || [ ! -s dmi ]; then + atf_fail "raw SMBIOS files must not be empty" + fi + + anchor=$(dd if=eps bs=1 count=4 2>/dev/null) + if [ "${anchor}" != "_SM_" ]; then + anchor=$(dd if=eps bs=1 count=5 2>/dev/null) + if [ "${anchor}" != "_SM3_" ]; then + atf_fail "invalid SMBIOS entry point signature: ${anchor}" + fi + fi + checksum=$(od -An -tu1 eps | awk ' + { for (i = 1; i <= NF; i++) sum += $i } + END { print sum % 256 }') + if [ "${checksum}" -ne 0 ]; then + atf_fail "invalid SMBIOS entry point checksum" + fi + + atf_check -s exit:0 -e ignore dd if=eps of=expected bs=1 skip=4 count=8 + atf_check -s exit:0 -e ignore dd if="${eps}" of=actual bs=1 skip=4 count=8 + atf_check -s exit:0 cmp expected actual +} + +raw_dmi_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 raw_dmi +}