summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes1
-rw-r--r--rules.d/70-memory.rules8
-rw-r--r--rules.d/meson.build1
-rw-r--r--src/udev/dmi_memory_id/dmi_memory_id.c710
-rw-r--r--src/udev/meson.build1
-rw-r--r--test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.binbin0 -> 3081 bytes
-rw-r--r--test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin.txt33
-rw-r--r--test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.binbin0 -> 5074 bytes
-rw-r--r--test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin.txt67
-rw-r--r--test/meson.build7
-rwxr-xr-xtest/udev-dmi-memory-id-test.sh31
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
new file mode 100644
index 0000000000..4c02fb71ce
--- /dev/null
+++ b/test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin
Binary files differ
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
new file mode 100644
index 0000000000..c07e7a29be
--- /dev/null
+++ b/test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin
Binary files differ
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