UEFI Shell 下 SPD 读取工具

最近需要读取内存条的 SPD 信息,于是研究了一下,在 CloverBootloader 项目中【参考1】找到了一份代码。经过修改后分析出来一个独立的代码, 实体机上测试如下:

Shell 下读取 SPD 测试
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/DebugLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/PciIo.h>
#include <IndustryStandard/Pci22.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/IoLib.h>
#include <Library/UefiBootServicesTableLib.h>

#include "memvendors.h"
#include "spd.h"

// Intel SMB reg offsets
#define SMBHSTSTS 0
#define SMBHSTCNT 2
#define SMBHSTCMD 3
#define SMBHSTADD 4
#define SMBHSTDAT 5
#define SMBHSTDAT1 6
#define SBMBLKDAT 7
// MCP and nForce SMB reg offsets
#define SMBHPRTCL_NV 0 /* protocol, PEC */
#define SMBHSTSTS_NV 1 /* status */
#define SMBHSTADD_NV 2 /* address */
#define SMBHSTCMD_NV 3 /* command */
#define SMBHSTDAT_NV 4 /* 32 data registers */
//

// XMP memory profile
#define SPD_XMP_SIG1 176
#define SPD_XMP_SIG1_VALUE 0x0C
#define SPD_XMP_SIG2 177
#define SPD_XMP_SIG2_VALUE 0x4A
#define SPD_XMP_PROFILES 178
#define SPD_XMP_VERSION 179
#define SPD_XMP_PROF1_DIVISOR 180
#define SPD_XMP_PROF1_DIVIDEND 181
#define SPD_XMP_PROF2_DIVISOR 182
#define SPD_XMP_PROF2_DIVIDEND 183
#define SPD_XMP_PROF1_RATIO 186
#define SPD_XMP_PROF2_RATIO 221


#define SPD_XMP20_SIG1 0x180
#define SPD_XMP20_SIG2 0x181
#define SPD_XMP20_PROFILES 0x182
#define SPD_XMP20_VERSION 0x183
/* 0x189 */
#define SPD_XMP20_PROF1_MINCYCLE 0x18C
#define SPD_XMP20_PROF1_FINEADJUST 0x1AF
/* 0x1B8 */
#define SPD_XMP20_PROF2_MINCYCLE 0x1BB
#define SPD_XMP20_PROF2_FINEADJUST 0x1DE
/* 0x1E7 */

BOOLEAN       smbIntel;
UINT8           smbPage;

/** Read one byte from i2c, used for reading SPD */
UINT8 smb_read_byte(UINT32 base, UINT8 adr, UINT16 cmd)
{
        //   INTN l1, h1, l2, h2;
        UINT64 t;
        UINT8 page;
        UINT8 c;
        //	UINT8 p;

        if (smbIntel)
        {
                IoWrite8(base + SMBHSTSTS, 0x1f);				// reset SMBus Controller (set busy)
                IoWrite8(base + SMBHSTDAT, 0xff);
                t=0;
                while ( IoRead8(base + SMBHSTSTS) & 0x01)     // wait until host is not busy
                {
                        if (t > 5)
                        {
                                gBS->Stall(1000000UL);
                                Print(L"host is busy for too long for byte %2hhX:%d!\n", adr, cmd);
                                return 0xFF;                  // break
                        }
                        t++;
                }

                page = (cmd >> 8) & 1;
                if (page != smbPage)
                {
                        // p = 0xFF;
                        IoWrite8(base + SMBHSTCMD, 0x00);
                        IoWrite8(base + SMBHSTADD, 0x6C + (page << 1)); // Set SPD Page Address
#if 0
                        IoWrite8(base + SMBHSTCNT, 0x48); // Start + Byte Data Write
                        // Don't use "Byte Data Write" because status goes from 0x41 (Busy) -> 0x44 (Error)
#else
                        IoWrite8(base + SMBHSTCNT, 0x40); // Start + Quick Write
                        // status goes from 0x41 (Busy) -> 0x42 (Completed)
#endif
                        smbPage = page;

                        t=0;
                        while (!( (c=IoRead8(base + SMBHSTSTS)) & 0x02))  	// wait until command finished
                        {
                                gBS->Stall(1000000UL);
                                if (c & 4)
                                {
                                        Print(L"spd page change error for byte %2hhX:%d!\n", adr, cmd);
                                        break;
                                }
                                if (t > 5)
                                {
                                        Print(L"spd page change taking too long for byte %2hhX:%d!\n", adr, cmd);
                                        break;									// break after 5ms
                                }
                                t++;
                        }
                        return smb_read_byte(base, adr, cmd);
                }

                // p = 0xFF;
                IoWrite8(base + SMBHSTCMD, (UINT8)(cmd & 0xFF)); // SMBus uses 8 bit commands
                IoWrite8(base + SMBHSTADD, (adr << 1) | 0x01 ); // read from spd
                IoWrite8(base + SMBHSTCNT, 0x48 ); // Start + Byte Data Read
                // status goes from 0x41 (Busy) -> 0x42 (Completed) or 0x44 (Error)

                t=0;
                while (!( (c=IoRead8(base + SMBHSTSTS)) & 0x02))  	// wait until command finished
                {
                        gBS->Stall(1000000UL);
                        if (c & 4)
                        {
                                // This alway happens when trying to read the memory type (cmd 2) of an empty slot
                                // Print(L"spd byte read error for byte %2hhX:%d!\n", adr, cmd);
                                break;
                        }
                        if (t > 5)
                        {
                                // if (cmd != 2)
                                Print(L"spd byte read taking too long for byte %2hhX:%d!\n", adr, cmd);
                                break;									// break after 5ms
                        }
                        t++;
                }
                return IoRead8(base + SMBHSTDAT);
        }
        else
        {

        }
}

/* SPD i2c read optimization: prefetch only what we need, read non prefetcheable bytes on the fly */
#define READ_SPD(spd, base, slot, x) spd[x] = smb_read_byte(base, 0x50 + slot, x)


/** Read from smbus the SPD content and interpret it for detecting memory attributes */
STATIC void read_smb(EFI_PCI_IO_PROTOCOL *PciIo, UINT16	vid, UINT16	did)
{
        //	EFI_STATUS	Status;
        UINT8       i;// spd_size, spd_type;
        UINT8       j;
        UINT32			base, mmio, hostc;
        UINT16			Command;
        //RAM_SLOT_INFO*  slot;
        //XBool			fullBanks;
        UINT8*			spdbuf;
//  UINT16			vid, did;

        UINT8                  TotalSlotsCount;

        smbPage = 0; // valid pages are 0 and 1; assume the first page (page 0) is already selected
//  vid = gPci->Hdr.VendorId;
//  did = gPci->Hdr.DeviceId;

        /*Status = */
        PciIo->Pci.Read (
                PciIo,
                EfiPciIoWidthUint16,
                PCI_COMMAND_OFFSET,
                1,
                &Command
        );

        Command |= 1;
        /*Status = */
        PciIo->Pci.Write (
                PciIo,
                EfiPciIoWidthUint16,
                PCI_COMMAND_OFFSET,
                1,
                &Command
        );

        Print(L"SMBus CmdReg: 0x%X\n", Command);

        /*Status = */
        PciIo->Pci.Read (
                PciIo,
                EfiPciIoWidthUint32,
                0x10,
                1,
                &mmio
        );
        if (vid == 0x8086)
        {
                /*Status = */PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        0x20,
                        1,
                        &base
                );

                base &= 0xFFFE;
                smbIntel = TRUE;
        }
        else
        {
                /*Status = */PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        0x24, // iobase offset 0x24 on MCP
                        1,
                        &base
                );
                base &= 0xFFFC;
                smbIntel = FALSE;
        }
        /*Status = */PciIo->Pci.Read (
                PciIo,
                EfiPciIoWidthUint32,
                0x40,
                1,
                &hostc
        );


        Print(L"Scanning SMBus [%04X:%04X], mmio: 0x%X, ioport: 0x%X, hostc: 0x%X\n",
              vid, did, mmio, base, hostc);

        // needed at least for laptops
        //fullBanks = (gDMI->MemoryModules == gDMI->CntMemorySlots);

        spdbuf = AllocateZeroPool(MAX_SPD_SIZE);

        TotalSlotsCount = 8; //MAX_RAM_SLOTS;  -- spd can read only 8 slots
        Print(L"Slots to scan [%d]...\n", TotalSlotsCount);
        for (i = 0; i <  TotalSlotsCount; i++)
        {
                //<==
                ZeroMem(spdbuf, MAX_SPD_SIZE);
                READ_SPD(spdbuf, base, i, SPD_MEMORY_TYPE);
                if (spdbuf[SPD_MEMORY_TYPE] == 0xFF)
                {
                        //Print(L"SPD[%d]: Empty\n", i);
                        continue;
                }
                else if (spdbuf[SPD_MEMORY_TYPE] == 0)
                {
                        // First 0x40 bytes of DDR4 spd second page is 0. Maybe we need to change page, so do that and retry.
                        Print(L"SPD[%d]: Got invalid type %d @0x%X. Will set page and retry.\n", i, spdbuf[SPD_MEMORY_TYPE], 0x50 + i);
                        smbPage = 0xFF; // force page to be set
                        READ_SPD(spdbuf, base, i, SPD_MEMORY_TYPE);
                       
                }

                // Copy spd data into buffer
                Print(L"SPD[%d]: Type %d @0x%X\n", i, spdbuf[SPD_MEMORY_TYPE], 0x50 + i);
                 for (j=0;j<16;j++) {
                         READ_SPD(spdbuf, base, i, j);
                         Print(L"%X ",spdbuf[j]);
                        }
                 Print(L"\n");
        } // for
        if (smbPage != 0)
        {
                READ_SPD(spdbuf, base, 0, 0); // force first page when we're done
        }
}

void ScanSPD()
{
        EFI_STATUS            Status;
        EFI_HANDLE            *HandleBuffer = NULL;
        EFI_PCI_IO_PROTOCOL   *PciIo = NULL;
        UINTN                 HandleCount;
        UINTN                 Index;
        PCI_TYPE00            gPci;

        // Scan PCI handles
        Status = gBS->LocateHandleBuffer (
                         ByProtocol,
                         &gEfiPciIoProtocolGuid,
                         NULL,
                         &HandleCount,
                         &HandleBuffer
                 );

        if (!EFI_ERROR(Status))
        {
                for (Index = 0; Index < HandleCount; ++Index)
                {
                        Status = gBS->HandleProtocol(HandleBuffer[Index], &gEfiPciIoProtocolGuid, (void **)&PciIo);
                        if (!EFI_ERROR(Status))
                        {
                                // Read PCI BUS
                                //PciIo->GetLocation (PciIo, &Segment, &Bus, &Device, &Function);
                                Status = PciIo->Pci.Read (
                                                 PciIo,
                                                 EfiPciIoWidthUint32,
                                                 0,
                                                 sizeof (gPci) / sizeof (UINT32),
                                                 &gPci
                                         );
                                //SmBus controller has class = 0x0c0500
                                if ((gPci.Hdr.ClassCode[2] == 0x0c) && (gPci.Hdr.ClassCode[1] == 5)
                                                && (gPci.Hdr.ClassCode[0] == 0) && (gPci.Hdr.VendorId == 0x8086 || gPci.Hdr.VendorId == 0x10DE))
                                {
                                        Print(L"SMBus device : %04hX %04hX class=%02X%02X%02X status=%r\n",
                                              gPci.Hdr.VendorId,
                                              gPci.Hdr.DeviceId,
                                              gPci.Hdr.ClassCode[2],
                                              gPci.Hdr.ClassCode[1],
                                              gPci.Hdr.ClassCode[0],
                                              Status
                                             );
                                        read_smb(PciIo, gPci.Hdr.VendorId, gPci.Hdr.DeviceId);
                                }
                        }
                }
        }

}

/**
  UEFI application entry point which has an interface similar to a
  standard C main function.

  The ShellCEntryLib library instance wrappers the actual UEFI application
  entry point and calls this ShellAppMain function.

  @param  ImageHandle  The image handle of the UEFI Application.
  @param  SystemTable  A pointer to the EFI System Table.

  @retval  0               The application exited normally.
  @retval  Other           An error occurred.

**/
INTN
EFIAPI
ShellAppMain (
        IN UINTN Argc,
        IN CHAR16 **Argv
)
{
        ScanSPD();
        return EFI_SUCCESS;
}

完整代码和EFI 下载:

参考:

1. https://github.com/CloverHackyColor/CloverBootloader/releases

《UEFI Shell 下 SPD 读取工具》有10个想法

  1. 請問 SmbusHcProtocol->Execute 的 SlaveAddress 有辦法帶入 0xA0 嗎?
    看 source code 他這個變數的長度會被限制在 7 bits, 因此會被自動轉成 0x20, 會回傳 EFI_DEVICE_ERROR.

    1. I2C 设备7Bit地址如果是 Addr, 那么读的时候需要使用 Addr<<1+1, 写的地址是 Addr<<1+0. 不知道你问的是不是这个?

      1. 大大晚上好,謝謝您的回覆,已解決位置的問題了。
        另外想請問一下… 該如何進行 page 的切換?
        本人用的是 DDR5 的 memory,
        在 RU 底下直接選擇 SMBUS 讀取 SPD 後不知道該如何切換 Page, 有上網找了些資料但仍看不太懂,請問您知道該如何操作嗎?

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注