DDR(Double Data Rate)SPD(Serial Presence Detect)是一种存储在计算机内存模块上的小型EEPROM芯片,用于存储有关内存模块制造商、型号、速度等信息。SPD芯片通过计算机的内存控制器与主板通信,提供关键的信息以确保内存模块能够正确运行。
SPD芯片中存储的信息包括但不限于:
- 制造商信息:制造商名称、产品型号等
- 内存规格:容量、速度等
- 时序参数:内存模块的时序设置,如CAS延迟、RAS预充电时间等
- 校准信息:内存模块的校准数据,用于确保内存稳定运行
DDR4 SPD:
DDR5 :
Byte 2 (0x002): Key Byte / Host Bus Command Protocol Typ
Byte 4 (0x004): First SDRAM Density and Package
SPD Dump 实例: 16 G 单Rank
Dump bytes[SDP Info]: 0001: RDIMM
30 10 12 01 04 00 20 62 00 00 00 00 B0 02 01 00
00 00 00 00 A0 01 F2 03 7A 0D 00 00 00 00 80 3E
80 3E 80 3E 00 7D 80 BB 30 75 27 01 A0 00 82 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 88 13 08 88 13 08 20 4E 20 10
27 10 15 34 20 10 27 10 C4 09 04 4C 1D 0C 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
10 00 80 B3 80 21 0B 2A 81 12 00 00 00 00 00 00
00 00 80 B3 C0 12 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 11 21 03 81 00 32 00 00 00 00
80 B3 80 33 00 00 00 00 2E 4E 55 00 01 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
SPD Socket 0 Channel 11 Dimm 0: 0x00091d18
0xC0 :
10 00 80 B3 80 21 0B 2A 81 12 00 00 00 00 00 00
00 00 80 B3 C0 12 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 11 21 03 81 00 32 00 00 00 00
80 B3 80 33 00 00 00 00 2E 4E 55 00 01 00 00 00
Module Manufacturer ID Code : ce80(Samsung)
Module Revision Code : 0000
Module Part Number : M321R2GA3BB6-CQKET
Module Serial Number : 450e2416
Module Manufacturing Date : 23-15(year-week)
DRAM Manufacturer ID Code : ce80(Samsung)
DRAM stepping : 0095
SPD Manufacturer ID Code : b380
SPD Device Type : 0080
SPD Revision Number : 0021
PMIC 0 Manufacturer ID Code : 2a0b
PMIC 0 Device Type : 0081
PMIC 0 Revision Number : 0012
PMIC 1 Manufacturer ID Code : 0000
PMIC 1 Device Type : 0000
PMIC 1 Revision Number : 0000
PMIC 2 Manufacturer ID Code : 0000
PMIC 2 Device Type : 0000
PMIC 2 Revision Number : 0000
TS Manufacturer ID Code : b380
TS Device Type : 00c0
TS Revision Number : 0012
RCD Manufacturer ID Code : b380(Renesa)
RCD Device Type : 0080
RCD Revision Number : 0033
DB ID Code : 0000
DB Device Type : 0000
DB Revision Number : 0000
[Socket 0 Channel 0 Dimm 0] Manu:Samsung, PN:M321R2GA3BB6-CQKET, Size:16GB, 1Rx8
DDR5, ECC:Yes, SN:450E22EF
0x200
80 CE 04 23 15 45 0E 24 17 4D 33 32 31 52 32 47
41 33 42 42 36 2D 43 51 4B 45 54 20 20 20 20 20
20 20 20 20 20 20 20 00 80 CE 95 54 30 55 39 30
30 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01 01 01 01 01 01 00 00 80 00 00 00 00 00 00 00
Byte 514 (0x202): Module Manufacturing Location
Bytes 515~516 (0x203~0x204): Module Manufacturing Date
Bytes 517~520 (0x205~0x208): Module Serial Number
Bytes 521~550 (0x209~0x226): Module Part Number
BIOS中读取SPD 信息
DDR5 :
HgpiStatus = HgpiReadSpd (0, &ReadSpd);
HGPI_STATUS
HgpiReadSpd (
IN UINTN FcnData,
IN OUT HGPI_READ_SPD_PARAMS *ReadSpd
)
{
EFI_PEI_SERVICES **PeiServices;
PEI_HYGON_PLATFORM_DIMM_SPD_PPI *PlatformDimmPpi;
EFI_STATUS Status;
HGPI_STATUS HgpiStatus;
HYGON_MEMORY_INIT_COMPLETE_PPI *MemoryInitCompletePpiPtr;
UINT8 Socket;
UINT8 Channel;
UINT8 Dimm;
UINT16 SpdSize;
PeiServices = (EFI_PEI_SERVICES **)GetPeiServicesTablePointer ();
Socket = ReadSpd->SocketId;
Channel = ReadSpd->MemChannelId;
Dimm = ReadSpd->DimmId;
IDS_HDT_CONSOLE (MAIN_FLOW, " HgpiReadSpd Entry\n");
HgpiStatus = HGPI_SUCCESS;
SpdSize = PcdGetBool (PcdDDR5Enable) ? DDR5_SPD_SIZE : DDR4_SPD_SIZE;
if (PcdGetBool (PcdHgpiInternalSpdInfo)) {
// get spd data from fixed buffer
// MemInitReadSpdBuffer (ReadSpd);
} else if (PcdGetBool (PcdHgpiSpdUseHpobData)) {
Status = (*PeiServices)->LocatePpi (
PeiServices,
&gHygonMemoryInitCompletePpiGuid,
0,
NULL,
&MemoryInitCompletePpiPtr
);
if (EFI_SUCCESS != Status) {
IDS_HDT_CONSOLE (MAIN_FLOW, "gHygonMemoryInitCompletePpiGuid Not Found.\n");
return HGPI_ERROR;
}
if (MemoryInitCompletePpiPtr->HygonDimmSpInfo.DimmSpdInfo[Socket][Channel][Dimm].DimmPresent) {
if (MemoryInitCompletePpiPtr->HygonDimmSpInfo.DimmSpdInfo[Socket][Channel][Dimm].SpdDataPtr != NULL) {
LibHygonMemCopy (
ReadSpd->Buffer,
MemoryInitCompletePpiPtr->HygonDimmSpInfo.DimmSpdInfo[Socket][Channel][Dimm].SpdDataPtr,
SpdSize,
&ReadSpd->StdHeader
);
IDS_HDT_CONSOLE (MAIN_FLOW, "Get Spd Data success.\n");
return HGPI_SUCCESS;
} else {
IDS_HDT_CONSOLE (MAIN_FLOW, "Dimm present, but no SPD data.\n");
return HGPI_ERROR;
}
} else {
IDS_HDT_CONSOLE (MAIN_FLOW, "Dimm not present.\n");
return HGPI_ERROR;
}
} else {
//
// Locate DIMM SPD read PPI.
//
Status = (**PeiServices).LocatePpi (
PeiServices,
&gHygonPlatformDimmSpdPpiGuid,
0,
NULL,
&PlatformDimmPpi
);
if (EFI_ERROR (Status)) {
IDS_HDT_CONSOLE (MAIN_FLOW, " PlatformDimmSpdRead.LocatePpi %r\n", Status);
return HGPI_ERROR;
}
//
// Invoke SPD Read
//
Status = PlatformDimmPpi->PlatformDimmSpdRead (PeiServices, PlatformDimmPpi, ReadSpd);
IDS_HDT_CONSOLE (MAIN_FLOW, " PlatformDimmSpdRead Exit %r\n", Status);
if (!EFI_ERROR (Status)) {
HgpiStatus = HGPI_SUCCESS;
} else {
HgpiStatus = HGPI_ERROR;
}
}
return HgpiStatus;
}
/// HPOB SPD Data for each DIMM.
typedef struct _HPOB_SPD_STRUCT
{
UINT8 SocketNumber; ///< Indicates the socket number
UINT8 ChannelNumber; ///< Indicates the channel number
UINT8 DimmNumber; ///< Indicates the channel number
UINT8 PageAddress; ///< Indicates the 256 Byte EE Page the data belongs to
// 0 = Lower Page
// 1 = Upper Page
TECHNOLOGY_TYPE Technology; ///< Indicates the type of Technology used in SPD
///< DDR3_TECHNOLOGY = Use DDR3 DIMMs
///< DDR4_TECHNOLOGY = Use DDR4 DIMMs
///< DDR5_TECHNOLOGY = Use DDR5 DIMMs
///< LPDDR3_TECHNOLOGY = Use LPDDR3
BOOLEAN DimmPresent; // < Indicates if the DIMM is present
UINT8 MuxPresent; ///< SpdMux Present or not. if 1, then yes otherwise no
UINT8 MuxI2CAddress; ///< MuxI2cAddress
UINT8 MuxChannel; ///< MuxChannel no.
UINT32 Address; ///< SMBus address of the DRAM
UINT32 SerialNumber; ///< DIMM Serial Number
UINT32 DeviceWidth:3; ///< Device Width i.e. x4, x8, x16 and x32
UINT32 DpprSupported:2; ///< Dppr Support Present
UINT32 SpprSupported:1; ///< Sppd Support Present
UINT32 Reserved:26; ///< Reserved for Future Use
UINT8 Data[1024]; ///< Buffer for 1024 Bytes of SPD data from DIMM
} HPOB_SPD_STRUCT;
使用Smbus Controller
// SMBus Controller bdf 0:0B:00 Fch Smbus Controller
addr = PCI_BASE | (PCI_SMBUS_BUS << 16) | (PCI_SMBUS_DEV << 11) | (PCI_SMBUS_FUN << 8);
// Set the I/O Space enable bit
// PCICMD_OFFSET 0x04
outpd(0xCF8, addr + PCICMD_OFFSET);
data = inpd(0xCFC);
if ((data & 0x01) == 0)
{
outpd(0xCFC, data | 0x01);
}
// Set the HST_EN bit
// HOSTC_OFFSET 0x40
outpd(0xCF8, addr + HOSTC_OFFSET);
data = inpd(0xCFC);
if ((data & 0x01) == 0)
{
data = outpd(0xCFC, data | 0x01);
}
// Program the SMBus Base Address Register
// SMB_BASE_OFFSET 0x20
outpd(0xCF8, addr + SMB_BASE_OFFSET);
SMBBASE = inpd(0xCFC) & 0xFFE0;
// Clear the status bits
// HST_STS (Host Status Register) 0x00
outp(SMBBASE + HST_STS, 0x1E);
// ee1004 set current page
// XMIT_SLVA (Transmit Slave Address Register) 0x04
// 0x6C for page0, and 0x6E for page1
outp(SMBBASE + XMIT_SLVA, 0x6C);
// Data is ignored
// HST_CMD (Host Command Register) 0x03
outp(SMBBASE + HST_CMD, 0x00);
// Execute the Byte command
// HST_CNT (Host Control Register) 0x02
outp(SMBBASE + HST_CNT, 0x44);
// Error handling
PCI 设备的BDF 通过lspci -vvv | grep -i smbus 获取BDF
PCI_BASE =0x80000000
addr = PCI_BASE | (PCI_SMBUS_BUS << 16) | (PCI_SMBUS_DEV << 11) | (PCI_SMBUS_FUN << 8);
汇编方式读取:
;----------smbus 访问规范--------
call iodelay
mov dx,sm_base ;SMbus 的基地址
add dx,00h ;status register
mov al,0feh ;
out dx,al
call iodelay ;延时
mov dx,sm_base
add dx,04h ;slave address register
mov ax,sm_devnum ;从设备地址:a0/a2/a4/a6 ,sm_devnum=a0h
or al,01h ;末位:1 - 表示读
out dx,al
call iodelay
mov dx,sm_base
add dx,03h ;command register
mov ax,sm_regnum ;寄存器索引
out dx,al
call iodelay
inc sm_regnum
mov dx,sm_base
add dx,02h ;control register
mov al,48h ;设置读写模式:字节(48h)、字(4ch)、块(54h)
out dx,al
call iodelay
call iodelay;
mov dx,sm_base
add dx,00h
in al,dx;
mov dx,sm_base
add dx,05h ;data0 register
in al,dx ;回读数据
push ax
mov bl,0fh
call print_al
call print_blank
call print_blank
pop ax
mov es:[di],al ;数据保存到buffer 中
inc di
cmp sm_regnum,07fh
ja endsm ;128-byte 读完
jmp nextch
endsm: ;读完结束,打印buffer 后,结束