ExpansionOption ROM
必读资料
什么是Expansion/option rom?
- Expansion rom是pci/pcie设备可选的一个外接的eprom芯片,其中用来存储相应pci设备的初始化代码或者系统启动代码(比如pxe或者pci boot)。
- BIOS在POST(Power-on Self Test)阶段,会扫描pci设备是否有expansion_rom,有的话将其拷贝到ram中执行。
- 在PCI规范中称为expansion rom,在BIOS术语里面称为option rom。
- PCI Agent设备配置空间 0x30地址,4字节 为expansion rom信息
作用是什么?
在《PCIE体系结构》P46中解释:
有些PCI设备在处理器还没运行系统之前,比如在BIOS中,就需要完成基本配置,如PC的显卡,键盘,USB等。 为了实现这个”预先执行“功能,PCI设备需要提供一段ROM程序,处理器在初始化时运行这段ROM程序来初始化相关设备。 Expansion ROM 记载了ROM程序地址
总结:系统启动前的预初始化程序,存储在PCI设备EEPROM中,系统上电时执行这部分代码来初始化基础外设。
注:Expansion ROM和BAR空间互斥使用,同一时间只能使能一个。
分布结构和使用
寄存器结构如上图:
- bit[31:11]是PCIE域基地址
- [10:1]Reserved
- [0]使能位
怎么知道PCIE设备是否包含Expansion ROM?
并不是所有PCIe设备都包含Expansion ROM,所以就需要软件进行判定。
《PCIe Expansion Roms》中讲了:
判断长度是否为0即可。
怎么知道Expansion ROM的空间大小
在Expansion ROM的判定阶段,有一个过程是向Expansion ROM Base Address Register写入0xFFFF FFFF,然后读取它的值,利用这个值就可以判定出Expansion ROM的空间大小。
假设写入0xFFFF FFFF之后,读出的值是0xFFFE_0000/0xFFFE_0001. bit[0]代表是否使能
那么第17位就是非0值的最低位,那么Expansion ROM的大小就是2^17 Bytes= 128KB。
PCI文档规定,ROM最大可为16MB,即bit[31:25]必须是可读/可写的。
Expansion ROM的初始化过程
BIOS的POST阶段,扫描并执行pci设备的expansion的过程大概分以下几步:
- 首先判断pci设备是否实现“Expansion rom base address”寄存器,有才可以用:
如何实现? 和BAR一样写入0xFFFF_FFFF到这个配置空间,然后可以读取到长度,非0代表有数据; - 如果有实现了expansion的基址寄存器,则配置和使能expansion rom,然后查找expansion rom是否有”AA55”的标示字符,如果有则说明设备有真实的expansion rom芯片存在;
- 如果expansion rom已经存在,则扫描是否有适合本设备和本CPU架构的image代码存在;
- 如果有适合本环境的image代码存在,则把相应的代码拷贝到ram的合适位置,并跳入header format中指定的初始化入口执行;
- 最后关闭expansion rom的使能。
在Linux读取设备的Expansion ROM方法
echo 1 > /sys/bus/pci/devices/0000:01:00.0/enable # 需要是能command的memory位才能读写。
echo 1 > /sys/bus/pci/devices/0000:01:00.0/rom
cat /sys/bus/pci/devices/0000:01:00.0/rom > /tmp/image.rom
echo 0 > /sys/bus/pci/devices/0000:01:00.0/rom
echo 0 > /sys/bus/pci/devices/0000:01:00.0/enable
Expansion rom解析工具: https://github.com/awilliam/rom-parser
Linux代码读取过程
这部分推荐参考sysfs中读取optionRom代码,没必要自己写。
参考:driver/pci/pci-sysfs.c即可
// 1.使能 《PCIE体系结构》P48:command寄存器只有在使能IO/MEMORY后才能访问BAR和Expansion ROM空间pci_enable_device
// 2.获取资源空间,因为这里地址是PCI域地址,所以不能直接读取配置空间
// 《PCIE体系结构》P48:不能使用读取配置空间操作来访问这块地址,必须用
// pci_dev->resource[6].start或者pci_resource_start 系列函数
// struct pci_dev中:
// struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */
// 由宏可知 0-5是bar空间,6是Expansion rom空间
for(loop=0;loop<DEVICE_COUNT_RESOURCE; ++loop){
printk("resource: start:%#llx,end:%#llx,name:%s,flags:%#lx,desc:%#lx\n",
r->start,r->end,r->name,r->flags,r->desc);
r++;
}
// 判断Expansion rom是否存在?如果存在,读取
struct pci_bus *bus;
struct pci_ops *ops;
bus = pci_dev->bus;
ops = bus->ops;
printk("check Expansion ROM"); // devfn need to be ensuere
ops->write(bus,0,0x30,4,0xffffffff);
ops->read(bus,0,0x30,4,&tmp_val);
printk("Expansion ROM len is %#x\n",tmp_val); // 可以读出长度
ops->write(bus,0,0x30,4,0x00000000); // 用完之后记得释放