通讯协议
LPC协议实战
之前,有对LPC进行了初步认识与协议原理记录,这里介绍下LPC协议在UEFI中的实现逻辑。和其他串口或者并口通信协议不同,在LPC协议中使用的是MMIO,内存映射的通信方式,也是现阶段主板与EC芯片交互的核心协议。
通讯逻辑
在现代架构(如ARM、RISC-V)中,传统I/O端口通讯效率还是太低,取而代之的是内存映射I/O。
MMIO会将外设寄存器映射到内存地址空间,CPU可以像访问内存一样访问这些寄存器,提升通信效率。
通过查询EC芯片的手册,均会发现有相关PMC控制寄存器的介绍。比如以下是FIC6288手册中的记录。

还有专门寄存器介绍:
1 2 3 4
| 0x62 PMDIR PMC 主机数据写入寄存器 0x62 PMDOR PMC 主机数据读取寄存器 0x66 PMCMDR PMC 主机命令写入寄存器 0x66 PMSTR PMC 主机状态寄存器
|
我们通常都是用PM1用做电源EC控制。
所以在而HOST端,需要配置好命令和数据端口。示例如下:
1 2 3 4 5 6 7
| #define EC_SC 0x66 #define EC_DATA 0x62
#define SMC_READ_EC 0x80 #define SMC_WRITE_EC 0x81
|
一般掌握读写命令即可,还有其他常规EC控制命令:
| 嵌入式控制器命令 |
命令字节编码 |
场景应用 |
| 读取嵌入式控制器 (RD_EC) |
0x80 |
主机读取数据 |
| 写入嵌入式控制器 (WR_EC) |
0x81 |
主机写入数据 |
| 启用嵌入式控制器突发模式 (BE_EC) |
0x82 |
防止EC处理或其他任务,强制其服务主处理器 |
| 禁用嵌入式控制器突发模式 (BD_EC) |
0x83 |
恢复EC的默认工作模式 |
| 查询嵌入式控制器 (QR_EC) |
0x84 |
查询引起SCI的事件号 |
当然,也可以自定义命令,不过需要在EC端实现相关处理逻辑。
UEFI实现逻辑
以下是飞腾平台UEFI代码中,LPC协议相关实现逻辑,仅供学习参考:
端口定义
1 2 3
| #define LPC_BASE_ADDR FixedPcdGet64 (PcdLpcBaseAddress) #define EC_COMMAND_PORT LPC_BASE_ADDR + EC_SC #define EC_DATA_PORT LPC_BASE_ADDR + EC_DATA
|
LPC_BASE_ADDR 是LPC的基地址,通过PCD配置,需要参考对应硬件平台手册配置。
EC_COMMAND_PORT和EC_DATA_PORT分别是EC的命令端口和数据端口。
状态检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| STATIC UINT8 EcIbFree () { UINT8 Status; do { Status = MmioRead8 (EC_COMMAND_PORT); } while (Status & 2); return SUCCESS; }
STATIC UINT8 EcObFull () { UINT8 Status; do { Status = MmioRead8 (EC_COMMAND_PORT); } while (!(Status & 1)); return SUCCESS; }
|
EcIbFree() 检查EC输入缓冲区是否空闲(IBF = Input Buffer Full)。
EcObFull() 检查EC输出缓冲区是否已满(OBF = Output Buffer Full)。
- 这两个函数通过轮询状态寄存器实现同步。
命令和数据传输
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| UINT8 EcWriteCmd (UINT8 Cmd) { EcIbFree (); MmioWrite8 (EC_COMMAND_PORT, Cmd); return SUCCESS; }
UINT8 EcWriteData (UINT8 Data) { EcIbFree (); MmioWrite8 (EC_DATA_PORT, Data); return SUCCESS; }
UINT8 EcReadData (UINT8 *PData) { *PData = MmioRead8 (EC_DATA_PORT); EcObFull (); *PData = MmioRead8 (EC_DATA_PORT); return SUCCESS; }
|
EcWriteCmd():向EC发送命令。
EcWriteData():向EC发送数据。
EcReadData():从EC读取数据。
- 所有操作都通过
MmioRead8 和 MmioWrite8 实现,这是基于内存映射I/O(MMIO)的方式。
内存访问函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| UINT8 EcReadMem (UINT8 Index, UINT8 *Data) { UINT8 Cmd = SMC_READ_EC; EcWriteCmd (Cmd); EcWriteData (Index); EcReadData (Data); return SUCCESS; }
UINT8 EcWriteMem (UINT8 Index, UINT8 Data) { UINT8 Cmd = SMC_WRITE_EC; EcWriteCmd (Cmd); EcWriteData (Index); EcWriteData (Data); return SUCCESS; }
|
EcReadMem():从EC内存中读取指定索引的数据。
EcWriteMem():向EC内存中写入数据到指定索引。
- 使用特定的命令(如
SMC_READ_EC 和 SMC_WRITE_EC)完成操作。
大致流程:UEFI下发操作命令-> 传输命令/数据-> 等待EC响应->读取返回数据。
HOST端一般都会抛出两个读写接口函数,如飞腾平台UEFI代码中EcReadMem()和EcWriteMem()。目前这些均在EDK2开源框架已经实现,具体实现逻辑在DxeCore/xxxx/Ec.c中。在开发时,也可以自行修改,但需要根据具体硬件平台进行适配。
EC端实现逻辑
EC端为LPC通信的从设备,因为芯片的不同,具体实现逻辑也不同,这里以FIC6288为例,简单介绍一些必要内容。
寄存器配置
在EC端,除了配置和HOST端一样的命令端口和还需要额外的配置控制寄存器。需要激活或者配置中断类型。如下PM1控制寄存器介绍:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| static const BYTE code FIC_SioTabInit[]= { 0x07 ,0x06, 0x70 ,0x01, 0x30 ,0x01, 0x71 ,0x03, 0x07 ,0x05, 0x70 ,0x0C, 0x30 ,0x01, 0x71 ,0x03, 0x07 ,0x11, 0x70 ,0x00, 0x30 ,0x01, 0x07 ,0x12, 0x70 ,0x00, 0x30 ,0x01, 0x07 ,0x04, 0x30 ,0x00,
};
|
其中0x07选择逻辑设备,0x70 清除中断,0x30 使能逻辑设备。代码中使能了PM1和PM2两个逻辑设备,其中PM1就是我们用来管理电源LPC通讯的链路。
逻辑设备定义如下:

1 2 3 4 5 6 7 8 9 10
|
void FIC_SioInit(void) { FIC_PNPTableDataInit(FIC_SioTabInit, sizeof(FIC_SioTabInit)); }
|
中断逻辑处理
- 中断向量配置
在寄存器发生中断时,会根据向量表检索到响应的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13
| static const pfn_ptr_v_v code FIC_IrqServTab[] = {
FIC_IrqInt24KBCIBF, FIC_IrqInt25PMC1IBF, FIC_IrqInt26PMC2OBE, FIC_IrqInt27PMC2IBF, ... .. . }
|
知道寄存器PMC1IBF 响应函数FIC_IrqInt25PMC1IBF,根据入口可以找到对应的命令处理函数。
- 中断寄存器响应
大致都会对收到的对应命令,分别处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| void FIC_ServPci2F(void) { unsigned char tempCmd ; do { if ( PM1STS & P_C_D ) { PM1Cmd = PM1DI; PM1Step = 0x00; tempCmd = PM1Cmd; if(PM1Cmd == 0x80) { FIC_ACPICmd80(); FIC_OEMACPICmd80();
} else if(PM1Cmd == 0x81) { FIC_ACPICmd81(); FIC_OEMACPICmd81();
} else if(PM1Cmd == 0x82) { FIC_ACPICmd82(); FIC_OEMACPICmd82();
} else if(PM1Cmd == 0x83) { FIC_ACPICmd83(); FIC_OEMACPICmd83();
} else if(PM1Cmd == 0x84) { FIC_ACPICmd84(); FIC_OEMACPICmd84();
} else { FIC_CorePort66(PM1Cmd); FIC_Hook66Port(PM1Cmd); }
} else { PM1Data = PM1DI; if (PM1Step != 0x00) { if(PM1Cmd == 0x80) { FIC_ACPICmd80Data(); } else if(PM1Cmd == 0x81) { FIC_ACPICmd81Data(); } else if(PM1Cmd == 0x82) { FIC_ACPICmd82Data(); } else if(PM1Cmd == 0x83) { FIC_ACPICmd83Data(); } else if(PM1Cmd == 0x84) { FIC_ACPICmd84Data(); } else { FIC_CorePort62(PM1Cmd); FIC_Hook62Port(PM1Cmd); } PM1Step--; } } } while((IS_MASK_SET(PM1STS, BURST) || ECCheckBurstMode) && (FIC_BurstModeCheck() == 1));
TR1 = 0; TF1 = 0; ET1 = 1; FIC_AllInterruptCtrl(ENABLE); FIC_PMCIBFIntEnable(); FIC_HookACPIComm(); }
|
核心逻辑:区分命令端口与数据端口数据,根据不同指令码(0x80~0x84)匹配对应的处理函数,同时支持OEM自定义钩子函数,方便功能扩展。
ACPI发送数据
EC完成处理后,通过ACPI通道将结果回传给HOST主机,完成一次完整的LPC通信交互。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void FIC_ACPICmd80Data(void) { if (PM1Step == 1) { FIC_SendFromACPI(FIC_HookMapECSpaceRead(PM1Data)); ECCheckBurstMode = 0; } }
void FIC_SendFromACPI(BYTE ecdata) { PM1DO = ecdata; }
|
将EC处理完成的数据写入PM1输出寄存器,根据ACPI协议上报至主机,完成主机操作的闭环。
这次先分享到这,后续有更新再补充。