From 4dd465cb4e0706095ff7a80f58b23b8974fee37d Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Wed, 2 Dec 2020 12:40:42 +0100 Subject: [PATCH] udev: Extract RAM properties from DMI information Add memory_id program to set properties about the physical memory devices in the system. This is useful on machines with removable memory modules to show how the machine can be upgraded, and on all devices to detect the actual RAM size, without relying on the OS accessible amount. Closes: #16651 --- .gitattributes | 1 + rules.d/70-memory.rules | 8 + rules.d/meson.build | 1 + src/udev/dmi_memory_id/dmi_memory_id.c | 710 ++++++++++++++++++ src/udev/meson.build | 1 + .../Lenovo-ThinkPad-X280-dmidecode-dump.bin | Bin 0 -> 3081 bytes ...enovo-ThinkPad-X280-dmidecode-dump.bin.txt | 33 + ...enovo-Thinkcentre-m720s-dmidecode-dump.bin | Bin 0 -> 5074 bytes ...o-Thinkcentre-m720s-dmidecode-dump.bin.txt | 67 ++ test/meson.build | 7 + test/udev-dmi-memory-id-test.sh | 31 + 11 files changed, 859 insertions(+) create mode 100644 rules.d/70-memory.rules create mode 100644 src/udev/dmi_memory_id/dmi_memory_id.c create mode 100644 test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin create mode 100644 test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin.txt create mode 100644 test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin create mode 100644 test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin.txt create mode 100755 test/udev-dmi-memory-id-test.sh 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 + * Copyright (C) 2002-2020 Jean Delvare + * Copyright (C) 2020 Bastien Nocera + * + * 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 + +#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 "" + +#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 ""; + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..4c02fb71cec1e877235e829853fac61ee3868485 GIT binary patch literal 3081 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 > "$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