diff options
-rw-r--r-- | .gitattributes | 1 | ||||
-rw-r--r-- | rules.d/70-memory.rules | 8 | ||||
-rw-r--r-- | rules.d/meson.build | 1 | ||||
-rw-r--r-- | src/udev/dmi_memory_id/dmi_memory_id.c | 710 | ||||
-rw-r--r-- | src/udev/meson.build | 1 | ||||
-rw-r--r-- | test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin | bin | 0 -> 3081 bytes | |||
-rw-r--r-- | test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin.txt | 33 | ||||
-rw-r--r-- | test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin | bin | 0 -> 5074 bytes | |||
-rw-r--r-- | test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin.txt | 67 | ||||
-rw-r--r-- | test/meson.build | 7 | ||||
-rwxr-xr-x | test/udev-dmi-memory-id-test.sh | 31 |
11 files changed, 859 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes index 18415085c7..f89d1fc6fe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ *.[ch] whitespace=tab-in-indent,trailing-space +test/dmidecode-dumps/*.bin binary diff --git a/rules.d/70-memory.rules b/rules.d/70-memory.rules new file mode 100644 index 0000000000..f2610ff97e --- /dev/null +++ b/rules.d/70-memory.rules @@ -0,0 +1,8 @@ +# do not edit this file, it will be overwritten on update + +ACTION=="remove", GOTO="memory_end" +SUBSYSTEM!="dmi", GOTO="memory_end" + +IMPORT{program}="dmi_memory_id" + +LABEL="memory_end" diff --git a/rules.d/meson.build b/rules.d/meson.build index 7e46abd559..650addc3b3 100644 --- a/rules.d/meson.build +++ b/rules.d/meson.build @@ -17,6 +17,7 @@ rules = files(''' 60-serial.rules 70-joystick.rules 70-mouse.rules + 70-memory.rules 70-touchpad.rules 75-net-description.rules 75-probe_mtd.rules diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c new file mode 100644 index 0000000000..dfb243483c --- /dev/null +++ b/src/udev/dmi_memory_id/dmi_memory_id.c @@ -0,0 +1,710 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * System Memory information + * + * Copyright (C) 2000-2002 Alan Cox <alan@redhat.com> + * Copyright (C) 2002-2020 Jean Delvare <jdelvare@suse.de> + * Copyright (C) 2020 Bastien Nocera <hadess@hadess.net> + * + * Unless specified otherwise, all references are aimed at the "System + * Management BIOS Reference Specification, Version 3.2.0" document, + * available from http://www.dmtf.org/standards/smbios. + * + * Note to contributors: + * Please reference every value you add or modify, especially if the + * information does not come from the above mentioned specification. + * + * Additional references: + * - Intel AP-485 revision 36 + * "Intel Processor Identification and the CPUID Instruction" + * http://www.intel.com/support/processors/sb/cs-009861.htm + * - DMTF Common Information Model + * CIM Schema version 2.19.1 + * http://www.dmtf.org/standards/cim/ + * - IPMI 2.0 revision 1.0 + * "Intelligent Platform Management Interface Specification" + * http://developer.intel.com/design/servers/ipmi/spec.htm + * - AMD publication #25481 revision 2.28 + * "CPUID Specification" + * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/25481.pdf + * - BIOS Integrity Services Application Programming Interface version 1.0 + * http://www.intel.com/design/archives/wfm/downloads/bisspec.htm + * - DMTF DSP0239 version 1.1.0 + * "Management Component Transport Protocol (MCTP) IDs and Codes" + * http://www.dmtf.org/standards/pmci + * - "TPM Main, Part 2 TPM Structures" + * Specification version 1.2, level 2, revision 116 + * https://trustedcomputinggroup.org/tpm-main-specification/ + * - "PC Client Platform TPM Profile (PTP) Specification" + * Family "2.0", Level 00, Revision 00.43, January 26, 2015 + * https://trustedcomputinggroup.org/pc-client-platform-tpm-profile-ptp-specification/ + * - "RedFish Host Interface Specification" (DMTF DSP0270) + * https://www.dmtf.org/sites/default/files/DSP0270_1.0.1.pdf + */ + +#include <getopt.h> + +#include "alloc-util.h" +#include "build.h" +#include "fileio.h" +#include "main-func.h" +#include "string-util.h" +#include "udev-util.h" +#include "unaligned.h" + +#define SUPPORTED_SMBIOS_VER 0x030300 + +#define OUT_OF_SPEC_STR "<OUT OF SPEC>" + +#define SYS_FIRMWARE_DIR "/sys/firmware/dmi/tables" +#define SYS_ENTRY_FILE SYS_FIRMWARE_DIR "/smbios_entry_point" +#define SYS_TABLE_FILE SYS_FIRMWARE_DIR "/DMI" + +/* + * Per SMBIOS v2.8.0 and later, all structures assume a little-endian + * ordering convention. + */ +#define WORD(x) (unaligned_read_le16(x)) +#define DWORD(x) (unaligned_read_le32(x)) +#define QWORD(x) (unaligned_read_le64(x)) + +struct dmi_header { + uint8_t type; + uint8_t length; + uint16_t handle; + const uint8_t *data; +}; + +static const char *arg_source_file = NULL; + +static bool verify_checksum(const uint8_t *buf, size_t len) { + uint8_t sum = 0; + + for (size_t a = 0; a < len; a++) + sum += buf[a]; + return sum == 0; +} + +/* + * Type-independant Stuff + */ + +static const char *dmi_string(const struct dmi_header *dm, uint8_t s) { + const char *bp = (const char *) dm->data; + + if (s == 0) + return "Not Specified"; + + bp += dm->length; + for (;s > 1 && !isempty(bp); s--) + bp += strlen(bp) + 1; + + if (isempty(bp)) + return "<BAD INDEX>"; + + return bp; +} + +typedef enum { + MEMORY_SIZE_UNIT_BYTES, + MEMORY_SIZE_UNIT_KB +} MemorySizeUnit; + +static void dmi_print_memory_size( + const char *attr_prefix, const char *attr_suffix, + int slot_num, uint64_t code, MemorySizeUnit unit) { + if (unit == MEMORY_SIZE_UNIT_KB) + code <<= 10; + + if (slot_num >= 0) + printf("%s_%u_%s=%"PRIu64"\n", attr_prefix, slot_num, attr_suffix, code); + else + printf("%s_%s=%"PRIu64"\n", attr_prefix, attr_suffix, code); +} + +/* + * 7.17 Physical Memory Array (Type 16) + */ + +static void dmi_memory_array_location(uint8_t code) { + /* 7.17.1 */ + static const char *location[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "System Board Or Motherboard", + [0x04] = "ISA Add-on Card", + [0x05] = "EISA Add-on Card", + [0x06] = "PCI Add-on Card", + [0x07] = "MCA Add-on Card", + [0x08] = "PCMCIA Add-on Card", + [0x09] = "Proprietary Add-on Card", + [0x0A] = "NuBus", + }; + static const char *location_0xA0[] = { + [0x00] = "PC-98/C20 Add-on Card", /* 0xA0 */ + [0x01] = "PC-98/C24 Add-on Card", /* 0xA1 */ + [0x02] = "PC-98/E Add-on Card", /* 0xA2 */ + [0x03] = "PC-98/Local Bus Add-on Card", /* 0xA3 */ + [0x04] = "CXL Flexbus 1.0", /* 0xA4 */ + }; + const char *str = OUT_OF_SPEC_STR; + + if (code < ELEMENTSOF(location) && location[code]) + str = location[code]; + else if (code >= 0xA0 && code < (ELEMENTSOF(location_0xA0) + 0xA0)) + str = location_0xA0[code - 0xA0]; + + printf("MEMORY_ARRAY_LOCATION=%s\n", str); +} + +static void dmi_memory_array_ec_type(uint8_t code) { + /* 7.17.3 */ + static const char *type[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "None", + [0x04] = "Parity", + [0x05] = "Single-bit ECC", + [0x06] = "Multi-bit ECC", + [0x07] = "CRC", + }; + + if (code != 0x03) /* Do not print "None". */ + printf("MEMORY_ARRAY_EC_TYPE=%s\n", + code < ELEMENTSOF(type) && type[code] ? type[code] : OUT_OF_SPEC_STR); +} + +/* + * 7.18 Memory Device (Type 17) + */ + +static void dmi_memory_device_string( + const char *attr_suffix, unsigned slot_num, + const struct dmi_header *h, uint8_t s) { + char *str; + + str = strdupa(dmi_string(h, s)); + str = strstrip(str); + if (!isempty(str)) + printf("MEMORY_DEVICE_%u_%s=%s\n", slot_num, attr_suffix, str); +} + +static void dmi_memory_device_width( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + + /* If no memory module is present, width may be 0 */ + if (!IN_SET(code, 0, 0xFFFF)) + printf("MEMORY_DEVICE_%u_%s=%u\n", slot_num, attr_suffix, code); +} + +static void dmi_memory_device_size(unsigned slot_num, uint16_t code) { + if (code == 0) + return (void) printf("MEMORY_DEVICE_%u_PRESENT=0\n", slot_num); + if (code == 0xFFFF) + return; + + uint64_t s = code & 0x7FFF; + if (!(code & 0x8000)) + s <<= 10; + dmi_print_memory_size("MEMORY_DEVICE", "SIZE", slot_num, s, MEMORY_SIZE_UNIT_KB); +} + +static void dmi_memory_device_extended_size(unsigned slot_num, uint32_t code) { + uint64_t capacity = (uint64_t) code * 1024 * 1024; + + printf("MEMORY_DEVICE_%u_SIZE=%"PRIu64"\n", slot_num, capacity); +} + +static void dmi_memory_device_rank(unsigned slot_num, uint8_t code) { + code &= 0x0F; + if (code != 0) + printf("MEMORY_DEVICE_%u_RANK=%u\n", slot_num, code); +} + +static void dmi_memory_device_voltage_value( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + if (code == 0) + return; + if (code % 100 != 0) + printf("MEMORY_DEVICE_%u_%s=%g\n", slot_num, attr_suffix, (double)code / 1000); + else + printf("MEMORY_DEVICE_%u_%s=%.1g\n", slot_num, attr_suffix, (double)code / 1000); +} + +static void dmi_memory_device_form_factor(unsigned slot_num, uint8_t code) { + /* 7.18.1 */ + static const char *form_factor[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "SIMM", + [0x04] = "SIP", + [0x05] = "Chip", + [0x06] = "DIP", + [0x07] = "ZIP", + [0x08] = "Proprietary Card", + [0x09] = "DIMM", + [0x0A] = "TSOP", + [0x0B] = "Row Of Chips", + [0x0C] = "RIMM", + [0x0D] = "SODIMM", + [0x0E] = "SRIMM", + [0x0F] = "FB-DIMM", + [0x10] = "Die", + }; + + printf("MEMORY_DEVICE_%u_FORM_FACTOR=%s\n", slot_num, + code < ELEMENTSOF(form_factor) && form_factor[code] ? form_factor[code] : OUT_OF_SPEC_STR); +} + +static void dmi_memory_device_set(unsigned slot_num, uint8_t code) { + if (code == 0xFF) + printf("MEMORY_DEVICE_%u_SET=%s\n", slot_num, "Unknown"); + else if (code != 0) + printf("MEMORY_DEVICE_%u_SET=%"PRIu8"\n", slot_num, code); +} + +static void dmi_memory_device_type(unsigned slot_num, uint8_t code) { + /* 7.18.2 */ + static const char *type[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "DRAM", + [0x04] = "EDRAM", + [0x05] = "VRAM", + [0x06] = "SRAM", + [0x07] = "RAM", + [0x08] = "ROM", + [0x09] = "Flash", + [0x0A] = "EEPROM", + [0x0B] = "FEPROM", + [0x0C] = "EPROM", + [0x0D] = "CDRAM", + [0x0E] = "3DRAM", + [0x0F] = "SDRAM", + [0x10] = "SGRAM", + [0x11] = "RDRAM", + [0x12] = "DDR", + [0x13] = "DDR2", + [0x14] = "DDR2 FB-DIMM", + [0x15] = "Reserved", + [0x16] = "Reserved", + [0x17] = "Reserved", + [0x18] = "DDR3", + [0x19] = "FBD2", + [0x1A] = "DDR4", + [0x1B] = "LPDDR", + [0x1C] = "LPDDR2", + [0x1D] = "LPDDR3", + [0x1E] = "LPDDR4", + [0x1F] = "Logical non-volatile device", + [0x20] = "HBM", + [0x21] = "HBM2", + }; + + printf("MEMORY_DEVICE_%u_TYPE=%s\n", slot_num, + code < ELEMENTSOF(type) && type[code] ? type[code] : OUT_OF_SPEC_STR); +} + +static void dmi_memory_device_type_detail(unsigned slot_num, uint16_t code) { + /* 7.18.3 */ + static const char *detail[] = { + [1] = "Other", + [2] = "Unknown", + [3] = "Fast-paged", + [4] = "Static Column", + [5] = "Pseudo-static", + [6] = "RAMBus", + [7] = "Synchronous", + [8] = "CMOS", + [9] = "EDO", + [10] = "Window DRAM", + [11] = "Cache DRAM", + [12] = "Non-Volatile", + [13] = "Registered (Buffered)", + [14] = "Unbuffered (Unregistered)", + [15] = "LRDIMM", + }; + + if ((code & 0xFFFE) == 0) + printf("MEMORY_DEVICE_%u_TYPE_DETAIL=%s\n", slot_num, "None"); + else { + bool first_element = true; + + printf("MEMORY_DEVICE_%u_TYPE_DETAIL=", slot_num); + for (size_t i = 1; i < ELEMENTSOF(detail); i++) + if (code & (1 << i)) { + printf("%s%s", first_element ? "" : " ", detail[i]); + first_element = false; + } + printf("\n"); + } +} + +static void dmi_memory_device_speed( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + if (code != 0) + printf("MEMORY_DEVICE_%u_%s=%u\n", slot_num, attr_suffix, code); +} + +static void dmi_memory_device_technology(unsigned slot_num, uint8_t code) { + /* 7.18.6 */ + static const char * const technology[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "DRAM", + [0x04] = "NVDIMM-N", + [0x05] = "NVDIMM-F", + [0x06] = "NVDIMM-P", + [0x07] = "Intel Optane DC persistent memory", + }; + + printf("MEMORY_DEVICE_%u_MEMORY_TECHNOLOGY=%s\n", slot_num, + code < ELEMENTSOF(technology) && technology[code] ? technology[code] : OUT_OF_SPEC_STR); +} + +static void dmi_memory_device_operating_mode_capability(unsigned slot_num, uint16_t code) { + /* 7.18.7 */ + static const char * const mode[] = { + [1] = "Other", + [2] = "Unknown", + [3] = "Volatile memory", + [4] = "Byte-accessible persistent memory", + [5] = "Block-accessible persistent memory", + }; + + if ((code & 0xFFFE) != 0) { + bool first_element = true; + + printf("MEMORY_DEVICE_%u_MEMORY_OPERATING_MODE_CAPABILITY=", slot_num); + for (size_t i = 1; i < ELEMENTSOF(mode); i++) + if (code & (1 << i)) { + printf("%s%s", first_element ? "" : " ", mode[i]); + first_element = false; + } + printf("\n"); + } +} + +static void dmi_memory_device_manufacturer_id( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + /* 7.18.8 */ + /* 7.18.10 */ + /* LSB is 7-bit Odd Parity number of continuation codes */ + if (code != 0) + printf("MEMORY_DEVICE_%u_%s=Bank %d, Hex 0x%02X\n", slot_num, attr_suffix, + (code & 0x7F) + 1, code >> 8); +} + +static void dmi_memory_device_product_id( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + /* 7.18.9 */ + /* 7.18.11 */ + if (code != 0) + printf("MEMORY_DEVICE_%u_%s=0x%04X\n", slot_num, attr_suffix, code); +} + +static void dmi_memory_device_size_detail( + const char *attr_suffix, + unsigned slot_num, uint64_t code) { + /* 7.18.12 */ + /* 7.18.13 */ + if (!IN_SET(code, 0x0LU, 0xFFFFFFFFFFFFFFFFLU)) + dmi_print_memory_size("MEMORY_DEVICE", attr_suffix, slot_num, code, MEMORY_SIZE_UNIT_BYTES); +} + +static void dmi_decode(const struct dmi_header *h) { + const uint8_t *data = h->data; + static unsigned next_slot_num = 0; + unsigned slot_num; + + /* + * Note: DMI types 37 and 42 are untested + */ + switch (h->type) { + case 16: /* 7.17 Physical Memory Array */ + log_debug("Physical Memory Array"); + if (h->length < 0x0F) + break; + + if (data[0x05] != 0x03) /* 7.17.2, Use == "System Memory" */ + break; + + log_debug("Use: System Memory"); + dmi_memory_array_location(data[0x04]); + dmi_memory_array_ec_type(data[0x06]); + if (DWORD(data + 0x07) != 0x80000000) + dmi_print_memory_size("MEMORY_ARRAY", "MAX_CAPACITY", -1, DWORD(data + 0x07), MEMORY_SIZE_UNIT_KB); + else if (h->length >= 0x17) + dmi_print_memory_size("MEMORY_ARRAY", "MAX_CAPACITY", -1, QWORD(data + 0x0F), MEMORY_SIZE_UNIT_BYTES); + printf("MEMORY_ARRAY_NUM_DEVICES=%u\n", WORD(data + 0x0D)); + + break; + + case 17: /* 7.18 Memory Device */ + slot_num = next_slot_num; + next_slot_num++; + + log_debug("Memory Device"); + if (h->length < 0x15) + break; + + dmi_memory_device_width("TOTAL_WIDTH", slot_num, WORD(data + 0x08)); + dmi_memory_device_width("DATA_WIDTH", slot_num, WORD(data + 0x0A)); + if (h->length >= 0x20 && WORD(data + 0x0C) == 0x7FFF) + dmi_memory_device_extended_size(slot_num, DWORD(data + 0x1C)); + else + dmi_memory_device_size(slot_num, WORD(data + 0x0C)); + dmi_memory_device_form_factor(slot_num, data[0x0E]); + dmi_memory_device_set(slot_num, data[0x0F]); + dmi_memory_device_string("LOCATOR", slot_num, h, data[0x10]); + dmi_memory_device_string("BANK_LOCATOR", slot_num, h, data[0x11]); + dmi_memory_device_type(slot_num, data[0x12]); + dmi_memory_device_type_detail(slot_num, WORD(data + 0x13)); + if (h->length < 0x17) + break; + + dmi_memory_device_speed("SPEED_MTS", slot_num, WORD(data + 0x15)); + if (h->length < 0x1B) + break; + + dmi_memory_device_string("MANUFACTURER", slot_num, h, data[0x17]); + dmi_memory_device_string("SERIAL_NUMBER", slot_num, h, data[0x18]); + dmi_memory_device_string("ASSET_TAG", slot_num, h, data[0x19]); + dmi_memory_device_string("PART_NUMBER", slot_num, h, data[0x1A]); + if (h->length < 0x1C) + break; + + dmi_memory_device_rank(slot_num, data[0x1B]); + if (h->length < 0x22) + break; + + dmi_memory_device_speed("CONFIGURED_SPEED_MTS", slot_num, WORD(data + 0x20)); + if (h->length < 0x28) + break; + + dmi_memory_device_voltage_value("MINIMUM_VOLTAGE", slot_num, WORD(data + 0x22)); + dmi_memory_device_voltage_value("MAXIMUM_VOLTAGE", slot_num, WORD(data + 0x24)); + dmi_memory_device_voltage_value("CONFIGURED_VOLTAGE", slot_num, WORD(data + 0x26)); + if (h->length < 0x34) + break; + + dmi_memory_device_technology(slot_num, data[0x28]); + dmi_memory_device_operating_mode_capability(slot_num, WORD(data + 0x29)); + dmi_memory_device_string("FIRMWARE_VERSION", slot_num, h, data[0x2B]); + dmi_memory_device_manufacturer_id("MODULE_MANUFACTURER_ID", slot_num, WORD(data + 0x2C)); + dmi_memory_device_product_id("MODULE_PRODUCT_ID", slot_num, WORD(data + 0x2E)); + dmi_memory_device_manufacturer_id("MEMORY_SUBSYSTEM_CONTROLLER_MANUFACTURER_ID", + slot_num, WORD(data + 0x30)); + dmi_memory_device_product_id("MEMORY_SUBSYSTEM_CONTROLLER_PRODUCT_ID", + slot_num, WORD(data + 0x32)); + if (h->length < 0x3C) + break; + + dmi_memory_device_size_detail("NON_VOLATILE_SIZE", slot_num, QWORD(data + 0x34)); + if (h->length < 0x44) + break; + + dmi_memory_device_size_detail("VOLATILE_SIZE", slot_num, QWORD(data + 0x3C)); + if (h->length < 0x4C) + break; + + dmi_memory_device_size_detail("CACHE_SIZE", slot_num, QWORD(data + 0x44)); + if (h->length < 0x54) + break; + + dmi_memory_device_size_detail("LOGICAL_SIZE", slot_num, QWORD(data + 0x4C)); + + break; + } +} + +static void dmi_table_decode(const uint8_t *buf, size_t len, uint16_t num) { + const uint8_t *data = buf; + + /* 4 is the length of an SMBIOS structure header */ + for (uint16_t i = 0; (i < num || num == 0) && data + 4 <= buf + len; i++) { + struct dmi_header h = (struct dmi_header) { + .type = data[0], + .length = data[1], + .handle = WORD(data + 2), + .data = data, + }; + bool display = !IN_SET(h.type, 126, 127); + const uint8_t *next; + + /* If a short entry is found (less than 4 bytes), not only it + * is invalid, but we cannot reliably locate the next entry. + * Better stop at this point, and let the user know his/her + * table is broken. */ + if (h.length < 4) + break; + + /* In quiet mode, stop decoding at end of table marker */ + if (h.type == 127) + break; + + /* Look for the next handle */ + next = data + h.length; + while ((size_t)(next - buf + 1) < len && (next[0] != 0 || next[1] != 0)) + next++; + next += 2; + + /* Make sure the whole structure fits in the table */ + if ((size_t)(next - buf) > len) + break; + + if (display) + dmi_decode(&h); + + data = next; + } +} + +static int dmi_table(int64_t base, uint32_t len, uint16_t num, const char *devmem, bool no_file_offset) { + _cleanup_free_ uint8_t *buf = NULL; + size_t size; + int r; + + /* + * When reading from sysfs or from a dump file, the file may be + * shorter than announced. For SMBIOS v3 this is expcted, as we + * only know the maximum table size, not the actual table size. + * For older implementations (and for SMBIOS v3 too), this + * would be the result of the kernel truncating the table on + * parse error. + */ + r = read_full_file_full(AT_FDCWD, devmem, no_file_offset ? 0 : base, len, + 0, NULL, (char **) &buf, &size); + if (r < 0) + return log_error_errno(r, "Failed to read table: %m"); + + dmi_table_decode(buf, size, num); + + return 0; +} + +/* Same thing for SMBIOS3 entry points */ +static int smbios3_decode(const uint8_t *buf, const char *devmem, bool no_file_offset) { + uint64_t offset; + + /* Don't let checksum run beyond the buffer */ + if (buf[0x06] > 0x20) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Entry point length too large (%"PRIu8" bytes, expected %u).", + buf[0x06], 0x18U); + + if (!verify_checksum(buf, buf[0x06])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Faied to verify checksum."); + + offset = QWORD(buf + 0x10); + if (!no_file_offset && (offset >> 32) != 0 && sizeof(int64_t) < 8) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "64-bit addresses not supported."); + + return dmi_table(offset, DWORD(buf + 0x0C), 0, devmem, no_file_offset); +} + +static int smbios_decode(const uint8_t *buf, const char *devmem, bool no_file_offset) { + /* Don't let checksum run beyond the buffer */ + if (buf[0x05] > 0x20) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Entry point length too large (%"PRIu8" bytes, expected %u).", + buf[0x05], 0x1FU); + + if (!verify_checksum(buf, buf[0x05]) + || memcmp(buf + 0x10, "_DMI_", 5) != 0 + || !verify_checksum(buf + 0x10, 0x0F)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to verify checksum."); + + return dmi_table(DWORD(buf + 0x18), WORD(buf + 0x16), WORD(buf + 0x1C), + devmem, no_file_offset); +} + +static int legacy_decode(const uint8_t *buf, const char *devmem, bool no_file_offset) { + if (!verify_checksum(buf, 0x0F)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to verify checksum."); + + return dmi_table(DWORD(buf + 0x08), WORD(buf + 0x06), WORD(buf + 0x0C), + devmem, no_file_offset); +} + +static int help(void) { + printf("Usage: %s [options]\n" + " -F,--from-dump FILE read DMI information from a binary file\n" + " -h,--help print this help text\n\n", + program_invocation_short_name); + return 0; +} + +static int parse_argv(int argc, char * const *argv) { + static const struct option options[] = { + { "from-dump", required_argument, NULL, 'F' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + int c; + + while ((c = getopt_long(argc, argv, "F:hV", options, NULL)) >= 0) + switch (c) { + case 'F': + arg_source_file = optarg; + break; + case 'V': + printf("%s\n", GIT_VERSION); + return 0; + case 'h': + return help(); + case '?': + return -EINVAL; + default: + assert_not_reached("Unknown option"); + } + + return 1; +} + +static int run(int argc, char* const* argv) { + _cleanup_free_ uint8_t *buf = NULL; + bool no_file_offset = false; + size_t size; + int r; + + log_set_target(LOG_TARGET_AUTO); + udev_parse_config(); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + /* Read from dump if so instructed */ + r = read_full_file_full(AT_FDCWD, + arg_source_file ?: SYS_ENTRY_FILE, + 0, 0x20, 0, NULL, (char **) &buf, &size); + if (r < 0) + return log_full_errno(!arg_source_file && r == -ENOENT ? LOG_DEBUG : LOG_ERR, + r, "Reading \"%s\" failed: %m", + arg_source_file ?: SYS_ENTRY_FILE); + + if (!arg_source_file) { + arg_source_file = SYS_TABLE_FILE; + no_file_offset = true; + } + + if (size >= 24 && memory_startswith(buf, size, "_SM3_")) + return smbios3_decode(buf, arg_source_file, no_file_offset); + if (size >= 31 && memory_startswith(buf, size, "_SM_")) + return smbios_decode(buf, arg_source_file, no_file_offset); + if (size >= 15 && memory_startswith(buf, size, "_DMI_")) + return legacy_decode(buf, arg_source_file, no_file_offset); + + return -EINVAL; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/udev/meson.build b/src/udev/meson.build index 9170b58a8a..d8b91f2383 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -174,6 +174,7 @@ foreach prog : [['ata_id/ata_id.c'], 'scsi_id/scsi_serial.c', 'scsi_id/scsi.h'], ['v4l_id/v4l_id.c'], + ['dmi_memory_id/dmi_memory_id.c'], ['mtd_probe/mtd_probe.c', 'mtd_probe/mtd_probe.h', 'mtd_probe/probe_smartmedia.c']] diff --git a/test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin b/test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin Binary files differnew file mode 100644 index 0000000000..4c02fb71ce --- /dev/null +++ b/test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin diff --git a/test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin.txt b/test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin.txt new file mode 100644 index 0000000000..26a8faf5d8 --- /dev/null +++ b/test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin.txt @@ -0,0 +1,33 @@ +MEMORY_ARRAY_LOCATION=System Board Or Motherboard +MEMORY_ARRAY_MAX_CAPACITY=34359738368 +MEMORY_ARRAY_NUM_DEVICES=2 +MEMORY_DEVICE_0_TOTAL_WIDTH=64 +MEMORY_DEVICE_0_DATA_WIDTH=64 +MEMORY_DEVICE_0_SIZE=4294967296 +MEMORY_DEVICE_0_FORM_FACTOR=SODIMM +MEMORY_DEVICE_0_LOCATOR=ChannelA-DIMM0 +MEMORY_DEVICE_0_BANK_LOCATOR=BANK 0 +MEMORY_DEVICE_0_TYPE=DDR4 +MEMORY_DEVICE_0_TYPE_DETAIL=Synchronous Unbuffered (Unregistered) +MEMORY_DEVICE_0_SPEED_MTS=2400 +MEMORY_DEVICE_0_MANUFACTURER=0000 +MEMORY_DEVICE_0_SERIAL_NUMBER=00000000 +MEMORY_DEVICE_0_ASSET_TAG=None +MEMORY_DEVICE_0_RANK=1 +MEMORY_DEVICE_0_CONFIGURED_SPEED_MTS=2400 +MEMORY_DEVICE_0_CONFIGURED_VOLTAGE=1 +MEMORY_DEVICE_1_TOTAL_WIDTH=64 +MEMORY_DEVICE_1_DATA_WIDTH=64 +MEMORY_DEVICE_1_SIZE=4294967296 +MEMORY_DEVICE_1_FORM_FACTOR=SODIMM +MEMORY_DEVICE_1_LOCATOR=ChannelB-DIMM0 +MEMORY_DEVICE_1_BANK_LOCATOR=BANK 2 +MEMORY_DEVICE_1_TYPE=DDR4 +MEMORY_DEVICE_1_TYPE_DETAIL=Synchronous Unbuffered (Unregistered) +MEMORY_DEVICE_1_SPEED_MTS=2400 +MEMORY_DEVICE_1_MANUFACTURER=0000 +MEMORY_DEVICE_1_SERIAL_NUMBER=00000000 +MEMORY_DEVICE_1_ASSET_TAG=None +MEMORY_DEVICE_1_RANK=1 +MEMORY_DEVICE_1_CONFIGURED_SPEED_MTS=2400 +MEMORY_DEVICE_1_CONFIGURED_VOLTAGE=1 diff --git a/test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin b/test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin Binary files differnew file mode 100644 index 0000000000..c07e7a29be --- /dev/null +++ b/test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin diff --git a/test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin.txt b/test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin.txt new file mode 100644 index 0000000000..c90af66a7b --- /dev/null +++ b/test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin.txt @@ -0,0 +1,67 @@ +MEMORY_ARRAY_LOCATION=System Board Or Motherboard +MEMORY_ARRAY_MAX_CAPACITY=68719476736 +MEMORY_ARRAY_NUM_DEVICES=4 +MEMORY_DEVICE_0_TOTAL_WIDTH=64 +MEMORY_DEVICE_0_DATA_WIDTH=64 +MEMORY_DEVICE_0_SIZE=8589934592 +MEMORY_DEVICE_0_FORM_FACTOR=DIMM +MEMORY_DEVICE_0_LOCATOR=ChannelA-DIMM0 +MEMORY_DEVICE_0_BANK_LOCATOR=BANK 0 +MEMORY_DEVICE_0_TYPE=DDR4 +MEMORY_DEVICE_0_TYPE_DETAIL=Synchronous +MEMORY_DEVICE_0_SPEED_MTS=2667 +MEMORY_DEVICE_0_MANUFACTURER=Samsung +MEMORY_DEVICE_0_SERIAL_NUMBER=416433E9 +MEMORY_DEVICE_0_ASSET_TAG=9876543210 +MEMORY_DEVICE_0_PART_NUMBER=M378A1K43CB2-CTD +MEMORY_DEVICE_0_RANK=1 +MEMORY_DEVICE_0_CONFIGURED_SPEED_MTS=2400 +MEMORY_DEVICE_0_MINIMUM_VOLTAGE=1 +MEMORY_DEVICE_0_MAXIMUM_VOLTAGE=1 +MEMORY_DEVICE_0_CONFIGURED_VOLTAGE=1 +MEMORY_DEVICE_1_TOTAL_WIDTH=64 +MEMORY_DEVICE_1_DATA_WIDTH=64 +MEMORY_DEVICE_1_SIZE=8589934592 +MEMORY_DEVICE_1_FORM_FACTOR=DIMM +MEMORY_DEVICE_1_LOCATOR=ChannelA-DIMM1 +MEMORY_DEVICE_1_BANK_LOCATOR=BANK 1 +MEMORY_DEVICE_1_TYPE=DDR4 +MEMORY_DEVICE_1_TYPE_DETAIL=Synchronous +MEMORY_DEVICE_1_SPEED_MTS=2400 +MEMORY_DEVICE_1_MANUFACTURER=859B +MEMORY_DEVICE_1_SERIAL_NUMBER=A02550A6 +MEMORY_DEVICE_1_ASSET_TAG=9876543210 +MEMORY_DEVICE_1_PART_NUMBER=BLT8G4D26AFTA.16FBD +MEMORY_DEVICE_1_RANK=2 +MEMORY_DEVICE_1_CONFIGURED_SPEED_MTS=2400 +MEMORY_DEVICE_1_MINIMUM_VOLTAGE=1 +MEMORY_DEVICE_1_MAXIMUM_VOLTAGE=1 +MEMORY_DEVICE_1_CONFIGURED_VOLTAGE=1 +MEMORY_DEVICE_2_PRESENT=0 +MEMORY_DEVICE_2_FORM_FACTOR=Unknown +MEMORY_DEVICE_2_LOCATOR=ChannelB-DIMM0 +MEMORY_DEVICE_2_BANK_LOCATOR=BANK 2 +MEMORY_DEVICE_2_TYPE=Unknown +MEMORY_DEVICE_2_TYPE_DETAIL=None +MEMORY_DEVICE_2_MANUFACTURER=Not Specified +MEMORY_DEVICE_2_SERIAL_NUMBER=Not Specified +MEMORY_DEVICE_2_ASSET_TAG=Not Specified +MEMORY_DEVICE_2_PART_NUMBER=Not Specified +MEMORY_DEVICE_3_TOTAL_WIDTH=64 +MEMORY_DEVICE_3_DATA_WIDTH=64 +MEMORY_DEVICE_3_SIZE=8589934592 +MEMORY_DEVICE_3_FORM_FACTOR=DIMM +MEMORY_DEVICE_3_LOCATOR=ChannelB-DIMM1 +MEMORY_DEVICE_3_BANK_LOCATOR=BANK 3 +MEMORY_DEVICE_3_TYPE=DDR4 +MEMORY_DEVICE_3_TYPE_DETAIL=Synchronous +MEMORY_DEVICE_3_SPEED_MTS=2400 +MEMORY_DEVICE_3_MANUFACTURER=859B +MEMORY_DEVICE_3_SERIAL_NUMBER=A0254F38 +MEMORY_DEVICE_3_ASSET_TAG=9876543210 +MEMORY_DEVICE_3_PART_NUMBER=BLT8G4D26AFTA.16FBD +MEMORY_DEVICE_3_RANK=2 +MEMORY_DEVICE_3_CONFIGURED_SPEED_MTS=2400 +MEMORY_DEVICE_3_MINIMUM_VOLTAGE=1 +MEMORY_DEVICE_3_MAXIMUM_VOLTAGE=1 +MEMORY_DEVICE_3_CONFIGURED_VOLTAGE=1 diff --git a/test/meson.build b/test/meson.build index 799d74c7ae..a71cf24eaf 100644 --- a/test/meson.build +++ b/test/meson.build @@ -133,4 +133,11 @@ if conf.get('ENABLE_HWDB') == 1 endif endif +if want_tests != false + udev_dmi_memory_id_test = find_program('udev-dmi-memory-id-test.sh') + test('udev-dmi-memory-id-test', + udev_dmi_memory_id_test, + timeout : 90) +endif + subdir('fuzz') diff --git a/test/udev-dmi-memory-id-test.sh b/test/udev-dmi-memory-id-test.sh new file mode 100755 index 0000000000..2b6dee5b74 --- /dev/null +++ b/test/udev-dmi-memory-id-test.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# SPDX-License-Identifier: LGPL-2.1-or-later +# + +set -e + +export SYSTEMD_LOG_LEVEL=info +ROOTDIR=$(dirname $(dirname $(readlink -f $0))) +UDEV_DMI_MEMORY_ID=./src/udev/dmi_memory_id + +if [ ! -x "$UDEV_DMI_MEMORY_ID" ]; then + echo "$UDEV_DMI_MEMORY_ID does not exist, please build first" + exit 1 +fi + +D=$(mktemp --tmpdir --directory "udev-dmi-memory-id.XXXXXXXXXX") +trap "rm -rf '$D'" EXIT INT QUIT PIPE + +for i in $ROOTDIR/test/dmidecode-dumps/*.bin ; do + $("$UDEV_DMI_MEMORY_ID" -F "$i" 2>&1 > "$D"/out.txt) && rc= || rc=$? + if [ -n "$rc" ]; then + echo "$UDEV_DMI_MEMORY_ID returned $rc" + exit $rc + fi + err=$(diff -u "$D"/out.txt "$i.txt" 2>&1) && rc= || rc=$? + if [ -n "$rc" ]; then + echo "Parsing DMI memory information from \"$i\" didn't match expected:" + echo "$err" + exit $rc + fi +done |