PCI
PCI总线
PCI (Peripheral Component Interconnect) 总线被定义为建立高性能和低成本的本地总线,该总线在几代产品中都会持续保留。 通过结合从132 MB/s (33 MHz时32位) 到528 MB/s (66 MHz时64位) 以及5伏和3.3伏信令环境的透明升级路径,PCI总线同时满足低端桌面系统和高端局域网服务器的需求。 PCI总线组件和附加卡接口是独立于处理器的,从而可以有效地过渡到未来的处理器,并可以与多种处理器体系结构一起使用。 PCI总线的缺点是它可以驱动的电气负载数量有限。 单个PCI总线最多可以驱动10个负载。(请记住,在计算总线上的负载数时,一个连接器算作一个负载,而PCI设备算作另一个负载,有时甚至是两个。)
配置空间
PCI规范通过单独的配置地址空间,提供了PCI总线上每个设备 (或目标) 的完全软件驱动初始化和配置。 除主机总线桥外,所有PCI设备都需要为此提供256字节的配置寄存器。
配置读/写周期用于访问每个目标设备的配置空间。 当其IDSEL信号被断言时,在配置访问期间选择目标。 IDSEL充当经典的 “片选(chip select)” 信号。 在配置周期地址的阶段,处理器可以通过在地址行2至7 (AD[7..2]) 和字节使能行上放置所需的寄存器号来对配置空间内的64个32位寄存器中的一个进行寻址。
PCI设备本质上是小端序,这意味着所有多个字节字段在较低地址处的有效值最小。 这需要大端处理器 (例如Power PC) 对从PCI设备读取或写入的数据执行正确的字节交换,包括对配置地址空间的任何访问。
系统必须提供一种允许访问PCI配置空间的机制,因为大多数CPU没有任何这样的机制。 此任务通常由主机到PCI桥 (Host Bridge) 执行。 PCI定义了两种不同的机制,以允许软件生成所需的配置访问。 配置机制 #1是首选方法,而机制 #2是为了向后兼容而提供的。 这里将仅描述配置机制 #1,因为它是将来将使用的唯一访问机制。
配置空间访问机制 #1
使用两个32位I/O位置,第一个位置 (0xCF8
) 称为CONFIG_ADDRESS,第二个 (0xCFC
) 称为CONFIG_DATA。 CONFIG_ADDRESS指定访问所需的配置地址,而对CONFIG_DATA的访问实际上将生成配置访问,并将数据传输到CONFIG_DATA寄存器或从CONFIG_DATA寄存器传输数据。
CONFIG_ADDRESS是一个32位寄存器,格式如下图所示。 位31是一个使能标志,用于确定何时应将对CONFIG_DATA的访问转换为配置周期。 位23到16允许配置软件在系统中选择特定的PCI总线。 位15至11选择PCI总线上的特定设备。 位10至8选择设备中的特定功能 (如果设备支持多种功能)。
最低有效字节选择通过此方法可用的256字节配置空间中的偏移量。 由于所有读取和写入都必须是32位并对齐以在所有实现上工作,因此CONFIG_ADDRESS的两个最低位必须始终为零,其余六位允许你选择64个32位字中的每一个。 如果你不需要全部32位,则必须通过对齐地址,然后屏蔽和移动应答来执行软件中的未对齐访问。
Bit 31 | Bits 30-24 | Bits 23-16 | Bits 15-11 | Bits 10-8 | Bits 7-0 |
---|---|---|---|---|---|
Enable Bit | Reserved | Bus Number | Device Number | Function Number | Register Offset1 |
1 寄存器偏移量必须指向连续的DWORDs,即位1:0始终为0b00 (它们仍然是寄存器偏移量的一部分)。
下面的代码段说明了使用配置机制 #1从配置空间中读取16位字段。 请注意,此段 outl (端口,值) 和inl (端口) 函数是指OUTL和INL Pentium汇编语言指令。
uint16_t pciConfigReadWord(uint8_t bus, uint8_t slot, uint8_t func, uint8_t offset) {
uint32_t address;
uint32_t lbus = (uint32_t)bus;
uint32_t lslot = (uint32_t)slot;
uint32_t lfunc = (uint32_t)func;
uint16_t tmp = 0;
// Create configuration address as per Figure 1
address = (uint32_t)((lbus << 16) | (lslot << 11) |
(lfunc << 8) | (offset & 0xFC) | ((uint32_t)0x80000000));
// Write out the address
outl(0xCF8, address);
// Read in the data
// (offset & 2) * 8) = 0 will choose the first word of the 32-bit register
tmp = (uint16_t)((inl(0xCFC) >> ((offset & 2) * 8)) & 0xFFFF);
return tmp;
}
当配置访问尝试选择不存在的设备时,主机网桥将毫无错误地完成访问,在写入时丢弃所有数据,并在读取时返回所有数据。 下面的代码段说明了对不存在设备的读取。
uint16_t pciCheckVendor(uint8_t bus, uint8_t slot) {
uint16_t vendor, device;
/* Try and read the first configuration register. Since there are no
* vendors that == 0xFFFF, it must be a non-existent device. */
if ((vendor = pciConfigReadWord(bus, slot, 0, 0)) != 0xFFFF) {
device = pciConfigReadWord(bus, slot, 0, 2);
. . .
} return (vendor);
}
配置空间访问机制 #2
此配置空间访问机制在PCI版本2.0中已弃用。 这意味着它仅可能存在于1992 (引入PCI 1.0时)到1993年 (引入PCI 2.0时)左右的硬件上,这将其限制在80486和早期的奔腾主板上。
对于访问机制 #2,0xCF8
处的IO端口是8位端口,用于启用/禁用访问机制并设置功能号。 它具有以下格式:
Bits 7-4 | Bits 3-1 | Bit 0 |
---|---|---|
Key (0 = access mechanism disabled, non-zero = access mechanism enabled) | Function number | Special cycle enabled if set |
The IO port at 0xCFA
(the "Forwarding Register") is also an 8-bit port, and is used to set the bus number for subsequent PCI configuration space accesses.
启用访问机制后; 对IO端口 0xC000
到 0xCFFF
的访问用于访问PCI配置空间。 IO端口号的格式如下:
Bits 15-12 | Bits 11-8 | Bits 7-2 | Bits 1-0 |
---|---|---|---|
Must be 1100b | Device number | Register index | Must be zero |
请注意,这将系统限制为每个PCI总线16个设备。
内存映射的PCI配置空间访问
PCI Express引入了一种访问PCI配置空间的新方法,它只是内存映射,并且不使用IO端口。 PCI Express 中描述了这种访问机制。
请注意,确实提供内存映射访问机制的系统也需要支持PCI访问机制#1以实现向后兼容性。
检测配置空间访问机制
一般来说有4种情况:
- 计算机不支持PCI (计算机太旧,或者你的OS运行在取代PCI后将来的某个时间)
- 计算机支持机制 #2
- 计算机支持机制 #1,但不支持内存映射访问机制
- 计算机同时支持 #1机制和内存映射访问机制
对于BIOS系统,int 0x1A,AX=0xB101
会告诉你系统是使用机制#1还是机制#2。 如果此功能不存在,则无法确定计算机是否支持PCI。 如果它说支持机制#1,你将无法确定是否也支持内存映射访问机制。
对于UEFI系统,最好先假设不支持机制#2; 你可以通过检查 “PCI总线支持” 协议是否存在来测试计算机是否支持PCI。 如果支持PCI,则没有简单的方法来确定 (例如) 计算机是否支持机制#1。
对于BIOS和UEFI系统,你都可以检查ACPI表以确定是否支持内存映射访问机制。
还有一些其他情况未覆盖到 (例如,你不知道是否支持机制#1或#2,尽管尝试了以上所有)。 对于这些情况,剩下的唯一选择是手动探测。这意味着2个特定的测试-是否支持机制#1,如果不支持那机制#2是否支持。 请注意,手动探测有风险; 因为如果没有PCI (例如系统只有ISA),IO端口访问可能会导致未定义的行为 (尤其是在ISA总线忽略IO端口地址最高6位的系统上,其中访问IO端口 0xCF8
与访问IO端口 0xF8
相同)。
PCI设备结构
PCI规范定义了256字节配置空间寄存器的组织,并为该空间施加了特定的模板。 图2和3显示了256字节配置空间的布局。 所有符合PCI的设备必须支持供应商ID、设备ID、命令和状态、修订ID、类代码和标题类型字段。 根据设备功能,其他寄存器的实现是可选的。
公共标题字段
以下字段描述对于所有标头类型都是通用的:
- Device ID: 标识特定设备。其中有效id由供应商分配。
- Vendor ID: 标识设备的制造商。其中有效id由pci-sig分配 (列表在此处)以确保唯一性,并且
0xFFFF
是无效值,将在读取对不存在设备的配置空间寄存器的访问时返回。 - Status: 用于记录PCI总线相关事件的状态信息的寄存器。
- Command: 提供对设备生成和响应PCI周期的能力的控制。保证所有设备支持的唯一功能,当将0写入此寄存器时,除配置空间访问外,所有访问都将断开设备与PCI总线的连接。
- Class Code: 指定设备执行的功能类型的只读寄存器。
- Subclass: 指定设备执行的特定功能的只读寄存器。
- Prog IF(Programming Interface Byte): 只读寄存器,它指定设备具有的寄存器级编程接口 (如果有的话)。
- Revision ID: 指定特定设备的修订标识符。其中有效id由供应商分配。
- BIST: 表示该状态并允许控制设备BIST (内置自检)。
- Header Type: 标识标头其余部分的布局,从标头的字节
0x10
开始,还指定设备是否具有多种功能。 其中0x0
的值指定通用设备,0x1
的值指定PCI到PCI的桥,而0x2
的值指定CardBus桥。 如果设置了此寄存器的位7,则该设备具有多种功能; 否则,它是单个功能设备。 - Latency Timer: 以PCI总线时钟为单位指定延迟计时器。
- Cache Line Size: 以32位单位指定系统缓存行大小。 设备可以限制它可以支持的缓存行大小的数量,如果将不受支持的值写入此字段,则设备的行为就像写入了值0一样。
Header Type 0x0
如果Header类型为 0x0
,则此表适用。 (Figure 2)
Register | Offset | Bits 31-24 | Bits 23-16 | Bits 15-8 | Bits 7-0 |
---|---|---|---|---|---|
0x0 | 0x0 | Device ID | Vendor ID | ||
0x1 | 0x4 | Status | Command | ||
0x2 | 0x8 | Class code | Subclass | Prog IF | Revision ID |
0x3 | 0xC | BIST | Header type | Latency Timer | Cache Line Size |
0x4 | 0x10 | Base address #0 (BAR0) | |||
0x5 | 0x14 | Base address #1 (BAR1) | |||
0x6 | 0x18 | Base address #2 (BAR2) | |||
0x7 | 0x1C | Base address #3 (BAR3) | |||
0x8 | 0x20 | Base address #4 (BAR4) | |||
0x9 | 0x24 | Base address #5 (BAR5) | |||
0xA | 0x28 | Cardbus CIS Pointer | |||
0xB | 0x2C | Subsystem ID | Subsystem Vendor ID | ||
0xC | 0x30 | Expansion ROM base address | |||
0xD | 0x34 | Reserved | Capabilities Pointer | ||
0xE | 0x38 | Reserved | |||
0xF | 0x3C | Max latency | Min Grant | Interrupt PIN | Interrupt Line |
如果Header类型为 0x0
,则以下字段说明适用:
- CardBus CIS Pointer: 指向卡信息结构,并由在CardBus和PCI之间共享硅的设备使用。
- Interrupt Line: 指定设备的中断引脚连接到系统中断控制器的哪个输入,并由使用中断引脚的任何设备实现。对于x86架构,此寄存器对应于PIC IRQ编号0-15 (而不是I/O APIC IRQ编号),并且
0xFF
的值不定义连接。
- Interrupt Pin: 指定设备使用哪个中断引脚。其中
0x1
的值为INTA #,0x2
为INTB #,0x3
为INTC #,0x4
为INTD #,0x0
表示设备不使用中断引脚。
- Max Latency: 只读寄存器,指定设备需要访问PCI总线的频率 (以1/4微秒为单位)。
- Min Grant: 一种只读寄存器,它以1/4微秒为单位指定设备所需的突发周期长度 (假设为33 MHz时钟速率)。
- Capabilities Pointer: 指向设备实现的新功能的链接列表 (即,该功能的配置空间的偏移量)。 如果状态寄存器的位4 (功能列表位) 设置为1,则使用。 底部的两位是保留的,在使用指针访问配置空间之前应该被屏蔽。
Header Type 0x1 (PCI-to-PCI bridge)
如果Header类型为 0x1
(PCI到PCI桥),则此表适用 (图3)
Register | Offset | Bits 31-24 | Bits 23-16 | Bits 15-8 | Bits 7-0 |
---|---|---|---|---|---|
0x0 | 0x0 | Device ID | Vendor ID | ||
0x1 | 0x4 | Status | Command | ||
0x2 | 0x8 | Class code | Subclass | Prog IF | Revision ID |
0x3 | 0xC | BIST | Header type | Latency Timer | Cache Line Size |
0x4 | 0x10 | Base address #0 (BAR0) | |||
0x5 | 0x14 | Base address #1 (BAR1) | |||
0x6 | 0x18 | Secondary Latency Timer | Subordinate Bus Number | Secondary Bus Number | Primary Bus Number |
0x7 | 0x1C | Secondary Status | I/O Limit | I/O Base | |
0x8 | 0x20 | Memory Limit | Memory Base | ||
0x9 | 0x24 | Prefetchable Memory Limit | Prefetchable Memory Base | ||
0xA | 0x28 | Prefetchable Base Upper 32 Bits | |||
0xB | 0x2C | Prefetchable Limit Upper 32 Bits | |||
0xC | 0x30 | I/O Limit Upper 16 Bits | I/O Base Upper 16 Bits | ||
0xD | 0x34 | Reserved | Capability Pointer | ||
0xE | 0x38 | Expansion ROM base address | |||
0xF | 0x3C | Bridge Control | Interrupt PIN | Interrupt Line |
Header Type Register
下面是Header类型寄存器的布局:
Bit 7 | Bits 6-0 |
---|---|
MF | Header Type |
- MF - If MF = 1 Then this device has multiple functions.
- Header Type -
0x0
Standard Header -0x1
PCI-to-PCI Bridge -0x2
CardBus Bridge
BIST Register
以下是BIST寄存器的布局:
Bit 7 | Bit 6 | Bits 4-5 | Bits 0-3 |
---|---|---|---|
BIST Capable | Start BIST | Reserved | Completion Code |
- BIST Capable - 将返回1设备支持BIST。
- Start BIST - 设置为1时,将调用BIST。 BIST完成后,此位将重置。 如果BIST在2秒钟后未完成,则系统软件应使设备发生故障。
- Completion Code - 如果测试成功完成,则在BIST执行后返回0。
Header Type 0x2 (PCI-to-CardBus bridge)
如果Header类型为 0x2
(PCI-to-CardBus桥),则此表适用
Register | Offset | Bits 31-24 | Bits 23-16 | Bits 15-8 | Bits 7-0 |
---|---|---|---|---|---|
0x0 | 0x0 | Device ID | Vendor ID | ||
0x1 | 0x4 | Status | Command | ||
0x2 | 0x8 | Class code | Subclass | Prog IF | Revision ID |
0x3 | 0xC | BIST | Header type | Latency Timer | Cache Line Size |
0x4 | 0x10 | CardBus Socket/ExCa base address | |||
0x5 | 0x14 | Secondary status | Reserved | Offset of capabilities list | |
0x6 | 0x18 | CardBus latency timer | Subordinate bus number | CardBus bus number | PCI bus number |
0x7 | 0x1C | Memory Base Address 0 | |||
0x8 | 0x20 | Memory Limit 0 | |||
0x9 | 0x24 | Memory Base Address 1 | |||
0xA | 0x28 | Memory Limit 1 | |||
0xB | 0x2C | I/O Base Address 0 | |||
0xC | 0x30 | I/O Limit 0 | |||
0xD | 0x34 | I/O Base Address 1 | |||
0xE | 0x38 | I/O Limit 1 | |||
0xF | 0x3C | Bridge Control | Interrupt PIN | Interrupt Line | |
0x10 | 0x40 | Subsystem Vendor ID | Subsystem Device ID | ||
0x11 | 0x44 | 16-bit PC Card legacy mode base address |
Command Register
下面是命令寄存器的布局:
Bits 11-15 | Bit 10 | Bit 9 | Bit 8 | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|---|---|---|---|
Reserved | Interrupt Disable | Fast Back-to-Back Enable | SERR# Enable | Reserved | Parity Error Response | VGA Palette Snoop | Memory Write and Invalidate Enable | Special Cycles | Bus Master | Memory Space | I/O Space |
- Interrupt Disable - 如果设置为1,则禁用设备INTx# 信号的断言; 否则,启用信号的断言。
- Fast Back-Back Enable - 如果设置为1,则表示允许设备生成快速背靠背事务; 否则,仅允许同一代理进行快速背靠背事务。
- SERR# Enable - 如果设置为1,则启用SERR# 驱动程序; 否则,该驱动程序将被禁用。
- Bit 7 - 在PCI本地总线规范的修订版3.0中,该位被硬连线到0。 在该规范的早期版本中,该位被设备使用,并且可能已硬连线到0,1,或实现为读/写位。
- Parity Error Response - 如果设置为1,则当检测到奇偶校验错误时,设备将采取正常行动; 否则,当检测到错误时,设备将设置状态寄存器的位15 (检测到奇偶校验错误状态位),但不会断言PERR # (奇偶校验错误) 引脚,并将继续正常运行。
- VGA Palette Snoop - 如果设置为1,则设备不会响应调色板寄存器写入,并且会监听数据; 否则,设备将像所有其他访问一样执行调色板写入访问。
- Memory Write and Invalidate Enable - 如果设置为1,则设备可以生成内存写入和无效命令; 否则,必须使用内存写入命令。
- Special Cycles - 如果设置为1,则设备可以监视特殊的循环操作; 否则,设备将忽略它们。
- Bus Master - 如果设置为1,则设备可以充当总线主控器; 否则,设备将无法生成PCI访问。
- Memory Space - 如果设置为1,则设备可以响应内存空间访问; 否则,设备的响应将被禁用。
- I/O Space - 如果设置为1,则设备可以响应I/O空间访问; 否则,设备的响应将被禁用。
如果内核配置了设备的BAR,则内核还必须启用位0和1才能激活。
Status Register
以下是状态寄存器的布局:
Bit 15 | Bit 14 | Bit 13 | Bit 12 | Bit 11 | Bits 9-10 | Bit 8 | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bits 0-2 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Detected Parity Error | Signaled System Error | Received Master Abort | Received Target Abort | Signaled Target Abort | DEVSEL Timing | Master Data Parity Error | Fast Back-to-Back Capable | Reserved | 66 MHz Capable | Capabilities List | Interrupt Status | Reserved |
- Detected Parity Error - 每当设备检测到奇偶校验错误时,即使禁用奇偶校验错误处理,该位也将设置为1。
- Signalled System Error - 每当设备断言SERR # 时,此位将设置为1。
- Received Master Abort - 每当主设备的事务 (特殊循环事务除外) 以master-Abort终止时,该位将被设置为1。
- Received Target Abort - 每当它的事务以Target-Abort终止时,主设备将此位设置为1。
- Signalled Target Abort - 每当目标设备终止使用目标中止的事务时,此位将设置为1。
- DEVSEL Timing - 仅读取表示设备将为除配置空间读写之外的任何总线命令断言DEVSEL# 的最慢时间的位。 其中
0x0
的值表示快速定时,0x1
的值表示中等定时,而0x2
的值表示慢速定时。 - Master Data Parity Error - 仅当满足以下条件时才设置此位。总线代理在读取时断言PERR# 或在写入时观察到PERR #的断言,代理设置该位充当发生错误的操作的总线主控器,并且命令寄存器的位6 (奇偶错误响应位) 设置为1。
- Fast Back-to-Back Capable - 如果设置为1,则设备可以接受不是来自同一代理的快速背靠背交易; 否则,只能从同一代理接受交易。
- Bit 6 - 在PCI本地总线规范的修订版3.0中,保留该位。 在规范的修订2.1中,该位用于指示设备是否支持用户可定义的特征。
- 66 MHz Capable - 如果设置为1,则该设备能够以66 MHz运行; 否则,该设备将以33 MHz运行。
- Capabilities List - 如果设置为1,则设备将在offset
0x34
处为新功能链接列表实现指针; 否则,链接列表不可用。 - Interrupt Status - 表示设备的INTx# 信号的状态。 如果设置为1,并且命令寄存器的位10 (中断禁用位) 设置为0,则信号将被断言; 否则,信号将被忽略。
回想一下,PCI设备遵循小端序。 较低的地址包含字段的最低有效部分。 操纵此结构的软件必须特别注意,端序顺序遵循PCI设备,而不是cpu。
Base Address Registers
基址寄存器 (或条) 可用于保存设备使用的内存地址,或端口地址的偏移量。 通常,内存地址栏需要位于物理ram中,而I/O空格栏可以驻留在任何内存地址 (甚至超出物理内存)。 为了区分它们,你可以检查最低位的值。 下表描述了两种类型的条形:
Bits 31-4 | Bit 3 | Bits 2-1 | Bit 0 |
---|---|---|---|
16-Byte Aligned Base Address | Prefetchable | Type | Always 0 |
Bits 31-2 | Bit 1 | Bit 0 |
---|---|---|
4-Byte Aligned Base Address | Reserved | Always 1 |
内存空格键布局的类型字段指定基本寄存器的大小以及可以在内存中映射的位置。 如果它的值为 0x0
,则基本寄存器为32位宽,可以映射到32位内存空间中的任何位置。 值为 0x2
表示基寄存器为64位宽,并且可以映射到64位内存空间中的任何位置 (64位基址寄存器消耗2个可用的基址寄存器)。 值 0x1
保留为PCI本地总线规范的修订版3.0。 在早期版本中,它用于支持1mb以下的内存空间 (16位宽的基本寄存器,可以映射到16位内存空间中的任何位置)。
当基址寄存器被标记为预取时,这意味着该区域没有读取副作用 (从该内存范围读取不会改变任何状态),并且允许CPU从该内存区域缓存负载并以突发方式读取它 (通常是缓存行大小)。 还允许硬件将重复存储合并到同一地址的一个存储中。 如果你正在使用分页并希望获得最大的性能,则应将可预取的MMIO区域映射为WT (写入),而不是UC (不可缓存)。 在x86上,帧缓冲区是例外,它们应该几乎总是被映射为WC (写合并)。
Address and size of the BAR
当你想要检索条的实际基址时,请务必屏蔽较低的位。 对于16位内存空格键,你计算 (BAR[x] & 0xFFF0)
。 对于32位内存空格键,你计算 (BAR[x] & 0xFFFFFFF0)
。 对于64位内存空格键,你计算I/O空格键的 (BAR[x] & 0xfffff0) ((BAR[x 1] & 0xffffffff) << 32)
,你可以计算 (BAR[x] & 0xFFFFFFFC)
。
要确定PCI设备所需的地址空间量,必须保存条的原始值,将所有1的值写入寄存器,然后将其读回。 然后可以通过掩蔽信息位,执行按位不 (C中的 “〜”) 并将值递增1来确定存储器的量。 然后应该恢复条的原始值。 条形寄存器自然对齐,因此你只能修改设置的位。 例如,如果设备使用16 MB,则BAR0将填充0xFF000000 (解码后为0x1000000),并且你只能修改较高的8位。 20150101180929/https://www.pcisig.com/reflector/ msg05233.html
Class Codes
类代码,子类和Prog IF寄存器分别用于标识设备的类型,设备的功能和设备的寄存器级编程接口。
下表详细介绍了大多数已知的设备类型和功能:
Class Code | Subclass | Prog IF |
---|---|---|
0x0 - Unclassified | 0x0 - Non-VGA-Compatible Unclassified Device | -- |
0x1 - VGA-Compatible Unclassified Device | -- | |
0x1 - Mass Storage Controller | 0x0 - SCSI Bus Controller | -- |
0x1 - IDE Controller | 0x0 - ISA Compatibility mode-only controller | |
0x5 - PCI native mode-only controller | ||
0xA - ISA Compatibility mode controller, supports both channels switched to PCI native mode | ||
0xF - PCI native mode controller, supports both channels switched to ISA compatibility mode | ||
0x80 - ISA Compatibility mode-only controller, supports bus mastering | ||
0x85 - PCI native mode-only controller, supports bus mastering | ||
0x8A - ISA Compatibility mode controller, supports both channels switched to PCI native mode, supports bus mastering | ||
0x8F - PCI native mode controller, supports both channels switched to ISA compatibility mode, supports bus mastering | ||
0x2 - Floppy Disk Controller | -- | |
0x3 - IPI Bus Controller | -- | |
0x4 - RAID Controller | -- | |
0x5 - ATA Controller | 0x20 - Single DMA | |
0x30 - Chained DMA | ||
0x6 - Serial ATA Controller | 0x0 - Vendor Specific Interface | |
0x1 - AHCI 1.0 | ||
0x2 - Serial Storage Bus | ||
0x7 - Serial Attached SCSI Controller | 0x0 - SAS | |
0x1 - Serial Storage Bus | ||
0x8 - Non-Volatile Memory Controller | 0x1 - NVMHCI | |
0x2 - NVM Express | ||
0x80 - Other | -- | |
0x2 - Network Controller | 0x0 - Ethernet Controller | -- |
0x1 - Token Ring Controller | -- | |
0x2 - FDDI Controller | -- | |
0x3 - ATM Controller | -- | |
0x4 - ISDN Controller | -- | |
0x5 - WorldFip Controller | -- | |
0x6 - PICMG 2.14 Multi Computing Controller | -- | |
0x7 - Infiniband Controller | -- | |
0x8 - Fabric Controller | -- | |
0x80 - Other | -- | |
0x3 - Display Controller | 0x0 - VGA Compatible Controller | 0x0 - VGA Controller |
0x1 - 8514-Compatible Controller | ||
0x1 - XGA Controller | -- | |
0x2 - 3D Controller (Not VGA-Compatible) | -- | |
0x80 - Other | -- | |
0x4 - Multimedia Controller | 0x0 - Multimedia Video Controller | -- |
0x1 - Multimedia Audio Controller | -- | |
0x2 - Computer Telephony Device | -- | |
0x3 - Audio Device | -- | |
0x80 - Other | -- | |
0x5 - Memory Controller | 0x0 - RAM Controller | -- |
0x1 - Flash Controller | -- | |
0x80 - Other | -- | |
0x6 - Bridge | 0x0 - Host Bridge | -- |
0x1 - ISA Bridge | -- | |
0x2 - EISA Bridge | -- | |
0x3 - MCA Bridge | -- | |
0x4 - PCI-to-PCI Bridge | 0x0 - Normal Decode | |
0x1 - Subtractive Decode | ||
0x5 - PCMCIA Bridge | -- | |
0x6 - NuBus Bridge | -- | |
0x7 - CardBus Bridge | -- | |
0x8 - RACEway Bridge | 0x0 - Transparent Mode | |
0x1 - Endpoint Mode | ||
0x9 - PCI-to-PCI Bridge | 0x40 - Semi-Transparent, Primary bus towards host CPU | |
0x80 - Semi-Transparent, Secondary bus towards host CPU | ||
0x0A - InfiniBand-to-PCI Host Bridge | -- | |
0x80 - Other | -- | |
0x7 - Simple Communication Controller | 0x0 - Serial Controller | 0x0 - 8250-Compatible (Generic XT) |
0x1 - 16450-Compatible | ||
0x2 - 16550-Compatible | ||
0x3 - 16650-Compatible | ||
0x4 - 16750-Compatible | ||
0x5 - 16850-Compatible | ||
0x6 - 16950-Compatible | ||
0x1 - Parallel Controller | 0x0 - Standard Parallel Port | |
0x1 - Bi-Directional Parallel Port | ||
0x2 - ECP 1.X Compliant Parallel Port | ||
0x3 - IEEE 1284 Controller | ||
0xFE - IEEE 1284 Target Device | ||
0x2 - Multiport Serial Controller | -- | |
0x3 - Modem | 0x0 - Generic Modem | |
0x1 - Hayes 16450-Compatible Interface | ||
0x2 - Hayes 16550-Compatible Interface | ||
0x3 - Hayes 16650-Compatible Interface | ||
0x4 - Hayes 16750-Compatible Interface | ||
0x4 - IEEE 488.1/2 (GPIB) Controller | -- | |
0x5 - Smart Card Controller | -- | |
0x80 - Other | -- | |
0x8 - Base System Peripheral | 0x0 - PIC | 0x0 - Generic 8259-Compatible |
0x1 - ISA-Compatible | ||
0x2 - EISA-Compatible | ||
0x10 - I/O APIC Interrupt Controller | ||
0x20 - I/O(x) APIC Interrupt Controller | ||
0x01 - DMA Controller | 0x00 - Generic 8237-Compatible | |
0x01 - ISA-Compatible | ||
0x02 - EISA-Compatible | ||
0x02 - Timer | 0x00 - Generic 8254-Compatible | |
0x01 - ISA-Compatible | ||
0x02 - EISA-Compatible | ||
0x03 - HPET | ||
0x3 - RTC Controller | 0x0 - Generic RTC | |
0x1 - ISA-Compatible | ||
0x4 - PCI Hot-Plug Controller | -- | |
0x5 - SD Host controller | -- | |
0x6 - IOMMU | -- | |
0x80 - Other | -- | |
0x9 - Input Device Controller | 0x0 - Keyboard Controller | -- |
0x1 - Digitizer Pen | -- | |
0x2 - Mouse Controller | -- | |
0x3 - Scanner Controller | -- | |
0x4 - Gameport Controller | 0x0 - Generic | |
0x10 - Extended | ||
0x80 - Other | -- | |
0xA - Docking Station | 0x0 - Generic | -- |
0x80 - Other | -- | |
0xB - Processor | 0x0 - 386 | -- |
0x1 - 486 | -- | |
0x2 - Pentium | -- | |
0x3 - Pentium Pro | -- | |
0x10 - Alpha | -- | |
0x20 - PowerPC | -- | |
0x30 - MIPS | -- | |
0x40 - Co-Processor | -- | |
0x80 - Other | -- | |
0xC - Serial Bus Controller | 0x0 - FireWire (IEEE 1394) Controller | 0x0 - Generic |
0x10 - OHCI | ||
0x1 - ACCESS Bus Controller | -- | |
0x2 - SSA | -- | |
0x3 - USB Controller | 0x0 - UHCI Controller | |
0x10 - OHCI Controller | ||
0x20 - EHCI (USB2) Controller | ||
0x30 - XHCI (USB3) Controller | ||
0x80 - Unspecified | ||
0xFE - USB Device (Not a host controller) | ||
0x4 - Fibre Channel | -- | |
0x5 - SMBus Controller | -- | |
0x6 - InfiniBand Controller | -- | |
0x7 - IPMI Interface | 0x0 - SMIC | |
0x1 - Keyboard Controller Style | ||
0x2 - Block Transfer | ||
0x8 - SERCOS Interface (IEC 61491) | -- | |
0x9 - CANbus Controller | -- | |
0x80 - Other | -- | |
0xD - Wireless Controller | 0x0 - iRDA Compatible Controller | -- |
0x1 - Consumer IR Controller | -- | |
0x10 - RF Controller | -- | |
0x11 - Bluetooth Controller | -- | |
0x12 - Broadband Controller | -- | |
0x20 - Ethernet Controller (802.1a) | -- | |
0x21 - Ethernet Controller (802.1b) | -- | |
0x80 - Other | -- | |
0xE - Intelligent Controller | 0x0 - I20 | -- |
0xF - Satellite Communication Controller | 0x1 - Satellite TV Controller | -- |
0x2 - Satellite Audio Controller | -- | |
0x3 - Satellite Voice Controller | -- | |
0x4 - Satellite Data Controller | -- | |
0x10 - Encryption Controller | 0x0 - Network and Computing Encrpytion/Decryption | -- |
0x10 - Entertainment Encryption/Decryption | -- | |
0x80 - Other | -- | |
0x11 - Signal Processing Controller | 0x0 - DPIO Modules | -- |
0x1 - Performance Counters | -- | |
0x10 - Communication Synchronizer | -- | |
0x20 - Signal Processing Management | -- | |
0x80 - Other | -- | |
0x12 - Processing Accelerator | -- | -- |
0x13 - Non-Essential Instrumentation | -- | -- |
0x14 - 0x3F (Reserved) | -- | -- |
0x40 - Co-Processor | -- | -- |
0x41 - 0xFE (Reserved) | -- | -- |
0xFF - Unassigned Class (Vendor specific) | -- | -- |
枚举PCI总线
有3种方法可以枚举PCI总线上的设备。 第一种方法是 “蛮力”,检查每个PCI总线上的每个设备 (无论PCI总线是否存在)。 第二种方法通过在扫描时找出有效的总线号来避免大量工作,并且由于涉及递归而稍微复杂一些。 对于这两种方法,你都依赖某些东西 (固件) 来正确配置PCI总线 (设置PCI到PCI桥以将请求从一个总线转发到另一个总线)。 第三种方法就像第二种方法,除了你在做的时候配置PCI桥。
对于所有3种方法,你需要能够检查特定总线上的特定设备是否存在,以及它是否具有多功能功能。 伪代码可能看起来像这样:
void checkDevice(uint8_t bus, uint8_t device) {
uint8_t function = 0;
vendorID = getVendorID(bus, device, function);
if (vendorID == 0xFFFF) return; // Device doesn't exist
checkFunction(bus, device, function);
headerType = getHeaderType(bus, device, function);
if( (headerType & 0x80) != 0) {
// It's a multi-function device, so check remaining functions
for (function = 1; function < 8; function++) {
if (getVendorID(bus, device, function) != 0xFFFF) {
checkFunction(bus, device, function);
}
}
}
}
void checkFunction(uint8_t bus, uint8_t device, uint8_t function) {
}
请注意,如果你不检查标头类型的位7并扫描所有功能,那么某些单功能设备将为每个功能报告 “功能0” 的详细信息。
"Brute Force" Scan
对于蛮力方法,剩下的代码相对简单。 伪代码可能看起来像这样:
void checkAllBuses(void) {
uint16_t bus;
uint8_t device;
for (bus = 0; bus < 256; bus++) {
for (device = 0; device < 32; device++) {
checkDevice(bus, device);
}
}
}
对于这种方法,每总线有32个设备和256个总线,因此你调用 “checkDevice()” 8192次。
递归扫描
递归扫描的第一步是实现扫描一条总线的功能。伪代码可能看起来像这样:
void checkBus(uint8_t bus) {
uint8_t device;
for (device = 0; device < 32; device++) {
checkDevice(bus, device);
}
}
下一步是在 “checkFunction()” 中添加代码,以检测该函数是否是PCI到PCI的桥。 如果设备是PCI到PCI网桥,那么你希望从网桥的配置空间中提取 “辅助总线号”,并使用网桥另一侧的总线号调用 “checkBus()”。
伪代码可能看起来像这样:
void checkFunction(uint8_t bus, uint8_t device, uint8_t function) {
uint8_t baseClass;
uint8_t subClass;
uint8_t secondaryBus;
baseClass = getBaseClass(bus, device, function);
subClass = getSubClass(bus, device, function);
if ((baseClass == 0x6) && (subClass == 0x4)) {
secondaryBus = getSecondaryBus(bus, device, function);
checkBus(secondaryBus);
}
}
最后一步是正确处理具有多个PCI主机控制器的系统。 首先检查总线0处的设备是否为多功能设备。 如果它不是多功能设备,那么只有一个PCI主机控制器和总线0,设备0,功能0将由PCI主机控制器负责总线0。 如果是多功能设备,则总线0、设备0、功能0将由PCI主机控制器负责总线0; 总线0、设备0、功能1将由PCI主机控制器负责总线1等 (最多支持的功能数)。
伪代码可能看起来像这样:
void checkAllBuses(void) {
uint8_t function;
uint8_t bus;
headerType = getHeaderType(0, 0, 0);
if ((headerType & 0x80) == 0) {
// Single PCI host controller
checkBus(0);
} else {
// Multiple PCI host controllers
for (function = 0; function < 8; function++) {
if (getVendorID(0, 0, function) != 0xFFFF) break;
bus = function;
checkBus(bus);
}
}
}
Recursive Scan With Bus Configuration
这类似于上面的递归扫描; 除了你将PCI中的 “辅助总线” 字段设置为PCI网桥 (使用类似 setSecondaryBus (总线、设备、功能、nextBusNumber );
而不是 getSecondaryBus();
)。 但是; 如果要配置PCI总线,则还应负责配置PCI功能中的内存区域/条,并确保PCI将请求从其主总线转发到其辅助总线。
不建议在不深入了解PCI规范的情况下编写代码来支持这一点; 而且,如果你对PCI规范有深刻的了解,则不需要伪代码。 因此,这里没有此方法的示例代码。
配置PCI到PCI的网桥
要配置此功能,内核必须暂时忘记BIOS,首先扫描根PCI设备 (检查扫描多个总线是否多功能)。 根总线始终为0。
辅助总线和辅助总线充当PCI到PCI桥将管理的总线的范围起点。
然后,在这一步之后,它就可以实现了: 扫描每个设备,然后如果找到了一个桥,则为其分配一个总线号 (注意: PCI到PCI的桥可以在其中有多个桥)。 扫描该总线并找到更多设备,一旦找到更多网桥,就为找到的每个网桥在从属总线上添加1,因为PCI到PCI网桥可以管理多个网桥。
这只是开始: 分配总线号后,你需要分配MMIO,如果不是因为PCI有3个区域内核管理的事实,那将是微不足道的: IO,预取和内存。
一个桥可以管理多个总线,但这意味着它跨越了这些总线的所有内存,如果设备1在桥2后面,桥1后面,那么桥2将包含设备1的内存区域任何其他设备的区域,假设IO是4M,内存为16M,预取为5mb (假设桥接器的2总线中有3个设备),桥接器2将包含这些设备,请输入标题类型 0x1
的参考表。 但是,桥接器1将包含桥接器2的区域。桥接器1总线中的任何其他设备。
一旦分配了所有内存区域,就可以使用设备。请注意,PCI到PCI的桥接器也有BAR的。
如果内核未配置PCI到PCI的网桥,则BIOS可能会这样做,但是在没有BIOS的环境中,此方法是强制性的,否则该网桥后面的设备将不会显示。
IRQ Handling
如果你使用的是旧的 PIC,那么你的生活真的很轻松。 你有标头的 “中断行” 字段,该字段是读/写 (你可以更改它的值!),并且它说PCI设备需要注意时会触发哪个中断。
如果你打算使用 I/O APIC,你的生活将是一场噩梦。 你有4个新的IRQ,分别是INTA# 、INTB# 、INTC# 和INTD#。 你可以在 “中断行” 字段中找到设备将使用哪个IRQ。 在ACPI AML表中,你会发现 (使用 ACPICA) INTA# 连接到指定的中断线路,INTB# 连接到另一条中断线路,等等。
到目前为止还不错。 比方说,你有20台设备。 其中10个使用INTA#,5个用于INTB#,5个用于INTC#,而不用于INTD#。 因此,当IRQ号与 #INTC相关时,你必须扫描5台设备以了解谁是感兴趣的设备。 因此,有很多IRQ共享,尤其是INTA#。
随着时间的过去,制造商开始主要使用INTA#,忘记了其他引脚的存在。 因此,你可能在INTA# 上有18个设备,在INTB# 上有2个设备。 主板制造商决定控制局势。 因此,在启动时,将重新映射INTx#,以便你将有5个设备用于INTA#,5个用于INTB#,5个用于INTC#,5个用于INTD# (在最佳情况下)。 太棒了!IRQ平衡,IRQ共享减少。 唯一的问题是,你不知道什么设备映射。 如果你读了 “中断针”,你仍然会得到INTA#。 现在,你需要解析MP表或 ACPI 表来解决混乱。祝你好运。
或者,你可以仅使用MSI或msi-x,然后跳过所有讨厌的ACPI内容。
Message Signaled Interrupts
自PCI 2.2以来,已支持消息信号中断或MSI。 但是,在PCIe设备中,对它们的支持是 “强制性的”,因此你可以确定它们可以在现代硬件上使用。
Use of MSI and MSI-X are mutually exclusive.
启用MSI
首先,检查设备是否具有指向功能列表的指针 (状态寄存器位4设置为1)。
然后,遍历能力列表。 能力寄存器的低8位是MSI的ID-0x5
。 接下来的8位是下一个功能的偏移量 (在 PCI配置空间 中)。
MSI能力如下:
Register | Offset | Bits 31-24 | Bits 23-16 | Bits 15-8 | Bits 7-0 |
---|---|---|---|---|---|
Cap + 0x0 | Cap + 0x0 | Message Control | Next pointer | Capability ID = 05 | |
Cap + 0x1 | Cap + 0x4 | Message Address [Low] | |||
Cap + 0x2 | Cap + 0x8 | [Message Address High] | |||
Cap + 0x2/0x3 | Cap + 0x8/0xC | Reserved | Message Data | ||
Cap + 0x4 | Cap + 0x10 | [Mask] | |||
Cap + 0x5 | Cap + 0x14 | [Pending] |
这里是消息控制寄存器的布局:
Bits 15-9 | Bit 8 | Bit 7 | Bits 6-4 | Bits 3-1 | Bit 0 |
---|---|---|---|---|---|
Reserved | Per-vector masking | 64-bit | Multiple Message Enable | Multiple Message Capable | Enable |
消息地址/数据是特定于体系结构的。 在x86(-64) 上,如下所示:
uint64_t arch_msi_address(uint64_t *data, size_t vector, uint32_t processor, uint8_t edgetrigger, uint8_t deassert) {
*data = (vector & 0xFF) | (edgetrigger == 1 ? 0 : (1 << 15)) | (deassert == 1 ? 0 : (1 << 14));
return (0xFEE00000 | (processor << 12));
}
MSI中断似乎总是边缘触发得很高。
Multiple messages:
MME / MMI | Interrupts |
---|---|
000 | 1 |
001 | 2 |
010 | 4 |
011 | 8 |
100 | 16 |
101 | 32 |
在MME中,指定设备可能修改的消息数据的低位数。
因此,分配的中断向量块必须相应地对齐。
Interrupt masking
如果有能力,则可以通过在掩码寄存器中设置相应的位 (1 << n) 来掩码单个消息。
如果消息是挂起的,则设置挂起寄存器中的相应位。
请注意,如果消息地址为32位,则PCI规范不会指定这些寄存器的位置。 这是因为需要一个支持掩码的函数来实现64位寻址!
启用MSI-X
像MSI一样,你必须找到MSI-X功能,但是MSI-X的ID为 0x11
结构如下:
Register | Offset | Bits 31-24 | Bits 23-16 | Bits 15-8 | Bits 7-3 | Bits 2-0 |
---|---|---|---|---|---|---|
Cap + 0x0 | Cap + 0x0 | Message Control | Next Pointer | Capability ID = 11 | ||
Cap + 0x1 | Cap + 0x4 | Table Offset | BIR | |||
Cap + 0x2 | Cap + 0x8 | Pending Bit Offset | Pending Bit BIR |
与MSI不同,MSI-X支持2048中断。 这是通过在PCI设备的地址空间中维护中断表来实现的。 PCI 3.0规范的措辞指示该 “必须” 经由存储器条。
BIR 指定消息表使用哪个栏。 这可能是一个64位的条,并且是零索引的 (因此BIR = 0,BAR0,offset 0x10
到标头中)。
Table Offset 是消息表所在栏的偏移量。 请注意,它是8字节对齐的-所以简单地掩码BIR。
消息控制的格式如下:
Bit 15 | Bit 14 | Bits 13-11 | Bits 10-0 |
---|---|---|---|
Enable | Function Mask | Reserved | Table Size |
Table Size 是n-1编码的,是msi-x表中的条目数。 此字段是只读的。
现在,你拥有查找msi-x表所需的所有信息:
Bits 127-96 | Bits 95-64 | Bits 63-32 | Bits 31-0 |
---|---|---|---|
Vector Control (0) | Message Data (0) | Message Address High (0) | Message Address Low (0) |
Vector Control (1) | Message Data (1) | Message Address High (1) | Message Address Low (1) |
... | ... | ... | ... |
Vector Control (N - 1) | Message Data (N - 1) | Message Address High (N - 1) | Message Address Low (N - 1) |
Vector Control is as follows:
Bits 31-1 | Bit 0 |
---|---|
Reserved | Masked |
请注意,“消息地址” 是4字节对齐的,因此,再次屏蔽低位。 如果 “'masked'” 设置为1,中断将被屏蔽。
消息地址和数据与特定于MSI架构的一样。 但是,与MSI不同,你可以为所有中断指定独立的向量,仅受具有相同的高32位消息地址的限制。
多功能设备
多功能设备的行为方式与普通PCI设备相同。 检测多功能设备的最简单方法是标头类型字段的位7。 如果设置了 (值 = 0x80
),则该设备是多功能的-否则不是。 确定标题类型时,请确保屏蔽此位。 要检测功能数量,你需要扫描每个功能的PCI配置空间-未使用的功能具有供应商 0xFFFF
。 设备id和类代码在函数之间有所不同。 函数不一定按顺序排列-你可以使用函数 0x0
,0x1
和 0x7
。
免责声明
这段文字源自 "Pentium on VME",作者不明,md5sum d292807a3c56881c6faba7a1ecfd4c79。 原始文档显然不再出现在网络上...
参考文献
- PCI Local Bus Specification, revision 3.0, PCI Special Interest Group, August 12, 2002