mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-11-10 13:37:25 +01:00
PhysicalMemory (Linux): reads unprivileged DMI physical memory slots from /run/udev/data/+dmi:id
This commit is contained in:
1 parent
c018073384
commit
6843b56f1f
3 files changed
+467
-233
No files matched your search
+8
-7
@@ -620,6 +620,7 @@ if(LINUX)
|
||||
src/detection/dns/dns_linux.c
|
||||
src/detection/physicaldisk/physicaldisk_linux.c
|
||||
src/detection/physicalmemory/physicalmemory_linux.c
|
||||
src/detection/physicalmemory/physicalmemory_shared.c
|
||||
src/detection/diskio/diskio_linux.c
|
||||
src/detection/displayserver/linux/displayserver_linux.c
|
||||
src/detection/displayserver/linux/common.c
|
||||
@@ -798,7 +799,7 @@ elseif(FreeBSD)
|
||||
src/detection/disk/disk_bsd.c
|
||||
src/detection/dns/dns_linux.c
|
||||
src/detection/physicaldisk/physicaldisk_bsd.c
|
||||
src/detection/physicalmemory/physicalmemory_linux.c
|
||||
src/detection/physicalmemory/physicalmemory_shared.c
|
||||
src/detection/diskio/diskio_bsd.c
|
||||
src/detection/displayserver/linux/displayserver_linux.c
|
||||
src/detection/displayserver/linux/common.c
|
||||
@@ -897,7 +898,7 @@ elseif(NetBSD)
|
||||
src/detection/disk/disk_bsd.c
|
||||
src/detection/dns/dns_linux.c
|
||||
src/detection/physicaldisk/physicaldisk_nbsd.c
|
||||
src/detection/physicalmemory/physicalmemory_linux.c
|
||||
src/detection/physicalmemory/physicalmemory_shared.c
|
||||
src/detection/diskio/diskio_nbsd.c
|
||||
src/detection/displayserver/linux/displayserver_linux.c
|
||||
src/detection/displayserver/linux/common.c
|
||||
@@ -983,7 +984,7 @@ elseif(OpenBSD)
|
||||
src/detection/disk/disk_bsd.c
|
||||
src/detection/dns/dns_linux.c
|
||||
src/detection/physicaldisk/physicaldisk_obsd.c
|
||||
src/detection/physicalmemory/physicalmemory_linux.c
|
||||
src/detection/physicalmemory/physicalmemory_shared.c
|
||||
src/detection/diskio/diskio_obsd.c
|
||||
src/detection/displayserver/linux/displayserver_linux.c
|
||||
src/detection/displayserver/linux/common.c
|
||||
@@ -1124,7 +1125,7 @@ elseif(APPLE)
|
||||
src/detection/board/board_windows.c
|
||||
src/detection/chassis/chassis_windows.c
|
||||
src/detection/host/host_windows.c
|
||||
src/detection/physicalmemory/physicalmemory_linux.c
|
||||
src/detection/physicalmemory/physicalmemory_shared.c
|
||||
)
|
||||
else()
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
@@ -1188,7 +1189,7 @@ elseif(WIN32)
|
||||
src/detection/media/media_windows.cpp
|
||||
src/detection/memory/memory_windows.c
|
||||
src/detection/mouse/mouse_windows.c
|
||||
src/detection/physicalmemory/physicalmemory_linux.c
|
||||
src/detection/physicalmemory/physicalmemory_shared.c
|
||||
src/detection/netio/netio_windows.c
|
||||
src/detection/opengl/opengl_windows.c
|
||||
src/detection/os/os_windows.c
|
||||
@@ -1244,7 +1245,7 @@ elseif(SunOS)
|
||||
src/detection/disk/disk_sunos.c
|
||||
src/detection/dns/dns_linux.c
|
||||
src/detection/physicaldisk/physicaldisk_sunos.c
|
||||
src/detection/physicalmemory/physicalmemory_linux.c
|
||||
src/detection/physicalmemory/physicalmemory_shared.c
|
||||
src/detection/diskio/diskio_sunos.c
|
||||
src/detection/displayserver/linux/displayserver_linux.c
|
||||
src/detection/displayserver/linux/common.c
|
||||
@@ -1329,7 +1330,7 @@ elseif(Haiku)
|
||||
src/detection/disk/disk_haiku.cpp
|
||||
src/detection/dns/dns_linux.c
|
||||
src/detection/physicaldisk/physicaldisk_haiku.c
|
||||
src/detection/physicalmemory/physicalmemory_linux.c
|
||||
src/detection/physicalmemory/physicalmemory_shared.c
|
||||
src/detection/diskio/diskio_nosupport.c
|
||||
src/detection/displayserver/displayserver_haiku.cpp
|
||||
src/detection/font/font_haiku.cpp
|
||||
|
||||
@@ -1,244 +1,225 @@
|
||||
#include "physicalmemory.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/io.h"
|
||||
#include "common/smbios.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
// 7.18
|
||||
typedef struct [[gnu::packed]] FFSmbiosMemoryDevice {
|
||||
FFSmbiosHeader Header;
|
||||
// systemd >= 248 decodes SMBIOS Type 16 / 17 into udev properties of the `dmi:id` device
|
||||
// (see systemd's src/udev/dmi_memory_id/dmi_memory_id.c). The database is world readable,
|
||||
// while /sys/firmware/dmi/tables/DMI and /dev/mem are root-only, so try it first.
|
||||
#define FF_UDEV_DMI_ID_DB "/run/udev/data/+dmi:id"
|
||||
#define FF_UDEV_MEMORY_DEVICE_PREFIX "E:MEMORY_DEVICE_"
|
||||
#define FF_UDEV_MAX_MEMORY_DEVICES 256
|
||||
|
||||
// 2.1+
|
||||
uint16_t PhysicalMemoryArrayHandle; // varies
|
||||
uint16_t MemoryErrorInformationHandle; // varies
|
||||
uint16_t TotalWidth; // varies
|
||||
uint16_t DataWidth; // varies
|
||||
uint16_t Size; // varies
|
||||
uint8_t FormFactor; // enum
|
||||
uint8_t DeviceSet; // varies
|
||||
uint8_t DeviceLocator; // string
|
||||
uint8_t BankLocator; // string
|
||||
uint8_t MemoryType; // enum
|
||||
uint16_t TypeDetail; // bit field
|
||||
static void ffUdevResultAdd(FFlist* result) {
|
||||
*FF_LIST_ADD(FFPhysicalMemoryResult, *result) = (FFPhysicalMemoryResult){
|
||||
// Doubles as parse-time state: absent PRESENT / SIZE means "present, unknown
|
||||
// size", so it starts true and is only ever cleared by PRESENT=0 or SIZE=0.
|
||||
.installed = true,
|
||||
.type = ffStrbufCreate(),
|
||||
.formFactor = ffStrbufCreate(),
|
||||
.locator = ffStrbufCreate(),
|
||||
.partNumber = ffStrbufCreate(),
|
||||
.vendor = ffStrbufCreate(),
|
||||
.serial = ffStrbufCreate(),
|
||||
};
|
||||
}
|
||||
|
||||
// 2.3+
|
||||
uint16_t Speed; // varies
|
||||
uint8_t Manufacturer; // string
|
||||
uint8_t SerialNumber; // string
|
||||
uint8_t AssetTag; // string
|
||||
uint8_t PartNumber; // string
|
||||
static void ffUdevResultDestroy(FFPhysicalMemoryResult* device) {
|
||||
ffStrbufDestroy(&device->type);
|
||||
ffStrbufDestroy(&device->formFactor);
|
||||
ffStrbufDestroy(&device->locator);
|
||||
ffStrbufDestroy(&device->partNumber);
|
||||
ffStrbufDestroy(&device->vendor);
|
||||
ffStrbufDestroy(&device->serial);
|
||||
}
|
||||
|
||||
// 2.6+
|
||||
uint8_t Attributes; // varies
|
||||
// udev hex-escapes characters that are not allowed in property values (\xHH).
|
||||
// Also drop the placeholder strings ("Unknown", "0x0000...", "<OUT OF SPEC>", ...).
|
||||
static void ffUdevSetString(FFstrbuf* strbuf, const char* value) {
|
||||
ffStrbufSetS(strbuf, value);
|
||||
ffStrbufDecodeHexEscapeSequences(strbuf);
|
||||
// Older systemd versions print an empty value, newer ones "<OUT OF SPEC>".
|
||||
if (ffStrbufStartsWithS(strbuf, "<OUT OF SPEC>")) {
|
||||
ffStrbufClear(strbuf);
|
||||
}
|
||||
ffCleanUpSmbiosValue(strbuf);
|
||||
}
|
||||
|
||||
// 2.7+
|
||||
uint32_t ExtendedSize; // varies
|
||||
uint16_t ConfiguredMemorySpeed; // varies
|
||||
// `locator` is the "bank/device" join, but the two properties arrive in no guaranteed
|
||||
// order. Prepending the bank and appending the device yields the same result either way.
|
||||
// systemd emits each property at most once per device.
|
||||
static void ffUdevAppendLocator(FFPhysicalMemoryResult* device, const char* value, bool bank) {
|
||||
FF_STRBUF_AUTO_DESTROY part = ffStrbufCreate();
|
||||
ffUdevSetString(&part, value);
|
||||
if (part.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.8+
|
||||
uint16_t MinimumVoltage; // varies
|
||||
uint16_t MaximumVoltage; // varies
|
||||
uint16_t ConfiguredVoltage; // varies
|
||||
if (device->locator.length > 0) {
|
||||
if (bank) {
|
||||
ffStrbufPrependC(&device->locator, '/');
|
||||
} else {
|
||||
ffStrbufAppendC(&device->locator, '/');
|
||||
}
|
||||
}
|
||||
if (bank) {
|
||||
ffStrbufPrepend(&device->locator, &part);
|
||||
} else {
|
||||
ffStrbufAppend(&device->locator, &part);
|
||||
}
|
||||
}
|
||||
|
||||
// 3.2+
|
||||
uint8_t MemoryTechnology; // varies
|
||||
uint16_t MemoryOperatingMode; // bit field
|
||||
uint8_t FirmwareVersion; // string
|
||||
uint16_t ModuleManufacturerID; // varies
|
||||
uint16_t ModuleProductID; // varies
|
||||
uint16_t MemorySubsystemControllerManufacturerID; // vaies
|
||||
uint16_t MemorySubsystemControllerProductID; // varies
|
||||
uint64_t NonVolatileSize; // varies
|
||||
uint64_t VolatileSize; // varies
|
||||
uint64_t CacheSize; // varies
|
||||
uint64_t LogicalSize; // varies
|
||||
// Splits a line like "E:MEMORY_DEVICE_0_SIZE=4294967296" in place. Returns the field
|
||||
// name, or nullptr when the line is unrelated or has an out-of-range device index.
|
||||
// The mere presence of such a line means the udev database describes the memory devices
|
||||
// completely, whether or not its field is recognized or its value is a placeholder.
|
||||
static const char* ffUdevParseHeader(char* line, uint32_t* index, const char** value) {
|
||||
if (!ffStrStartsWith(line, FF_UDEV_MEMORY_DEVICE_PREFIX)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 3.3+
|
||||
uint32_t ExtendedSpeed; // varies
|
||||
uint32_t ExtendedConfiguredSpeed; // varies
|
||||
char* eq = strchr(line, '=');
|
||||
if (!eq) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 3.7+
|
||||
uint16_t Pmic0ManufacturerID; // varies
|
||||
uint16_t Pmic0RevisionNumber; // varies
|
||||
uint16_t RcdManufacturerID; // varies
|
||||
uint16_t RcdRevisionNumber; // varies
|
||||
} FFSmbiosMemoryDevice;
|
||||
const char* p = line + sizeof(FF_UDEV_MEMORY_DEVICE_PREFIX) - 1;
|
||||
char* end = nullptr;
|
||||
unsigned long n = strtoul(p, &end, 10);
|
||||
if (end == p || *end != '_' || n >= FF_UDEV_MAX_MEMORY_DEVICES) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static_assert(offsetof(FFSmbiosMemoryDevice, RcdRevisionNumber) == 0x62,
|
||||
"FFSmbiosMemoryDevice: Wrong struct alignment");
|
||||
*eq = '\0';
|
||||
*index = (uint32_t) n;
|
||||
*value = eq + 1;
|
||||
return end + 1;
|
||||
}
|
||||
|
||||
// The udev database is authoritative: as soon as it contains any MEMORY_DEVICE_*
|
||||
// property, the set of memory devices it describes is considered complete and the
|
||||
// caller must not fall back to SMBIOS -- even when every slot is empty, all values
|
||||
// are placeholders, or showEmptySlots filters everything out of `result`.
|
||||
static bool ffDetectPhysicalMemoryFromUdev(FFPhysicalMemoryOptions* options, FFlist* result) {
|
||||
FF_STRBUF_AUTO_DESTROY db = ffStrbufCreate();
|
||||
if (!ffReadFileBuffer(FF_UDEV_DMI_ID_DB, &db)) {
|
||||
FF_DEBUG("Failed to read %s", FF_UDEV_DMI_ID_DB);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The udev index addresses `result` directly: MEMORY_DEVICE_n_* maps to entry
|
||||
// `base + n`. Entries are therefore naturally sorted and can be updated in place,
|
||||
// without a staging list, a comparator or any result copying. FFstrbuf holds no
|
||||
// pointer into itself, so FF_LIST_ADD may relocate existing entries freely.
|
||||
// Sparse indices leave placeholder entries behind; they are removed below.
|
||||
bool seen[FF_UDEV_MAX_MEMORY_DEVICES] = { false };
|
||||
uint16_t totalWidths[FF_UDEV_MAX_MEMORY_DEVICES] = { 0 };
|
||||
uint16_t dataWidths[FF_UDEV_MAX_MEMORY_DEVICES] = { 0 };
|
||||
const uint32_t base = result->length;
|
||||
bool found = false;
|
||||
|
||||
char* line = nullptr;
|
||||
size_t len = 0;
|
||||
while (ffStrbufGetline(&line, &len, &db)) {
|
||||
// `len` must stay intact: the next ffStrbufGetline call advances the cursor by it.
|
||||
if (len > 0 && line[len - 1] == '\r') {
|
||||
line[len - 1] = '\0';
|
||||
}
|
||||
|
||||
uint32_t index;
|
||||
const char* value;
|
||||
const char* field = ffUdevParseHeader(line, &index, &value);
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while (base + index >= result->length) {
|
||||
ffUdevResultAdd(result);
|
||||
}
|
||||
found = true;
|
||||
seen[index] = true;
|
||||
FFPhysicalMemoryResult* device = FF_LIST_GET(FFPhysicalMemoryResult, *result, base + index);
|
||||
|
||||
if (ffStrEquals(field, "SIZE")) {
|
||||
device->size = strtoull(value, nullptr, 10);
|
||||
if (device->size == 0) {
|
||||
device->installed = false;
|
||||
}
|
||||
} else if (ffStrEquals(field, "PRESENT")) {
|
||||
if (strtoul(value, nullptr, 10) == 0) {
|
||||
device->installed = false;
|
||||
}
|
||||
} else if (ffStrEquals(field, "TOTAL_WIDTH")) {
|
||||
totalWidths[index] = (uint16_t) strtoul(value, nullptr, 10);
|
||||
} else if (ffStrEquals(field, "DATA_WIDTH")) {
|
||||
dataWidths[index] = (uint16_t) strtoul(value, nullptr, 10);
|
||||
} else if (ffStrEquals(field, "SPEED_MTS")) {
|
||||
device->maxSpeed = (uint32_t) strtoul(value, nullptr, 10);
|
||||
} else if (ffStrEquals(field, "CONFIGURED_SPEED_MTS")) {
|
||||
device->runningSpeed = (uint32_t) strtoul(value, nullptr, 10);
|
||||
} else if (ffStrEquals(field, "FORM_FACTOR")) {
|
||||
ffUdevSetString(&device->formFactor, value);
|
||||
} else if (ffStrEquals(field, "BANK_LOCATOR")) {
|
||||
ffUdevAppendLocator(device, value, true);
|
||||
} else if (ffStrEquals(field, "LOCATOR")) {
|
||||
ffUdevAppendLocator(device, value, false);
|
||||
} else if (ffStrEquals(field, "TYPE")) {
|
||||
ffUdevSetString(&device->type, value);
|
||||
} else if (ffStrEquals(field, "MANUFACTURER")) {
|
||||
ffUdevSetString(&device->vendor, value);
|
||||
} else if (ffStrEquals(field, "SERIAL_NUMBER")) {
|
||||
ffUdevSetString(&device->serial, value);
|
||||
} else if (ffStrEquals(field, "PART_NUMBER")) {
|
||||
ffUdevSetString(&device->partNumber, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
FF_DEBUG("No MEMORY_DEVICE_* properties in %s", FF_UDEV_DMI_ID_DB);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finalize and drop sparse / empty slots. `i` tracks the current list position
|
||||
// while `index` tracks the original udev index, which survives the removals.
|
||||
uint32_t i = base;
|
||||
for (uint32_t index = 0; i < result->length; ++index) {
|
||||
FFPhysicalMemoryResult* device = FF_LIST_GET(FFPhysicalMemoryResult, *result, i);
|
||||
if (!seen[index] || (!device->installed && !options->showEmptySlots)) {
|
||||
ffUdevResultDestroy(device);
|
||||
FF_LIST_REMOVE_AT(FFPhysicalMemoryResult, *result, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
device->ecc = totalWidths[index] > 0 && dataWidths[index] > 0 &&
|
||||
totalWidths[index] > dataWidths[index];
|
||||
|
||||
if (!device->installed) {
|
||||
device->maxSpeed = device->runningSpeed = 0;
|
||||
ffStrbufSetStatic(&device->type, "Empty");
|
||||
ffStrbufClear(&device->vendor);
|
||||
ffStrbufClear(&device->serial);
|
||||
ffStrbufClear(&device->partNumber);
|
||||
} else {
|
||||
if (device->type.length == 0) {
|
||||
ffStrbufSetStatic(&device->type, "Unknown");
|
||||
}
|
||||
FFPhysicalMemoryUpdateVendorString(device);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* result) {
|
||||
const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
|
||||
if (!smbiosTable) {
|
||||
FF_DEBUG("Failed to get SMBIOS data");
|
||||
return "Failed to get SMBIOS data";
|
||||
if (ffDetectPhysicalMemoryFromUdev(options, result)) {
|
||||
FF_DEBUG("Physical memory detected from %s", FF_UDEV_DMI_ID_DB);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FFSmbiosMemoryDevice* data = (const FFSmbiosMemoryDevice*) (*smbiosTable)[FF_SMBIOS_TYPE_MEMORY_DEVICE];
|
||||
if (!data) {
|
||||
FF_DEBUG("Memory device is not found in SMBIOS data");
|
||||
return "Memory device is not found in SMBIOS data";
|
||||
}
|
||||
|
||||
const FFSmbiosMemoryDevice* endOfTable = (const FFSmbiosMemoryDevice*) (*smbiosTable)[FF_SMBIOS_TYPE_END_OF_TABLE];
|
||||
for (; data != endOfTable; data = (const FFSmbiosMemoryDevice*) ffSmbiosNextEntry(&data->Header)) {
|
||||
if (data->Header.Type != FF_SMBIOS_TYPE_MEMORY_DEVICE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* strings = (const char*) data + data->Header.Length;
|
||||
uint16_t size = FF_READ_LE(data->Size);
|
||||
bool installed = size != 0;
|
||||
|
||||
if (!installed && !options->showEmptySlots) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FFPhysicalMemoryResult* device = FF_LIST_ADD(FFPhysicalMemoryResult, *result);
|
||||
ffStrbufInit(&device->type);
|
||||
ffStrbufInit(&device->formFactor);
|
||||
ffStrbufInit(&device->locator);
|
||||
ffStrbufInit(&device->vendor);
|
||||
ffStrbufInit(&device->serial);
|
||||
ffStrbufInit(&device->partNumber);
|
||||
device->size = 0;
|
||||
device->maxSpeed = 0;
|
||||
device->runningSpeed = 0;
|
||||
device->installed = installed;
|
||||
device->ecc = false;
|
||||
|
||||
uint16_t totalWidth = FF_READ_LE(data->TotalWidth);
|
||||
uint16_t dataWidth = FF_READ_LE(data->DataWidth);
|
||||
if (installed && totalWidth != 0xFFFF && dataWidth != 0xFFFF) {
|
||||
device->ecc = totalWidth > dataWidth;
|
||||
}
|
||||
|
||||
if (installed && size != 0xFFFF) {
|
||||
if (size == 0x7FFF) {
|
||||
device->size = (FF_READ_LE(data->ExtendedSize) & ~(1ULL << 31)) * 1024ULL * 1024ULL;
|
||||
} else if (size & (1 << 15)) {
|
||||
// in kB
|
||||
device->size = (size & ~(1ULL << 15)) * 1024ULL;
|
||||
} else {
|
||||
// in MB
|
||||
device->size = size * 1024ULL * 1024ULL;
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/fastfetch-cli/fastfetch/issues/1051#issuecomment-2206687345
|
||||
const char* lbank = ffSmbiosLocateString(strings, data->BankLocator);
|
||||
const char* ldevice = ffSmbiosLocateString(strings, data->DeviceLocator);
|
||||
if (lbank && ldevice) {
|
||||
ffStrbufSetF(&device->locator, "%s/%s", lbank, ldevice);
|
||||
} else if (lbank) {
|
||||
ffStrbufSetS(&device->locator, lbank);
|
||||
} else if (ldevice) {
|
||||
ffStrbufSetS(&device->locator, ldevice);
|
||||
}
|
||||
|
||||
const char* formFactorNames[] = {
|
||||
nullptr, // 0x00 (Placeholder for indexing)
|
||||
"Other", // 0x01
|
||||
"Unknown", // 0x02
|
||||
"SIMM", // 0x03
|
||||
"SIP", // 0x04
|
||||
"Chip", // 0x05
|
||||
"DIP", // 0x06
|
||||
"ZIP", // 0x07
|
||||
"Proprietary Card", // 0x08
|
||||
"DIMM", // 0x09
|
||||
"TSOP", // 0x0A
|
||||
"Row of chips", // 0x0B
|
||||
"RIMM", // 0x0C
|
||||
"SODIMM", // 0x0D
|
||||
"SRIMM", // 0x0E
|
||||
"FBDIMM", // 0x0F
|
||||
"Die", // 0x10
|
||||
"CAMM", // 0x11
|
||||
"CUDIMM", // 0x12
|
||||
"CSODIMM", // 0x13
|
||||
};
|
||||
if (data->FormFactor > 0 && data->FormFactor < ARRAY_SIZE(formFactorNames)) {
|
||||
ffStrbufSetS(&device->formFactor, formFactorNames[data->FormFactor]);
|
||||
} else {
|
||||
ffStrbufSetF(&device->formFactor, "Unknown (%d)", (int) data->FormFactor);
|
||||
}
|
||||
|
||||
const char* memoryTypeNames[] = {
|
||||
nullptr, // 0x00 (Placeholder for indexing)
|
||||
"Other", // 0x01
|
||||
"Unknown", // 0x02
|
||||
"DRAM", // 0x03
|
||||
"EDRAM", // 0x04
|
||||
"VRAM", // 0x05
|
||||
"SRAM", // 0x06
|
||||
"RAM", // 0x07
|
||||
"ROM", // 0x08
|
||||
"FLASH", // 0x09
|
||||
"EEPROM", // 0x0A
|
||||
"FEPROM", // 0x0B
|
||||
"EPROM", // 0x0C
|
||||
"CDRAM", // 0x0D
|
||||
"3DRAM", // 0x0E
|
||||
"SDRAM", // 0x0F
|
||||
"SGRAM", // 0x10
|
||||
"RDRAM", // 0x11
|
||||
"DDR", // 0x12
|
||||
"DDR2", // 0x13
|
||||
"DDR2 FB-DIMM", // 0x14
|
||||
"Reserved", // 0x15
|
||||
"Reserved", // 0x16
|
||||
"Reserved", // 0x17
|
||||
"DDR3", // 0x18
|
||||
"FBD2", // 0x19
|
||||
"DDR4", // 0x1A
|
||||
"LPDDR", // 0x1B
|
||||
"LPDDR2", // 0x1C
|
||||
"LPDDR3", // 0x1D
|
||||
"LPDDR4", // 0x1E
|
||||
"Logical non-volatile device", // 0x1F
|
||||
"HBM", // 0x20
|
||||
"HBM2", // 0x21
|
||||
"DDR5", // 0x22
|
||||
"LPDDR5", // 0x23
|
||||
"HBM3", // 0x24
|
||||
"MRDIMM", // 0x25
|
||||
};
|
||||
if (!installed) {
|
||||
ffStrbufSetStatic(&device->type, "Empty");
|
||||
} else if (data->MemoryType > 0 && data->MemoryType < ARRAY_SIZE(memoryTypeNames)) {
|
||||
ffStrbufSetStatic(&device->type, memoryTypeNames[data->MemoryType]);
|
||||
} else {
|
||||
ffStrbufSetF(&device->type, "Unknown (%d)", (int) data->MemoryType);
|
||||
}
|
||||
|
||||
if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, Speed)) // 2.3+
|
||||
{
|
||||
uint16_t speed = FF_READ_LE(data->Speed);
|
||||
if (speed) {
|
||||
device->maxSpeed = speed == 0xFFFF ? FF_READ_LE(data->ExtendedSpeed) : speed;
|
||||
}
|
||||
|
||||
ffStrbufSetStatic(&device->vendor, ffSmbiosLocateString(strings, data->Manufacturer));
|
||||
ffCleanUpSmbiosValue(&device->vendor);
|
||||
FFPhysicalMemoryUpdateVendorString(device);
|
||||
|
||||
ffStrbufSetStatic(&device->serial, ffSmbiosLocateString(strings, data->SerialNumber));
|
||||
ffCleanUpSmbiosValue(&device->serial);
|
||||
|
||||
ffStrbufSetStatic(&device->partNumber, ffSmbiosLocateString(strings, data->PartNumber));
|
||||
ffCleanUpSmbiosValue(&device->partNumber);
|
||||
}
|
||||
|
||||
if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, ConfiguredMemorySpeed)) // 2.7+
|
||||
{
|
||||
uint16_t configuredSpeed = FF_READ_LE(data->ConfiguredMemorySpeed);
|
||||
if (configuredSpeed) {
|
||||
device->runningSpeed = configuredSpeed == 0xFFFF
|
||||
? FF_READ_LE(data->ExtendedConfiguredSpeed)
|
||||
: configuredSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
FF_DEBUG("Falling back to reading SMBIOS directly");
|
||||
// Implemented in physicalmemory_shared.c. On Linux it is used as a fallback when udev data
|
||||
// is unavailable; on other SMBIOS capable platforms it backs ffDetectPhysicalMemory directly.
|
||||
const char* ffDetectPhysicalMemoryFromSmbios(FFPhysicalMemoryOptions * options, FFlist * result);
|
||||
return ffDetectPhysicalMemoryFromSmbios(options, result);
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
#include "physicalmemory.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/smbios.h"
|
||||
|
||||
// 7.18
|
||||
typedef struct [[gnu::packed]] FFSmbiosMemoryDevice {
|
||||
FFSmbiosHeader Header;
|
||||
|
||||
// 2.1+
|
||||
uint16_t PhysicalMemoryArrayHandle; // varies
|
||||
uint16_t MemoryErrorInformationHandle; // varies
|
||||
uint16_t TotalWidth; // varies
|
||||
uint16_t DataWidth; // varies
|
||||
uint16_t Size; // varies
|
||||
uint8_t FormFactor; // enum
|
||||
uint8_t DeviceSet; // varies
|
||||
uint8_t DeviceLocator; // string
|
||||
uint8_t BankLocator; // string
|
||||
uint8_t MemoryType; // enum
|
||||
uint16_t TypeDetail; // bit field
|
||||
|
||||
// 2.3+
|
||||
uint16_t Speed; // varies
|
||||
uint8_t Manufacturer; // string
|
||||
uint8_t SerialNumber; // string
|
||||
uint8_t AssetTag; // string
|
||||
uint8_t PartNumber; // string
|
||||
|
||||
// 2.6+
|
||||
uint8_t Attributes; // varies
|
||||
|
||||
// 2.7+
|
||||
uint32_t ExtendedSize; // varies
|
||||
uint16_t ConfiguredMemorySpeed; // varies
|
||||
|
||||
// 2.8+
|
||||
uint16_t MinimumVoltage; // varies
|
||||
uint16_t MaximumVoltage; // varies
|
||||
uint16_t ConfiguredVoltage; // varies
|
||||
|
||||
// 3.2+
|
||||
uint8_t MemoryTechnology; // varies
|
||||
uint16_t MemoryOperatingMode; // bit field
|
||||
uint8_t FirmwareVersion; // string
|
||||
uint16_t ModuleManufacturerID; // varies
|
||||
uint16_t ModuleProductID; // varies
|
||||
uint16_t MemorySubsystemControllerManufacturerID; // vaies
|
||||
uint16_t MemorySubsystemControllerProductID; // varies
|
||||
uint64_t NonVolatileSize; // varies
|
||||
uint64_t VolatileSize; // varies
|
||||
uint64_t CacheSize; // varies
|
||||
uint64_t LogicalSize; // varies
|
||||
|
||||
// 3.3+
|
||||
uint32_t ExtendedSpeed; // varies
|
||||
uint32_t ExtendedConfiguredSpeed; // varies
|
||||
|
||||
// 3.7+
|
||||
uint16_t Pmic0ManufacturerID; // varies
|
||||
uint16_t Pmic0RevisionNumber; // varies
|
||||
uint16_t RcdManufacturerID; // varies
|
||||
uint16_t RcdRevisionNumber; // varies
|
||||
} FFSmbiosMemoryDevice;
|
||||
|
||||
static_assert(offsetof(FFSmbiosMemoryDevice, RcdRevisionNumber) == 0x62,
|
||||
"FFSmbiosMemoryDevice: Wrong struct alignment");
|
||||
|
||||
const char* ffDetectPhysicalMemoryFromSmbios(FFPhysicalMemoryOptions* options, FFlist* result) {
|
||||
const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
|
||||
if (!smbiosTable) {
|
||||
FF_DEBUG("Failed to get SMBIOS data");
|
||||
return "Failed to get SMBIOS data";
|
||||
}
|
||||
|
||||
const FFSmbiosMemoryDevice* data = (const FFSmbiosMemoryDevice*) (*smbiosTable)[FF_SMBIOS_TYPE_MEMORY_DEVICE];
|
||||
if (!data) {
|
||||
FF_DEBUG("Memory device is not found in SMBIOS data");
|
||||
return "Memory device is not found in SMBIOS data";
|
||||
}
|
||||
|
||||
const FFSmbiosMemoryDevice* endOfTable = (const FFSmbiosMemoryDevice*) (*smbiosTable)[FF_SMBIOS_TYPE_END_OF_TABLE];
|
||||
for (; data != endOfTable; data = (const FFSmbiosMemoryDevice*) ffSmbiosNextEntry(&data->Header)) {
|
||||
if (data->Header.Type != FF_SMBIOS_TYPE_MEMORY_DEVICE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* strings = (const char*) data + data->Header.Length;
|
||||
uint16_t size = FF_READ_LE(data->Size);
|
||||
bool installed = size != 0;
|
||||
|
||||
if (!installed && !options->showEmptySlots) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FFPhysicalMemoryResult* device = FF_LIST_ADD(FFPhysicalMemoryResult, *result);
|
||||
*device = (FFPhysicalMemoryResult) {
|
||||
.installed = installed,
|
||||
.type = ffStrbufCreate(),
|
||||
.formFactor = ffStrbufCreate(),
|
||||
.locator = ffStrbufCreate(),
|
||||
.partNumber = ffStrbufCreate(),
|
||||
.vendor = ffStrbufCreate(),
|
||||
.serial = ffStrbufCreate(),
|
||||
};
|
||||
|
||||
uint16_t totalWidth = FF_READ_LE(data->TotalWidth);
|
||||
uint16_t dataWidth = FF_READ_LE(data->DataWidth);
|
||||
if (installed && totalWidth != 0xFFFF && dataWidth != 0xFFFF) {
|
||||
device->ecc = totalWidth > dataWidth;
|
||||
}
|
||||
|
||||
if (installed && size != 0xFFFF) {
|
||||
if (size == 0x7FFF) {
|
||||
device->size = (FF_READ_LE(data->ExtendedSize) & ~(1ULL << 31)) * 1024ULL * 1024ULL;
|
||||
} else if (size & (1 << 15)) {
|
||||
// in kB
|
||||
device->size = (size & ~(1ULL << 15)) * 1024ULL;
|
||||
} else {
|
||||
// in MB
|
||||
device->size = size * 1024ULL * 1024ULL;
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/fastfetch-cli/fastfetch/issues/1051#issuecomment-2206687345
|
||||
const char* lbank = ffSmbiosLocateString(strings, data->BankLocator);
|
||||
const char* ldevice = ffSmbiosLocateString(strings, data->DeviceLocator);
|
||||
if (lbank) {
|
||||
ffStrbufSetS(&device->locator, lbank);
|
||||
}
|
||||
if (lbank && ldevice) {
|
||||
ffStrbufAppendC(&device->locator, '/');
|
||||
}
|
||||
if (ldevice) {
|
||||
ffStrbufAppendS(&device->locator, ldevice);
|
||||
}
|
||||
|
||||
const char* formFactorNames[] = {
|
||||
nullptr, // 0x00 (Placeholder for indexing)
|
||||
"Other", // 0x01
|
||||
"Unknown", // 0x02
|
||||
"SIMM", // 0x03
|
||||
"SIP", // 0x04
|
||||
"Chip", // 0x05
|
||||
"DIP", // 0x06
|
||||
"ZIP", // 0x07
|
||||
"Proprietary Card", // 0x08
|
||||
"DIMM", // 0x09
|
||||
"TSOP", // 0x0A
|
||||
"Row of chips", // 0x0B
|
||||
"RIMM", // 0x0C
|
||||
"SODIMM", // 0x0D
|
||||
"SRIMM", // 0x0E
|
||||
"FBDIMM", // 0x0F
|
||||
"Die", // 0x10
|
||||
"CAMM", // 0x11
|
||||
"CUDIMM", // 0x12
|
||||
"CSODIMM", // 0x13
|
||||
};
|
||||
if (data->FormFactor > 0 && data->FormFactor < ARRAY_SIZE(formFactorNames)) {
|
||||
ffStrbufSetS(&device->formFactor, formFactorNames[data->FormFactor]);
|
||||
} else {
|
||||
ffStrbufSetF(&device->formFactor, "Unknown (%d)", (int) data->FormFactor);
|
||||
}
|
||||
|
||||
const char* memoryTypeNames[] = {
|
||||
nullptr, // 0x00 (Placeholder for indexing)
|
||||
"Other", // 0x01
|
||||
"Unknown", // 0x02
|
||||
"DRAM", // 0x03
|
||||
"EDRAM", // 0x04
|
||||
"VRAM", // 0x05
|
||||
"SRAM", // 0x06
|
||||
"RAM", // 0x07
|
||||
"ROM", // 0x08
|
||||
"FLASH", // 0x09
|
||||
"EEPROM", // 0x0A
|
||||
"FEPROM", // 0x0B
|
||||
"EPROM", // 0x0C
|
||||
"CDRAM", // 0x0D
|
||||
"3DRAM", // 0x0E
|
||||
"SDRAM", // 0x0F
|
||||
"SGRAM", // 0x10
|
||||
"RDRAM", // 0x11
|
||||
"DDR", // 0x12
|
||||
"DDR2", // 0x13
|
||||
"DDR2 FB-DIMM", // 0x14
|
||||
"Reserved", // 0x15
|
||||
"Reserved", // 0x16
|
||||
"Reserved", // 0x17
|
||||
"DDR3", // 0x18
|
||||
"FBD2", // 0x19
|
||||
"DDR4", // 0x1A
|
||||
"LPDDR", // 0x1B
|
||||
"LPDDR2", // 0x1C
|
||||
"LPDDR3", // 0x1D
|
||||
"LPDDR4", // 0x1E
|
||||
"Logical non-volatile device", // 0x1F
|
||||
"HBM", // 0x20
|
||||
"HBM2", // 0x21
|
||||
"DDR5", // 0x22
|
||||
"LPDDR5", // 0x23
|
||||
"HBM3", // 0x24
|
||||
"MRDIMM", // 0x25
|
||||
};
|
||||
if (!installed) {
|
||||
ffStrbufSetStatic(&device->type, "Empty");
|
||||
} else if (data->MemoryType > 0 && data->MemoryType < ARRAY_SIZE(memoryTypeNames)) {
|
||||
ffStrbufSetStatic(&device->type, memoryTypeNames[data->MemoryType]);
|
||||
} else {
|
||||
ffStrbufSetF(&device->type, "Unknown (%d)", (int) data->MemoryType);
|
||||
}
|
||||
|
||||
if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, Speed)) // 2.3+
|
||||
{
|
||||
uint16_t speed = FF_READ_LE(data->Speed);
|
||||
if (speed) {
|
||||
device->maxSpeed = speed == 0xFFFF ? FF_READ_LE(data->ExtendedSpeed) : speed;
|
||||
}
|
||||
|
||||
ffStrbufSetStatic(&device->vendor, ffSmbiosLocateString(strings, data->Manufacturer));
|
||||
ffCleanUpSmbiosValue(&device->vendor);
|
||||
FFPhysicalMemoryUpdateVendorString(device);
|
||||
|
||||
ffStrbufSetStatic(&device->serial, ffSmbiosLocateString(strings, data->SerialNumber));
|
||||
ffCleanUpSmbiosValue(&device->serial);
|
||||
|
||||
ffStrbufSetStatic(&device->partNumber, ffSmbiosLocateString(strings, data->PartNumber));
|
||||
ffCleanUpSmbiosValue(&device->partNumber);
|
||||
}
|
||||
|
||||
if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, ConfiguredMemorySpeed)) // 2.7+
|
||||
{
|
||||
uint16_t configuredSpeed = FF_READ_LE(data->ConfiguredMemorySpeed);
|
||||
if (configuredSpeed) {
|
||||
device->runningSpeed = configuredSpeed == 0xFFFF
|
||||
? FF_READ_LE(data->ExtendedConfiguredSpeed)
|
||||
: configuredSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// On Linux ffDetectPhysicalMemory is provided by physicalmemory_linux.c, which prefers udev
|
||||
// data and only falls back to the SMBIOS parser above. Every other platform uses it directly.
|
||||
#ifndef __linux__
|
||||
const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* result) {
|
||||
return ffDetectPhysicalMemoryFromSmbios(options, result);
|
||||
}
|
||||
#endif
|
||||
Reference in new issue
Block a user