3693cbacf7
As sizeof(int64_t) is always 8.
714 lines
26 KiB
C
714 lines
26 KiB
C
/* 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 expected, 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), "Failed to verify checksum.");
|
|
|
|
offset = QWORD(buf + 0x10);
|
|
|
|
#if __SIZEOF_SIZE_T__ != 8
|
|
if (!no_file_offset && (offset >> 32) != 0)
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "64-bit addresses not supported on 32-bit systems.");
|
|
#endif
|
|
|
|
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);
|