diff --git a/Controllers/KingstonFuryDRAMController/KingstonFuryDRAMController.h b/Controllers/KingstonFuryDRAMController/KingstonFuryDRAMController.h index 5ccb2441..cc2cc9e4 100644 --- a/Controllers/KingstonFuryDRAMController/KingstonFuryDRAMController.h +++ b/Controllers/KingstonFuryDRAMController/KingstonFuryDRAMController.h @@ -22,7 +22,6 @@ #define FURY_BASE_ADDR_DDR4 0x58 #define FURY_BASE_ADDR_DDR5 0x60 #define FURY_DELAY std::chrono::milliseconds(10) -#define FURY_MAX_SLOTS 8 #define FURY_LEDS_PER_DIMM_DDR4 10 #define FURY_LEDS_PER_DIMM_DDR5 12 #define FURY_MAX_MODE_COLORS 10 diff --git a/Controllers/KingstonFuryDRAMController/KingstonFuryDRAMControllerDetect.cpp b/Controllers/KingstonFuryDRAMController/KingstonFuryDRAMControllerDetect.cpp index 63f1f11d..357fa60d 100644 --- a/Controllers/KingstonFuryDRAMController/KingstonFuryDRAMControllerDetect.cpp +++ b/Controllers/KingstonFuryDRAMController/KingstonFuryDRAMControllerDetect.cpp @@ -27,132 +27,6 @@ typedef enum RESULT_ERROR = 2 } TestResult; -typedef enum -{ - SPD_RESERVED = 0, - SPD_FPM_DRAM = 1, - SPD_EDO = 2, - SPD_NIBBLE = 3, - SPD_SDR_SDRAM = 4, - SPD_MUX_ROM = 5, - SPD_DDR_SGRAM = 6, - SPD_DDR_SDRAM = 7, - SPD_DDR2_SDRAM = 8, - SPD_FB_DIMM = 9, - SPD_FB_PROBE = 10, - SPD_DDR3_SDRAM = 11, - SPD_DDR4_SDRAM = 12, - SPD_RESERVED2 = 13, - SPD_DDR4E_SDRAM = 14, - SPD_LPDDR3_SDRAM = 15, - SPD_LPDDR4_SDRAM = 16, - SPD_LPDDR4X_SDRAM = 17, - SPD_DDR5_SDRAM = 18, - SPD_LPDDR5_SDRAM = 19 -} SPDMemoryType; - -// DDR5 SPD hub detection -TestResult TestForSPDHub(i2c_smbus_interface *bus, int spd_address, int &mem_type) -{ - int ddr5Magic = bus->i2c_smbus_read_byte_data(spd_address, 0x00); - int ddr5Sensor = bus->i2c_smbus_read_byte_data(spd_address, 0x01); - std::this_thread::sleep_for(1ms); - - if(ddr5Magic < 0 || ddr5Sensor < 0) - { - return RESULT_ERROR; - } - - if(ddr5Magic == 0x51 && (ddr5Sensor & 0xEF) == 0x08) - { - // These values are invalid for any other memory type - mem_type = SPD_DDR5_SDRAM; - return RESULT_PASS; - } - return RESULT_FAIL; -} - -TestResult TestSPDForKingston(i2c_smbus_interface *bus, SPDMemoryType &fury_type, std::vector &slots) -{ - int mem_type = -1; - - for(int slot_index = 0; slot_index < FURY_MAX_SLOTS; slot_index++) - { - int spd_address = 0x50 + slot_index; - - TestResult result = TestForSPDHub(bus, spd_address, mem_type); - if(result == RESULT_ERROR) - { - LOG_TRACE("[%s] SPD Hub check [0x%02X] failed. No device.", - FURY_CONTROLLER_NAME, spd_address); - continue; - } - - if(mem_type != SPD_DDR4_SDRAM && mem_type != SPD_DDR5_SDRAM) - { - // Get memory type from SPD for DDR4 or older - bus->i2c_smbus_write_byte_data(0x36, 0x00, 0xFF); - std::this_thread::sleep_for(1ms); - mem_type = bus->i2c_smbus_read_byte_data(spd_address, 0x02); - } - - if(mem_type != SPD_DDR4_SDRAM && mem_type != SPD_DDR5_SDRAM) - { - LOG_TRACE("[%s] SPD check [0x%02X] - wrong memory type => %02X", - FURY_CONTROLLER_NAME, spd_address, mem_type); - continue; - } - fury_type = static_cast(mem_type); - - LOG_TRACE("[%s] SPD check [0x%02X]: memory type => %02X, ", - FURY_CONTROLLER_NAME, spd_address, mem_type); - // Switch SPD page - if(mem_type == SPD_DDR4_SDRAM) - { - bus->i2c_smbus_write_byte_data(0x37, 0x00, 0xFF); - } - else - { - bus->i2c_smbus_write_byte_data(spd_address, 0x0B, 4); - } - std::this_thread::sleep_for(1ms); - - // Get ManufacturerID - int vendorHi, vendorLo; - if(mem_type == SPD_DDR4_SDRAM) - { - vendorHi = bus->i2c_smbus_read_byte_data(spd_address, 0x40); - vendorLo = bus->i2c_smbus_read_byte_data(spd_address, 0x41); - } - else - { - vendorHi = bus->i2c_smbus_read_byte_data(spd_address, 0x80); - vendorLo = bus->i2c_smbus_read_byte_data(spd_address, 0x81); - } - std::this_thread::sleep_for(1ms); - - LOG_DEBUG("[%s] SPD check [0x%02X]: vendorHi => %02X, vendorLo => %02X, ", - FURY_CONTROLLER_NAME, spd_address, vendorHi, vendorLo); - - // Switch SPD page back to 0 - if(mem_type == SPD_DDR4_SDRAM) - { - bus->i2c_smbus_write_byte_data(0x36, 0x00, 0xFF); - } - else - { - bus->i2c_smbus_write_byte_data(spd_address, 0x0B, 0); - } - std::this_thread::sleep_for(1ms); - - if(vendorHi == 0x01 && vendorLo == 0x98) - { - slots.push_back(slot_index); - } - } - return RESULT_PASS; -} - bool TestDDR4Models(char code) { return (code == FURY_MODEL_BEAST_WHITE_DDR4 || @@ -173,10 +47,15 @@ TestResult TestForFurySignature(i2c_smbus_interface *bus, unsigned int slot_addr char test_str[] = "FURY"; int res; + LOG_DEBUG("[%s] looking at 0x%02X", + FURY_CONTROLLER_NAME, slot_addr); + // Start transaction res = bus->i2c_smbus_write_byte_data(slot_addr, FURY_REG_APPLY, FURY_BEGIN_TRNSFER); if(res < 0) { + LOG_DEBUG("[%s] DIMM not present at 0x%02X", + FURY_CONTROLLER_NAME, slot_addr); return RESULT_ERROR; } @@ -240,6 +119,41 @@ TestResult TestForFurySignature(i2c_smbus_interface *bus, unsigned int slot_addr return passed ? RESULT_PASS : RESULT_FAIL; } +void DetectKingstonFuryDRAMControllers(i2c_smbus_interface* bus, std::vector &slots, + uint8_t fury_base_addr, bool (*modelChecker)(char), std::vector &fury_slots) +{ + // Are these the Kingston Fury DRAMs + for(SPDWrapper *slot : slots) + { + TestResult result; + int retries = 0; + + result = RESULT_ERROR; + while(retries < 3 && result == RESULT_ERROR) + { + result = TestForFurySignature(bus, fury_base_addr + slot->index(), modelChecker); + if(result == RESULT_PASS) + { + break; + } + if(result == RESULT_ERROR) + { + // I/O error - wait for a bit and retry + retries++; + std::this_thread::sleep_for(FURY_DELAY); + } + } + + // RAM module successfully detected in the slot 'slot_index' + if(result == RESULT_PASS) + { + LOG_DEBUG("[%s] detected at slot index %d", + FURY_CONTROLLER_NAME, slot->index()); + fury_slots.push_back(slot->index()); + } + } +} + /******************************************************************************************\ * * * DetectKingstonFuryDRAMControllers * @@ -248,82 +162,39 @@ TestResult TestForFurySignature(i2c_smbus_interface *bus, unsigned int slot_addr * * \******************************************************************************************/ -void DetectKingstonFuryDRAMControllers(std::vector &busses) +void DetectKingstonFuryDDR4Controllers(i2c_smbus_interface* bus, std::vector &slots) { - SPDMemoryType fury_type = SPD_RESERVED; + std::vector fury_slots; - for(unsigned int bus = 0; bus < busses.size(); bus++) + DetectKingstonFuryDRAMControllers(bus, slots, FURY_BASE_ADDR_DDR4, TestDDR4Models, fury_slots); + + if(!fury_slots.empty()) { - IF_DRAM_SMBUS(busses[bus]->pci_vendor, busses[bus]->pci_device) - { - TestResult result; - std::vector occupied_slots, fury_slots; - int fury_base_addr; - bool (*modelChecker)(char); - - // Do we have Kingston DRAMs? - result = TestSPDForKingston(busses[bus], fury_type, occupied_slots); - if(result != RESULT_PASS || (fury_type != SPD_DDR4_SDRAM && fury_type != SPD_DDR5_SDRAM)) - { - continue; - } - - if(fury_type == SPD_DDR4_SDRAM) - { - fury_base_addr = FURY_BASE_ADDR_DDR4; - modelChecker = TestDDR4Models; - } - else - { - fury_base_addr = FURY_BASE_ADDR_DDR5; - modelChecker = TestDDR5Models; - } - - // Are these the Kingston Fury DRAMs - for(int slot_index : occupied_slots) - { - int retries = 0; - result = RESULT_ERROR; - - while(retries < 3 && result == RESULT_ERROR) - { - result = TestForFurySignature(busses[bus], - fury_base_addr + slot_index, modelChecker); - if(result == RESULT_PASS) - { - break; - } - if(result == RESULT_ERROR) - { - // I/O error - wait for a bit and retry - retries++; - std::this_thread::sleep_for(100ms); - } - } - - // RAM module successfully detected in the slot 'slot_index' - if(result == RESULT_PASS) - { - LOG_DEBUG("[%s] detected at slot index %d", - FURY_CONTROLLER_NAME, slot_index); - fury_slots.push_back(slot_index); - } - } - - if(!fury_slots.empty()) - { - unsigned char base_addr = - (fury_type == SPD_DDR4_SDRAM) ? FURY_BASE_ADDR_DDR4 : FURY_BASE_ADDR_DDR5; - KingstonFuryDRAMController* controller = - new KingstonFuryDRAMController(busses[bus], base_addr, fury_slots); - RGBController_KingstonFuryDRAM* rgb_controller = - new RGBController_KingstonFuryDRAM(controller); - rgb_controller->name = - (fury_type == SPD_DDR4_SDRAM) ? "Kingston Fury DDR4 RGB" : "Kingston Fury DDR5 RGB"; - ResourceManager::get()->RegisterRGBController(rgb_controller); - } - } + KingstonFuryDRAMController* controller = + new KingstonFuryDRAMController(bus, FURY_BASE_ADDR_DDR4, fury_slots); + RGBController_KingstonFuryDRAM* rgb_controller = + new RGBController_KingstonFuryDRAM(controller); + rgb_controller->name = "Kingston Fury DDR4 RGB"; + ResourceManager::get()->RegisterRGBController(rgb_controller); } } -REGISTER_I2C_DETECTOR("Kingston Fury DDR4/5 DRAM", DetectKingstonFuryDRAMControllers); +void DetectKingstonFuryDDR5Controllers(i2c_smbus_interface* bus, std::vector &slots) +{ + std::vector fury_slots; + + DetectKingstonFuryDRAMControllers(bus, slots, FURY_BASE_ADDR_DDR5, TestDDR5Models, fury_slots); + + if(!fury_slots.empty()) + { + KingstonFuryDRAMController* controller = + new KingstonFuryDRAMController(bus, FURY_BASE_ADDR_DDR5, fury_slots); + RGBController_KingstonFuryDRAM* rgb_controller = + new RGBController_KingstonFuryDRAM(controller); + rgb_controller->name = "Kingston Fury DDR5 RGB"; + ResourceManager::get()->RegisterRGBController(rgb_controller); + } +} + +REGISTER_I2C_DIMM_DETECTOR("Kingston Fury DDR4 DRAM", DetectKingstonFuryDDR4Controllers, JEDEC_KINGSTON, SPD_DDR4_SDRAM); +REGISTER_I2C_DIMM_DETECTOR("Kingston Fury DDR5 DRAM", DetectKingstonFuryDDR5Controllers, JEDEC_KINGSTON, SPD_DDR5_SDRAM); diff --git a/Detector.h b/Detector.h index 7e1226d1..09bf7211 100644 --- a/Detector.h +++ b/Detector.h @@ -13,6 +13,7 @@ #define REGISTER_DETECTOR(name, func) static DeviceDetector device_detector_obj_##func(name, func) #define REGISTER_I2C_DETECTOR(name, func) static I2CDeviceDetector device_detector_obj_##func(name, func) +#define REGISTER_I2C_DIMM_DETECTOR(name, func, jedec_id, dimm_type) static I2CDIMMDeviceDetector device_detector_obj_##func(name, func, jedec_id, dimm_type) #define REGISTER_I2C_PCI_DETECTOR(name, func, ven, dev, subven, subdev, addr) static I2CPCIDeviceDetector device_detector_obj_##ven##dev##subven##subdev##addr##func(name, func, ven, dev, subven, subdev, addr) #define REGISTER_I2C_BUS_DETECTOR(func) static I2CBusDetector device_detector_obj_##func(func) #define REGISTER_HID_DETECTOR(name, func, vid, pid) static HIDDeviceDetector device_detector_obj_##vid##pid(name, func, vid, pid, HID_INTERFACE_ANY, HID_USAGE_PAGE_ANY, HID_USAGE_ANY) @@ -29,6 +30,7 @@ #define REGISTER_PRE_DETECTION_HOOK(func) static PreDetectionHook device_detector_obj_##func(func) #define REGISTER_DYNAMIC_I2C_DETECTOR(name, func) I2CDeviceDetector device_detector_obj_##func(name, func) +#define REGISTER_DYNAMIC_I2C_DIMM_DETECTOR(name, func, jedec_id, dimm_type) I2CDIMMDeviceDetector device_detector_obj_##func(name, func, jedec_id, dimm_type) #define REGISTER_DYNAMIC_I2C_PCI_DETECTOR(name, func, ven, dev, subven, subdev, addr) I2CPCIDeviceDetector device_detector_obj_##ven##dev##subven##subdev##addr##func(name, func, ven, dev, subven, subdev, addr) #define REGISTER_DYNAMIC_I2C_BUS_DETECTOR(func) I2CBusDetector device_detector_obj_##func(func) #define REGISTER_DYNAMIC_HID_DETECTOR(name, func, vid, pid) HIDDeviceDetector device_detector_obj_##vid##pid(name, func, vid, pid, HID_INTERFACE_ANY, HID_USAGE_PAGE_ANY, HID_USAGE_ANY) diff --git a/DeviceDetector.h b/DeviceDetector.h index 56886f5b..66d3bdac 100644 --- a/DeviceDetector.h +++ b/DeviceDetector.h @@ -33,6 +33,15 @@ public: } }; +class I2CDIMMDeviceDetector +{ +public: + I2CDIMMDeviceDetector(std::string name, I2CDIMMDeviceDetectorFunction detector, uint16_t jedec_id, uint8_t dimm_type) + { + ResourceManager::get()->RegisterI2CDIMMDeviceDetector(name, detector, jedec_id, dimm_type); + } +}; + class I2CPCIDeviceDetector { public: diff --git a/OpenRGB.pro b/OpenRGB.pro index e2004c87..3d938d61 100644 --- a/OpenRGB.pro +++ b/OpenRGB.pro @@ -208,6 +208,7 @@ SOURCES += PluginManager.cpp \ ProfileManager.cpp \ ResourceManager.cpp \ + SPDAccessor.cpp \ SettingsManager.cpp \ i2c_smbus/i2c_smbus.cpp \ i2c_tools/i2c_tools.cpp \ diff --git a/ResourceManager.cpp b/ResourceManager.cpp index 6ff4dafc..cf9c8263 100644 --- a/ResourceManager.cpp +++ b/ResourceManager.cpp @@ -20,6 +20,7 @@ #include #include #include "cli.h" +#include "pci_ids/pci_ids.h" #include "ResourceManager.h" #include "ProfileManager.h" #include "LogManager.h" @@ -271,6 +272,18 @@ void ResourceManager::RegisterI2CDeviceDetector(std::string name, I2CDeviceDetec i2c_device_detectors.push_back(detector); } +void ResourceManager::RegisterI2CDIMMDeviceDetector(std::string name, I2CDIMMDeviceDetectorFunction detector, uint16_t jedec_id, uint8_t dimm_type) +{ + I2CDIMMDeviceDetectorBlock block; + + block.name = name; + block.function = detector; + block.jedec_id = jedec_id; + block.dimm_type = dimm_type; + + i2c_dimm_device_detectors.push_back(block); +} + void ResourceManager::RegisterI2CPCIDeviceDetector(std::string name, I2CPCIDeviceDetectorFunction detector, uint16_t ven_id, uint16_t dev_id, uint16_t subven_id, uint16_t subdev_id, uint8_t i2c_addr) { I2CPCIDeviceDetectorBlock block; @@ -1009,7 +1022,7 @@ void ResourceManager::DetectDevicesThreadFunction() current_hid_device = current_hid_device->next; } - percent_denominator = (float)(i2c_device_detectors.size() + i2c_pci_device_detectors.size() + device_detectors.size()) + (float)hid_device_count; + percent_denominator = (float)(i2c_device_detectors.size() + i2c_dimm_device_detectors.size() + i2c_pci_device_detectors.size() + device_detectors.size()) + (float)hid_device_count; /*-------------------------------------------------*\ | Start at 0% detection progress | @@ -1105,6 +1118,70 @@ void ResourceManager::DetectDevicesThreadFunction() detection_percent = (unsigned int)(percent * 100.0f); } + /*-------------------------------------------------*\ + | Detect i2c DIMM modules | + \*-------------------------------------------------*/ + LOG_INFO("------------------------------------------------------"); + LOG_INFO("| Detecting I2C DIMM modules |"); + LOG_INFO("------------------------------------------------------"); + for(unsigned int bus = 0; bus < busses.size(); bus++) + { + IF_DRAM_SMBUS(busses[bus]->pci_vendor, busses[bus]->pci_device) + { + std::vector slots; + SPDMemoryType dimm_type = SPD_RESERVED; + + for(uint8_t spd_addr = 0x50; spd_addr < 0x58; spd_addr++) + { + SPDDetector spd(busses[bus], spd_addr, dimm_type); + if(spd.is_valid()) + { + SPDWrapper accessor(spd); + dimm_type = spd.memory_type(); + LOG_INFO("Detected occupied slot %d, bus %d, type %s", spd_addr - 0x50 + 1, bus, spd_memory_type_name[dimm_type]); + LOG_DEBUG("Jedec ID: 0x%04x", accessor.jedec_id()); + slots.push_back(accessor); + } + } + + for(unsigned int i2c_detector_idx = 0; i2c_detector_idx < i2c_dimm_device_detectors.size() && detection_is_required.load(); i2c_detector_idx++) + { + if(i2c_dimm_device_detectors[i2c_detector_idx].dimm_type == dimm_type && + is_jedec_in_slots(slots, i2c_dimm_device_detectors[i2c_detector_idx].jedec_id)) + { + detection_string = i2c_dimm_device_detectors[i2c_detector_idx].name.c_str(); + + /*-------------------------------------------------*\ + | Check if this detector is enabled | + \*-------------------------------------------------*/ + bool this_device_enabled = true; + if(detector_settings.contains("detectors") && detector_settings["detectors"].contains(detection_string)) + { + this_device_enabled = detector_settings["detectors"][detection_string]; + } + + LOG_DEBUG("[%s] is %s", detection_string, ((this_device_enabled == true) ? "enabled" : "disabled")); + if(this_device_enabled) + { + DetectionProgressChanged(); + + std::vector matching_slots = slots_with_jedec(slots, i2c_dimm_device_detectors[i2c_detector_idx].jedec_id); + i2c_dimm_device_detectors[i2c_detector_idx].function(busses[bus], matching_slots); + } + + LOG_TRACE("[%s] detection end", detection_string); + } + + /*-------------------------------------------------*\ + | Update detection percent | + \*-------------------------------------------------*/ + percent = ((float)i2c_detector_idx + 1.0f) / percent_denominator; + + detection_percent = (unsigned int)(percent * 100.0f); + } + } + } + /*-------------------------------------------------*\ | Detect i2c PCI devices | \*-------------------------------------------------*/ diff --git a/ResourceManager.h b/ResourceManager.h index 56e69c79..feeb2677 100644 --- a/ResourceManager.h +++ b/ResourceManager.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include "SPDAccessor.h" #include "hidapi_wrapper.h" #include "i2c_smbus.h" #include "filesystem.h" @@ -38,6 +40,7 @@ class SettingsManager; typedef std::function I2CBusDetectorFunction; typedef std::function DeviceDetectorFunction; typedef std::function&)> I2CDeviceDetectorFunction; +typedef std::function&)> I2CDIMMDeviceDetectorFunction; typedef std::function I2CPCIDeviceDetectorFunction; typedef std::function HIDDeviceDetectorFunction; typedef std::function HIDWrappedDeviceDetectorFunction; @@ -80,6 +83,14 @@ typedef struct uint8_t i2c_addr; } I2CPCIDeviceDetectorBlock; +typedef struct +{ + std::string name; + I2CDIMMDeviceDetectorFunction function; + uint16_t jedec_id; + uint8_t dimm_type; +} I2CDIMMDeviceDetectorBlock; + typedef void (*DeviceListChangeCallback)(void *); typedef void (*DetectionProgressCallback)(void *); typedef void (*DetectionStartCallback)(void *); @@ -144,6 +155,7 @@ public: void RegisterI2CBusDetector (I2CBusDetectorFunction detector); void RegisterDeviceDetector (std::string name, DeviceDetectorFunction detector); void RegisterI2CDeviceDetector (std::string name, I2CDeviceDetectorFunction detector); + void RegisterI2CDIMMDeviceDetector (std::string name, I2CDIMMDeviceDetectorFunction detector, uint16_t jedec_id, uint8_t dimm_type); void RegisterI2CPCIDeviceDetector (std::string name, I2CPCIDeviceDetectorFunction detector, uint16_t ven_id, uint16_t dev_id, uint16_t subven_id, uint16_t subdev_id, uint8_t i2c_addr); void RegisterHIDDeviceDetector (std::string name, HIDDeviceDetectorFunction detector, @@ -289,6 +301,7 @@ private: std::vector i2c_bus_detectors; std::vector i2c_device_detectors; std::vector i2c_device_detector_strings; + std::vector i2c_dimm_device_detectors; std::vector i2c_pci_device_detectors; std::vector hid_device_detectors; std::vector hid_wrapped_device_detectors; diff --git a/SPDAccessor.cpp b/SPDAccessor.cpp new file mode 100644 index 00000000..400a1f10 --- /dev/null +++ b/SPDAccessor.cpp @@ -0,0 +1,519 @@ +/*---------------------------------------------------------*\ +| SPDAccessor.cpp | +| | +| Access to SPD information on various DIMMs | +| | +| Milan Cermak (krysmanta) 09 Nov 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include "LogManager.h" +#include "SPDAccessor.h" +#include "filesystem.h" + +using namespace std::chrono_literals; + +const char *spd_memory_type_name[] = +{ + "Reserved", + "FPM", + "EDO", + "Nibble", + "SDR", + "Multiplex ROM", + "DDR", + "DDR", + "DDR2", + "FB", + "FB Probe", + "DDR3", + "DDR4", + "Reserved", + "DDR4e", + "LPDDR3", + "LPDDR4", + "LPDDR4X", + "DDR5", + "LPDDR5" +}; + +SPDDetector::SPDDetector(i2c_smbus_interface *bus, uint8_t address, SPDMemoryType mem_type = SPD_RESERVED) + : bus(bus), address(address), mem_type(mem_type), valid(false) +{ + detect_memory_type(); +} + +bool SPDDetector::is_valid() const +{ + return valid; +} + +SPDMemoryType SPDDetector::memory_type() const +{ + return mem_type; +} + +void SPDDetector::detect_memory_type() +{ + SPDAccessor *accessor; + + LOG_DEBUG("Probing DRAM on address 0x%02x", address); + +#ifdef __linux__ + if(EE1004Accessor::isAvailable(bus, address)) + { + accessor = new EE1004Accessor(bus, address); + } + else if(SPD5118Accessor::isAvailable(bus, address)) + { + accessor = new SPD5118Accessor(bus, address); + } + else +#endif + if((mem_type == SPD_RESERVED || mem_type == SPD_DDR4_SDRAM || mem_type == SPD_DDR4E_SDRAM || + mem_type == SPD_LPDDR4_SDRAM || mem_type == SPD_LPDDR4X_SDRAM) && + DDR4DirectAccessor::isAvailable(bus, address)) + { + accessor = new DDR4DirectAccessor(bus, address); + } + else if((mem_type == SPD_RESERVED || mem_type == SPD_DDR5_SDRAM || mem_type == SPD_LPDDR5_SDRAM) && + DDR5DirectAccessor::isAvailable(bus, address)) + { + accessor = new DDR5DirectAccessor(bus, address); + } + else if(mem_type == SPD_RESERVED) + { + // Probe the SPD directly - probably an older system than DDR4 + LOG_TRACE("Probing memory type older than DDR4"); + int value = bus->i2c_smbus_read_byte_data(address, 0x02); + if(value < 0) + { + valid = false; + } + else + { + mem_type = (SPDMemoryType) value; + valid = true; + } + return; + } + else + { + valid = false; + return; + } + + valid = true; + mem_type = accessor->memory_type(); + delete accessor; +} + +uint8_t SPDDetector::spd_address() const +{ + return this->address; +} + +i2c_smbus_interface *SPDDetector::smbus() const +{ + return this->bus; +} + +SPDWrapper::SPDWrapper(const SPDWrapper &wrapper) +{ + this->accessor = wrapper.accessor->copy(); + this->address = wrapper.address; + this->mem_type = wrapper.mem_type; +} + +SPDWrapper::SPDWrapper(const SPDDetector &detector) +{ + this->address = detector.spd_address(); + this->mem_type = detector.memory_type(); + + // Allocate a new accessor + this->accessor = SPDAccessor::for_memory_type(this->mem_type, detector.smbus(), this->address); +} + +SPDWrapper::~SPDWrapper() +{ + delete accessor; +} + +SPDMemoryType SPDWrapper::memory_type() +{ + return mem_type; +} + +int SPDWrapper::index() +{ + return this->address - 0x50; +} + +uint16_t SPDWrapper::jedec_id() +{ + if(accessor == nullptr) + { + return 0x0000; + } + return accessor->jedec_id(); +} + +/*-------------------------------------------------------------------------*\ +| Helper functions for easier collection handling. | +\*-------------------------------------------------------------------------*/ + +bool is_jedec_in_slots(std::vector &slots, uint16_t jedec_id) +{ + for(SPDWrapper &slot : slots) + { + if(slot.jedec_id() == jedec_id) + { + return true; + } + } + return false; +} + +std::vector slots_with_jedec(std::vector &slots, uint16_t jedec_id) +{ + std::vector matching_slots; + + for(SPDWrapper &slot : slots) + { + if(slot.jedec_id() == jedec_id) + { + matching_slots.push_back(&slot); + } + } + + return matching_slots; +} + +/*-------------------------------------------------------------------------*\ +| Internal implementation for specific memory type. | +\*-------------------------------------------------------------------------*/ + +SPDAccessor::SPDAccessor(i2c_smbus_interface *bus, uint8_t spd_addr) +{ + this->bus = bus; + this->address = spd_addr; +} + +SPDAccessor::~SPDAccessor() +{ +} + +SPDAccessor *SPDAccessor::for_memory_type(SPDMemoryType type, i2c_smbus_interface *bus, uint8_t spd_addr) +{ + if(type == SPD_DDR4_SDRAM) + { +#ifdef __linux__ + if(EE1004Accessor::isAvailable(bus, spd_addr)) + { + return new EE1004Accessor(bus, spd_addr); + } +#endif + return new DDR4DirectAccessor(bus, spd_addr); + } + if(type == SPD_DDR5_SDRAM) + { +#ifdef __linux__ + if(SPD5118Accessor::isAvailable(bus, spd_addr)) + { + return new SPD5118Accessor(bus, spd_addr); + } +#endif + return new DDR5DirectAccessor(bus, spd_addr); + } + + return nullptr; +}; + +DDR4Accessor::DDR4Accessor(i2c_smbus_interface *bus, uint8_t spd_addr) + : SPDAccessor(bus, spd_addr) +{ +} + +DDR4Accessor::~DDR4Accessor() +{ +} + +SPDMemoryType DDR4Accessor::memory_type() +{ + return (SPDMemoryType)(this->at(0x02)); +} + +uint16_t DDR4Accessor::jedec_id() +{ + return (this->at(0x140) << 8) + (this->at(0x141) & 0x7f) - 1; +} + +DDR5Accessor::DDR5Accessor(i2c_smbus_interface *bus, uint8_t spd_addr) + : SPDAccessor(bus, spd_addr) +{ +} + +DDR5Accessor::~DDR5Accessor() +{ +} + +SPDMemoryType DDR5Accessor::memory_type() +{ + return (SPDMemoryType)(this->at(0x02)); +} + +uint16_t DDR5Accessor::jedec_id() +{ + return (this->at(0x200) << 8) + (this->at(0x201) & 0x7f) - 1; +} + +DDR4DirectAccessor::DDR4DirectAccessor(i2c_smbus_interface *bus, uint8_t spd_addr) + : DDR4Accessor(bus, spd_addr) +{ +} + +DDR4DirectAccessor::~DDR4DirectAccessor() +{ +} + +bool DDR4DirectAccessor::isAvailable(i2c_smbus_interface *bus, uint8_t spd_addr) +{ + int value = bus->i2c_smbus_write_quick(0x36, 0x00); + if(value < 0) + { + return false; + } + + // Do page switch + bus->i2c_smbus_write_byte_data(0x36, 0x00, 0xFF); + std::this_thread::sleep_for(SPD_IO_DELAY); + + value = bus->i2c_smbus_read_byte_data(spd_addr, 0x00); + return (value == 0x23); +} + +SPDAccessor *DDR4DirectAccessor::copy() +{ + return new DDR4DirectAccessor(bus, address); +} + +uint8_t DDR4DirectAccessor::at(uint16_t addr) +{ + if(addr >= SPD_DDR4_EEPROM_LENGTH) + { + //throw OutOfBoundsError(addr); + return 0xFF; + } + set_page(addr >> SPD_DDR4_EEPROM_PAGE_SHIFT); + uint8_t offset = (uint8_t)(addr & SPD_DDR4_EEPROM_PAGE_MASK); + uint32_t value = bus->i2c_smbus_read_byte_data(address, offset); + std::this_thread::sleep_for(SPD_IO_DELAY); + return (uint8_t)value; +} + +void DDR4DirectAccessor::set_page(uint8_t page) +{ + if(current_page != page) + { + bus->i2c_smbus_write_byte_data(0x36 + page, 0x00, 0xFF); + current_page = page; + std::this_thread::sleep_for(SPD_IO_DELAY); + } +} + +#ifdef __linux__ +const char *EE1004Accessor::SPD_EE1004_PATH = "/sys/bus/i2c/drivers/ee1004/%u-%04x/eeprom"; + +EE1004Accessor::EE1004Accessor(i2c_smbus_interface *bus, uint8_t spd_addr) + : DDR4Accessor(bus, spd_addr), valid(false) +{ +} + +EE1004Accessor::~EE1004Accessor() +{ +} + +bool EE1004Accessor::isAvailable(i2c_smbus_interface *bus, uint8_t spd_addr) +{ + int size = snprintf(nullptr, 0, SPD_EE1004_PATH, bus->port_id, spd_addr); + char *path = new char[size+1]; + snprintf(path, size+1, SPD_EE1004_PATH, bus->port_id, spd_addr); + bool result = std::filesystem::exists(path); + delete[] path; + return result; +} + +SPDAccessor *EE1004Accessor::copy() +{ + EE1004Accessor *access = new EE1004Accessor(bus, address); + memcpy(access->dump, this->dump, sizeof(this->dump)); + access->valid = this->valid; + return access; +} + +uint8_t EE1004Accessor::at(uint16_t addr) +{ + if(!valid) + { + readEeprom(); + } + return dump[addr]; +} + +void EE1004Accessor::readEeprom() +{ + int size = snprintf(nullptr, 0, SPD_EE1004_PATH, bus->port_id, address); + char *filename = new char[size+1]; + snprintf(filename, size+1, SPD_EE1004_PATH, bus->port_id, address); + + std::ifstream eeprom_file(filename, std::ios::in | std::ios::binary); + if(eeprom_file) + { + eeprom_file.read((char*)dump, sizeof(dump)); + eeprom_file.close(); + } + delete[] filename; +} +#endif + +DDR5DirectAccessor::DDR5DirectAccessor(i2c_smbus_interface *bus, uint8_t spd_addr) + : DDR5Accessor(bus, spd_addr) +{ +} + +DDR5DirectAccessor::~DDR5DirectAccessor() +{ +} + +bool DDR5DirectAccessor::isAvailable(i2c_smbus_interface *bus, uint8_t spd_addr) +{ + bool retry = true; + + while(true) + { + int ddr5Magic = bus->i2c_smbus_read_byte_data(spd_addr, 0x00); + int ddr5Sensor = bus->i2c_smbus_read_byte_data(spd_addr, 0x01); + std::this_thread::sleep_for(SPD_IO_DELAY); + + if(ddr5Magic < 0 || ddr5Sensor < 0) + { + break; + } + + LOG_TRACE("SPD Hub Magic: 0x%02x 0x%02x", ddr5Magic, ddr5Sensor); + + if(ddr5Magic == 0x51 && (ddr5Sensor & 0xEF) == 0x08) + { + return true; + } + + int page = bus->i2c_smbus_read_byte_data(spd_addr, SPD_DDR5_MREG_VIRTUAL_PAGE); + std::this_thread::sleep_for(SPD_IO_DELAY); + + LOG_TRACE("SPD Page: 0x%02x", page); + if(page < 0) + { + break; + } + else if(retry && page > 0 && page < (SPD_DDR5_EEPROM_LENGTH >> SPD_DDR5_EEPROM_PAGE_SHIFT)) + { + // This still might be a DDR5 module, just the page is off + bus->i2c_smbus_write_byte_data(spd_addr, SPD_DDR5_MREG_VIRTUAL_PAGE, 0); + std::this_thread::sleep_for(SPD_IO_DELAY); + retry = false; + } + else + { + break; + } + } + return false; +} + +SPDAccessor *DDR5DirectAccessor::copy() +{ + DDR5DirectAccessor *access = new DDR5DirectAccessor(bus, address); + access->current_page = this->current_page; + return access; +} + +uint8_t DDR5DirectAccessor::at(uint16_t addr) +{ + if(addr >= SPD_DDR5_EEPROM_LENGTH) + { + //throw OutOfBoundsError(addr); + return 0xFF; + } + set_page(addr >> SPD_DDR5_EEPROM_PAGE_SHIFT); + uint8_t offset = (uint8_t)(addr & SPD_DDR5_EEPROM_PAGE_MASK) | 0x80; + uint32_t value = bus->i2c_smbus_read_byte_data(address, offset); + std::this_thread::sleep_for(SPD_IO_DELAY); + return (uint8_t)value; +} + +void DDR5DirectAccessor::set_page(uint8_t page) +{ + if(current_page != page) + { + bus->i2c_smbus_write_byte_data(address, SPD_DDR5_MREG_VIRTUAL_PAGE, page); + current_page = page; + std::this_thread::sleep_for(SPD_IO_DELAY); + } +} + +#ifdef __linux__ +const char *SPD5118Accessor::SPD_SPD5118_PATH = "/sys/bus/i2c/drivers/spd5118/%u-%04x/eeprom"; + +SPD5118Accessor::SPD5118Accessor(i2c_smbus_interface *bus, uint8_t spd_addr) + : DDR5Accessor(bus, spd_addr), valid(false) +{ +} + +SPD5118Accessor::~SPD5118Accessor() +{ +} + +bool SPD5118Accessor::isAvailable(i2c_smbus_interface *bus, uint8_t spd_addr) +{ + int size = snprintf(nullptr, 0, SPD_SPD5118_PATH, bus->port_id, spd_addr); + char *path = new char[size+1]; + snprintf(path, size+1, SPD_SPD5118_PATH, bus->port_id, spd_addr); + bool result = std::filesystem::exists(path); + delete[] path; + return result; +} + +SPDAccessor *SPD5118Accessor::copy() +{ + SPD5118Accessor *access = new SPD5118Accessor(bus, address); + memcpy(access->dump, this->dump, sizeof(this->dump)); + access->valid = this->valid; + return access; +} + +uint8_t SPD5118Accessor::at(uint16_t addr) +{ + if(!valid) + { + readEeprom(); + } + return dump[addr]; +} + +void SPD5118Accessor::readEeprom() +{ + int size = snprintf(nullptr, 0, SPD_SPD5118_PATH, bus->port_id, address); + char *filename = new char[size+1]; + snprintf(filename, size+1, SPD_SPD5118_PATH, bus->port_id, address); + + std::ifstream eeprom_file(filename, std::ios::in | std::ios::binary); + if(eeprom_file) + { + eeprom_file.read((char*)dump, sizeof(dump)); + eeprom_file.close(); + } + delete[] filename; +} +#endif diff --git a/SPDAccessor.h b/SPDAccessor.h new file mode 100644 index 00000000..dfb935d1 --- /dev/null +++ b/SPDAccessor.h @@ -0,0 +1,223 @@ +/*---------------------------------------------------------*\ +| SPDAccessor.h | +| | +| Access to SPD information on various DIMMs | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#pragma once + +#include +#include "i2c_smbus.h" + +typedef enum +{ + JEDEC_KINGSTON = 0x0117, + JEDEC_CORSAIR = 0x021d, + JEDEC_ADATA = 0x044a, + JEDEC_GSKILL = 0x044c, + JEDEC_TEAMGROUP = 0x046e, + JEDEC_MUSHKIN = 0x8313, + JEDEC_GIGABYTE = 0x8971, + JEDEC_THERMALTAKE = 0x8a41 +} JedecIdentifier; + +typedef enum +{ + SPD_RESERVED = 0, + SPD_FPM_DRAM = 1, + SPD_EDO = 2, + SPD_NIBBLE = 3, + SPD_SDR_SDRAM = 4, + SPD_MUX_ROM = 5, + SPD_DDR_SGRAM = 6, + SPD_DDR_SDRAM = 7, + SPD_DDR2_SDRAM = 8, + SPD_FB_DIMM = 9, + SPD_FB_PROBE = 10, + SPD_DDR3_SDRAM = 11, + SPD_DDR4_SDRAM = 12, + SPD_RESERVED2 = 13, + SPD_DDR4E_SDRAM = 14, + SPD_LPDDR3_SDRAM = 15, + SPD_LPDDR4_SDRAM = 16, + SPD_LPDDR4X_SDRAM = 17, + SPD_DDR5_SDRAM = 18, + SPD_LPDDR5_SDRAM = 19 +} SPDMemoryType; + +#define SPD_IO_DELAY 1ms + +extern const char *spd_memory_type_name[]; + +class SPDDetector +{ + public: + SPDDetector(i2c_smbus_interface *bus, uint8_t address, SPDMemoryType mem_type); + + bool is_valid() const; + SPDMemoryType memory_type() const; + uint8_t spd_address() const; + i2c_smbus_interface *smbus() const; + + private: + i2c_smbus_interface *bus; + uint8_t address; + SPDMemoryType mem_type; + bool valid; + + void detect_memory_type(); +}; + +class SPDAccessor +{ + public: + SPDAccessor(i2c_smbus_interface *bus, uint8_t address); + virtual ~SPDAccessor(); + + static SPDAccessor *for_memory_type(SPDMemoryType type, i2c_smbus_interface *bus, uint8_t address); + + virtual SPDMemoryType memory_type() = 0; + virtual uint16_t jedec_id() = 0; + + virtual SPDAccessor *copy() = 0; + + virtual uint8_t at(uint16_t addr) = 0; + + protected: + i2c_smbus_interface *bus; + uint8_t address; +}; + +class SPDWrapper +{ + public: + SPDWrapper(const SPDWrapper &wrapper); + SPDWrapper(const SPDDetector &detector); + ~SPDWrapper(); + + SPDMemoryType memory_type(); + int index(); + uint16_t jedec_id(); + + private: + SPDAccessor *accessor; + uint8_t address; + SPDMemoryType mem_type; +}; + +/*-------------------------------------------------------------------------*\ +| Internal implementation for specific memory type. | +\*-------------------------------------------------------------------------*/ + +class DDR4Accessor : public SPDAccessor +{ + public: + DDR4Accessor(i2c_smbus_interface *bus, uint8_t address); + virtual ~DDR4Accessor(); + virtual SPDMemoryType memory_type(); + virtual uint16_t jedec_id(); +}; + +class DDR4DirectAccessor : public DDR4Accessor +{ + public: + DDR4DirectAccessor(i2c_smbus_interface *bus, uint8_t address); + virtual ~DDR4DirectAccessor(); + + static bool isAvailable(i2c_smbus_interface *bus, uint8_t address); + + virtual SPDAccessor *copy(); + virtual uint8_t at(uint16_t addr); + + private: + uint8_t current_page = 0xFF; + static const uint16_t SPD_DDR4_EEPROM_LENGTH = 512; + static const uint8_t SPD_DDR4_EEPROM_PAGE_SHIFT = 8; + static const uint8_t SPD_DDR4_EEPROM_PAGE_MASK = 0xFF; + + void set_page(uint8_t page); +}; + +#ifdef __linux__ +class EE1004Accessor : public DDR4Accessor +{ + public: + EE1004Accessor(i2c_smbus_interface *bus, uint8_t address); + virtual ~EE1004Accessor(); + + static bool isAvailable(i2c_smbus_interface *bus, uint8_t address); + + virtual SPDAccessor *copy(); + virtual uint8_t at(uint16_t addr); + + private: + static const char *SPD_EE1004_PATH; + + uint8_t dump[512]; + bool valid; + + void readEeprom(); +}; +#endif + +class DDR5Accessor : public SPDAccessor +{ + public: + DDR5Accessor(i2c_smbus_interface *bus, uint8_t address); + virtual ~DDR5Accessor(); + virtual SPDMemoryType memory_type(); + virtual uint16_t jedec_id(); +}; + +class DDR5DirectAccessor : public DDR5Accessor +{ + public: + DDR5DirectAccessor(i2c_smbus_interface *bus, uint8_t address); + virtual ~DDR5DirectAccessor(); + + static bool isAvailable(i2c_smbus_interface *bus, uint8_t address); + + virtual SPDAccessor *copy(); + virtual uint8_t at(uint16_t addr); + + private: + uint8_t current_page = 0xFF; + static const uint16_t SPD_DDR5_EEPROM_LENGTH = 2048; + static const uint8_t SPD_DDR5_EEPROM_PAGE_SHIFT = 7; + static const uint8_t SPD_DDR5_EEPROM_PAGE_MASK = 0x7F; + static const uint8_t SPD_DDR5_MREG_VIRTUAL_PAGE = 0x0B; + + void set_page(uint8_t page); +}; + +#ifdef __linux__ +class SPD5118Accessor : public DDR5Accessor +{ + public: + SPD5118Accessor(i2c_smbus_interface *bus, uint8_t address); + virtual ~SPD5118Accessor(); + + static bool isAvailable(i2c_smbus_interface *bus, uint8_t address); + + virtual SPDAccessor *copy(); + virtual uint8_t at(uint16_t addr); + + private: + static const char *SPD_SPD5118_PATH; + + uint8_t dump[2048]; + bool valid; + + void readEeprom(); +}; +#endif + +/*-------------------------------------------------------------------------*\ +| Helper functions for easier collection handling. | +\*-------------------------------------------------------------------------*/ + +bool is_jedec_in_slots(std::vector &slots, uint16_t jedec_id); +std::vector slots_with_jedec(std::vector &slots, uint16_t jedec_id);