【转】PCI学习笔记
1.PCI设备编号
每一个PCI device都有其unique PFA(PCI Fcntion Address)
PFA由 bus number、device number、function number组成。
一条PCI总线支持256个PFA,即支持256个PCI device。
每个PCI芯片都有自己的device number(取决于IDSEL管脚),每个PCI芯片占用8个PFA。
每个PCI芯片的第一个PCI device的PFA必为8的倍数。
若PCI device的配置空间中PCI_HEADER_TYPE寄存器的最高bit为1,说明此芯片还有其他PFA,即还有其他device,即当前芯片是multi-function device.
Each function on a multi-function device has its own configuration space。
在系统中,每个PCI芯片上的所独有的信号线是:INTA、INTB、INTC、INTD、IDSEL
每个芯片上的IDSEL需要连到PCI总线中AD[31:11]中的一根,这对应于PCI device PFA的device number
IDSEL is equivalent to chip select on the CPU side during address phase of CFG RD\WR command.
DEVSEL is used in every transaction when the target claims the cycle address to it. It is like saying this cycle in mine, and nobody touches it!
2.PCI配置空间
每个PCI逻辑设备都有自己的配置空间,里面存储了一些基本信息,生产商,IRQ中断号,还有就是定义了mem空间和io空间的起始地址和大小。
CPU通过两个寄存器访问PCI设备的配置空间:CFG_ADDR 和 CFG_DATA。
对CFG_DATA的操作就是对配置空间相应寄存器的操作,没有专门的bit来制定操作类型r/w。
PCI配置空间中的BAR(Base Address Register)用来映射PCI设备的寄存器,里面的值是bus地址首地址,至于空间的大小,先向bar中写0xFFFFFFFFF,然后读取,选最低的一位非0的,比如为0x1000,那个空间的大小就为0x1000。
这里需要注意,当PCI配置成64bit或32bit时,BAR有区别
3.PCI地址转换
当CPU访问PCI设备的mem空间和io空间的寄存器时,需要进行地址转换。
MPC8560有5个outbound窗口0~5用来将CPU内部地址转换为PCI总线地址。上电时只有窗口0使能的,其他都是disable状态。每个outbound窗口有如下三类寄存器:
1.外部PCI总线地址的基址
2.CPU内部32bit地址的基址(EA)
3.窗口属性寄存器,大小、转换类型等等
outbound窗口转换后(外部PCI总线地址的基址,当使用64bit PCI时会用到第二个扩展寄存器)
outbound窗口转换前地址(CPU内部32bit地址的基址)
outbound窗口属性寄存器
EN:设置此窗口是否使能
RTT/WTT:分别设置此窗口的存取方式(memory或io)
OWS:设置此窗口大小
同理,当PCI设备访问MPC8560时,有3个inbound窗口1~3用来将PCI总线地址转换为CPU内部地址。和outbound窗口一样,inbound窗口有如下三类寄存器:
CPU内部32bit地址的基址(EA)
外部PCI总线地址的基址
窗口属性寄存器,大小、转换类型等等
EN:设置此窗口是否使能
PF:设置此窗口是否开启prefetchable特性
TGI:Target Interface,见datasheet P883
RTT/WTT:设置PCI外设访问CPU时的存取方式(snoop L2cache 等等)
IWA:设置此窗口的大小
3.PCI拓扑结构
PCI拓扑结构,有如下例子
在上图的总线结构中,ethernet设备和pci-pci bridge的资源空间必须要是pci bus0的一个子集
同理,SCSI和VIDEO同类型资源必须要是pci_bus1的子集。
CPU访问PCI的过程是这样的(只有一个根总线和pci-pci bridge过滤窗口功能打开的情况):
1.cpu向pci发出一个I/O请求。
首先经过根总线.它会判断是否在它的资源范围内.如果在它的范围,它就会丢向总线所在的每一个设备.包括pci bridge. 如果没有在根总线的资源范围,则不会处理这个请求.
2.如果pci设备判断该地址属于它的资源范围,则处理后发出应答
3.pci bridge接收到这个请求,它会判断I/O地址是否在它的资源范围内.如果在它的范围,它会把它丢到它的下层子线.
4.下层总线经过经过相同的处理后,就会找到这个PCI设备了
一个PCI设备访问其它PCI设备或者其它外设的过程:
1.首先这个PCI发出一个请求,这个请求会在总线上广播
2.如果要请求的设备是在同级总线,就会产生应答
3.请求的设备不是在同层总线,就会进行pci bridge.pci桥判断该请求不在它的范围内(目的地不是它下层的设备),就会将它丢向上层.
4.这样辗转之后,就能找到对应的设备了
3.PCI枚举过程
通过PCI枚举,CPU知道当前系统上有多少PCI设备,多少根PCI总线,PCI配置空间初始化。
PCI 总线扫描的原理是从总线 0 扫描到总线 255,对于每条总线,系统都会扫描所有(总线号,设备号,功能号),读出每个设备配置空间的Device ID和Vendor ID寄存器,如果这两个寄存器的值是个无效值(0xFFFF),则说明当前位置上没有设备,接着扫描下一个位置。
如果是有效值(非0xFFFF),当前位置是个有效的 PCI 设备/桥。进而再读取该设备的 Header Type 寄存器,如果该寄存器为 1,则表示当前设备是 PCI 桥,否则是 PCI 设备。
Register Number:配置空间寄存器偏移量
Function Number:多功能设备有多个功能号
Device Number:设备编号
Bus Number:总线编号
对所有 PCI 总线进行编号
PCI 桥如何知道它所连接的 PCI 总线情况呢?这就需要对 PCI 桥进行总线编号。前面介绍过 PCI 桥提供了 Primary Bus Number、Secondary Bus Number 和 Subordinate Bus Number 三个寄存器用于标志该桥所连接的 PCI 总线,下面通过一个示例来说明内核对于 PCI 总线是如何进行编号的。
1.系统运行初始,Bus A 为 0,通过上面的 PCI 总线扫描得到连接在 Bus A 上的 PCI 桥(即图中Bridge 1)
2.下面开始设置 Bridge 1 的 Bus 寄存器。将 Primary Bus Number 寄存器设置成 Bus A 的编号,即 0。将 Secondary Bus Number 寄存器设置成 Bus B 的编号,它的值等于(Bus A + 1),也就是 1。由于暂时无法知道该桥所能访问的所有下行总线数目,Subordinate Bus Number 寄存器暂时设置成 0xFF。
3.当扫描完所有 Bus A 上所有(设备号,功能号)后,开始扫描 Bus B,Bus B 的编号在扫描完 Bus A 后已经得到,为 1。Bus B 的扫描方法同步骤(1),先扫描出 Bus B 上的 PCI 桥(即图中的 Bridge 2),然后配置 Primary Bus Number 寄存器为 1,Secondary Bus Number 寄存器为 2,而 Subordinate Bus Number 寄存器依然为 0xFF。
4.Bus B 扫描完后得到 Bus C 的编号,为2。下面开始扫描 Bus C,因为 Bus C 上没有 PCI 桥,于是在扫描完其它(设备号,功能号)后,Bus C 的扫描结束。
5.由于 Bridge 2 所能访问到的最大 Bus 编号是 2,因此重新设置 Bridge 2 的 Subordinate Bus Number 寄存器为 2。
6.由于 Bridge 1 所能访问到的最大 Bus 编号也是 2,因此重新设置 Bridge 1 的 Subordinate Bus Number 寄存器为 2。
7.总线编号结束。
3.Linux内核PCI数据结构
内核(linux-2.6.24) 提供了三类数据结构用以描述 PCI 控制器、PCI 设备以及 PCI 总线。
数据结构关系如下所示
PCI 控制器 用 pci_controller 结构来描述,它有以下几个主要的属性:
index:该属性标志 PCI 控制器的编号。
next:该属性指向下一个 PCI 控制器,通过 next 属性,PCI 控制器可以形成一个单向链表。
first_busno:该属性标志了连接在该控制器上第一条总线的编号。
last_busno:该属性标志了连接在该控制器上最后一条总线的编号。
ops:该属性标志了当前 PCI 控制器所对应的 PCI 配制空间读写操作函数。
io_space:该属性标志了当前 PCI 控制器所支持的 IO 地址空间。
mem_space:该属性标志了当前 PCI 控制器所支持的 Memory 地址区间。
cfg_addr:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要写入的地址空间。
cfg_data:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要读写的数据空间。
bus:该属性标志了当前 PCI 控制器所连接的 PCI 总线,它对应的数据结构是 pci_bus。
PCI 总线 用 pci_bus 结构来描述,它有以下几个主要的属性:
parent:可通过该属性索引到上层 PCI 总线。
self:该属性标志了连接的上行 PCI 桥(对应的数据结构是 pci_dev)。
children:该属性标志了总线连接的所有 PCI 子总线链表。
devices:该属性标志了总线连接的所有 PCI 设备链表。
ops:该属性标志了总线上所有 PCI 设备的配制空间读写操作函数。
number:该属性标志了当前 PCI 总线的编号。
primary:该属性标志了 PCI 上行总线编号。
secondary:该属性标志了 PCI 下行总线编号。
subordinate:该属性标志了能够访问到的最大总线编号。
resource:该属性标志了 Memory/IO 地址空间。
PCI 设备 用 pci_dev 结构来描述,它有以下几个主要的属性:
global_list:Linux 定义了一个全局列表来索引所有PCI 设备,该属性标志了这个全局列表的首指针。
bus:该属性标志了当前设备所在的 PCI 总线(对应的数据结构是 pci_bus)。
devfn:该属性标志了设备编号和功能编号。
vendor:该属性标志了供应商编号。
device:该属性标志了设备编号。
driver:该属性标志了设备对应的驱动代码(对应的数据结构是 pci_driver)。
irq:该属性标志了中断号。
resource:该属性标志了 Memory/IO 地址区间。
当 Linux 内核在做 PCI 初始化工作时,它会根据建立一个由 pci_controller、pci_bus 和 pci_dev 三者组成的一个组织结构图。根据这个结构,软件开发者可以很方便的通过 PCI 控制器索引到每个 PCI 设备或者 PCI 总线。
4.Linux的PCI子系统初始化流程
第一步:Linux分配数据结构pci_contoller,并初始化,包括PCI的mem/io空间范围和访问PCI配置空间所需的handler。
第二步:PCI设备的枚举:扫描系统上所有PCI设备,初始化它们的配置空间。(硬件上的初始化)
第三步:用数据结构将PCI设备信息联系起来,构建PCI树。(软件上的初始化)
第四步:加载PCI设备驱动。
4.1 初始化PCI控制器
pci_controller结构是内核描述PCI子系统信息的数据结构,里面定义了可供PCI设备使用的mem资源和io资源的范围,访问pci设备配置空间的handler等等。
函数调用关系:
start_kernel --> mpc8560ads_setup_arch --> mpc85xx_setup_hose()
此函数分配并初始化了pci_controller
mpc85xx_setup_hose()
-->pcibios_alloc_controller() //分配数据结构pci_controller
ppc_md.pci_map_irq = mpc85xx_map_irq; //用来获得pci设备irq号的handler
-->setup_indirect_pci() //设置pci_controller里访问PCI配置空间的钩子函数
//使用ioremap后的CFG_ADDR和CFG_DATA的虚拟地址
-->mpc85xx_setup_pci1() //设置PCI inbound和outbound窗口寄存器。
PCI子系统的mem和io地址空间范围也在函数mpc85xx_setup_hose()中定义:
pci_controller *hose_a;
hose_a->mem_space.start = MPC85XX_PCI1_LOWER_MEM;
hose_a->mem_space.end = MPC85XX_PCI1_UPPER_MEM;
hose_a->io_space.start = MPC85XX_PCI1_LOWER_IO;
hose_a->io_space.end = MPC85XX_PCI1_UPPER_IO;
4.2 PCI枚举过程
目前为止,内核只知道PCI子系统总的mem资源和io资源地址范围。
接下来,内核需要扫描所有PCI设备,把这些资源分配给PCI设备,具体方法参考前面讲的PCI枚举过程。
内核分配mem资源时是从高地址开始分配的。
mpc85xx_setup_hose()
-->pciauto_bus_scan()
for (pci_devfn = 0; pci_devfn < 0xff; pci_devfn++) { //遍历0号总线上所有PCI设备
if (读当前设备配置空间PCI_HEADER_TYPE失败)
continue; //当前设备不存在,扫描下一个
读当前设备配置空间PCI_CLASS_REVISION
If(当前设备是PCI桥){
pciauto_setup_bars() //分配pci_controller的资源的子集
pciauto_prescan_setup_bridge() //写PCI桥配置空间 PCI_PRIMARY_BUS
//PCI_SECONDARY_BUS
//PCI_SUBORDINATE_BUS
pciauto_bus_scan() //递归扫描下一条pci bus
pciauto_postscan_setup_bridge() //写PCI桥配置空间PCI_SUBORDINATE_BUS
}
。。。。。略去
else {
//当前设备是PCI设备
pciauto_setup_bars()
}
}
注意:执行完 pciauto_bus_scan后,所有pci设备(包括桥)的配置空间都已经傻瓜式的简单初始化
但这些桥和设备还没有通过数据结构组织起来,这些工作要在第四步 pcibios_init里来完成
4.3 创建PCI树
上面说了,PCI设备配置空间都初始化差不多了,但是PCI设备还没有通过数据结构组织起来。
do_initcalls()
-->pcibios_init() //所在文件 arch/ppc/kernel/pci.c
pcibios_init()
(1):为PCI设备构造数据结构,组织成PCI树
-->pci_scan_bus(hose->first_busno, hose->ops, hose) //hose就是上面的pci_controller结构
-->pci_scan_bus_parented
-->pci_create_bus // 建立 PCI bus 0 对应的数据结构,这个bus的资源尚未初始化
-->pci_scan_child_bus // 从PCI bus 0 开始扫描生成PCI树,使用了递归
-->pci_scan_slot
-->pci_scan_single_dev
-->pci_scan_device() //创建 pci_dev结构
-->pci_setup_device() //区分桥与设备,分别进行初始化
-->pci_read_bases(); //在这才初始化了pci_dev->resource[]
pci_dev->resouce[]中保存的才是cpu internal address(EA),可以对这些地址进行用ioremap,在驱动程序对bar所指位置读写的时候一定要用这个
(2)给PCI设备分配IRQ号
-->pci_fixup_irqs()
//遍历所有pci设备,调用pdev_fixup_irq()
-->pdev_fixup_irq
-->ppc_md.pci_swizzle() //实际调用common_swizzle(),获得pci所在slot编号
读PCI设备配置空间PCI_INTERRUPT_PIN,获得中断pin编号[1到4]
之所以是1到4,因为PCI规范里设备最多4个管脚(除第一个外,其他3个仅用于多功能设备)
-->ppc_md.pci_map_irq() //实际调用mpc85xx_map_irq()
//根据slot,pin 和 pci_irq_table[][4]
//来计算出irq号
-->pcibios_update_irq(pci_dev,irq)
//将irq号写入pci设备的配置空间PCI_INTERRUPT_LINE
//注意,这里寄存器只是用来保存结果,例如把其值8改为9,并不能改变中断号
(3)PCI结构树有了,现在构建PCI的资源树,有冲突就修改
-->pcibios_allocate_bus_resource()
//只考虑pci_bus,形成bus级资源树(并同时check,资源冲突了就修改)
-->pcibios_allocate_resources()
//把pci_dev也考虑进去,完成资源树
4.3 加载PCI设备驱动
以e1000 PCI 网卡 82546GB为例,讲解一个PCI驱动的加载过程
驱动里有如下代码:
static struct pci_driver e1000_driver = {
.name = e1000_driver_name,
.id_table = e1000_pci_tbl,
.probe = e1000_probe,
.remove = __devexit_p(e1000_remove),
#ifdef CONFIG_PM
/* Power Managment Hooks */
.suspend = e1000_suspend,
.resume = e1000_resume,
#endif
.shutdown = e1000_shutdown,
.err_handler = &e1000_err_handler
};
module_init(e1000_init_module);
do_initcalls()
-->e1000_init_module
pci_register_driver(&e1000_driver)
// e1000_driver.driver.bus = &pci_bus_type;
-->driver_register(e1000_driver.driver);
//pci_bus_type.probe() 非空,即调用pci_device_probe()
pci_device_probe(*device)
-->__pci_device_probe(*pci_driver,*pci_dev)
-->pci_match_device(*pci_driver,*pci_dev)
-->pci_call_probe(*pci_driver,*pci_dev,pci_device_id)
-->drv->probe(*pci_device,*pci_device_id)
即执行 e1000_probe(*pci_device,*pci_device_id)
接下来就是e1000 PCI 网卡驱动的具体代码。
转:http://blog.chinaunix.net/uid-24148050-id-101021.html
【转】PCI学习笔记的更多相关文章
- 《Linux内核分析》第三周学习笔记
<Linux内核分析>第三周学习笔记 构造一个简单的Linux系统MenuOS 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.stud ...
- Linux内核分析第三周学习笔记
linux内核分析第三周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...
- KVM性能优化学习笔记
本学习笔记系列都是采用CentOS6.x操作系统,KVM虚拟机的管理也是采用virsh方式,网上的很多的文章都基于ubuntu高版本内核下,KVM的一些新的特性支持更好,本文只是记录了CentOS6. ...
- java学习笔记8--接口总结
接着前面的学习: java学习笔记7--抽象类与抽象方法 java学习笔记6--类的继承.Object类 java学习笔记5--类的方法 java学习笔记4--对象的初始化与回收 java学习笔记3- ...
- kvm虚拟化学习笔记(四)之kvm虚拟机日常管理与配置
KVM虚拟化学习笔记系列文章列表----------------------------------------kvm虚拟化学习笔记(一)之kvm虚拟化环境安装http://koumm.blog.51 ...
- 红帽学习笔记[RHCSA] 第二周
目录 红帽学习笔记[RHCSA]第二周 环境 第七课[网络配置相关] 在Vmware中添加网卡 将网卡添加到虚拟机上 关于网卡命名规则 配置网络 网络配置命令总结 更改hostname 关于SSH的一 ...
- Testbench学习笔记
Testbench学习笔记(一) 书写testbench是数字电路设计中不可或缺的一项设计方法,主要是提供的是激励.尽管现在各种开发工具都通过绘制波形图的方法生成测试激励,测试书写的代码,但是其不可移 ...
- 【学习笔记】Linux基础(零):预备知识
学习笔记(连载)之Linux系列 Note:本学习笔记源自<鸟哥的Linux私房菜(基础学习篇)>一书,为此书重要内容的摘要和总结,对于一些常识性的知识不再归纳 新型冠状病毒引发的肺炎战& ...
- AI学习笔记:特征工程
一.概述 Andrew Ng:Coming up with features is difficult, time-consuming, requires expert knowledge. &quo ...
随机推荐
- mysql 卸载 linux
root@localhost ~]# rpm -qa | grep -i mysqlMySQL-client-5.5.52-1.linux2.6.x86_64MySQL-server-5.5.52-1 ...
- java线程池ThreadPoolExecutor理解
Java通过Executors提供四种线程池,分别为:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.newFixe ...
- World Wind .NET源码编译问题处理
World Wind .NET源码编译问题处理 下载了World_Wind_1.4.0_Source源码(http://worldwindcentral.com/wiki/NASA_World_W ...
- SQL Server 2008连接字符串写法大全
一..NET Framework Data Provider for SQL Server 类型:.NET Framework类库使用:System.Data.SqlClient.SqlConnect ...
- redhat 更新 python 为 2.7.6
1. 下载 wget http://python.org/ftp/python/2.7.6/Python-2.7.6.tgz 2. 解压,编译 tar zxvf Python-2.7.6.tgz ./ ...
- 自动刷新页面为了session不过期
为了保证在打开页面期间session不过期,估做了一个隐藏Iframe每隔若干秒来刷新一下页面,在隐藏页面给session赋值. <script type="text/javascri ...
- ASP.NET连接数据库时,提示“用户 'sa' 登录失败原因: 未与信任 SQL Server 连接相关联
用ASP.NET连接数据库时,提示"用户 'sa' 登录失败.原因: 未与信任 SQL Server 连接相关联.".解决方法:首先检查是不是web.config文件内的用户名密码 ...
- USB 设备的PID-Product ID,VID-Vendor ID
根据USB规范的规定,所有的USB设备都有供应商ID(VID)和产品识别码(PID),主机通过不同的VID和PID来区别不同的设备,VID 和PID都是两个字节长,其中,供应商ID(VID)由供应商向 ...
- Codeforces Round #342 (Div. 2)-B. War of the Corporations
time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...
- css+div解决文字溢出控制显示字数
一.一般的文字截断(适用于内联与块): Example Source Code [www.mb5u.com] .text-overflow {display:block;/*内联对象需加*/widt ...