summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastien Nocera <hadess@hadess.net>2020-12-02 12:40:42 +0100
committerBastien Nocera <hadess@hadess.net>2020-12-16 12:41:56 +0100
commit55266302bab69ab53933be2d359e1d26665818c0 (patch)
tree884f4ead900b935ba409d55c7347064c703a77dd
parent8bc63a0b1355a8362e50b449b2ae9979122a2521 (diff)
downloadsystemd-wip/hadess/memory-id.tar.gz
udev: Extract RAM properties from DMI informationwip/hadess/memory-id
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
-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