-
-
PCI总线初始化过程(linux-2.4.0内核中的pci_init()函数分析)
-
发表于: 2023-7-13 19:07 17353
-
PCI设备(包括PCI桥),与主板之间的连接关系为:设备-[(PCI次层总线)-(PCI-PCI桥)]*-{(PCI主总线)-(宿主-PCI桥)}-主板(*表示0或多层)。总之,所有PCI设备,都直接或间接的连接到了主板上,并且每个PCI设备的内部,必然存在一些存储单元,服务于设备功能,包括寄存器、RAM,甚至ROM,本文将它们称为”功能存储区间”,以便与”配置寄存器组”(用于设备配置的存储区间)区分。
“配置寄存器组”,一方面提供设备的出厂信息,另一方面,用于系统软件对设备进行配置。其中,前64字节,必须按照PCI标准使用,称为”配置寄存器组”的头部,头部又分为”0型”、”1型”和”2型”,不过,不管哪种头部,前16字节的格式都是一样的(头部类型就包含在其中),只是后48字节格式不同。头部之后的192字节,由具体设备自行使用,如果不需要,没有也是可以的。
根据PCI规范说明,可以通过”0xCF8”和”0xCFC”两个32位寄存器,读写PCI设备的”配置寄存器组”(对于内核开发,直接按照规范使用即可,不需要关心PCI规范的硬件实现,比如为什么是”0xCF8”和”0xCFC”这两个I/O端口号,以及根据其中内容寻找并读写PCI设备的具体过程):
地址寄存器:用于指定配置寄存器地址;
数据寄存器:对于读操作,用于从中获取读取结果,对于写操作,用于往里填充写入内容。
由于目标地址包含总线号和设备号,所以读写前,所有PCI总线和设备,都要有自己的编号。其中,对于总线编号, PCI桥提供了PCI_PRIMARY_BUS和PCI_SECONDARY_BUS配置寄存器,由软件进行设置,而设备编号,却没有对应的配置寄存器。个人理解,这是因为,总线编号,受总线在树中的位置决定,是PCI厂商无法预测的,而设备编号,只要能够表明设备在单条总线上的局部位置即可,所以可以与PCI插槽位置等效,这在PCI总线出厂时就能确定,所以不需要软件执行枚举编号。
至此,还可以看出PCI设备中,配置寄存器与”功能存储区间”读写渠道的区别:通过”0xCF8”和”0xCFC”寄存器读写”配置寄存器”->通过”配置寄存器”,配置”功能存储区间”地址->直接根据配置地址访问”功能存储区间”。
BIOS程序烧刻在ROM(只读/不挥发内存),其中,有些厂商的主板,BIOS提供了PCI操作功能,就称为”PCI BIOS”。对于CPU来说,主板加电后,BIOS程序立即就可以执行,而内核程序,必须先由BIOS从MBR(主引导扇区),加载引导程序,再由引导程序加载到RAM(内存),才可以执行。
“PCI BIOS”包含的很多PCI操作功能,在加电自检阶段就会被执行,本意是为内核省事情。但是,由于一些厂商的BIOS,存在这样或那样的问题,以及嵌入式主板中,甚至没有BIOS,所以Linux内核自己实现了一套独立的PCI操作接口,可以完全不依赖BIOS,不过对于已经在自检阶段完成的操作,Linux内核会尽量保持BIOS的设置,有些情况,会调用自己实现的接口,对其进行补充或修正,有些情况,甚至不再调用自己实现的接口,完全接受BIOS的处理结果。
“PCI BIOS”还有一些PCI操作功能,作为接口提供给内核程序使用,当内核希望自己完成一些PCI操作,又不想过分关注PCI硬件spec时,就可以通过lcall指令,直接调用“PCI BIOS”提供的功能。不过,同样为了避免BIOS中的各种问题,以及没有BIOS的情况,Linux内核对这些接口,也自己实现了一份,并且,Linux内核中的PCI操作函数,分别通过CONFIG_PCI_BIOS和CONFIG_PCI_DIRECT宏,选择调用哪套接口。显然,这两个宏至少要有一个是打开的,并且,两者互不相斥,都打开时,内核通常优先使用自己的接口,或用自己的接口,对BIOS接口的操作结果进行修正。
《Linux内核源代码情景分析》,p1025:
《Linux内核源代码情景分析》,p1030:
对于书上的说法,我一开始误解为:不管什么情况,即使没有BIOS,内核也能自己完成PCI操作,而是否依赖,可以通过CONFIG_PCI_BIOS宏,进行控制。
带着这个误解,分析完i386架构下的pci_init()函数后,我产生了一个困惑:没有看到设置PCI设备PCI_IO_BASE和PCI_MEMORY_BASE寄存器的代码。然而,为各个PCI设备配置内存和I/O空间,可是PCI初始化的根本目标之一,所以既然内核没有设置,那就一定依赖了BIOS的设置,这就跟我的误解冲突了。
为了重新理解书上的说法,我首先想到的是,不定义CONFIG_PCI_BIOS宏,只会影响内核代码的编译结果,关闭内核函数对BIOS接口的调用,并不能影响加电自检时BIOS执行哪些PCI操作,因为BIOS程序在主板出厂时,就已经固定了。
i386架构下,内核代码所有对BIOS接口的调用,都在pcibios_init()或它调用的函数中:
另外,为防止看漏了,我对内核设置PCI_MEMORY_BASE寄存器的语句,进行了搜索:
结果发现,i386架构下,内核代码确实没有设置过PCI_IO_BASE和PCI_MEMORY_BASE寄存器,但在arm架构下(arch/arm/kernel/bios32.c),是由内核自己配置:
所以,结合以上两点可知,判断pcibios_init()是否依赖BIOS,还要看它是否依赖BIOS在加电自检阶段的操作结果,并且i386架构下的pcibios_init()函数,确实是依赖的。
那就是说,虽然Linux内核独立实现了一套PCI操作接口,使得Linux内核可以完全不依赖BIOS,实现任何PCI操作,不过,这只是为最终的PCI操作是否完全不依赖BIOS,提供了选择,比如arm CPU常用于嵌入式系统,通常根本没有BIOS,所以arm架构下实现PCI操作,必须选择完全使用内核接口,而对于i386 CPU,可以确定加电自检阶段,有些操作必然已由BIOS完成,并且完成的没问题时,就可以选择仍然”依赖”BIOS(可以依赖时,不依赖白不依赖)。
搞清楚这一点很重要,要不然分析代码时,就可能会期待在pci_init()函数中,看到根据所有PCI设备汇总信息,分配PCI地址的过程,却看不到,有些配置也会让人觉得”来历不明”,还没见到设置,内核就认为已经设置了。
高
8
位
|
-
0x02
:网络设备
| |
-
低
8
位
=
=
0
&& PCI_CLASS_PROG
=
=
0
| |
-
Ethernet网卡
|
-
0x07
:简单通信控制器
| |
-
低
8
位
=
=
0x01
:并行口
| |
-
PCI_CLASS_PROG
| |
-
0
:单向
| |
-
1
:双向
| |
-
2
:符合ECP
1.0
规定
|
-
0x06
:PCI桥
|
-
低
8
位
|
-
0x00
:
"宿主-PCI"
桥
|
-
0x01
:
"PCI-ISA"
桥
|
-
0x04
:
"PCI-PCI"
桥
|
-
0x07
:
"PCI-CARDBUS"
桥
高
8
位
|
-
0x02
:网络设备
| |
-
低
8
位
=
=
0
&& PCI_CLASS_PROG
=
=
0
| |
-
Ethernet网卡
|
-
0x07
:简单通信控制器
| |
-
低
8
位
=
=
0x01
:并行口
| |
-
PCI_CLASS_PROG
| |
-
0
:单向
| |
-
1
:双向
| |
-
2
:符合ECP
1.0
规定
|
-
0x06
:PCI桥
|
-
低
8
位
|
-
0x00
:
"宿主-PCI"
桥
|
-
0x01
:
"PCI-ISA"
桥
|
-
0x04
:
"PCI-PCI"
桥
|
-
0x07
:
"PCI-CARDBUS"
桥
pcibios_init()
|
-
pci_find_bios()
/
/
#ifdef CONFIG_PCI_BIOS
| |
-
check_pcibios()
| | |
-
lcall &pci_indirect
/
/
通过call指令,进入BIOS调用入口
| |
-
return
&pci_bios_access
| |
-
pci_bios_OP_config_SIZE()
/
/
OP: read
/
write; SISE: byte
/
word
/
dword
| |
-
lcall &pci_indirect
|
-
pcibios_irq_init()
| |
-
pcibios_get_irq_routing_table()
/
/
#ifdef CONFIG_PCI_BIOS
| | |
-
lcall &pci_indirect
| |
-
pirq_find_router()
| |
-
pirq_router
=
&pirq_bios_router
/
/
#ifdef CONFIG_PCI_BIOS
| |
-
pcibios_set_irq_routing()
| |
-
lcall &pci_indirect
|
-
pcibios_sort()
/
/
#ifdef CONFIG_PCI_BIOS
|
-
pci_bios_find_device()
|
-
lcall &pci_indirect
pcibios_init()
|
-
pci_find_bios()
/
/
#ifdef CONFIG_PCI_BIOS
| |
-
check_pcibios()
| | |
-
lcall &pci_indirect
/
/
通过call指令,进入BIOS调用入口
| |
-
return
&pci_bios_access
| |
-
pci_bios_OP_config_SIZE()
/
/
OP: read
/
write; SISE: byte
/
word
/
dword
| |
-
lcall &pci_indirect
|
-
pcibios_irq_init()
| |
-
pcibios_get_irq_routing_table()
/
/
#ifdef CONFIG_PCI_BIOS
| | |
-
lcall &pci_indirect
| |
-
pirq_find_router()
| |
-
pirq_router
=
&pirq_bios_router
/
/
#ifdef CONFIG_PCI_BIOS
| |
-
pcibios_set_irq_routing()
| |
-
lcall &pci_indirect
|
-
pcibios_sort()
/
/
#ifdef CONFIG_PCI_BIOS
|
-
pci_bios_find_device()
|
-
lcall &pci_indirect
pcibios_init()
|
-
pci_assign_unassigned_resources()
|
-
pbus_assign_resources()
|
-
pci_setup_bridge()
|
-
pci_write_config_dword(bridge, PCI_IO_BASE, l)
|
-
pci_write_config_dword(bridge, PCI_MEMORY_BASE, l)
pcibios_init()
|
-
pci_assign_unassigned_resources()
|
-
pbus_assign_resources()
|
-
pci_setup_bridge()
|
-
pci_write_config_dword(bridge, PCI_IO_BASE, l)
|
-
pci_write_config_dword(bridge, PCI_MEMORY_BASE, l)
/
/
声明:以下分析的代码,是针对i386 CPU的实现!!
pci_init()
|
|
-
pcibios_init()
/
/
arch
/
i386
/
kernel
/
pci
-
pc.c !!
| |
/
/
早期,该函数完全通过调用BIOS接口实现,所以函数名中包含了
"bios"
| |
/
/
后来,该函数演变为,可以通过CONFIG_PCI_DIRECT和CONFIG_PCI_BIOS宏,选择调用BIOS接口,还是调用内核自己实现的接口
| |
/
/
而且两者不互斥,如果
2
个宏都打开,内核通常优先使用自己的接口,或用自己的接口,对BIOS接口的操作结果进行修正
| |
| |
/
/
获取BIOS提供的PCI操作接口 (
#ifdef CONFIG_PCI_BIOS)
| |
-
pci_root_ops
=
pci_find_bios()
| | |
| | |
/
/
0xe0000
~
0xffff0
为BIOS占用的物理内存
| | |
-
for
(check
=
(union bios32
*
) __va(
0xe0000
); check <
=
(union bios32
*
) __va(
0xffff0
);
+
+
check)
| | |
| | |
/
/
每个bios32对象,都包含
16
字节头部,其中包含signature和checksum字段
| | |
-
if
(check
-
>fields.signature !
=
BIOS32_SIGNATURE)
| | | |
-
continue
| | |
| | |
/
/
构造bios32对象时,通过设置checksum字段值,保证了整个对象占用的字节相加为
0
| | |
-
for
(i
=
0
; i < length ;
+
+
i)
| | | |
-
sum
+
=
check
-
>chars[i]
| | |
-
if
(
sum
!
=
0
)
| | | |
-
continue
| | |
| | |
/
/
进一步检查
| | |
-
if
(check_pcibios())
| | |
-
if
((pcibios_entry
=
bios32_service(PCI_SERVICE)))
| | | |
-
pci_indirect.address
=
pcibios_entry
+
PAGE_OFFSET
/
/
设置BIOS的PCI服务接口的段内偏移
| | | |
-
...
/
/
设置参数:PCIBIOS_PCI_BIOS_PRESENT
| | | |
-
lcall &pci_indirect
/
/
调用接口
| | | |
-
pci_indirect
=
{
0
, __KERNEL_CS }
/
/
初始化时,已将段基址设置为__KERNEL_CS
| | |
-
return
&pci_bios_access
| |
| |
/
/
获取内核自己实现的PCI操作接口 (
#ifdef CONFIG_PCI_DIRECT)
| |
-
pci_root_ops
=
pci_check_direct()
| | |
| | |
/
/
探测主总线是否为PCI总线
| | |
-
if
(pci_probe & PCI_PROBE_CONF1)
| | | |
-
outb (
0x01
,
0xCFB
)
/
/
32
位地址寄存器最高字节
0xCFB
,称为
"PMC寄存器"
,它的bit1~
7
保留,bit0用于选择
"PCI配置访问机制"
:
| | | |
/
/
参考:https:
/
/
html.datasheetbank.com
/
datasheet
-
html
/
251876
/
Intel
/
34page
/
290479
-
004.html
(PCI datasheet, page
34
)
| | | |
/
/
bit0
=
0
| PCI配置访问机制
0
|
2
型PCI设备
| | | |
/
/
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
| | | |
/
/
bit0
=
1
| PCI配置访问机制
1
|
1
型PCI设备
| | | |
-
tmp
=
inl (
0xCF8
)
/
/
从地址寄存器,读取原始值 (对于常规存储单元,先向a写入
1
(a
=
1
),再读取a(b
=
a),结果会为
1
,但是PCI配置寄存器往往不是这样)
| | | |
-
outl (
0x80000000
,
0xCF8
)
/
/
向地址寄存器,写入
0x80000000
| | | |
/
/
至于为什么非要写入
0x80000000
进行检查,估计也是PCI datasheet规定,后续分析不会过于纠结这种细节,建议尝试根据PCI datasheet查找
| | | |
| | | |
/
/
读到的还是
0x80000000
(),并且通过pci_sanity_check()检查
| | | |
-
if
(inl (
0xCF8
)
=
=
0x80000000
&& pci_sanity_check(&pci_direct_conf1))
| | | | | |
| | | | | |
-
for
(dev.devfn
=
0
; dev.devfn <
0x100
; dev.devfn
+
+
)
/
/
一条PCI总线,最多可以有
256
个逻辑设备
| | | | | |
-
if
((!o
-
>read_word(&dev, PCI_CLASS_DEVICE, &x) &&
| | | | | | (x
=
=
PCI_CLASS_BRIDGE_HOST || x
=
=
PCI_CLASS_DISPLAY_VGA)) ||
| | | | | | (!o
-
>read_word(&dev, PCI_VENDOR_ID, &x) &&
| | | | | | (x
=
=
PCI_VENDOR_ID_INTEL || x
=
=
PCI_VENDOR_ID_COMPAQ)))
/
/
连接了PCI_CLASS_BRIDGE_HOST或PCI_CLASS_DISPLAY_VGA设备,认为是PCI总线
| | | | | |
/
/
连接的设备由Intel或Compaq厂商制造,也认为是PCI总线
| | | | | |
-
return
1
| | | | |
| | | | |
-
outl (tmp,
0xCF8
)
/
/
为地址寄存器恢复原始值
| | | | |
-
request_region(
0xCF8
,
8
,
"PCI conf1"
)
/
/
在内核I
/
O设备资源树增加一个节点,用于记录
"宿主-PCI桥"
占用的I
/
O地址区间
| | | | |
-
return
&pci_direct_conf1
/
/
返回pci_direct_conf1对象地址,包含read_byte()、write_byte()等函数
/
PCI操作列表
| | | |
| | | |
-
outl (tmp,
0xCF8
)
/
/
为地址寄存器恢复原始值
| | |
| | |
/
/
探测主总线是否为ISA总线 (现已不用)
| | |
-
if
(pci_probe & PCI_PROBE_CONF2)
| | |
-
outb (
0x00
,
0xCFB
)
/
/
PMC寄存器
=
0
,选择机制
0
,访问
2
型PCI设备的配置寄存器
| | |
-
...
| | |
-
return
&pci_direct_conf2
| |
| |
/
/
扫描主总线,探测与枚举连接在主总线上的PCI设备,如果设备为
"PCI-PCI"
桥,则递归扫描其连接的次层总线
| |
/
/
对于i386 CPU,必定已由BIOS在加电自检阶段枚举过一遍了,pci_scan_bus()相当于直接读取BIOS的设置成果,并分配管理对象进行记录
| |
-
pci_root_bus
=
pci_scan_bus(
0
, pci_root_ops, NULL)
| | |
| | |
/
/
分配pci_bus对象,用于记录总线信息
| | |
-
struct pci_bus
*
b
=
pci_alloc_primary_bus()
| | | |
-
if
(pci_bus_exists(&pci_root_buses, bus))
/
/
检查pci_root_buses全局链表,是否已经存在总线号为bus的pci_bus对象
| | | | |
-
return
NULL
| | | |
-
b
=
pci_alloc_bus()
/
/
分配pci_bus对象
| | | |
-
list_add_tail(&b
-
>node, &pci_root_buses)
/
/
添加到pci_root_buses全局链表
| | | |
-
b
-
>number
=
b
-
>secondary
=
bus
/
/
记录总线号
| | | |
/
/
number表示当前总线的编号
| | | |
/
/
primary表示当前总线的上层总线编号
| | | |
/
/
secondary表示总线所在PCI桥的次层总线编号(也就是当前总线自己),所以总是等于number (额外搞个成员变量,估计是为了提高代码可读性)
| | | |
/
*
pci_dev对象的resource[DEVICE_COUNT_RESOURCE]:
| | | | PCI设备本身占用:
| | | | resource[
0
~
5
]:设备上可能有的内存地址区间
| | | | resource[
6
]:设备上可能有的扩充ROM区间
| | | | PCI桥是一种特殊的PCI设备,额外包含次层总线占用的
4
个区间:
| | | | resource[
7
]:I
/
O地址窗口
| | | | resource[
8
]:内存地址窗口
| | | | resource[
9
]:
"可预取"
内存地址窗口
| | | | resource[
10
]:扩充ROM区间窗口
*
/
| | | |
/
/
pci_bus对象的
*
resource[
4
],分别指向所在PCI桥的&resource[
7
~
10
]
| | | |
-
b
-
>resource[
0
]
=
&ioport_resource
/
/
resource[
0
]:可由当前总线使用的I
/
O地址区间 (由于是主总线,所以指向整个&ioport_resource)
| | | |
-
b
-
>resource[
1
]
=
&iomem_resource
/
/
resource[
1
]:可由当前总线使用的内存地址区间 (由于是主总线,所以指向整个&iomem_resource)
| | |
| | |
/
/
开始扫描
| | |
-
if
(b)
| | |
-
b
-
>ops
=
ops
/
/
次层总线的ops,同样设置为pci_root_ops
| | |
-
b
-
>subordinate
=
pci_do_scan_bus(b)
/
/
逐个发现连接在总线上的设备,为其建立pci_dev对象
| | |
-
for
(devfn
=
0
; devfn <
0x100
; devfn
+
=
8
)
/
/
每次扫描
1
个PCI插槽
| | | |
-
dev0.devfn
=
devfn
| | | |
-
pci_scan_slot(&dev0)
| | | |
-
for
(func
=
0
; func <
8
; func
+
+
, temp
-
>devfn
+
+
)
/
/
每个PCI设备,最多可以有
8
个功能,相当于
8
个逻辑设备 (逻辑设备号
=
5
位设备号
+
3
位功能号)
| | | |
| | | |
-
if
(func && !is_multi)
/
/
非
0
功能号,只在多功能设备中才需要处理
| | | | |
-
continue
/
/
疑问:这里为什么不直接
break
??
| | | |
| | | |
/
/
读取
"配置寄存器组"
头部中的PCI_HEADER_TYPE字段,根据它的最高位是否为
1
,判断设备是否为单功能,从而决定下一轮循环是否需要继续
| | | |
-
pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)
/
/
pci_read_config_byte()函数,根据PCI_OP(read, byte, u8
*
)宏展开定义
| | | | |
-
dev
-
>bus
-
>ops
-
>read_byte()
/
/
ops通常为pci_direct_conf1 (见pcibios_init()入口,对pci_root_ops的设置)
| | | | |
| | | | |
-
pci_conf1_read_config_byte()
/
/
如果ops
=
&pci_direct_conf1
| | | | ' |
-
outl(CONFIG_CMD(dev,where),
0xCF8
)
/
/
按照PCI规范,向地址寄存器(
0xCF8
),写入
"总线号,逻辑设备号,寄存器偏移(where)"
| | | | ' | |
-
(
0x80000000
| (dev
-
>bus
-
>number <<
16
) | (dev
-
>devfn <<
8
) | (where & ~
3
))
| | | | ' |
-
*
value
=
inb(
0xCFC
+
(where&
2
))
/
/
从数据寄存器(
0xCFC
),读取设备的PCI_HEADER_TYPE寄存器值
| | | | '
| | | | '
-
pci_bios_read_config_byte()
/
/
如果ops
=
&pci_bios_access
| | | | |
-
...
/
/
设置参数:PCIBIOS_READ_CONFIG_WORD
| | | | |
-
lcall &pci_indirect
/
/
调用BIOS的PCI服务接口 (段基址、偏移,已分别由初始化和check_pcibios()函数设置)
| | | |
| | | |
-
temp
-
>hdr_type
=
hdr_type &
0x7f
/
/
低
7
位代表设备类型:
| | | |
/
/
PCI_HEADER_TYPE_NORMAL(普通PCI设备)、PCI_HEADER_TYPE_BRIDGE(PCI
-
PCI桥)、PCI_HEADER_TYPE_CARDBUS(PCI
-
CardBus桥)
| | | |
| | | |
-
dev
=
pci_scan_device(temp)
/
/
读取逻辑设备的出厂信息
| | | | |
/
/
"配置寄存器组"
,一方面提供软件对设备的配置界面,另一方面,携带设备的出厂信息
| | | | |
/
/
读取
"配置寄存器组"
头部中的PCI_HEADER_TYPE字段,低
16
位为厂商编号,高
16
位为设备编号
| | | | |
-
pci_read_config_dword(temp, PCI_VENDOR_ID, &l)
| | | | |
-
if
(l
=
=
0xffffffff
|| l
=
=
0x00000000
|| l
=
=
0x0000ffff
|| l
=
=
0xffff0000
)
/
/
全
0
或全
1
,都为无效的厂商编号、备编号
| | | | | |
-
return
NULL
| | | | |
-
dev
=
kmalloc(sizeof(
*
dev), GFP_KERNEL)
/
/
分配pci_dev对象
| | | | |
-
...
/
/
初始化pci_dev对象,设置厂商编号、设备编号
| | | | |
-
pci_setup_device(dev)
/
/
根据设备类型,进一步填充pci_dev对象
| | | | |
| | | | |
/
/
读取
"配置寄存器组"
头部中的PCI_CLASS_REVISION字段
| | | | |
-
pci_read_config_dword(dev, PCI_CLASS_REVISION, &
class
)
/
/
相比表示大类的hdr_type,
class
对设备类型进一步细分,其中包含:
| | | | |
/
/
PCI_CLASS_DEVICE(高
16
位)、PCI_CLASS_PROG(次低
8
位)、PCI_REVISION_ID(低
8
位)
| | | | |
-
switch (dev
-
>hdr_type)
| | | | |
| | | | |
-
case PCI_HEADER_TYPE_NORMAL
/
/
普通PCI设备
| | | | | |
| | | | | |
-
if
(
class
=
=
PCI_CLASS_BRIDGE_PCI)
| | | | | | |
-
goto bad
/
/
可以看出,只要不是
"PCI-PCI"
桥,都属于
0
型设备
| | | | | |
| | | | | |
/
/
读取
"配置寄存器组"
头部中的PCI_INTERRUPT_PIN和PCI_INTERRUPT_LINE字段
| | | | | |
-
pci_read_irq(dev)
| | | | | | |
/
/
PCI_INTERRUPT_PIN字段,表示设备与PCI插槽之间,中断请求信号线的连接关系 (由硬件决定,只读)
| | | | | | |
/
/
0
:表示设备不产生中断请求;
1
~
4
:分别表示设备与PCI插槽的INTA~INTD中断请求信号线连接
| | | | | | |
-
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq)
| | | | | | |
-
if
(irq)
| | | | | | | |
/
/
PCI_INTERRUPT_LINE字段,表示设备经过PCI插槽,最终连接了中断控制器的哪根中断请求信号线 (由软件设置)
| | | | | | | |
/
/
PCI_INTERRUPT_LINE字段只起记录作用,不起控制作用,修改其值,并不会修改连接关系,不过连接关系通常也可以由软件设置
| | | | | | | |
/
/
如果这时读取的irq不为
0
,一定是因为已由BIOS设置,内核后续不会对其进行修改
| | | | | | | |
-
pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &irq)
| | | | | | |
| | | | | | |
-
dev
-
>irq
=
irq
/
/
记录到pci_dev对象 (对于irq为
0
的情况,会在后续调用pcibios_lookup_irq()函数时,进行处理)
| | | | | |
| | | | | |
/
/
读取
"配置寄存器组"
头部中的PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5和PCI_ROM_ADDRESS字段
| | | | | |
-
pci_read_bases(dev,
6
, PCI_ROM_ADDRESS)
| | | | | | |
| | | | | | |
/
/
PCI设备中,通常包含寄存器或RAM
"(可挥发)功能存储区间"
(最多
6
个),PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5寄存器,用于记录它们的出厂信息,以及配置它们的映射地址
| | | | | | |
-
for
(pos
=
0
; pos<howmany; pos
=
next
)
| | | | | | | |
| | | | | | | |
/
/
对于已由BIOS配置过的寄存器,内核后续不会对其进行修改,只会在检查到配置错误的情况下,对其进行修正
| | | | | | | |
-
reg
=
PCI_BASE_ADDRESS_0
+
(pos <<
2
)
/
/
PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5寄存器,长度都为
4
字节
| | | | | | | |
-
pci_read_config_dword(dev, reg, &l)
/
/
读取区间起始地址
| | | | | | | |
-
pci_write_config_dword(dev, reg, ~
0
)
/
/
写入全
1
(根据PCI规范)
| | | | | | | |
-
pci_read_config_dword(dev, reg, &sz)
/
/
读取区间大小
| | | | | | | |
-
pci_write_config_dword(dev, reg, l)
/
/
恢复原始值
| | | | | | | |
| | | | | | | |
/
/
起始地址,bit0
=
0
,表示内存地址区间,bit0
=
1
表示I
/
O地址区间
| | | | | | | |
/
*
区间类型由映射到什么区间决定,而跟存储单元是什么介质(内存
/
寄存器),以及所在的硬件设备无关
| | | | | | | | Linux内核通常优先选择使用内存空间:
| | | | | | | |
1.
有些CPU没有I
/
O指令,因而没有I
/
O空间,寄存器也就都在存储器地址空间中
| | | | | | | |
2.
有些CPU有I
/
O指令,但是指令的寻址空间很小,比如i386 CPU的I
/
O指令,操作数最多
16
位,即I
/
O空间为
64KB
,非常拥挤
| | | | | | | |
3.
C语言没有访问I
/
O地址空间的语言成分,需要调用汇编子过程,不方便而且效率也稍低
*
/
| | | | | | | |
-
if
((l & PCI_BASE_ADDRESS_SPACE)
=
=
PCI_BASE_ADDRESS_SPACE_MEMORY)
| | | | | | | | |
| | | | | | | | |
/
/
内存区间,起始地址低
4
位为控制信息:
| | | | | | | | |
/
/
bit0:区间类型 (
0
: 内存区间;
1
: I
/
O区间)
| | | | | | | | |
/
/
bit1:区间大小是否超过
1MB
(
0
: 未超过;
1
: 超过)
| | | | | | | | |
/
/
bit2: 区间地址位数 (
0
:
32
位;
1
:
64
位)
| | | | | | | | |
/
/
bit3: 区间是否
"可预取"
(
0
: 否;
1
:是)
| | | | | | | | |
/
/
对于普通的内存单元,读操作是不会修改其内容的,但是如果内存区间是由寄存器区间映射的,内容就有可能被读操作修改:
| | | | | | | | |
/
/
1.
比如FIFO寄存器,读走队列头的元素后,下一个元素就成为首个元素,存入FIFO寄存器了
| | | | | | | | |
/
/
2.
有些状态寄存器,状态被读出之后,寄存器就会硬件自动清
0
| | | | | | | | |
/
/
"预读"
是指,程序在读第N个单元时,CPU会把第N
+
1
个单元也读入缓存:
| | | | | | | | |
/
/
对内容可能被读操作修改的存储单元,进行
"预读"
,就会存在问题:
| | | | | | | | |
/
/
比如,程序在读第N个单元时,CPU会把第N
+
1
个单元也读入缓存,但在缓存被冲刷掉之前,程序并没有取走第N
+
1
个单元,程序后面再想重新读取时,内存单元却已经被
"预读"
修改了
| | | | | | | | |
/
/
所以,通常将映射到内存空间的寄存器区间,
"可预取"
标志置为
0
| | | | | | | | |
-
res
-
>start
=
l & PCI_BASE_ADDRESS_MEM_MASK
| | | | | | | | |
| | | | | | | | |
/
/
PCI规范规定,高
28
位中,只有位置最低的
1
,用于表示区间大小,而出厂值还有其它位为
1
,是为了表示哪些位可以由软件设置,所以不能忽略
| | | | | | | | |
-
sz
=
pci_size(sz, PCI_BASE_ADDRESS_MEM_MASK)
| | | | | | | | |
| | | | | | | | |
/
/
假设:size
=
x100
| | | | | | | | |
/
/
则:size
-
1
=
x100
-
1
=
x011
| | | | | | | | |
/
/
=
> ~(size
-
1
)
=
X100 (其中:X & x
=
0
)
| | | | | | | | |
/
/
& x100 (size)
| | | | | | | | |
/
/
-
-
-
-
-
-
-
-
| | | | | | | | |
/
/
=
0100
(高位x不管为多少,都转换为
0
了)
| | | | | | | | |
-
size
=
size & ~(size
-
1
)
| | | | | | | | |
-
return
size
-
1
| | | | | | | |
-
else
| | | | | | | | |
| | | | | | | | |
/
/
I
/
O区间,起始地址低
3
位为控制信息
| | | | | | | | |
-
res
-
>start
=
l & PCI_BASE_ADDRESS_IO_MASK
| | | | | | | | |
-
sz
=
pci_size(sz, PCI_BASE_ADDRESS_IO_MASK &
0xffff
)
| | | | | | | |
| | | | | | | |
-
res
-
>end
=
res
-
>start
+
(unsigned
long
) sz
| | | | | | | |
-
...
/
/
暂不关心
64
位地址的情况
| | | | | | |
| | | | | | |
/
/
PCI设备中,有时也会包含ROM
"(不可挥发)功能存储区间"
(最多
1
个),PCI_ROM_ADDRESS寄存器,用于记录它的出厂信息,以及配置它的映射地址
| | | | | | |
-
if
(rom)
| | | | | | |
-
...
/
/
和PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5处理过程类似
| | | | | |
| | | | | |
/
/
读取其它字段
| | | | | |
-
pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev
-
>subsystem_vendor)
| | | | | |
-
pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev
-
>subsystem_device)
| | | | |
| | | | |
-
case PCI_HEADER_TYPE_BRIDGE
/
/
PCI
-
PCI桥
| | | | | |
| | | | | |
-
if
(
class
!
=
PCI_CLASS_BRIDGE_PCI)
| | | | | | |
-
goto bad
/
/
可以看出,仅
"PCI-PCI"
桥,属于
1
型设备
| | | | | |
| | | | | |
/
/
相比普通PCI设备,很多配置寄存器在PCI
-
PCI桥中不存在,并且PCI
-
PCI桥上,最多只有
2
个可挥发存储区间
| | | | | |
-
pci_read_bases(dev,
2
, PCI_ROM_ADDRESS1)
/
/
不过次层PCI总线,需要后续进行递归扫描
| | | | |
| | | | |
-
case PCI_HEADER_TYPE_CARDBUS
/
/
PCI
-
CardBus桥 (专用于笔记本,暂不关心)
| | | | |
-
...
| | | |
| | | |
-
pci_name_device(dev)
/
/
根据厂商和设备编号,获取厂商和设备名称 (转换表存储于drivers
/
pci
/
pci.ids文件)
| | | |
| | | |
-
if
(!func)
| | | | |
-
is_multi
=
hdr_type &
0x80
/
/
是否多功能
| | | |
-
list_add_tail(&dev
-
>global_list, &pci_devices)
/
/
将pci_dev对象添加到pci_devices全局链表
| | | |
-
list_add_tail(&dev
-
>bus_list, &bus
-
>devices)
/
/
将pci_dev对象添加到所属pci_bus的devices链表
| | | |
| | | |
/
/
有些设备出售后,厂商却发现固化在
"配置寄存器组"
中的初始化信息有问题,只好提供修正代码,在内核层面进行修改
| | | |
-
pci_fixup_device(PCI_FIXUP_HEADER, dev)
| | | |
| | | |
/
/
数组元素表示:对于由vendor厂商提供的device设备,在
pass
阶段,执行hook()修正函数
| | | |
/
*
pcibios_fixups[]:不同CPU架构,使用各自的
| | | | pci_fixups[]:所有CPU架构共用
| | | | 都是pci_fixup对象全局数组,以{
0
}元素结束
*
/
| | | |
/
/
疑问:如果存在PCI BIOS,加电自检阶段,就已经基于错误的出厂信息,枚举过设备了,到内核执行阶段才修复,来得及吗??
| | | |
-
pci_do_fixups(dev,
pass
, pcibios_fixups)
| | | |
-
pci_do_fixups(dev,
pass
, pci_fixups)
| | | |
| | | |
/
/
dev包含vendor和device信息
| | | |
/
/
pass
为PCI_FIXUP_HEADER,表示
"配置寄存器组"
头部信息读出以后,PCI_FIXUP_FINAL表示全部信息读出以后
| | | |
-
while
(f
-
>
pass
)
| | | |
-
if
(f
-
>
pass
=
=
pass
&&
| | | | | (f
-
>vendor
=
=
dev
-
>vendor || f
-
>vendor
=
=
(u16) PCI_ANY_ID) &&
| | | | | (f
-
>device
=
=
dev
-
>device || f
-
>device
=
=
(u16) PCI_ANY_ID))
| | | | |
| | | | |
-
f
-
>hook(dev)
/
/
pass
,vendor,device全部命中,执行对应的修正函数
| | | |
-
f
+
+
| | |
| | |
/
/
不光PCI设备中的出厂信息会有错,有些母板也存在设计问题
| | |
-
pcibios_fixup_bus(bus)
| | | |
| | | |
/
/
去除
"幻影"
(有些母板上,会在两个不同的综合地址中,读出同一设备的头部信息,从而造成重复枚举,在形成的PCI树中引入
"幻影"
)
| | | |
-
pcibios_fixup_ghosts(b)
| | | |
| | | |
/
/
pci_scan_slot()函数,只读取了PCI设备
"功能存储区间"
的信息,还没读取PCI桥为次层总线保存的地址窗口
| | | |
-
pci_read_bridge_bases(b)
| | | |
| | | |
/
*
PCI桥是一种特殊的PCI设备,本身并不一定有RAM和ROM,但是要有
3
个地址窗口 (也用resource对象描述):
| | | |
1.
I
/
O地址窗口 (窗口起始地址和大小,都以
4KB
对齐,PCI_IO_BASE和PCI_IO_LIMIT低
4
位全
0
时,表示I
/
O地址为
16
位,全
1
表示I
/
O地址为
32
位)
| | | | PCI_IO_BASE(
8
位): I
/
O起始地址(
16
位)
=
PCI_IO_BASE高
4
位
+
12
*
0
| | | | PCI_IO_LIMIT(
8
位): I
/
O结束地址(
16
位)
=
PCI_IO_LIMIT高
4
位
+
12
*
1
| | | | PCI_IO_BASE_UPPER16(
16
位): I
/
O起始地址(
32
位)
=
PCI_IO_BASE_UPPER16
+
PCI_IO_BASE高
4
位
+
12
*
0
| | | | PCI_IO_LIMIT_UPPER16(
16
位): I
/
O结束地址(
32
位)
=
PCI_IO_LIMIT_UPPER16
+
PCI_IO_LIMIT高
4
位
+
12
*
1
| | | |
2.
存储器地址窗口 (窗口起始地址和大小,都以
1MB
对齐,主要用于映射在存储器地址空间的I
/
O寄存器,最低
4
位固定为
0
)
| | | | PCI_MEMORY_BASE(
16
位): 存储器起始地址(
32
位)
=
PCI_MEMORY_BASE高
12
位
+
20
*
0
| | | | PCI_MEMORY_LIMIT(
16
位): 存储器结束地址(
32
位)
=
PCI_MEMORY_BASE高
12
位
+
20
*
1
| | | |
3.
"可预取"
存储器地址窗口 (窗口起始地址和大小,都以
1MB
对齐,最低
4
位为
0
表示地址为
32
位,为
1
表示地址为
64
位)
| | | | PCI_PREF_MEMORY_BASE(
16
位):
"可预取"
存储器起始地址(
32
位)
=
PCI_PREF_MEMORY_BASE高
12
位
+
20
*
0
| | | | PCI_PREF_MEMORY_LIMIT(
16
位):
"可预取"
存储器结束地址(
32
位)
=
PCI_PREF_MEMORY_LIMIT高
12
位
+
20
*
1
| | | | PCI_PREF_BASE_UPPER32(
32
位):
"可预取"
存储器起始地址(
64
位)
=
PCI_PREF_BASE_UPPER32
+
PCI_PREF_MEMORY_BASE高
12
位
+
20
*
0
| | | | PCI_PREF_LIMIT_UPPER32(
32
位):
"可预取"
存储器结束地址(
64
位)
=
PCI_PREF_BASE_UPPER32
+
PCI_PREF_MEMORY_LIMIT高
12
位
+
20
*
1
| | | | CPU一侧访问的地址,必须在某个窗口之内(位于当前总线),才能到达下游,设备一侧访问的地址,必须在所有窗口之外(不在当前总线),才能到达上游
| | | | 此外,PCI命令寄存器(
"配置寄存器组"
头部中的PCI_COMMAND字段),包含
"memory access enable"
和
"I/O access enable"
两个控制位
| | | | 控制位为
0
时,窗口对
2
个方向都关闭,用于保证还没为PCI设备各个区间分配合适的总线地址时,CPU和设备两侧不会相互干扰
*
/
| | | |
-
for
(i
=
0
; i<
3
; i
+
+
)
| | | | |
-
child
-
>resource[i]
=
&dev
-
>resource[PCI_BRIDGE_RESOURCES
+
i]
/
/
pci_bus对象中的resource只是指针,指向所在PCI桥的pci_dev对象的resource
| | | |
| | | |
/
/
读取PCI_IO_BASE,PCI_MEMORY_BASE,PCI_PREF_MEMORY_BASE等寄存器,记录到pci_bus对象的resource[
0
~
2
]
| | | |
/
/
对于i386 CPU,PCI桥的地址窗口,必然已在加电自检阶段,就由BIOS根据次层总线的需要,设置好了!!
| | | |
-
...
| | |
| | |
/
/
递归扫描PCI
-
PCI桥或PCI
-
CardBus桥上的次层总线
| | |
-
for
(
pass
=
0
;
pass
<
2
;
pass
+
+
)
/
/
第一趟针对已由BIOS处理过的PCI桥
| | |
/
/
第二趟针对未经BIOS处理过的PCI桥 (疑问:对于i386 CPU,为什么会存在这种情况??)
| | |
-
for
(ln
=
bus
-
>devices.
next
; ln !
=
&bus
-
>devices; ln
=
ln
-
>
next
)
/
/
遍历上层总线上面的PCI设备
| | |
-
dev
=
pci_dev_b(ln)
| | |
-
if
(dev
-
>hdr_type
=
=
PCI_HEADER_TYPE_BRIDGE || dev
-
>hdr_type
=
=
PCI_HEADER_TYPE_CARDBUS)
/
/
设备为
"PCI-PCI"
或
"PCI-CardBus"
桥
| | |
| | |
/
/
pci_scan_bridge()函数写的有点绕:
| | |
/
/
|
if
((buses &
0xffff00
)) |
pass
| run | 目的
| | |
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
| | |
/
/
已由BIOS处理,第一趟 | Y |
0
|
772
行 | 第一趟仅为已由BIOS枚举的总线分配pci_bus对象,并统计最大总线号
max
| | |
/
/
未经BIOS处理,第一趟 | N |
0
|
return
| 等待第一趟扫描完毕,
max
值完全确认后,在第二趟扫描中处理
| | |
/
/
已由BIOS处理,第二趟 | Y |
1
|
return
| 已经在第一趟扫描中处理过了
| | |
/
/
未经BIOS处理,第二趟 | N |
1
|
792
行 | 基于已由BIOS处理的最大总线号
max
,处理未经BIOS处理的总线
| | |
-
max
=
pci_scan_bridge(bus, dev,
max
,
pass
)
| | |
| | |
/
/
读取
"配置寄存器组"
头部中的PCI_PRIMARY_BUS字段 (byte0:主总线号; byte1:次总线号; byte2:子树中最大总线号)
| | |
-
pci_read_config_dword(dev, PCI_PRIMARY_BUS, &buses)
| | |
| | |
/
/
次总线号和子树中最大总线号不全为
0
,表示加电自检阶段,BIOS已经枚举过当前总线,在此之前就已经设置过了
| | |
-
if
((buses &
0xffff00
) && !pcibios_assign_all_busses())
| | | | |
-
空操作
| | | |
-
if
(
pass
)
/
/
已由BIOS枚举的总线,第二趟扫描不需要处理
| | | | |
-
return
max
| | | |
| | | |
-
child
=
pci_add_new_bus(bus, dev,
0
)
/
/
为新探测到的总线,分配和设置pci_bus对象 (逻辑简单,不再展开)
| | | |
-
...
/
/
继续设置pci_bus对象
| | | |
-
if
(!is_cardbus)
| | | | |
-
cmax
=
pci_do_scan_bus(child)
/
/
递归调用 (遇到次层总线,立即递归扫描,而不是探测到上层总线的所有次层总线后,再依次扫描次层总线,所以是深度优先)
| | | | |
-
if
(cmax >
max
)
max
=
cmax
| | | |
-
else
| | | |
-
cmax
=
child
-
>subordinate
/
/
PCI
-
CardBus桥不支持次层总线
| | | |
-
if
(cmax >
max
)
max
=
cmax
| | |
-
else
| | | |
-
if
(!
pass
)
/
/
未经BIOS处理的总线,等待第一趟扫描完毕
| | | | |
-
return
max
| | | |
| | | |
/
/
未经BIOS处理的PCI桥,相关配置寄存器,就得由内核设置 (读写过程涉及PCI规范)
| | | |
-
pci_read_config_word(dev, PCI_COMMAND, &cr)
| | | |
-
pci_write_config_word(dev, PCI_COMMAND,
0x0000
)
| | | |
-
pci_write_config_word(dev, PCI_STATUS,
0xffff
)
| | | |
-
child
=
pci_add_new_bus(bus, dev,
+
+
max
)
/
/
为新探测到的总线,分配和设置pci_bus对象
| | | |
-
buses
=
(buses &
0xff000000
)
| | | | | ((unsigned
int
)(child
-
>primary) <<
0
)
| | | | | ((unsigned
int
)(child
-
>secondary) <<
8
)
| | | | | ((unsigned
int
)(child
-
>subordinate) <<
16
)
/
/
按照PCI_PRIMARY_BUS寄存器的组成规范,进行拼接
| | | |
-
pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses)
/
/
将总线号写入PCI_PRIMARY_BUS寄存器
| | | |
-
if
(!is_cardbus)
| | | | |
-
max
=
pci_do_scan_bus(child)
/
/
递归调用
| | | |
-
else
| | | | |
-
max
+
=
3
| | | |
-
child
-
>subordinate
=
max
| | | |
-
pci_write_config_byte(dev, PCI_SUBORDINATE_BUS,
max
)
| | | |
-
pci_write_config_word(dev, PCI_COMMAND, cr)
| | |
| | |
-
return
max
| |
| |
/
*
到此为止,已经为
0
号主总线及其所有子总线分配了管理对象,并且获取了出厂信息,接下来继续完成如下操作:
| |
1.
设备中断请求线与中断控制器的连接
| |
2.
设备各个存储器区间,与总线地址的映射 (对于i386 CPU,一定已在加电自检阶段,由BIOS完成了映射,接下来只需检查并修正)
| | 另外,母板可以有多个
"宿主-PCI桥"
,从而可以接多条PCI主总线,然而以上过程只完成了
0
号PCI主总线树的枚举,因此,接下来还要完成其它主总线树的枚举和设置
*
/
| |
| |
/
/
确认PCI设备与中断控制器之间的连接,记录到PCI_INTERRUPT_LINE寄存器 (将来为设备设置中断处理函数时,需要知道登记到哪个中断服务队列)
| |
/
*
参照
"外设-PCI插槽-路径互连器-中断控制器"
之间的中断请求线连接关系图:
| |
"外设-PCI插槽"
之间:由硬件决定,记录在PCI设备的PCI_INTERRUPT_PIN寄存器,以上过程已经读取了 (
1
~
4
分别表示与INTA~INTD连接)
| |
"PCI插槽-路径互连器"
之间:由母板设计决定,为此,BIOS提供了一个
"中断请求路径表"
(irq_routing_table对象),记录每个PCI插槽的INTA~INTD中断请求线,分别与互连器的哪根请求线连接
| |
"路径互连器-中断控制器"
之间:部分由硬件设计决定,其余由软件设置 (使得内核可以将中断设备,均匀分布到中断控制器的引脚上)
*
/
| |
-
pcibios_irq_init()
| | |
| | |
/
/
irq_routing_table对象的slots[]数组,记录了各个设备所在PCI插槽的INTA~INTD,与互连器的哪根中断请求线连接,以及互连器的这根请求线,可以连接中断控制器的哪些线
| | |
-
pirq_table
=
pirq_find_routing_table()
/
/
利用内核自身接口,定位BIOS中的
"中断请求路径表"
| | | |
-
for
(addr
=
(u8
*
) __va(
0xf0000
); addr < (u8
*
) __va(
0x100000
); addr
+
=
16
)
| | | |
-
...
/
/
根据signature和checksum等特征,寻找irq_routing_table对象位置
| | |
-
if
(!pirq_table && (pci_probe & PCI_BIOS_IRQ_SCAN))
| | | |
-
pirq_table
=
pcibios_get_irq_routing_table()
/
/
#ifdef CONFIG_PCI_BIOS (利用BIOS接口,定位BIOS中的"中断请求路径表")
| | |
| | |
/
/
根据
"中断请求路径表"
信息,查询
"PCI设备-中断控制器"
连接关系:
| | |
/
/
硬件决定 中断请求路径表 部分由硬件决定,其余由软件设置
| | |
/
/
PCI设备
-
-
-
-
-
-
-
-
-
-
PCI插槽
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
路径互连器
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
中断控制器
| | |
-
if
(pirq_table)
/
/
疑问:如果BIOS中找不到
"中断请求路径表"
,为什么不返回错误??
| | |
/
/
这种情况一定意味着,不需要内核设置
"路径互连器-中断控制器"
之间的连接(已完全由硬件决定),相应的,BIOS阶段就能完全确定
"PCI设备-中断控制器"
之间的连接关系。
| | |
| | |
/
/
先额外搞点别的事
| | |
/
/
调用pcibios_irq_init()之前,只完成了
0
号PCI主总线树的枚举,pirq_peer_trick()根据irq_routing_table对象中出现的其它总线号,进行补充枚举
| | |
-
pirq_peer_trick()
| | | |
| | | |
/
/
每个中断路径表项对应PCI插槽所在的总线,一定是经过BIOS确认存在的,并且每一条都有可能是主总线
| | | |
-
for
(i
=
0
; i < (rt
-
>size
-
sizeof(struct irq_routing_table))
/
sizeof(struct irq_info); i
+
+
)
| | | | |
-
e
=
&rt
-
>slots[i]
| | | | |
-
busmap[e
-
>bus]
=
1
| | | |
| | | |
/
*
疑问
1
:busmap[]数组,不是
0
~pcibios_last_bus下标处必然为
1
吗??
| | | | 个人猜测,BIOS并不会为空PCI插槽分配slots[i]对象,此处是为了只对插了PCI设备的总线,调用pci_scan_bus(),进而分配总线管理对象
| | | | 后面调用的pcibios_fixup_peer_bridges()函数(穷举式扫描总线),相比此处,正是直接通过pcibios_last_bus遍历,但是多了
"总线上是否有PCI设备"
的判断
| | | |
| | | | 疑问
2
:根据《Linux内核源代码情景分析》说明,以下过程是为了补充扫描其它PCI主总线树,但是《PCI Express体系结构导读》(王齐),
2.4
.
3
节提到:
| | | |
"当处理器系统中存在多个HOST主桥时,将有多个编号为0的PCI总线,但是这些编号为0的PCI总线分属不同的PCI总线域"
| | | | 然而从此处看,linux
-
2.4
.
0
内核,并没有对PCI总线分域,并且期望的是:第N
+
1
条PCI主总线的编号
=
第N条PCI主总线树中最大总线编号
+
1
| | | | 为了求证这一点,我又粗略看了《存储技术原理分析:基于Linux_2.
6
内核源代码》,从linux
-
2.6
.
34
内核源码中,看到如下片段:
| | | | pirq_peer_trick()
/
/
arch
/
x86
/
pci
/
irq.c
| | | | |
-
...
| | | | |
-
for
(i
=
1
; i <
256
; i
+
+
)
| | | | |
-
if
(!busmap[i] || pci_find_bus(
0
, i))
/
/
如果已经存在于domain0,不再重复扫描
| | | | | |
-
continue
/
/
推测:linux
-
2.6
.
34
内核即使对PCI总线进行了分域,也没有期望每条PCI主总线,从
0
开始编号
| | | | |
-
pci_scan_bus_on_node()
| | | | |
-
pci_scan_bus()
| | | | |
-
pci_scan_bus_parented()
| | | | |
-
pci_create_bus()
| | | | |
-
pci_find_bus(pci_domain_nr(b), bus)
| | | | |
-
struct pci_sysdata
*
sd
=
bus
-
>sysdata
| | | | |
-
return
sd
-
>domain
| | | | 结论:linux
-
2.4
.
0
内核非常老,有些实现细节会跟较新的设计思路不一致,不用过于纠结。
| | | |
| | | | 疑问
3
:
0
号总线除外,总线要先编号,才能被访问(可以认为
0
号总线在一个固定位置,不需要编号),既然编号前不能访问,又怎么为它编号呢??
| | | | 对总线进行编号,只是设置总线所在PCI桥的PCI_SECONDARY_BUS寄存器,并不需要访问总线本身(总线本身也没有寄存器啥的)
| | | | 所以,
0
号之外的主总线,虽然编号不是固定的,但是所在
"宿主-PCI"
桥在母板的位置是固定的,可以随时对它编号并访问
*
/
| | | |
| | | |
/
/
扫描其它PCI主总线树 (根据明确的总线清单进行补漏,否则后期需要执行pcibios_fixup_peer_bridges(),进行低效的穷举式扫描)
| | | |
-
for
(i
=
1
; i<
256
; i
+
+
)
| | | |
| | | |
-
if
(busmap[i])
| | | | |
| | | | |
/
/
对源码中注释的解释:
| | | | |
/
/
由于总线编号,采用的是深度优先遍历方式,那么次层总线号必然大于所在主总线号,所以如果i是次层总线,则所在主总线一定会早于i被扫描,i总线也在那时就已经扫描了
| | | | |
/
/
所以pci_scan_bus()返回不为空时,i一定为PCI主总线
| | | | |
-
pci_scan_bus(i, pci_root_bus
-
>ops, NULL)
/
/
如果i总线之前已经扫描,pci_scan_bus()会保证不对其重复分配pci_dev对象并扫描
| | | |
| | | |
-
pcibios_last_bus
=
-
1
/
/
pcibios_last_bus表示加电阶段,BIOS设置的最大总线编号,此处设置为
-
1
,表示这些总线都已扫描,后期不再需要扫描
| | |
| | |
/
/
查找互连器的irq_router对象,赋值到全局变量pirq_router,供后期使用
| | |
/
/
(互连器作为一种特殊的PCI设备,除了要有pci_dev对象记录其设备信息,还要有irq_router对象记录其
"编程接口"
(互连器是可编程的))
| | |
-
pirq_find_router()
| | | |
| | | |
-
struct irq_routing_table
*
rt
=
pirq_table
/
/
"中断请求路径表"
| | | |
-
pirq_router
=
pirq_routers
+
sizeof(pirq_routers)
/
sizeof(pirq_routers[
0
])
-
1
/
/
pirq_routers[]:不同厂商的互连器信息表
| | | |
| | | |
/
/
在pci_devices全局链表中,找到互连器的pci_dev对象
| | | |
/
/
irq_routing_table对象的rtr_bus(所在总线)和rtr_devfn(设备
+
功能号)成员,记录了互连器的位置 (互连器通常与PCI
-
ISA桥集成在同一芯片,作为PCI设备连接在PCI总线上)
| | | |
-
pirq_router_dev
=
pci_find_slot(rt
-
>rtr_bus, rt
-
>rtr_devfn)
| | | | |
-
pci_for_each_dev(dev)
| | | | |
-
if
(dev
-
>bus
-
>number
=
=
bus && dev
-
>devfn
=
=
devfn)
| | | | |
-
return
dev
| | | |
| | | |
/
/
"中断请求路径表"
和互连器的pci_dev对象,都包含互连器的生产厂商和设备
ID
,优先以
"中断请求路径表"
提供的为准
| | | |
/
/
如果找不到对应的irq_router对象,表示互连器与中断控制器之间的连接,不需要软件设置 (比如全部为
"硬连接"
)
| | | |
-
for
(r
=
pirq_routers; r
-
>vendor; r
+
+
)
| | | |
-
if
(r
-
>vendor
=
=
rt
-
>rtr_vendor && r
-
>device
=
=
rt
-
>rtr_device)
| | | | |
-
pirq_router
=
r
| | | | |
-
break
| | | |
-
if
(r
-
>vendor
=
=
pirq_router_dev
-
>vendor && r
-
>device
=
=
pirq_router_dev
-
>device)
| | | |
-
pirq_router
=
r
| | |
| | |
/
/
"中断请求路径表"
还有一个exclusive_irqs字段,是一个位图,用于表示中断控制器上,应该避免共用的中断请求线 (arch
/
i386
/
kernel
/
pci
-
irq.c,
27
行):
| | |
/
*
| | |
*
Never use:
0
,
1
,
2
(timer, keyboard,
and
cascade) (
0
号中断请求线,由时钟中断专用)
| | |
*
Avoid using:
13
,
14
and
15
(FP error
and
IDE).
| | |
*
Penalize:
3
,
4
,
6
,
7
,
12
(known ISA uses: serial, floppy, parallel
and
mouse)
| | |
*
/
| | |
-
if
(pirq_table
-
>exclusive_irqs)
| | |
-
for
(i
=
0
; i<
16
; i
+
+
)
| | |
-
if
(!(pirq_table
-
>exclusive_irqs & (
1
<< i)))
| | |
-
pirq_penalty[i]
+
=
100
/
/
内核使用
"惩罚量"
,对应需要避免的程度
| |
| |
/
/
穷举式扫描还未枚举的总线
| |
-
pcibios_fixup_peer_bridges()
| | |
-
if
(pcibios_last_bus <
=
0
|| pcibios_last_bus >
=
0xff
)
/
/
如果根据
"中断请求路径表"
补过漏了,不用再扫描
| | | |
-
return
| | |
-
for
(n
=
0
; n <
=
pcibios_last_bus; n
+
+
)
| | |
-
for
(dev.devfn
=
0
; dev.devfn<
256
; dev.devfn
+
=
8
)
| | |
-
if
(!pci_read_config_word(&dev, PCI_VENDOR_ID, &l) && l !
=
0x0000
&& l !
=
0xffff
)
/
/
总线上有PCI设备,才会被扫描
| | |
-
pci_scan_bus(n, pci_root_ops, NULL)
| |
| |
/
/
pirq_peer_trick()和pcibios_fixup_peer_bridges()属于顺带任务,pcibios_irq_init()之后的主要任务,是继续处理中断请求线的连接问题
| |
-
pcibios_fixup_irqs()
| | |
| | |
/
/
累积惩罚值 (有些dev
-
>irq,在加电自检阶段,就已经被BIOS设置成非
0
值了,并由前面的pci_setup_device()读到了pci_dev对象中)
| | |
-
pci_for_each_dev(dev)
/
/
遍历pci_devices全局链表上的pci_dev对象
| | | |
-
if
(pirq_penalty[dev
-
>irq] >
=
100
&& pirq_penalty[dev
-
>irq] <
100000
)
| | | | |
-
pirq_penalty[dev
-
>irq]
=
0
| | | |
-
pirq_penalty[dev
-
>irq]
+
+
/
/
有了pirq_penalty[]参考,内核就可以尽量保证,将剩余未接的请求线,接在中断控制器相对空闲的请求线上
| | |
| | |
/
/
扫描所有pci设备,对于已经连接到中断控制器的设备,获取其连接的请求线,记录到PCI_INTERRUPT_LINE寄存器
| | |
-
pci_for_each_dev(dev)
| | |
-
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin)
| | |
-
...
/
/
#ifdef CONFIG_X86_IO_APIC (暂不关心)
| | |
-
if
(pin && !dev
-
>irq)
/
/
PCI_INTERRUPT_PIN寄存器不为
0
(表示设备可以产生中断),但是dev
-
>irq为
0
(表示还不知道设备连接中断控制器的哪根请求线)
| | |
| | |
/
/
根据
"外设-PCI插槽-路径互连器-中断控制器"
,逐层查找
| | |
/
/
不过dev设备连接的那根互连器请求线,可能还未与中断控制器连接,第
2
个参数传
0
,正是表示只查找、不连接,让pcibios_lookup_irq()先别管未连接的情况
| | |
-
pcibios_lookup_irq(dev,
0
)
/
/
疑问
1
:对于i386 CPU,此时就已连接的情况,必定也由BIOS设置好了PCI_INTERRUPT_LINE,那么为
0
就表示还未连接,还有必要查找吗??
| | |
/
/
疑问
2
:pcibios_lookup_irq()找到连接的请求线后,为什么并没有补充记录到PCI_INTERRUPT_LINE寄存器??
| | |
| | |
-
struct irq_router
*
r
=
pirq_router
| | |
-
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin)
| | |
-
pin
=
pin
-
1
/
/
pin
=
1
~
4
时,表示dev设备中断请求线,与PCI插槽INTA~INTD连接,此处减
1
,用于后续作为数组下标
| | |
-
info
=
pirq_get_info(dev)
/
/
获取dev设备所在插槽的irq_info对象 (用于描述PCI插槽上的INTA~INTD请求线,与互连器的连接信息)
| | | |
-
struct irq_routing_table
*
rt
=
pirq_table
| | | |
-
int
entries
=
(rt
-
>size
-
sizeof(struct irq_routing_table))
/
sizeof(struct irq_info)
/
/
"中断请求路径表"
中的irq_info对象个数
| | | |
-
for
(info
=
rt
-
>slots; entries
-
-
; info
+
+
)
| | | |
-
if
(info
-
>bus
=
=
dev
-
>bus
-
>number && PCI_SLOT(info
-
>devfn)
=
=
PCI_SLOT(dev
-
>devfn))
/
/
匹配dev设备,与各个插槽的irq_info对象
| | | |
-
return
info
| | |
-
pirq
=
info
-
>irq[pin].link
/
/
link:PCI插槽第pin根请求线,连接的互连器请求线号
| | |
/
/
高
4
位:是否
"硬连接"
; 低
4
位(仅
"硬连接"
时有意义):连接的互连器请求线号
| | |
-
mask
=
info
-
>irq[pin].bitmap
/
/
与PCI插槽第pin根请求线连接的互连器请求线,可以连接的中断控制器请求线位图
| | |
-
mask &
=
pcibios_irq_mask
/
/
中断控制器
0
~
2
请求线,禁止共用
| | |
| | |
/
/
运行至此处,dev
-
>irq一定为
0
,assign一定为
0
(见pcibios_lookup_irq()函数的调用条件,和第二个参数值)
| | |
-
newirq
=
dev
-
>irq
/
/
dev
-
>irq为
0
,表示还不知道设备连接中断控制器的哪根请求线,一种可能是连接了,但还没查询,另一种可能是还没连接
| | |
-
if
(!newirq && assign)
/
/
assign
=
1
时(一定是后期,此时assign为
0
),如果dev
-
>irq还是
0
,那就不得不进行连接了
| | | |
-
for
(i
=
0
; i <
16
; i
+
+
)
| | | |
-
if
(pirq_penalty[i] < pirq_penalty[newirq])
| | | |
-
request_irq(i, pcibios_test_irq_handler, SA_SHIRQ,
"pci-test"
, dev)
| | | |
-
newirq
=
i
| | |
| | |
/
/
当前场景,一定会执行到这里,并且进入以下第
1
或第
2
个分支,最终获取设备连接的中断控制器请求线 (assign
=
1
时,newirq才能非
0
,才能进入第
3
个分支)
| | |
-
if
((pirq &
0xf0
)
=
=
0xf0
)
| | | |
-
irq
=
pirq &
0xf
| | |
-
else
if
(r
-
>get)
| | | |
-
irq
=
r
-
>get(pirq_router_dev, dev, pirq)
| | |
-
else
if
(newirq && r
-
>
set
&& (dev
-
>
class
>>
8
) !
=
PCI_CLASS_DISPLAY_VGA)
| | | |
-
r
-
>
set
(pirq_router_dev, dev, pirq, newirq)
| | |
| | |
/
/
连接到互连器同一根请求线的设备,一定也连接到中断控制器的同一根请求线 (pci_for_each_dev(dev)后续遍历到这些设备时,就不需要再调用pcibios_lookup_irq())
| | |
-
pci_for_each_dev(dev2)
| | |
-
pci_read_config_byte(dev2, PCI_INTERRUPT_PIN, &pin)
| | |
-
info
=
pirq_get_info(dev2)
| | |
-
if
(info
-
>irq[pin].link
=
=
pirq)
| | |
-
dev2
-
>irq
=
irq
| | |
-
pirq_penalty[irq]
+
+
| |
| |
/
/
"配置寄存器组"
头部中保存的地址区间起始地址:
| |
/
/
出厂时,为设备内部的局部地址,对于i386 CPU,已在加电阶段,由BIOS设置为映射地址了
| |
/
/
内核为之建立resource对象,并加以验证和确认:过低的物理地址区间(被内核本身使用)、与其它区间冲突的地址区间,都要另行分配
| |
| |
/
*
resource结构:
| | start和end表示区间范围,flags包含表示区间属性的标志位,parent,sibling和child用于按照位置关系,将所有resource对象组织在一起
| |
| | 总线地址空间:(pci_init()之前,已经被
"批发"
走了一部分)
| | struct resource ioport_resource
=
{
"PCI IO"
,
0x0000
, IO_SPACE_LIMIT, IORESOURCE_IO };
| | struct resource iomem_resource
=
{
"PCI mem"
,
0x00000000
,
0xffffffff
, IORESOURCE_MEM };
*
/
| |
-
pcibios_resource_survey()
| |
| |
/
/
为每条PCI总线分配resource对象 (事后追认!!)
| |
/
/
每条PCI总线占用的地址资源,实际就是所在PCI桥的
3
个地址窗口:
| |
/
/
设备上,由所在PCI桥的PCI_IO_BASE,PCI_MEMORY_BASE,PCI_PREF_MEMORY_BASE寄存器记录
| |
/
/
内存中,由所在PCI桥pci_dev对象的resource[
7
~
9
]记录
| |
/
/
实际上,pcibios_allocate_bus_resources()并不需要分配什么:
| |
/
/
总线地址早已在加电阶段由BIOS分配,并设置到PCI_IO_BASE,PCI_MEMORY_BASE,PCI_PREF_MEMORY_BASE寄存器 (很多设置在加电自检阶段就已经完成了,pci_init()其实只是修复和补充)
| |
/
/
pci_bus对象的
*
resource[
0
~
2
],也已经在前面调用pci_read_bridge_bases()时,直接指向所在PCI桥pci_dev对象的&resource[
7
~
9
],因此也不需要分配
| |
-
pcibios_allocate_bus_resources(&pci_root_buses)
/
/
建议仔细看一下函数定义上方的注释
| | |
-
for
(ln
=
bus_list
-
>
next
; ln !
=
bus_list; ln
=
ln
-
>
next
)
/
/
被pcibios_resource_survey()调用时,bus_list
=
&pci_root_buses,递归调用时,bus_list
=
上层总线的children链表
| | |
| | |
-
bus
=
pci_bus_b(ln)
/
/
list_entry()的封装
| | |
-
if
((dev
=
bus
-
>
self
))
/
/
bus
-
>
self
:总线自身所在的设备(PCI桥) (bus
-
>devices:连接在总线上的设备)
| | | |
/
/
仅为窗口分配resource对象 (PCI桥本身具有的RAM、ROM区间,将在后面同普通PCI设备一起,调用pcibios_allocate_resources()函数进行分配)
| | | |
-
for
(idx
=
PCI_BRIDGE_RESOURCES; idx < PCI_NUM_RESOURCES; idx
+
+
)
| | | |
| | | |
-
r
=
&dev
-
>resource[idx]
| | | |
-
if
(!r
-
>start)
/
/
当前区间没有对地址资源的需求 (比如子设备不需要IO空间,则PCI桥就不需要IO窗口)
| | | | |
-
continue
| | | |
| | | |
/
/
向
"父节点"
批发地址资源 (对于i386 CPU,
"父节点"
需要占用多少地址资源,已经由BIOS计算好了,内核只是
"追加"
resource对象)
| | | |
-
pr
=
pci_find_parent_resource(dev, r)
/
/
dev:总线所在PCI桥的pci_dev对象
| | | | |
/
/
r:区间原始信息 (在设备内的局部地址、大小等)
| | | | |
-
for
(i
=
0
; i<
4
; i
+
+
)
| | | | | |
-
struct resource
*
r
=
bus
-
>resource[i]
| | | | | |
-
if
(res
-
>start && !(res
-
>start >
=
r
-
>start && res
-
>end <
=
r
-
>end))
| | | | | | |
-
continue
| | | | | |
-
if
((res
-
>flags ^ r
-
>flags) & (IORESOURCE_IO | IORESOURCE_MEM))
/
/
I
/
O区间,必须从I
/
O空间分配,存储器区间,必须从存储器区间分配
| | | | | | |
-
continue
| | | | | |
-
if
(!((res
-
>flags ^ r
-
>flags) & IORESOURCE_PREFETCH))
/
/
分配区间(res)要求
"可预取"
,但
"父节点"
区间(r)不支持
"可预取"
(
"可预取"
区间,不能从不支持
"可预取"
的空间分配)
| | | | | | |
/
/
或:分配区间(res)不要求
"可预取"
,但
"父节点"
区间(r)支持
"可预取"
(
"可预取"
空间,尽量留给要求
"可预取"
的区间)
| | | | | | |
/
/
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| | | | | | |
/
/
!((res
-
>flags ^ r
-
>flags) & IORESOURCE_PREFETCH)
| | | | | | |
/
/
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| | | | | | |
/
/
只要不是IORESOURCE_PREFETCH,直接返回
"父节点"
的这块空间
| | | | | | |
-
return
r
| | | | | |
-
if
((res
-
>flags & IORESOURCE_PREFETCH) && !(r
-
>flags & IORESOURCE_PREFETCH))
/
/
不要求
"可预取"
的区间,迫不得已时也可以从
"可预取"
空间分配
| | | | | | |
-
best
=
r
| | | | |
-
return
best
| | | |
| | | |
/
/
从批发的地址资源中,分配需要的地址区间
| | | |
-
if
(!pr)
| | | |
-
request_resource(pr, r)
/
/
从pr上面,切割出一个r
| | | |
-
__request_resource(root, new)
| | | |
-
if
(end < start)
/
/
冲突:new
-
>end < new
-
>start
| | | | |
-
return
root;
| | | |
-
if
(start < root
-
>start)
/
/
冲突:new
-
>start < root
-
>start
| | | | |
-
return
root;
| | | |
-
if
(end > root
-
>end)
/
/
冲突:new
-
>end > root
-
>end
| | | | |
-
return
root;
| | | |
-
p
=
&root
-
>child
| | | |
-
for
(;;)
| | | |
-
tmp
=
*
p
| | | |
-
if
(!tmp || tmp
-
>start > end)
/
/
如果tmp是第一个child,只需要tmp
-
>start > end即可
| | | | |
/
/
否则上次循环,一定是从
if
(tmp
-
>end < start)分支中,
continue
的,保证了上个child
-
>end < start
| | | | |
-
new
-
>sibling
=
tmp
| | | | |
-
*
p
=
new
| | | | |
-
new
-
>parent
=
root
| | | | |
-
return
NULL
/
/
没有冲突区间,返回NULL
| | | |
-
p
=
&tmp
-
>sibling
| | | |
-
if
(tmp
-
>end < start)
| | | | |
-
continue
| | | |
-
return
tmp
/
/
tmp
-
>start < end && tmp
-
>end > start,说明new与tmp有重叠部分
| | |
| | |
-
pcibios_allocate_bus_resources(&bus
-
>children)
/
/
递归
| |
| |
/
/
为PCI设备分配resource对象 (第一趟,处理已经生效的地址区间)
| |
-
pcibios_allocate_resources(
0
)
| | |
-
pci_for_each_dev(dev)
/
/
遍历所有PCI设备
| | |
-
pci_read_config_word(dev, PCI_COMMAND, &command)
| | |
-
for
(idx
=
0
; idx <
6
; idx
+
+
)
/
/
PCI设备本身的RAM区间 (最多
6
个)
| | | |
-
r
=
&dev
-
>resource[idx]
| | | |
-
if
(r
-
>parent)
/
/
parent已经指向
"父节点"
,表示已经分配了resource
| | | | |
-
continue
| | | |
-
if
(!r
-
>start)
/
/
区间起始地址为
0
,表示不需要分配地址资源的区间,或者暂时无法从
"父节点"
分配
| | | | |
-
continue
| | | |
/
/
需要分配地址资源的区间
| | | |
-
if
(r
-
>flags & IORESOURCE_IO)
| | | | |
-
disabled
=
!(command & PCI_COMMAND_IO)
| | | |
-
else
| | | | |
-
disabled
=
!(command & PCI_COMMAND_MEMORY)
| | | |
-
if
(
pass
=
=
disabled)
/
/
第一趟,
pass
=
0
| | | |
-
pr
=
pci_find_parent_resource(dev, r)
/
/
向
"父节点"
批发地址资源
| | | |
-
if
(!pr || request_resource(pr, r) <
0
)
/
/
从批发的地址资源中,分配需要的地址区间
| | | |
/
/
无法从
"父节点"
分配,先将该区间起始地址平移到
0
,后续由pcibios_assign_resources()统一变更到合适的位置
| | | |
-
r
-
>end
-
=
r
-
>start
| | | |
-
r
-
>start
=
0
| | |
| | |
-
if
(!
pass
)
/
/
仅第一趟执行,关闭ROM区间:
| | |
/
/
ROM区间一般只在初始化时由BIOS或具体的驱动程序使用,但地址资源还保留,如果需要,还可以在驱动程序中再打开
| | |
-
r
=
&dev
-
>resource[PCI_ROM_RESOURCE]
| | |
-
if
(r
-
>flags & PCI_ROM_ADDRESS_ENABLE)
| | |
-
r
-
>flags &
=
~PCI_ROM_ADDRESS_ENABLE
| | |
-
pci_read_config_dword(dev, dev
-
>rom_base_reg, ®)
| | |
-
pci_write_config_dword(dev, dev
-
>rom_base_reg, reg & ~PCI_ROM_ADDRESS_ENABLE)
| |
| |
/
/
为PCI设备分配resource对象 (第二趟,基于第一趟剩余的地址区间,处理未生效地址区间)
| |
-
pcibios_allocate_resources(
1
)
| | |
-
pci_for_each_dev(dev)
| | |
-
...
| |
| |
/
/
处理未能从
"父节点"
分配到地址资源(即起始地址为
0
)的区间 (真实意义上的
"分配"
,而不是
"追认"
)
| |
-
pcibios_assign_resources()
| |
-
pci_for_each_dev(dev)
/
/
遍历所有PCI设备
| |
-
int
class
=
dev
-
>
class
>>
8
/
/
"配置寄存器组"
PCI_CLASS_DEVICE字段,高
8
位代表设备类型
| |
-
if
(!
class
||
class
=
=
PCI_CLASS_BRIDGE_HOST)
/
/
设备无效(类型字段为
0
),或者是PCI桥,不处理
| | |
-
continue
| |
| |
-
for
(idx
=
0
; idx<
6
; idx
+
+
)
/
/
PCI设备本身的RAM区间 (最多
6
个)
| | |
-
r
=
&dev
-
>resource[idx]
| | |
-
if
((
class
=
=
PCI_CLASS_STORAGE_IDE && idx <
4
) ||
| | | | (
class
=
=
PCI_CLASS_DISPLAY_VGA && (r
-
>flags & IORESOURCE_IO)))
/
/
IDE存储设备(硬盘)的前
4
个区间,和VGA设备的I
/
O区间是特例,这些区间既不需要分配,也不能改变
| | | |
-
continue
| | |
-
if
(!r
-
>start && r
-
>end)
/
/
起始地址为
0
,但结束地址不为
0
,表示需要分配地址资源的区间 (比如之前分配失败,经过
"平移"
的区间)
| | |
-
pci_assign_resource(dev, idx)
/
/
分配总线地址,并设置
"配置寄存器组"
的相应字段
| | |
-
const struct pci_bus
*
bus
=
dev
-
>bus
| | |
-
struct resource
*
res
=
dev
-
>resource
+
i
| | |
-
size
=
res
-
>end
-
res
-
>start
+
1
/
/
区间大小
| | |
-
min
=
(res
-
>flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM
/
/
I
/
O地址区间位置不得低于
4KB
,内存地址区间位置不得低于
256MB
| | |
| | |
/
/
为PCI设备从所在总线分配地址资源 (IORESOURCE_PREFETCH参数表示:如果设备区间
"可预取"
,优先要求分配的地址区间也
"可预取"
)
| | |
-
if
(pci_assign_bus_resource(bus, dev, res, size,
min
, IORESOURCE_PREFETCH, i) <
0
)
| | | |
-
for
(i
=
0
; i <
4
; i
+
+
)
| | | |
| | | |
-
struct resource
*
r
=
bus
-
>resource[i]
/
/
r:所在总线的地址窗口
| | | |
-
if
((res
-
>flags ^ r
-
>flags) & type_mask)
/
/
res:待分配的地址区间
| | | | |
-
continue
| | | |
-
if
((r
-
>flags & IORESOURCE_PREFETCH) && !(res
-
>flags & IORESOURCE_PREFETCH))
| | | | |
-
continue
| | | |
| | | |
/
/
分配总线地址
| | | |
-
allocate_resource(r, res, size,
min
,
-
1
, size, pcibios_align_resource, dev)
| | | | | |
| | | | | |
/
/
避免使用bit8或bit9为非
0
的I
/
O地址:
| | | | | |
/
/
0000
,
0000B
~
1111
,
1111B
:保留给母板使用
| | | | | |
/
/
01
,
0000
,
0000B
~
11
,
1111
,
1111B
:有些外设只解析I
/
O地址低
10
位
| | | | | |
/
/
X01,
0000
,
0000B
~ X11,
1111
,
1111B
:比如将
001
,
0000
,
0000B
~
011
,
1111
,
1111B
分配给外设A,将
101
,
0000
,
0000B
~
111
,
1111
,
1111B
分配给外设B
| | | | | |
/
/
并且外设A、B正好都只解析低
10
位,则实际上它们都分配到
01
,
0000
,
0000B
~
11
,
1111
,
1111B
地址区间,导致冲突
| | | | |
/
/
寻找符合要求的区间 |
-
if
(res
-
>flags & IORESOURCE_IO)
| | | | |
/
/
root: 总线上的地址窗口 |
-
if
(start &
0x300
)
| | | | |
/
/
new: 待分配的地址区间 | |
-
start
=
(start
+
0x3ff
) & ~
0x3ff
| | | | |
/
/
size: 待分配区间的大小 (必为
2
的次幂) |
-
res
-
>start
=
start
| | | | |
/
/
align: 待分配区间地址地址对齐值 (
=
size)
| | | | |
/
/
alignf: 边界对齐函数指针 (pcibios_align_resource())
| | | | |
/
/
alignf_data: alignf()参数 (pci_dev对象)
| | | | |
-
err
=
find_resource(root, new, size,
min
,
max
, align, alignf, alignf_data)
| | | | | |
-
for
(;;)
| | | | | |
-
struct resource
*
this
=
root
-
>child
| | | | | |
-
new
-
>start
=
root
-
>start
| | | | | |
-
if
(this)
/
/
root有子节点
| | | | | | |
-
new
-
>end
=
this
-
>start
/
/
扫描子区间
| | | | | |
-
else
/
/
root为叶子节点,没有子节点
| | | | | | |
-
new
-
>end
=
root
-
>end
/
/
root起点,作为待分配区间的起点
| | | | | |
-
if
(new
-
>start <
min
)
| | | | | | |
-
new
-
>start
=
min
| | | | | |
-
if
(new
-
>end >
max
)
| | | | | | |
-
new
-
>end
=
max
| | | | | |
-
new
-
>start
=
(new
-
>start
+
align
-
1
) & ~(align
-
1
)
/
/
先按照align值对齐
| | | | | |
-
if
(alignf)
| | | | | | |
-
alignf(alignf_data, new, size)
/
/
再通过alignf()额外调整
| | | | | |
-
if
(new
-
>start < new
-
>end && new
-
>end
-
new
-
>start
+
1
>
=
size)
/
/
找到大小符合要求的区间
| | | | | | |
-
new
-
>end
=
new
-
>start
+
size
-
1
| | | | | | |
-
return
0
| | | | | |
-
if
(!this)
| | | | | | |
-
break
| | | | | |
-
new
-
>start
=
this
-
>end
+
1
| | | | | |
-
this
=
this
-
>sibling
| | | | |
| | | | |
/
/
将分配到的区间,添加到root
-
>child队列
| | | | |
-
__request_resource(root, new)
| | | |
| | | |
/
/
将分配地址设置到PCI设备的
"配置寄存器组"
| | | |
-
pcibios_update_resource(dev, r, res, resno)
| | | |
-
new
=
res
-
>start | (res
-
>flags & PCI_REGION_FLAG_MASK)
| | | |
-
if
(resource <
6
)
| | | | |
-
reg
=
PCI_BASE_ADDRESS_0
+
4
*
resource
/
/
PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5地址 (每个寄存器占
4
字节)
| | | |
-
else
if
(resource
=
=
PCI_ROM_RESOURCE)
| | | | |
-
res
-
>flags |
=
PCI_ROM_ADDRESS_ENABLE
| | | | |
-
new |
=
PCI_ROM_ADDRESS_ENABLE
| | | | |
-
reg
=
dev
-
>rom_base_reg
| | | |
-
else
| | | | |
-
return
| | | |
-
pci_write_config_dword(dev, reg, new)
| | | |
-
pci_read_config_dword(dev, reg, &check)
| | |
| | |
/
/
为PCI设备从所在总线分配地址资源 (情非得已并且设备区间
"可预取"
的情况下,分配到的地址区间与其不符(即不
"可预取"
),也是可以的,相当于放弃使用设备的
"可预取"
特性)
| | |
-
if
(!(res
-
>flags & IORESOURCE_PREFETCH) || pci_assign_bus_resource(bus, dev, res, size,
min
,
0
, i) <
0
)
| | |
-
return
-
EBUSY
| |
| |
-
if
(pci_probe & PCI_ASSIGN_ROMS)
| |
-
r
=
&dev
-
>resource[PCI_ROM_RESOURCE]
| |
-
r
-
>end
-
=
r
-
>start
| |
-
r
-
>start
=
0
| |
-
if
(r
-
>end)
| |
-
pci_assign_resource(dev, PCI_ROM_RESOURCE)
|
|
/
/
出厂信息修正 (PCI初始化结束阶段)
|
-
pci_for_each_dev(dev)
|
-
pci_fixup_device(PCI_FIXUP_FINAL, dev)
/
/
声明:以下分析的代码,是针对i386 CPU的实现!!
pci_init()
|
|
-
pcibios_init()
/
/
arch
/
i386
/
kernel
/
pci
-
pc.c !!
| |
/
/
早期,该函数完全通过调用BIOS接口实现,所以函数名中包含了
"bios"
| |
/
/
后来,该函数演变为,可以通过CONFIG_PCI_DIRECT和CONFIG_PCI_BIOS宏,选择调用BIOS接口,还是调用内核自己实现的接口
| |
/
/
而且两者不互斥,如果
2
个宏都打开,内核通常优先使用自己的接口,或用自己的接口,对BIOS接口的操作结果进行修正
| |
| |
/
/
获取BIOS提供的PCI操作接口 (
#ifdef CONFIG_PCI_BIOS)
| |
-
pci_root_ops
=
pci_find_bios()
| | |
| | |
/
/
0xe0000
~
0xffff0
为BIOS占用的物理内存
| | |
-
for
(check
=
(union bios32
*
) __va(
0xe0000
); check <
=
(union bios32
*
) __va(
0xffff0
);
+
+
check)
| | |
| | |
/
/
每个bios32对象,都包含
16
字节头部,其中包含signature和checksum字段
| | |
-
if
(check
-
>fields.signature !
=
BIOS32_SIGNATURE)
| | | |
-
continue
| | |
| | |
/
/
构造bios32对象时,通过设置checksum字段值,保证了整个对象占用的字节相加为
0
| | |
-
for
(i
=
0
; i < length ;
+
+
i)
| | | |
-
sum
+
=
check
-
>chars[i]
| | |
-
if
(
sum
!
=
0
)
| | | |
-
continue
| | |
| | |
/
/
进一步检查
| | |
-
if
(check_pcibios())
| | |
-
if
((pcibios_entry
=
bios32_service(PCI_SERVICE)))
| | | |
-
pci_indirect.address
=
pcibios_entry
+
PAGE_OFFSET
/
/
设置BIOS的PCI服务接口的段内偏移
| | | |
-
...
/
/
设置参数:PCIBIOS_PCI_BIOS_PRESENT
| | | |
-
lcall &pci_indirect
/
/
调用接口
| | | |
-
pci_indirect
=
{
0
, __KERNEL_CS }
/
/
初始化时,已将段基址设置为__KERNEL_CS
| | |
-
return
&pci_bios_access
| |
| |
/
/
获取内核自己实现的PCI操作接口 (
#ifdef CONFIG_PCI_DIRECT)
| |
-
pci_root_ops
=
pci_check_direct()
| | |
| | |
/
/
探测主总线是否为PCI总线
| | |
-
if
(pci_probe & PCI_PROBE_CONF1)
| | | |
-
outb (
0x01
,
0xCFB
)
/
/
32
位地址寄存器最高字节
0xCFB
,称为
"PMC寄存器"
,它的bit1~
7
保留,bit0用于选择
"PCI配置访问机制"
:
| | | |
/
/
参考:https:
/
/
html.datasheetbank.com
/
datasheet
-
html
/
251876
/
Intel
/
34page
/
290479
-
004.html
(PCI datasheet, page
34
)
| | | |
/
/
bit0
=
0
| PCI配置访问机制
0
|
2
型PCI设备
| | | |
/
/
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
| | | |
/
/
bit0
=
1
| PCI配置访问机制
1
|
1
型PCI设备
| | | |
-
tmp
=
inl (
0xCF8
)
/
/
从地址寄存器,读取原始值 (对于常规存储单元,先向a写入
1
(a
=
1
),再读取a(b
=
a),结果会为
1
,但是PCI配置寄存器往往不是这样)
| | | |
-
outl (
0x80000000
,
0xCF8
)
/
/
向地址寄存器,写入
0x80000000
| | | |
/
/
至于为什么非要写入
0x80000000
进行检查,估计也是PCI datasheet规定,后续分析不会过于纠结这种细节,建议尝试根据PCI datasheet查找
| | | |
| | | |
/
/
读到的还是
0x80000000
(),并且通过pci_sanity_check()检查
| | | |
-
if
(inl (
0xCF8
)
=
=
0x80000000
&& pci_sanity_check(&pci_direct_conf1))
| | | | | |
| | | | | |
-
for
(dev.devfn
=
0
; dev.devfn <
0x100
; dev.devfn
+
+
)
/
/
一条PCI总线,最多可以有
256
个逻辑设备
| | | | | |
-
if
((!o
-
>read_word(&dev, PCI_CLASS_DEVICE, &x) &&
| | | | | | (x
=
=
PCI_CLASS_BRIDGE_HOST || x
=
=
PCI_CLASS_DISPLAY_VGA)) ||
| | | | | | (!o
-
>read_word(&dev, PCI_VENDOR_ID, &x) &&
| | | | | | (x
=
=
PCI_VENDOR_ID_INTEL || x
=
=
PCI_VENDOR_ID_COMPAQ)))
/
/
连接了PCI_CLASS_BRIDGE_HOST或PCI_CLASS_DISPLAY_VGA设备,认为是PCI总线
| | | | | |
/
/
连接的设备由Intel或Compaq厂商制造,也认为是PCI总线
| | | | | |
-
return
1
| | | | |
| | | | |
-
outl (tmp,
0xCF8
)
/
/
为地址寄存器恢复原始值
| | | | |
-
request_region(
0xCF8
,
8
,
"PCI conf1"
)
/
/
在内核I
/
O设备资源树增加一个节点,用于记录
"宿主-PCI桥"
占用的I
/
O地址区间
| | | | |
-
return
&pci_direct_conf1
/
/
返回pci_direct_conf1对象地址,包含read_byte()、write_byte()等函数
/
PCI操作列表
| | | |
| | | |
-
outl (tmp,
0xCF8
)
/
/
为地址寄存器恢复原始值
| | |
| | |
/
/
探测主总线是否为ISA总线 (现已不用)
| | |
-
if
(pci_probe & PCI_PROBE_CONF2)
| | |
-
outb (
0x00
,
0xCFB
)
/
/
PMC寄存器
=
0
,选择机制
0
,访问
2
型PCI设备的配置寄存器
| | |
-
...
| | |
-
return
&pci_direct_conf2
| |
| |
/
/
扫描主总线,探测与枚举连接在主总线上的PCI设备,如果设备为
"PCI-PCI"
桥,则递归扫描其连接的次层总线
| |
/
/
对于i386 CPU,必定已由BIOS在加电自检阶段枚举过一遍了,pci_scan_bus()相当于直接读取BIOS的设置成果,并分配管理对象进行记录
| |
-
pci_root_bus
=
pci_scan_bus(
0
, pci_root_ops, NULL)
| | |
| | |
/
/
分配pci_bus对象,用于记录总线信息
| | |
-
struct pci_bus
*
b
=
pci_alloc_primary_bus()
| | | |
-
if
(pci_bus_exists(&pci_root_buses, bus))
/
/
检查pci_root_buses全局链表,是否已经存在总线号为bus的pci_bus对象
| | | | |
-
return
NULL
| | | |
-
b
=
pci_alloc_bus()
/
/
分配pci_bus对象
| | | |
-
list_add_tail(&b
-
>node, &pci_root_buses)
/
/
添加到pci_root_buses全局链表
| | | |
-
b
-
>number
=
b
-
>secondary
=
bus
/
/
记录总线号
| | | |
/
/
number表示当前总线的编号
| | | |
/
/
primary表示当前总线的上层总线编号
| | | |
/
/
secondary表示总线所在PCI桥的次层总线编号(也就是当前总线自己),所以总是等于number (额外搞个成员变量,估计是为了提高代码可读性)
| | | |
/
*
pci_dev对象的resource[DEVICE_COUNT_RESOURCE]:
| | | | PCI设备本身占用:
| | | | resource[
0
~
5
]:设备上可能有的内存地址区间
| | | | resource[
6
]:设备上可能有的扩充ROM区间
| | | | PCI桥是一种特殊的PCI设备,额外包含次层总线占用的
4
个区间:
| | | | resource[
7
]:I
/
O地址窗口
| | | | resource[
8
]:内存地址窗口
| | | | resource[
9
]:
"可预取"
内存地址窗口
| | | | resource[
10
]:扩充ROM区间窗口
*
/
| | | |
/
/
pci_bus对象的
*
resource[
4
],分别指向所在PCI桥的&resource[
7
~
10
]
| | | |
-
b
-
>resource[
0
]
=
&ioport_resource
/
/
resource[
0
]:可由当前总线使用的I
/
O地址区间 (由于是主总线,所以指向整个&ioport_resource)
| | | |
-
b
-
>resource[
1
]
=
&iomem_resource
/
/
resource[
1
]:可由当前总线使用的内存地址区间 (由于是主总线,所以指向整个&iomem_resource)
| | |
| | |
/
/
开始扫描
| | |
-
if
(b)
| | |
-
b
-
>ops
=
ops
/
/
次层总线的ops,同样设置为pci_root_ops
| | |
-
b
-
>subordinate
=
pci_do_scan_bus(b)
/
/
逐个发现连接在总线上的设备,为其建立pci_dev对象
| | |
-
for
(devfn
=
0
; devfn <
0x100
; devfn
+
=
8
)
/
/
每次扫描
1
个PCI插槽
| | | |
-
dev0.devfn
=
devfn
| | | |
-
pci_scan_slot(&dev0)
| | | |
-
for
(func
=
0
; func <
8
; func
+
+
, temp
-
>devfn
+
+
)
/
/
每个PCI设备,最多可以有
8
个功能,相当于
8
个逻辑设备 (逻辑设备号
=
5
位设备号
+
3
位功能号)
| | | |
| | | |
-
if
(func && !is_multi)
/
/
非
0
功能号,只在多功能设备中才需要处理
| | | | |
-
continue
/
/
疑问:这里为什么不直接
break
??
| | | |
| | | |
/
/
读取
"配置寄存器组"
头部中的PCI_HEADER_TYPE字段,根据它的最高位是否为
1
,判断设备是否为单功能,从而决定下一轮循环是否需要继续
| | | |
-
pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)
/
/
pci_read_config_byte()函数,根据PCI_OP(read, byte, u8
*
)宏展开定义
| | | | |
-
dev
-
>bus
-
>ops
-
>read_byte()
/
/
ops通常为pci_direct_conf1 (见pcibios_init()入口,对pci_root_ops的设置)
| | | | |
| | | | |
-
pci_conf1_read_config_byte()
/
/
如果ops
=
&pci_direct_conf1
| | | | ' |
-
outl(CONFIG_CMD(dev,where),
0xCF8
)
/
/
按照PCI规范,向地址寄存器(
0xCF8
),写入
"总线号,逻辑设备号,寄存器偏移(where)"
| | | | ' | |
-
(
0x80000000
| (dev
-
>bus
-
>number <<
16
) | (dev
-
>devfn <<
8
) | (where & ~
3
))
| | | | ' |
-
*
value
=
inb(
0xCFC
+
(where&
2
))
/
/
从数据寄存器(
0xCFC
),读取设备的PCI_HEADER_TYPE寄存器值
| | | | '
| | | | '
-
pci_bios_read_config_byte()
/
/
如果ops
=
&pci_bios_access
| | | | |
-
...
/
/
设置参数:PCIBIOS_READ_CONFIG_WORD
| | | | |
-
lcall &pci_indirect
/
/
调用BIOS的PCI服务接口 (段基址、偏移,已分别由初始化和check_pcibios()函数设置)
| | | |
| | | |
-
temp
-
>hdr_type
=
hdr_type &
0x7f
/
/
低
7
位代表设备类型:
| | | |
/
/
PCI_HEADER_TYPE_NORMAL(普通PCI设备)、PCI_HEADER_TYPE_BRIDGE(PCI
-
PCI桥)、PCI_HEADER_TYPE_CARDBUS(PCI
-
CardBus桥)
| | | |
| | | |
-
dev
=
pci_scan_device(temp)
/
/
读取逻辑设备的出厂信息
| | | | |
/
/
"配置寄存器组"
,一方面提供软件对设备的配置界面,另一方面,携带设备的出厂信息
| | | | |
/
/
读取
"配置寄存器组"
头部中的PCI_HEADER_TYPE字段,低
16
位为厂商编号,高
16
位为设备编号
| | | | |
-
pci_read_config_dword(temp, PCI_VENDOR_ID, &l)
| | | | |
-
if
(l
=
=
0xffffffff
|| l
=
=
0x00000000
|| l
=
=
0x0000ffff
|| l
=
=
0xffff0000
)
/
/
全
0
或全
1
,都为无效的厂商编号、备编号
| | | | | |
-
return
NULL
| | | | |
-
dev
=
kmalloc(sizeof(
*
dev), GFP_KERNEL)
/
/
分配pci_dev对象
| | | | |
-
...
/
/
初始化pci_dev对象,设置厂商编号、设备编号
| | | | |
-
pci_setup_device(dev)
/
/
根据设备类型,进一步填充pci_dev对象
| | | | |
| | | | |
/
/
读取
"配置寄存器组"
头部中的PCI_CLASS_REVISION字段
| | | | |
-
pci_read_config_dword(dev, PCI_CLASS_REVISION, &
class
)
/
/
相比表示大类的hdr_type,
class
对设备类型进一步细分,其中包含:
| | | | |
/
/
PCI_CLASS_DEVICE(高
16
位)、PCI_CLASS_PROG(次低
8
位)、PCI_REVISION_ID(低
8
位)
| | | | |
-
switch (dev
-
>hdr_type)
| | | | |
| | | | |
-
case PCI_HEADER_TYPE_NORMAL
/
/
普通PCI设备
| | | | | |
| | | | | |
-
if
(
class
=
=
PCI_CLASS_BRIDGE_PCI)
| | | | | | |
-
goto bad
/
/
可以看出,只要不是
"PCI-PCI"
桥,都属于
0
型设备
| | | | | |
| | | | | |
/
/
读取
"配置寄存器组"
头部中的PCI_INTERRUPT_PIN和PCI_INTERRUPT_LINE字段
| | | | | |
-
pci_read_irq(dev)
| | | | | | |
/
/
PCI_INTERRUPT_PIN字段,表示设备与PCI插槽之间,中断请求信号线的连接关系 (由硬件决定,只读)
| | | | | | |
/
/
0
:表示设备不产生中断请求;
1
~
4
:分别表示设备与PCI插槽的INTA~INTD中断请求信号线连接
| | | | | | |
-
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq)
| | | | | | |
-
if
(irq)
| | | | | | | |
/
/
PCI_INTERRUPT_LINE字段,表示设备经过PCI插槽,最终连接了中断控制器的哪根中断请求信号线 (由软件设置)
| | | | | | | |
/
/
PCI_INTERRUPT_LINE字段只起记录作用,不起控制作用,修改其值,并不会修改连接关系,不过连接关系通常也可以由软件设置
| | | | | | | |
/
/
如果这时读取的irq不为
0
,一定是因为已由BIOS设置,内核后续不会对其进行修改
| | | | | | | |
-
pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &irq)
| | | | | | |
| | | | | | |
-
dev
-
>irq
=
irq
/
/
记录到pci_dev对象 (对于irq为
0
的情况,会在后续调用pcibios_lookup_irq()函数时,进行处理)
| | | | | |
| | | | | |
/
/
读取
"配置寄存器组"
头部中的PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5和PCI_ROM_ADDRESS字段
| | | | | |
-
pci_read_bases(dev,
6
, PCI_ROM_ADDRESS)
| | | | | | |
| | | | | | |
/
/
PCI设备中,通常包含寄存器或RAM
"(可挥发)功能存储区间"
(最多
6
个),PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5寄存器,用于记录它们的出厂信息,以及配置它们的映射地址
| | | | | | |
-
for
(pos
=
0
; pos<howmany; pos
=
next
)
| | | | | | | |
| | | | | | | |
/
/
对于已由BIOS配置过的寄存器,内核后续不会对其进行修改,只会在检查到配置错误的情况下,对其进行修正
| | | | | | | |
-
reg
=
PCI_BASE_ADDRESS_0
+
(pos <<
2
)
/
/
PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5寄存器,长度都为
4
字节
| | | | | | | |
-
pci_read_config_dword(dev, reg, &l)
/
/
读取区间起始地址
| | | | | | | |
-
pci_write_config_dword(dev, reg, ~
0
)
/
/
写入全
1
(根据PCI规范)
| | | | | | | |
-
pci_read_config_dword(dev, reg, &sz)
/
/
读取区间大小
| | | | | | | |
-
pci_write_config_dword(dev, reg, l)
/
/
恢复原始值
| | | | | | | |
| | | | | | | |
/
/
起始地址,bit0
=
0
,表示内存地址区间,bit0
=
1
表示I
/
O地址区间
| | | | | | | |
/
*
区间类型由映射到什么区间决定,而跟存储单元是什么介质(内存
/
寄存器),以及所在的硬件设备无关
| | | | | | | | Linux内核通常优先选择使用内存空间:
| | | | | | | |
1.
有些CPU没有I
/
O指令,因而没有I
/
O空间,寄存器也就都在存储器地址空间中
| | | | | | | |
2.
有些CPU有I
/
O指令,但是指令的寻址空间很小,比如i386 CPU的I
/
O指令,操作数最多
16
位,即I
/
O空间为
64KB
,非常拥挤
| | | | | | | |
3.
C语言没有访问I
/
O地址空间的语言成分,需要调用汇编子过程,不方便而且效率也稍低
*
/
| | | | | | | |
-
if
((l & PCI_BASE_ADDRESS_SPACE)
=
=
PCI_BASE_ADDRESS_SPACE_MEMORY)
| | | | | | | | |
| | | | | | | | |
/
/
内存区间,起始地址低
4
位为控制信息:
| | | | | | | | |
/
/
bit0:区间类型 (
0
: 内存区间;
1
: I
/
O区间)
| | | | | | | | |
/
/
bit1:区间大小是否超过
1MB
(
0
: 未超过;
1
: 超过)
| | | | | | | | |
/
/
bit2: 区间地址位数 (
0
:
32
位;
1
:
64
位)
| | | | | | | | |
/
/
bit3: 区间是否
"可预取"
(
0
: 否;
1
:是)
| | | | | | | | |
/
/
对于普通的内存单元,读操作是不会修改其内容的,但是如果内存区间是由寄存器区间映射的,内容就有可能被读操作修改:
| | | | | | | | |
/
/
1.
比如FIFO寄存器,读走队列头的元素后,下一个元素就成为首个元素,存入FIFO寄存器了
| | | | | | | | |
/
/
2.
有些状态寄存器,状态被读出之后,寄存器就会硬件自动清
0
| | | | | | | | |
/
/
"预读"
是指,程序在读第N个单元时,CPU会把第N
+
1
个单元也读入缓存:
| | | | | | | | |
/
/
对内容可能被读操作修改的存储单元,进行
"预读"
,就会存在问题:
| | | | | | | | |
/
/
比如,程序在读第N个单元时,CPU会把第N
+
1
个单元也读入缓存,但在缓存被冲刷掉之前,程序并没有取走第N
+
1
个单元,程序后面再想重新读取时,内存单元却已经被
"预读"
修改了
| | | | | | | | |
/
/
所以,通常将映射到内存空间的寄存器区间,
"可预取"
标志置为
0
| | | | | | | | |
-
res
-
>start
=
l & PCI_BASE_ADDRESS_MEM_MASK
| | | | | | | | |
| | | | | | | | |
/
/
PCI规范规定,高
28
位中,只有位置最低的
1
,用于表示区间大小,而出厂值还有其它位为
1
,是为了表示哪些位可以由软件设置,所以不能忽略
| | | | | | | | |
-
sz
=
pci_size(sz, PCI_BASE_ADDRESS_MEM_MASK)
| | | | | | | | |
| | | | | | | | |
/
/
假设:size
=
x100
| | | | | | | | |
/
/
则:size
-
1
=
x100
-
1
=
x011
| | | | | | | | |
/
/
=
> ~(size
-
1
)
=
X100 (其中:X & x
=
0
)
| | | | | | | | |
/
/
& x100 (size)
| | | | | | | | |
/
/
-
-
-
-
-
-
-
-
| | | | | | | | |
/
/
=
0100
(高位x不管为多少,都转换为
0
了)
| | | | | | | | |
-
size
=
size & ~(size
-
1
)
| | | | | | | | |
-
return
size
-
1
| | | | | | | |
-
else
| | | | | | | | |
| | | | | | | | |
/
/
I
/
O区间,起始地址低
3
位为控制信息
| | | | | | | | |
-
res
-
>start
=
l & PCI_BASE_ADDRESS_IO_MASK
| | | | | | | | |
-
sz
=
pci_size(sz, PCI_BASE_ADDRESS_IO_MASK &
0xffff
)
| | | | | | | |
| | | | | | | |
-
res
-
>end
=
res
-
>start
+
(unsigned
long
) sz
| | | | | | | |
-
...
/
/
暂不关心
64
位地址的情况
| | | | | | |
| | | | | | |
/
/
PCI设备中,有时也会包含ROM
"(不可挥发)功能存储区间"
(最多
1
个),PCI_ROM_ADDRESS寄存器,用于记录它的出厂信息,以及配置它的映射地址
| | | | | | |
-
if
(rom)
| | | | | | |
-
...
/
/
和PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5处理过程类似
| | | | | |
| | | | | |
/
/
读取其它字段
| | | | | |
-
pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev
-
>subsystem_vendor)
| | | | | |
-
pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev
-
>subsystem_device)
| | | | |
| | | | |
-
case PCI_HEADER_TYPE_BRIDGE
/
/
PCI
-
PCI桥
| | | | | |
| | | | | |
-
if
(
class
!
=
PCI_CLASS_BRIDGE_PCI)
| | | | | | |
-
goto bad
/
/
可以看出,仅
"PCI-PCI"
桥,属于
1
型设备
| | | | | |
| | | | | |
/
/
相比普通PCI设备,很多配置寄存器在PCI
-
PCI桥中不存在,并且PCI
-
PCI桥上,最多只有
2
个可挥发存储区间
| | | | | |
-
pci_read_bases(dev,
2
, PCI_ROM_ADDRESS1)
/
/
不过次层PCI总线,需要后续进行递归扫描
| | | | |
| | | | |
-
case PCI_HEADER_TYPE_CARDBUS
/
/
PCI
-
CardBus桥 (专用于笔记本,暂不关心)
| | | | |
-
...
| | | |
| | | |
-
pci_name_device(dev)
/
/
根据厂商和设备编号,获取厂商和设备名称 (转换表存储于drivers
/
pci
/
pci.ids文件)
| | | |
| | | |
-
if
(!func)
| | | | |
-
is_multi
=
hdr_type &
0x80
/
/
是否多功能
| | | |
-
list_add_tail(&dev
-
>global_list, &pci_devices)
/
/
将pci_dev对象添加到pci_devices全局链表
| | | |
-
list_add_tail(&dev
-
>bus_list, &bus
-
>devices)
/
/
将pci_dev对象添加到所属pci_bus的devices链表
| | | |
| | | |
/
/
有些设备出售后,厂商却发现固化在
"配置寄存器组"
中的初始化信息有问题,只好提供修正代码,在内核层面进行修改
| | | |
-
pci_fixup_device(PCI_FIXUP_HEADER, dev)
| | | |
| | | |
/
/
数组元素表示:对于由vendor厂商提供的device设备,在
pass
阶段,执行hook()修正函数
| | | |
/
*
pcibios_fixups[]:不同CPU架构,使用各自的
| | | | pci_fixups[]:所有CPU架构共用
| | | | 都是pci_fixup对象全局数组,以{
0
}元素结束
*
/
| | | |
/
/
疑问:如果存在PCI BIOS,加电自检阶段,就已经基于错误的出厂信息,枚举过设备了,到内核执行阶段才修复,来得及吗??
| | | |
-
pci_do_fixups(dev,
pass
, pcibios_fixups)
| | | |
-
pci_do_fixups(dev,
pass
, pci_fixups)
| | | |
| | | |
/
/
dev包含vendor和device信息
| | | |
/
/
pass
为PCI_FIXUP_HEADER,表示
"配置寄存器组"
头部信息读出以后,PCI_FIXUP_FINAL表示全部信息读出以后
| | | |
-
while
(f
-
>
pass
)
| | | |
-
if
(f
-
>
pass
=
=
pass
&&
| | | | | (f
-
>vendor
=
=
dev
-
>vendor || f
-
>vendor
=
=
(u16) PCI_ANY_ID) &&
| | | | | (f
-
>device
=
=
dev
-
>device || f
-
>device
=
=
(u16) PCI_ANY_ID))
| | | | |
| | | | |
-
f
-
>hook(dev)
/
/
pass
,vendor,device全部命中,执行对应的修正函数
| | | |
-
f
+
+
| | |
| | |
/
/
不光PCI设备中的出厂信息会有错,有些母板也存在设计问题
| | |
-
pcibios_fixup_bus(bus)
| | | |
| | | |
/
/
去除
"幻影"
(有些母板上,会在两个不同的综合地址中,读出同一设备的头部信息,从而造成重复枚举,在形成的PCI树中引入
"幻影"
)
| | | |
-
pcibios_fixup_ghosts(b)
| | | |
| | | |
/
/
pci_scan_slot()函数,只读取了PCI设备
"功能存储区间"
的信息,还没读取PCI桥为次层总线保存的地址窗口
| | | |
-
pci_read_bridge_bases(b)
| | | |
| | | |
/
*
PCI桥是一种特殊的PCI设备,本身并不一定有RAM和ROM,但是要有
3
个地址窗口 (也用resource对象描述):
| | | |
1.
I
/
O地址窗口 (窗口起始地址和大小,都以
4KB
对齐,PCI_IO_BASE和PCI_IO_LIMIT低
4
位全
0
时,表示I
/
O地址为
16
位,全
1
表示I
/
O地址为
32
位)
| | | | PCI_IO_BASE(
8
位): I
/
O起始地址(
16
位)
=
PCI_IO_BASE高
4
位
+
12
*
0
| | | | PCI_IO_LIMIT(
8
位): I
/
O结束地址(
16
位)
=
PCI_IO_LIMIT高
4
位
+
12
*
1
| | | | PCI_IO_BASE_UPPER16(
16
位): I
/
O起始地址(
32
位)
=
PCI_IO_BASE_UPPER16
+
PCI_IO_BASE高
4
位
+
12
*
0
| | | | PCI_IO_LIMIT_UPPER16(
16
位): I
/
O结束地址(
32
位)
=
PCI_IO_LIMIT_UPPER16
+
PCI_IO_LIMIT高
4
位
+
12
*
1
| | | |
2.
存储器地址窗口 (窗口起始地址和大小,都以
1MB
对齐,主要用于映射在存储器地址空间的I
/
O寄存器,最低
4
位固定为
0
)
| | | | PCI_MEMORY_BASE(
16
位): 存储器起始地址(
32
位)
=
PCI_MEMORY_BASE高
12
位
+
20
*
0
| | | | PCI_MEMORY_LIMIT(
16
位): 存储器结束地址(
32
位)
=
PCI_MEMORY_BASE高
12
位
+
20
*
1
| | | |
3.
"可预取"
存储器地址窗口 (窗口起始地址和大小,都以
1MB
对齐,最低
4
位为
0
表示地址为
32
位,为
1
表示地址为
64
位)
| | | | PCI_PREF_MEMORY_BASE(
16
位):
"可预取"
存储器起始地址(
32
位)
=
PCI_PREF_MEMORY_BASE高
12
位
+
20
*
0
| | | | PCI_PREF_MEMORY_LIMIT(
16
位):
"可预取"
存储器结束地址(
32
位)
=
PCI_PREF_MEMORY_LIMIT高
12
位
+
20
*
1
| | | | PCI_PREF_BASE_UPPER32(
32
位):
"可预取"
存储器起始地址(
64
位)
=
PCI_PREF_BASE_UPPER32
+
PCI_PREF_MEMORY_BASE高
12
位
+
20
*
0
| | | | PCI_PREF_LIMIT_UPPER32(
32
位):
"可预取"
存储器结束地址(
64
位)
=
PCI_PREF_BASE_UPPER32
+
PCI_PREF_MEMORY_LIMIT高
12
位
+
20
*
1
| | | | CPU一侧访问的地址,必须在某个窗口之内(位于当前总线),才能到达下游,设备一侧访问的地址,必须在所有窗口之外(不在当前总线),才能到达上游
| | | | 此外,PCI命令寄存器(
"配置寄存器组"
头部中的PCI_COMMAND字段),包含
"memory access enable"
和
"I/O access enable"
两个控制位
| | | | 控制位为
0
时,窗口对
2
个方向都关闭,用于保证还没为PCI设备各个区间分配合适的总线地址时,CPU和设备两侧不会相互干扰
*
/
| | | |
-
for
(i
=
0
; i<
3
; i
+
+
)
| | | | |
-
child
-
>resource[i]
=
&dev
-
>resource[PCI_BRIDGE_RESOURCES
+
i]
/
/
pci_bus对象中的resource只是指针,指向所在PCI桥的pci_dev对象的resource
| | | |
| | | |
/
/
读取PCI_IO_BASE,PCI_MEMORY_BASE,PCI_PREF_MEMORY_BASE等寄存器,记录到pci_bus对象的resource[
0
~
2
]
| | | |
/
/
对于i386 CPU,PCI桥的地址窗口,必然已在加电自检阶段,就由BIOS根据次层总线的需要,设置好了!!
| | | |
-
...
| | |
| | |
/
/
递归扫描PCI
-
PCI桥或PCI
-
CardBus桥上的次层总线
| | |
-
for
(
pass
=
0
;
pass
<
2
;
pass
+
+
)
/
/
第一趟针对已由BIOS处理过的PCI桥
| | |
/
/
第二趟针对未经BIOS处理过的PCI桥 (疑问:对于i386 CPU,为什么会存在这种情况??)
| | |
-
for
(ln
=
bus
-
>devices.
next
; ln !
=
&bus
-
>devices; ln
=
ln
-
>
next
)
/
/
遍历上层总线上面的PCI设备
| | |
-
dev
=
pci_dev_b(ln)
| | |
-
if
(dev
-
>hdr_type
=
=
PCI_HEADER_TYPE_BRIDGE || dev
-
>hdr_type
=
=
PCI_HEADER_TYPE_CARDBUS)
/
/
设备为
"PCI-PCI"
或
"PCI-CardBus"
桥
| | |
| | |
/
/
pci_scan_bridge()函数写的有点绕:
| | |
/
/
|
if
((buses &
0xffff00
)) |
pass
| run | 目的
| | |
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
| | |
/
/
已由BIOS处理,第一趟 | Y |
0
|
772
行 | 第一趟仅为已由BIOS枚举的总线分配pci_bus对象,并统计最大总线号
max
| | |
/
/
未经BIOS处理,第一趟 | N |
0
|
return
| 等待第一趟扫描完毕,
max
值完全确认后,在第二趟扫描中处理
| | |
/
/
已由BIOS处理,第二趟 | Y |
1
|
return
| 已经在第一趟扫描中处理过了
| | |
/
/
未经BIOS处理,第二趟 | N |
1
|
792
行 | 基于已由BIOS处理的最大总线号
max
,处理未经BIOS处理的总线
| | |
-
max
=
pci_scan_bridge(bus, dev,
max
,
pass
)
| | |
| | |
/
/
读取
"配置寄存器组"
头部中的PCI_PRIMARY_BUS字段 (byte0:主总线号; byte1:次总线号; byte2:子树中最大总线号)
| | |
-
pci_read_config_dword(dev, PCI_PRIMARY_BUS, &buses)
| | |
| | |
/
/
次总线号和子树中最大总线号不全为
0
,表示加电自检阶段,BIOS已经枚举过当前总线,在此之前就已经设置过了
| | |
-
if
((buses &
0xffff00
) && !pcibios_assign_all_busses())
| | | | |
-
空操作
| | | |
-
if
(
pass
)
/
/
已由BIOS枚举的总线,第二趟扫描不需要处理
| | | | |
-
return
max
| | | |
| | | |
-
child
=
pci_add_new_bus(bus, dev,
0
)
/
/
为新探测到的总线,分配和设置pci_bus对象 (逻辑简单,不再展开)
| | | |
-
...
/
/
继续设置pci_bus对象
| | | |
-
if
(!is_cardbus)
| | | | |
-
cmax
=
pci_do_scan_bus(child)
/
/
递归调用 (遇到次层总线,立即递归扫描,而不是探测到上层总线的所有次层总线后,再依次扫描次层总线,所以是深度优先)
| | | | |
-
if
(cmax >
max
)
max
=
cmax
| | | |
-
else
| | | |
-
cmax
=
child
-
>subordinate
/
/
PCI
-
CardBus桥不支持次层总线
| | | |
-
if
(cmax >
max
)
max
=
cmax
| | |
-
else
| | | |
-
if
(!
pass
)
/
/
未经BIOS处理的总线,等待第一趟扫描完毕
| | | | |
-
return
max
| | | |
| | | |
/
/
未经BIOS处理的PCI桥,相关配置寄存器,就得由内核设置 (读写过程涉及PCI规范)
| | | |
-
pci_read_config_word(dev, PCI_COMMAND, &cr)
| | | |
-
pci_write_config_word(dev, PCI_COMMAND,
0x0000
)
| | | |
-
pci_write_config_word(dev, PCI_STATUS,
0xffff
)
| | | |
-
child
=
pci_add_new_bus(bus, dev,
+
+
max
)
/
/
为新探测到的总线,分配和设置pci_bus对象
| | | |
-
buses
=
(buses &
0xff000000
)
| | | | | ((unsigned
int
)(child
-
>primary) <<
0
)
| | | | | ((unsigned
int
)(child
-
>secondary) <<
8
)
| | | | | ((unsigned
int
)(child
-
>subordinate) <<
16
)
/
/
按照PCI_PRIMARY_BUS寄存器的组成规范,进行拼接
| | | |
-
pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses)
/
/
将总线号写入PCI_PRIMARY_BUS寄存器
| | | |
-
if
(!is_cardbus)
| | | | |
-
max
=
pci_do_scan_bus(child)
/
/
递归调用
| | | |
-
else
| | | | |
-
max
+
=
3
| | | |
-
child
-
>subordinate
=
max
| | | |
-
pci_write_config_byte(dev, PCI_SUBORDINATE_BUS,
max
)
| | | |
-
pci_write_config_word(dev, PCI_COMMAND, cr)
| | |
| | |
-
return
max
| |
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- 调试器:显示调用栈 2614
- helloworld减肥 + 单步执行原理 2954
- gcc -O2编译,gdb单步执行怪怪的 10360
- systemtap追踪自己开发的内核模块 11733
- PCI总线初始化过程(linux-2.4.0内核中的pci_init()函数分析) 17354