PhysicalMemory (Linux): reads unprivileged DMI physical memory slots from /run/udev/data/+dmi:id

This commit is contained in:
Carter Li committed 2026-11-07 21:13:21 +08:00
1 parent c018073384
commit 6843b56f1f
3 files changed
+467 -233

No files matched your search

+8 -7
View File
@@ -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