OpenRGB/SPDAccessor.cpp
2024-12-19 14:32:40 +00:00

527 lines
13 KiB
C++

/*---------------------------------------------------------*\
| 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;
// We are only interested in DDR4 and DDR5 systems
valid = (mem_type == SPD_DDR4_SDRAM || mem_type == SPD_DDR4E_SDRAM ||
mem_type == SPD_LPDDR4_SDRAM || mem_type == SPD_LPDDR4X_SDRAM ||
mem_type == SPD_DDR5_SDRAM || mem_type == SPD_LPDDR5_SDRAM);
}
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)
{
if(wrapper.accessor != nullptr)
{
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<SPDWrapper> &slots, uint16_t jedec_id)
{
for(SPDWrapper &slot : slots)
{
if(slot.jedec_id() == jedec_id)
{
return true;
}
}
return false;
}
std::vector<SPDWrapper*> slots_with_jedec(std::vector<SPDWrapper> &slots, uint16_t jedec_id)
{
std::vector<SPDWrapper*> 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)
{
std::this_thread::sleep_for(SPD_IO_DELAY);
int ddr5Magic = bus->i2c_smbus_read_byte_data(spd_addr, 0x00);
std::this_thread::sleep_for(SPD_IO_DELAY);
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