/*---------------------------------------------------------*\ | 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-or-later | \*---------------------------------------------------------*/ #include "DDR4DirectAccessor.h" #include "DDR5DirectAccessor.h" #include "LogManager.h" #include "SPDAccessor.h" #ifdef __linux__ #include "EE1004Accessor_Linux.h" #include "SPD5118Accessor_Linux.h" #endif using namespace std::chrono_literals; /*---------------------------------------------------------*\ | Sources for define values: | | - https://en.wikipedia.org/wiki/Serial_presence_detect | | - JEDEC DDR5 Serial Presence Detect (SPD): Table of | | contents | \*---------------------------------------------------------*/ #define BASIC_MEMORY_TYPE_ADDR (0x02) #define DDR4_JEDEC_ID_ADDR (0x140) #define DDR4_PART_NR_START (0x149) #define DDR4_PART_NR_END (0x15C) #define DDR4_PART_NR_LEN (DDR4_PART_NR_END - DDR4_PART_NR_START + 1) #define DDR4_MANUF_SPECIFIC_START (0x161) #define DDR4_MANUF_SPECIFIC_END (0x17D) #define DDR4_MANUF_SPECIFIC_LEN (DDR4_MANUF_SPECIFIC_END - DDR4_MANUF_SPECIFIC_START + 1) #define DDR5_JEDEC_ID_ADDR (0x200) #define DDR5_PART_NR_START (0x209) #define DDR5_PART_NR_END (0x226) #define DDR5_PART_NR_LEN (DDR5_PART_NR_END - DDR5_PART_NR_START + 1) #define DDR5_MANUF_SPECIFIC_START (0x22B) #define DDR5_MANUF_SPECIFIC_END (0x27F) #define DDR5_MANUF_SPECIFIC_LEN (DDR5_MANUF_SPECIFIC_END - DDR5_MANUF_SPECIFIC_START + 1) 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" }; 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) { /*-----------------------------------------------------*\ | DDR4 can use DDR4DirectAccessor or EE1004Accessor | \*-----------------------------------------------------*/ 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)); } /*-----------------------------------------------------*\ | DDR5 can use DDR5DirectAccessor or SPD5118Accessor | \*-----------------------------------------------------*/ 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); }; std::string SPDAccessor::read_part_nr_at(uint16_t address, std::size_t len) { std::string part_number; for(uint16_t i = 0; i < (uint16_t)len; i++) { uint16_t spd_addr = address + i; part_number += (char)this->at(spd_addr); } /*-----------------------------------------------------*\ | Find the true end of string and truncate it to that | | point. Part number should be padded with 0x20 | | (space) for DDR4 (Source: Wikipedia). | | It may be padded with 0x00 (Source: real-life tests | | on DDR5 memory). | | Note: To prevent infinite loop, end_of_string_idx | | MUST be signed. | \*-----------------------------------------------------*/ std::size_t end_of_string_idx = part_number.length(); for(; end_of_string_idx > 0; end_of_string_idx--) { if((part_number[end_of_string_idx - 1] != '\0') && (part_number[end_of_string_idx - 1] != ' ')) { break; } } part_number = part_number.substr(0, end_of_string_idx + 1); return part_number; } /*---------------------------------------------------------*\ | Internal implementation for specific memory type. | \*---------------------------------------------------------*/ DDR4Accessor::DDR4Accessor(i2c_smbus_interface *bus, uint8_t spd_addr) : SPDAccessor(bus, spd_addr) { } DDR4Accessor::~DDR4Accessor() { } SPDMemoryType DDR4Accessor::memory_type() { return((SPDMemoryType)(this->at(BASIC_MEMORY_TYPE_ADDR))); } uint16_t DDR4Accessor::jedec_id() { return((this->at(DDR4_JEDEC_ID_ADDR) << 8) + (this->at(DDR4_JEDEC_ID_ADDR+1) & 0x7f) - 1); } std::string DDR4Accessor::part_number() { return this->read_part_nr_at(DDR4_PART_NR_START, DDR4_PART_NR_LEN); } uint8_t DDR4Accessor::manufacturer_data(uint16_t index) { if(index > DDR4_MANUF_SPECIFIC_LEN-1) { return 0; } return this->at(DDR4_MANUF_SPECIFIC_START + index); } DDR5Accessor::DDR5Accessor(i2c_smbus_interface *bus, uint8_t spd_addr) : SPDAccessor(bus, spd_addr) { } DDR5Accessor::~DDR5Accessor() { } SPDMemoryType DDR5Accessor::memory_type() { return((SPDMemoryType)(this->at(BASIC_MEMORY_TYPE_ADDR))); } uint16_t DDR5Accessor::jedec_id() { return((this->at(DDR5_JEDEC_ID_ADDR) << 8) + (this->at(DDR5_JEDEC_ID_ADDR+1) & 0x7f) - 1); } std::string DDR5Accessor::part_number() { return this->read_part_nr_at(DDR5_PART_NR_START, DDR5_PART_NR_LEN); } uint8_t DDR5Accessor::manufacturer_data(uint16_t index) { if(index > DDR5_MANUF_SPECIFIC_LEN-1) { return 0; } return this->at(DDR5_MANUF_SPECIFIC_START + index); }