UEFI Bootloader中SPI寄存器读写应用及相关API/示例求助
UEFI下通过SPI接口读写寄存器的实现方案
嗨Hardik,作为UEFI新手想要通过SPI接口读写寄存器确实容易找不到方向,我来给你详细讲讲可行的方案和代码示例,帮你快速搞定这件事~
一、UEFI中SPI操作的核心依赖:EFI_SPI_PROTOCOL
UEFI规范里定义了专门的EFI_SPI_PROTOCOL来封装SPI控制器的操作,这是官方推荐的标准方式,比直接操作寄存器更稳定、跨平台性更好。这个Protocol由固件提供,我们只需要在应用中获取它就能调用相关API完成读写。
二、获取SPI Protocol的基础步骤
首先你需要在UEFI应用的入口函数里,通过系统表的LocateProtocol接口找到这个Protocol,代码示例如下:
#include <Uefi.h> #include <Protocol/Spi.h> #include <Library/UefiLib.h> EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_SPI_PROTOCOL *SpiProtocol; // 定位SPI Protocol Status = gBS->LocateProtocol( &gEfiSpiProtocolGuid, NULL, (VOID **)&SpiProtocol ); if (EFI_ERROR(Status)) { Print(L"Failed to locate SPI Protocol: %r\n", Status); return Status; } Print(L"SPI Protocol located successfully!\n"); // 后续读写操作写在这里 return EFI_SUCCESS; }
三、使用SPI Protocol读写寄存器的示例
拿到EFI_SPI_PROTOCOL之后,我们需要构造EFI_SPI_TRANSACTION结构体来描述SPI事务,然后调用ExecuteTransaction接口执行读写。
1. 写寄存器示例
比如向SPI设备的0x10地址写入数据0xAB:
// 构造写事务:先发送寄存器地址,再发送要写入的数据 UINT8 RegisterAddr = 0x10; UINT8 WriteData = 0xAB; UINT8 TxBuffer[2] = {RegisterAddr, WriteData}; EFI_SPI_TRANSACTION WriteTx = { .Flags = 0, // 根据设备需求设置,比如是否保持片选 .BusWidth = EFI_SPI_BUS_WIDTH_1, // 单总线模式(大部分设备用这个) .ClockFrequency = 1000000, // 1MHz时钟,根据你的SPI设备规格调整 .ChipSelect = 0, // 片选信号编号,对应硬件连接的CS引脚 .TxData = TxBuffer, .TxDataLength = sizeof(TxBuffer), .RxData = NULL, .RxDataLength = 0 }; Status = SpiProtocol->ExecuteTransaction(SpiProtocol, &WriteTx); if (EFI_ERROR(Status)) { Print(L"SPI write to 0x%X failed: %r\n", RegisterAddr, Status); } else { Print(L"SPI write success! Addr:0x%X, Data:0x%X\n", RegisterAddr, WriteData); }
2. 读寄存器示例
比如从SPI设备的0x10地址读取数据:
// 构造读事务:先发送寄存器地址,再读取返回的数据 UINT8 RegisterAddr = 0x10; UINT8 RxBuffer[1] = {0}; UINT8 TxBuffer[1] = {RegisterAddr}; EFI_SPI_TRANSACTION ReadTx = { .Flags = 0, .BusWidth = EFI_SPI_BUS_WIDTH_1, .ClockFrequency = 1000000, .ChipSelect = 0, .TxData = TxBuffer, .TxDataLength = sizeof(TxBuffer), .RxData = RxBuffer, .RxDataLength = sizeof(RxBuffer) }; Status = SpiProtocol->ExecuteTransaction(SpiProtocol, &ReadTx); if (EFI_ERROR(Status)) { Print(L"SPI read from 0x%X failed: %r\n", RegisterAddr, Status); } else { Print(L"SPI read success! Addr:0x%X, Data:0x%X\n", RegisterAddr, RxBuffer[0]); }
四、备选方案:直接操作SPI控制器寄存器(不推荐)
如果你的固件没有实现EFI_SPI_PROTOCOL,或者你需要更底层的操作,可以直接映射SPI控制器的MMIO地址到内存空间,然后读写寄存器。但这种方式依赖具体硬件平台的寄存器定义,兼容性差,风险较高:
// 示例:直接映射SPI控制器MMIO并操作 EFI_PHYSICAL_ADDRESS SpiBasePhy = 0xXXXXXXX; // 替换成你的SPI控制器物理基地址 VOID *SpiMmio; // 分配并映射物理地址到虚拟地址 Status = gBS->AllocatePages( AllocateAddress, EfiRuntimeServicesData, EFI_SIZE_TO_PAGES(0x1000), // 分配4KB内存页 &SpiBasePhy ); if (!EFI_ERROR(Status)) { SpiMmio = (VOID *)(UINTN)SpiBasePhy; // 示例:设置SPI控制寄存器(地址偏移和值需要参考硬件手册) *(UINT32 *)((UINTN)SpiMmio + 0x00) = 0x12345678; // 示例:读取SPI状态寄存器 UINT32 StatusReg = *(UINT32 *)((UINTN)SpiMmio + 0x04); Print(L"SPI Status Register: 0x%X\n", StatusReg); }
五、关键注意事项
- 确认你的UEFI固件支持
EFI_SPI_PROTOCOL:可以在UEFI Shell中执行dmpstore命令,搜索gEfiSpiProtocolGuid来验证。 - SPI事务参数必须匹配设备规格:时钟频率、总线宽度、片选编号等参数要和你的SPI设备手册一致,否则会操作失败。
- 编译环境:UEFI应用需要用EDKII工具链编译成PE/COFF格式,才能在UEFI环境中运行。
内容的提问来源于stack exchange,提问作者Hardik Prajapati
相关产品推荐
相关产品推荐

