
SPI Flash读写原理详解从硬件时序到BIOS代码层面本文从PCH内存映射窗口讲到EDK2源码把硬件时序和固件代码串成一条线。一、SPI Flash不是硬盘——它是内存映射窗口里的字节流刚入行的同事盯着电路板问我“师傅BIOS代码存在这颗Flash芯片里对吧CPU上电以后怎么执行能直接读Flash吗”能。但不是你以为的那种能。他以为CPU把Flash当硬盘——先读到内存再从内存执行。不是。CPU上电后的第一条指令直接从Flash里取。没有加载这个过程。Flash自己就是内存的一部分——或者更准确地说Flash通过PCHPlatform Controller Hub里的内存映射机制假装自己是ROM。这个假装是怎么做到的复位向量落在哪x86 CPU上电后第一条指令的地址是0xFFFFFFF0——复位向量。这个地址不在DRAM里因为DRAM还没初始化。DDR时序训练、MRC跑起来之前CPU根本没地方存东西。那0xFFFFFFF0落在哪落在SPI Flash的内存映射窗口里。PCH内部有一个Memory Map Decoder维护着一张地址路由表哪些物理地址范围交给DRAM Controller哪些交给SPI Controller。上电默认路由策略高地址段0xFF000000 ~ 0xFFFFFFFF16MB顶部空间映射到SPI Flash。1:1直接映射物理地址的偏移量直接等于Flash地址。CPU读0xFFF00000SPI Controller就去Flash的0x00F00000取数据。预取缓冲Prefetch BufferSPI Controller内部有缓存。CPU读一个地址Controller一口气取64字节甚至更多放进缓冲区。因为CPU取指令通常是顺序的预取能大幅降低SPI总线上的命令开销。关键数字SPI Flash发一次Quad I/O Read命令命令开销大约20~30个SPI时钟周期。如果CPU每次只取32字节Controller得不停发新命令——命令相位占的时间比数据相位还多。固件启动慢的根源不是CPU执行的指令多而是取指本身就慢。就像你每次看一页纸都要重新打开保险柜、伸手进去、找到那一页——而不是把整本手册拿在手里翻。这就是SPI Flash XIPExecute In Place的本质。二、Flash DescriptorFlash的分区表每颗SPI Flash最前面的4KB不是你的代码是Flash DescriptorIFDIntel Flash Descriptor。它是Flash的分区表没有它PCH根本不知道该从哪读BIOS。Region划分Region编号名称内容0Flash DescriptorIFD自身元数据1BIOS / UEFI Firmware你的固件代码2Intel MEManagement Engine固件3GbE网卡PXE ROM和配置4Platform DataEC固件或其他平台数据一个16MB SPI Flash的真实布局0x000000 - 0x000FFF Flash Descriptor (4KB) 0x001000 - 0x6FFFFF ME Region (~7MB) 0x700000 - 0xFEFFFF BIOS Region (~9MB) 0xFF0000 - 0xFFFFFF BIOS Region (顶部) (64KB) ← 复位向量在这里BIOS Region必须覆盖Flash顶部。CPU复位向量0xFFFFFFF0经PCH映射后落到Flash偏移0xFFFFF0——必须在BIOS Region内否则CPU取不到复位向量直接死。FREG寄存器PCH通过FREG寄存器读取Region的Base和Limit// Silicon/Intel/CoffeelakeSiliconPkg/Pch/Include/Register/PchRegsSpi.h#defineR_PCH_SPI_FREG00x54// Flash Region 0 (Flash Descriptor)#defineR_PCH_SPI_FREG10x58// Flash Region 1 (BIOS)#defineR_PCH_SPI_FREG20x5C// Flash Region 2 (ME)#defineR_PCH_SPI_FREG30x60// Flash Region 3 (GbE)#defineR_PCH_SPI_FREG40x64// Flash Region 4 (Platform Data)#defineB_PCH_SPI_FREG_BASE_MASK0x7FFF// Base字段bit[14:0]#defineB_PCH_SPI_FREG_LIMIT_MASK0x7FFF0000// Limit字段bit[30:16]Base和Limit以4KB为单位。看到Base0x0070、Limit0x00FE实际地址是Base * 0x1000到(Limit 1) * 0x1000 - 1。踩坑FREG1BIOS Region的Limit值少算了一个扇区。BIOS Region结束地址落在代码段中间后面的字节SPI Controller不认全部返回0xFF。CPU取指正好卡在那个边界上把0xFFFF解析为非法指令触发#UD异常。FREG的Base和Limit必须精确对齐到Flash扇区边界。FRAP寄存器谁能读、谁能写FRAP控制访问权限有两个独立的位掩码一个控制CPU对各Region的读/写权限另一个控制ME的权限。CPU能读BIOS Region不代表ME也能读。ME通常被关在ME Region里。踩坑拿DediProg烧录器直接把BIOS bin8MB烧进16MB Flash。主板亮了但ME设备挂黄叹号网络不通。原因BIOS bin只覆盖了BIOS RegionME Region和GbE Region是空的。必须用FIT工具把IFD ME BIOS GbE打包成完整Flash image再烧录。三、SPI Controller核心寄存器SPI Controller挂在PCI总线B:D:F 0:1F:5但寄存器通过专用MMIO窗口访问基地址约0xFED01000。偏移寄存器作用0x00BFPRBIOS Flash Primary Region基地址0x04HSFS硬件定序状态和命令查状态看它0x06HSFC硬件定序控制发命令看它0x08FADDR软件定序模式下的Flash目标地址0x0CFDATA0Flash数据寄存器低32位0x50FRAPFlash Region访问权限0x54~0x64FREG0~FREG4各Region的Base/Limit0x74~0x84PR0~PR4Protected Range寄存器组0x90SSFS软件定序状态0x91SSFC软件定序控制HSFS寄存器调试时最常用位名称含义0FDONEFlash操作完成硬件定序1FCERRFlash周期错误2AELAccess Error Log——有人访问了不该访问的Region5SCIPSPI Cycle In Progress——Flash正忙15FLOCKDNFlash配置锁定调试第一件事永远是看HSFS寄存器。SCIP是否忙、AEL是否越界访问、FCERR是否命令失败、FLOCKDN配置是否已锁——这四位的状态比你猜代码逻辑有用得多。四、硬件定序 vs 软件定序SPI Controller支持两种操作模式搞不清什么时候用哪种要么性能拉胯要么根本写不进去。硬件定序Hardware Sequencing你只需要告诉Controller“帮我把Flash偏移0x700000开始的128KB数据搬到内存地址0x80000000”。Controller自己搞定全部底层细节——发Read命令、检查WIP位、等待BUSY清除、读取数据。PEI阶段加载FV到CAR或DRAM时默认走硬件定序。一条HSFC写命令PCH自动处理地址转换、SPI命令组装、等待Flash就绪。软件定序Software Sequencing你直接操控SPI总线的每个时钟周期。发什么命令、什么地址、多少字节、什么时候读——全由代码控制。Flash的编程Program和块擦除Sector Erase必须走软件定序。因为硬件定序不支持这些特殊命令格式。比如Write Enable必须先发然后才是Erase命令中间不能断。// IntelSiliconPkg/Feature/PcieSpiLib/SpiFlashCommon.c// 硬件定序一条命令搞定PchSpiAndThenOr32(PCH_SPI_HSFC,(UINT32)~B_PCH_SPI_HSFC_FDBC_MASK,// 清空Flash Data Byte Count(UINT32)(ByteCount-1)// 设置数据字节数);PchSpiOr32(PCH_SPI_HSFC,B_PCH_SPI_HSFC_FGO);// 发起Flash Cycle Go// 软件定序手动控制每个步骤PchSpiWrite8(PCH_SPI_SSFC,(UINT8)(command));PchSpiWrite32(PCH_SPI_FADDR,flashAddr);// ... 逐字节读写 FDATA0/FDATA1操作用哪种为什么读数据硬件定序快PCH自动处理读JEDEC ID硬件定序标准操作硬件支持页编程Program软件定序需要先发Write Enable扇区擦除Erase软件定序命令序列硬件不支持读SFDP表软件定序非标准命令0x5A五、SPI读取时序从CPU指令到Flash数据用一次真实的CPU取指过程把整条链路走一遍。场景PEI阶段CPU要取复位向量0xFFFFFFF0处的指令。Step 1CPU取指单元向L1 I-Cache发地址0xFFFFFFF0。Cache Miss冷启动Cache是空的。Step 2地址通过Core Ring送到System Agent。Step 3System Agent查Memory Map Decoder。地址落在0xFF000000 ~ 0xFFFFFFFF → 路由到DMI/OPI链路 → 发给PCH。Step 4PCH收到DMI事务。查表这个地址属于BIOS Region的映射窗口。Step 5PCH查FRAP寄存器确认CPU对Region 1有读权限。Step 6PCH把物理地址0xFFFFFFF0转换成Flash偏移0xFFFFFFF0 - 0xFF000000 0x00FFFFF0。Step 7SPI Controller组装Quad I/O Read命令帧CS#: ──────┐ ┌──────────── IO0: ────── EB ── A23..A16 ── A15..A8 ── A7..A0 ── M3..M0 ═══ D0 ═══ D4 ═══ ... IO1: ────── EB ── A23..A16 ── A15..A8 ── A7..A0 ── M3..M0 ═══ D1 ═══ D5 ═══ ... IO2: ────── EB ── A23..A16 ── A15..A8 ── A7..A0 ── M3..M0 ═══ D2 ═══ D6 ═══ ... IO3: ────── EB ── A23..A16 ── A15..A8 ── A7..A0 ── M3..M0 ═══ D3 ═══ D7 ═══ ...0xEBQuad I/O Read命令字节3字节地址0xFFFFF01字节Mode 4个Dummy时钟周期数据输出4根线同时输出8个时钟周期出4个字节Step 8Flash在Dummy周期结束后吐出第一个字节。SPI Controller把4条数据线拼成字节放进Prefetch Buffer。Step 9Prefetch Buffer连续从Flash拉64字节再一次性通过DMI返回给CPU。Step 10CPU收到数据填入L1 I-Cache。取指成功开始执行第一条指令。在50MHz SPI时钟 Quad I/O模式下第一次取指延迟约300~500ns。命中Prefetch Buffer后降到几十纳秒。CPU一旦跳转Branch又会回到300~500ns。这就是为什么固件里会看到大量rep movsb——一次性把Flash里的FV搬到CAR或DRAM然后直接从内存执行。在Flash里原地执行XIP太慢了。六、SFDP表Flash的自我介绍——能信吗符合JESD216标准的SPI Flash都内置了SFDPSerial Flash Discoverable Parameters表。发一条Read SFDP命令0x5AFlash就把参数完整吐给你容量、扇区大小、支持的命令集、最大时钟频率、擦除时间、状态寄存器布局。有了SFDP理论上不用维护Flash型号表。Linux内核的SPI NOR框架就是这么干的——优先解析SFDP解析不到才查硬编码型号表。但固件世界不完全信任SFDP。EDK2里依然维护着芯片型号表typedefstruct{UINT8 DeviceId[3];// JEDEC Manufacturer ID Device IDUINT32 DeviceSize;// 总容量UINT32 BlockSize;// 擦除块大小UINT32 PageSize;// 页编程大小UINT8 OpcodeRead;// 读取操作码UINT8 OpcodeWrite;// 页编程操作码UINT8 OpcodeErase;// 扇区擦除操作码UINT8 AddressBytes;// 地址字节数3或4BOOLEAN SupportQuadRead;// 是否支持Quad I/O Read}SPI_PART_TABLE;踩坑国产Flash的SFDP说谎遇到一颗国产SPI NOR FlashSFDP表写着支持1-1-4 Quad I/O Read最大104MHz。信了配成Quad Read模式结果第3个字节开始全是垃圾。示波器挂上去看IO2引脚电平一直不动。这颗Flash的IO2内部被配成了普通GPIO没接到数据输出缓冲区。SFDP表是厂商烧进去的静态数据——小厂工程师可能直接把竞品的SFDP复制过来改了个ID。SFDP表里的参数可能是设计规格而不是实测规格。解决查Datasheet发现IO2需要先写厂商专用配置寄存器才能切Quad模式。SFDP没说这步。手动加了一段厂商专用命令后正常。经验SFDP可以信但不能全信。Vendor ID不是Micron/Winbond/Macronix/ISSI这些大厂就先用Dual Read做数据完整性验证确认Quad Read输出正确再切。七、三层写保护——你写不进Flash的原因SPI Controller的写保护不是开或关那么简单。有三层层层递进。写不进Flash三层都检查一遍。第一层Protected Range寄存器PR0 ~ PR4PR寄存器在硬件层面限制可写的Flash地址范围。设好后任何针对这个范围的SPI Write/Erase命令都被SPI Controller在硬件层直接拦截——不送到Flash芯片直接返回错误。PR寄存器本身可以被锁定。PR0FLOCKD位HSFS第14位置1后所有PR寄存器配置再也改不了——直到下次平台复位。BIOS在PEI阶段设好PR0保护Boot Block然后上锁。第二层SMM BIOS Lock EnableBLEHSFS的BLE位一旦置1只有SMM模式能改写保护相关配置寄存器。非SMM代码Ring 0的OS内核、DXE驱动想碰这些寄存器——SPI Controller直接忽略写操作连异常都不抛。// IntelSiliconPkg/.../Spi/Smm/PchSpiSmm.c// SMM模式下锁定Flash配置VOID EFIAPIPchSpiSetBiosWriteProtect(VOID){// 1. 先设置Protected RangePchSpiSetProtectedRange(PROTECTED_RANGE_BASE,PROTECTED_RANGE_LIMIT);// 2. 锁定PR配置HsfstsValuePchSpiRead32(PCH_SPI_HSFS);HsfstsValue|B_PCH_SPI_HSFS_PR0FLOCKDN;PchSpiWrite32(PCH_SPI_HSFS,HsfstsValue);// 3. 置BLE位——只有SMM能改配置HsfstsValue|B_PCH_SPI_HSFS_BLE;PchSpiWrite32(PCH_SPI_HSFS,HsfstsValue);// 4. 最后锁定整个Flash配置不可逆HsfstsValue|B_PCH_SPI_HSFS_FLOCKDN;PchSpiWrite32(PCH_SPI_HSFS,HsfstsValue);}注意顺序先锁BLE限制谁可以改再锁FLOCKDN彻底不允许改。反过来就完了——FLOCKDN锁了所有HSFS配置位BLE再也改不了。第三层Boot Guard / BIOS GuardIntel Boot Guard是PCH硬件级启动验证。不是防止写Flash而是**“验证你写的Flash对不对”**。生产线上OEM把公钥Hash烧入PCH一次性可编程熔丝OTP Fuse上电时PCH硬件自动读取Flash Descriptor定位Initial Boot BlockIBB用熔丝里的公钥Hash验证IBB的RSA签名签名不对PCH拒绝启动。屏幕不亮风扇转但什么都看不到踩坑BLE位被前一个驱动锁死了调试UEFI Capsule Update流程。Flash更新在DXE阶段执行刷入新固件前要临时解除写保护。每次运行到Flash Erase命令时HSFS的FCERR位跳起来。排查前一个DXE驱动OEM的BIOS Guard初始化模块在SMM模式下调用了Lock函数把PR0锁了、设了BLE但没在退出前恢复。Capsule Update在非SMM模式下运行无权改动PR寄存器。解决在Capsule Update流程里加SMI Handler。需要解锁时触发SMI让SMM代码临时清除PR0刷完再触发SMI恢复保护。所有Flash写保护的管理必须收敛到SMM。八、实战踩坑总结坑现象根因经验BIOS bin直接烧Flash主板亮但ME挂黄叹号、网络不通ME Region和GbE Region是空的必须用FIT打包完整ImageFREG边界没对齐S3恢复时PEI加载Recovery FV挂死Limit少算一个扇区后面全返回0xFFBase/Limit必须对齐到扇区边界国产Flash SFDP说谎Quad Read第3字节开始全是垃圾IO2引脚没切到数据模式先Dual Read验证再切QuadNVRAM写不进去BIOS Setup改了保存后重启又回来FPRR覆盖了NVRAM variable区域Flash保护不会报错只静默丢弃写命令Capsule Update失败FCERR位置位前一个DXE驱动在SMM里锁了PR0和BLEFlash写保护管理收敛到SMMTop Swap区太小进了Recovery Shell但没Flash驱动只放了64KB Shell没塞Flash驱动至少128KB带完整Flash驱动最后一条经验Write Protect不报错是设计如此不是bug。SPI协议里写保护本身就不该返回错误——它只是silently discard write command。正确做法是Write Verify写完了回读确认。源码路径索引内容关键文件SPI协议定义MdePkg/Include/Protocol/Spi.hSPI Controller寄存器定义Silicon/Intel/CoffeelakeSiliconPkg/Pch/Include/Register/PchRegsSpi.hFlash读写库IntelSiliconPkg/Feature/PcieSpiLib/SpiFlashCommon.cSMM写保护配置IntelSiliconPkg/.../Spi/Smm/PchSpiSmm.cFV Header定义MdePkg/Include/Pi/PiFirmwareVolume.hFlash Descriptor结构edk2-platforms/Silicon/Intel/*/Pch/Include/FirmwareDescriptor.h规范Intel PCH EDS SPI Controller章节 / JESD216 SFDP标准