mirror of
https://github.com/systemd/systemd.git
synced 2024-11-23 02:03:37 +08:00
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
This commit is contained in:
parent
84a1ff9457
commit
4dd465cb4e
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +1,2 @@
|
||||
*.[ch] whitespace=tab-in-indent,trailing-space
|
||||
test/dmidecode-dumps/*.bin binary
|
||||
|
8
rules.d/70-memory.rules
Normal file
8
rules.d/70-memory.rules
Normal file
@ -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"
|
@ -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
Normal file
710
src/udev/dmi_memory_id/dmi_memory_id.c
Normal file
@ -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);
|
@ -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
Normal file
BIN
test/dmidecode-dumps/Lenovo-ThinkPad-X280-dmidecode-dump.bin
Normal file
Binary file not shown.
@ -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
Normal file
BIN
test/dmidecode-dumps/Lenovo-Thinkcentre-m720s-dmidecode-dump.bin
Normal file
Binary file not shown.
@ -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
|
@ -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
Executable file
31
test/udev-dmi-memory-id-test.sh
Executable file
@ -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…
Reference in New Issue
Block a user