From 6843b56f1fac8572ec915e66b719a8251bc7ea3e Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sat, 7 Nov 2026 21:12:50 +0800 Subject: [PATCH] PhysicalMemory (Linux): reads unprivileged DMI physical memory slots from /run/udev/data/+dmi:id --- CMakeLists.txt | 15 +- .../physicalmemory/physicalmemory_linux.c | 433 +++++++++--------- .../physicalmemory/physicalmemory_shared.c | 252 ++++++++++ 3 files changed, 467 insertions(+), 233 deletions(-) create mode 100644 src/detection/physicalmemory/physicalmemory_shared.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 42b98908c..25f5cd644 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/detection/physicalmemory/physicalmemory_linux.c b/src/detection/physicalmemory/physicalmemory_linux.c index 6abd1021b..28bd8d717 100644 --- a/src/detection/physicalmemory/physicalmemory_linux.c +++ b/src/detection/physicalmemory/physicalmemory_linux.c @@ -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...", "", ...). +static void ffUdevSetString(FFstrbuf* strbuf, const char* value) { + ffStrbufSetS(strbuf, value); + ffStrbufDecodeHexEscapeSequences(strbuf); + // Older systemd versions print an empty value, newer ones "". + if (ffStrbufStartsWithS(strbuf, "")) { + 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); } diff --git a/src/detection/physicalmemory/physicalmemory_shared.c b/src/detection/physicalmemory/physicalmemory_shared.c new file mode 100644 index 000000000..a9febcaaa --- /dev/null +++ b/src/detection/physicalmemory/physicalmemory_shared.c @@ -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