Browse Source

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
master
Bastien Nocera 2 years ago committed by Zbigniew Jędrzejewski-Szmek
parent
commit
4dd465cb4e
  1. 1
      .gitattributes
  2. 8
      rules.d/70-memory.rules
  3. 1
      rules.d/meson.build
  4. 710
      src/udev/dmi_memory_id/dmi_memory_id.c
  5. 1
      src/udev/meson.build
  6. BIN
      test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin
  7. 33
      test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin.txt
  8. BIN
      test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin
  9. 67
      test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin.txt
  10. 7
      test/meson.build
  11. 31
      test/udev-dmi-memory-id-test.sh

1
.gitattributes vendored

@ -1 +1,2 @@
*.[ch] whitespace=tab-in-indent,trailing-space
test/dmidecode-dumps/*.bin binary

8
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"

1
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

710
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);

1
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']]

BIN
test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin

Binary file not shown.

33
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

BIN
test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin

Binary file not shown.

67
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

7
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')

31
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
Loading…
Cancel
Save