powerpc的PCI/PCIE控制器枚举PCI设备实在内核里面实现的,依赖的资源主要是设备树。
设备数提供了PCI/PCIE池的空间基地址/大小,用于分配PCI/PCIE的memory空间;
如果是传统PCI设备,设备树还提供了PCI设备的中断路由表。
例如:
powerpc CPU 的PCIE3对接了一个PCIE->PCI桥片,桥片接了一个PCI设备,其设备树可以如下描述:
pcie@f00008000 {
......
ranges = <0x2000000 0x0 0xe8000000 0xc 0xe8000000 0 0x08000000>
interrupt-parent = <&mpic>
......
interrupt-map = <
/**/
0x20a00 0x0 0x0 0x3 &mpic 0xb 0x1
......
>
}
其中,interrupt-map的含义如下:
根据pci扫描,pci设备的域:总线号:设备号:功能号分别是 0000:02:01.2
根据内核对设备树的解析,0x20a00 >>8bit == 0x20a
0x20a分解: 0010 0000 1010 => 0010 00000 010 == 02:01.2。
总线号是2,因为是PCIE3,设备号1是怎么来的呢?首先,查看硬件原理图,PCI设备的IDSEL是17,
紧接着看一下 http://blog.sina.com.cn/s/blog_6472c4cc0100qqni.html的解答:
PCI总线推荐了一种Device Number字段与AD[31:16]之间的映射关系。其中PCI设备0与Device Number字段的0b00000对应;PCI设备1与Device Number字段的0b00001对应,并以此类推,PCI设备15与Device Number字段的0b01111对应。
在这种映射关系之下,一条PCI总线中,与信号线AD16相连的PCI设备其设备号为0;与信号线AD17相连的PCI设备其设备号为1;以此类推,与信号线AD31相连的PCI设备其设备号为15。在Type 00h配置请求中,设备号并没有像Function Number和Register Number那样以编码的形式出现在AD总线上,而是与AD信号一一对应
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YkW0WTOy-1658386703981)(https://img-blog.csdn.net/20170530110525752?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2hpcGluc2t5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)]
根据PCI对中断的定义,功能号0对应INTA,1对应INTB,2对应INTC,3对应INTD,但是桥片在INTB_L错开一位,所以功能2实际对应桥片的INTD_L,而INTD_L透传到了CPU的PCIE3的INTD。
我们用的某PCI USB控制器设备有3个中断,INTA INTB INTC;其中
INTA:is the PCI interrupt signal for OHCI HOST Controller! #1
INTB:is the PCI interrupt signal for OHCI HOST Controller! #2
INTC:is the PCI interrupt signal for EHCI HOST Controller!
我们用的是EHCI模式,所以使用INTC中断。
根据飞思卡尔发布的官方手册,PCIE3的 INTD对应的中断号是11,即0xB。
0x3的含义:根据PCI协议,表示PCI设备用的中断是INTC。1,2,3,4分别表示INTA B C D。
0x1表示低电平触发。
PCI初始化流程:
pci_setup pcibios_setup //解析bootloader传递下来的参数
define_machine(mpc8572_ds) {
.name = "MPC8572 DS",
.probe = mpc8572_ds_probe,
.setup_arch = mpc85xx_ds_setup_arch,
.init_IRQ = mpc85xx_ds_pic_init,
#ifdef CONFIG_PCI
.pcibios_fixup_bus = fsl_pcibios_fixup_bus,
#endif
.get_irq = mpic_get_irq,
.restart = fsl_rstcr_restart,
.calibrate_decr = generic_calibrate_decr,
.progress = udbg_progress,
};
mpc85xx_ds_setup_arch
->for_each_node_by_type(np, "pci") {
if (of_device_is_compatible(np, "fsl,mpc8540-pci") || of_device_is_compatible(np, "fsl,mpc8548-pcie"))
of_address_to_resource(np, 0, &rsrc);
fsl_add_bridge(np, 0);//解析设备树,并申请pci_controller并将设备树的信息填入
->hose = pcibios_alloc_controller(dev);//分配pci_controller并根据设备树填充基本信息,申请pci_controller成功的同时会将其添加到host_list列表里面,host_list列表是PCI控制器的全局列表集合
->setup_pci_atmu //根据设备树提供的信息,设置outbound和inbound窗口,进行存储器域和PCI域的转换
我们再看一下设备树:以2.6.27原版为例
pci2: pcie@ffe0a000 {
cell-index = <2>;
compatible = "fsl,mpc8548-pcie";
device_type = "pci";
#interrupt-cells = <1>;
#size-cells = <2>;//表示几个cell,表示长度,详见 我眼中的设备树:http://www.linuxidc.com/Linux/2016-01/127337.htm
#address-cells = <3>;//表示几个cell,表示地址,详见 蜗窝科技 device tree 基本概念 http://www.wowotech.net/device_model/dt_basic_concept.html
reg = <0xffe0a000 0x1000>;
bus-range = <0 255>;
ranges = <0x2000000 0x0 0xc0000000 0xc0000000 0x0 0x20000000
0x1000000 0x0 0x0 0xffc20000 0x0 0x10000>;
clock-frequency = <33333333>;
interrupt-parent = <&mpic>;
interrupts = <27 2>;
interrupt-map-mask = <0xf800 0x0 0x0 0x7>;
interrupt-map = <
/* IDSEL 0x0 */
0000 0x0 0x0 0x1 &mpic 0x0 0x1
0000 0x0 0x0 0x2 &mpic 0x1 0x1
0000 0x0 0x0 0x3 &mpic 0x2 0x1
0000 0x0 0x0 0x4 &mpic 0x3 0x1
>;
pcie@0 {
reg = <0x0 0x0 0x0 0x0 0x0>;
#size-cells = <2>;
#address-cells = <3>;
device_type = "pci";
ranges = <0x2000000 0x0 0xc0000000
0x2000000 0x0 0xc0000000
0x0 0x20000000
0x1000000 0x0 0x0
0x1000000 0x0 0x0
0x0 0x100000>;
};
};
上述设备树生成pci总线的platform device设备,fsl,mpc8548-pcie会触发fsl_add_bridge的执行
of_pci_phb_probe
->of_address_to_resource(dev, 0, &rsrc)//解析设备树
->bus_range = of_get_property(dev, "bus-range", &len);//同上
->hose = pcibios_alloc_controller(dev);
of_address_to_resource() 在设备树中找到第一个"reg",并将解析到的信息填充在"res"结构体里。这个例子里"reg = < 0x50000000 0x1000 >”, 指的是分配一块起始物理地址是0x50000000,长度为0x1000字节的空间。of_address_to_resource()会设置res.start = 0x50000000, res.end = 0x50000fff。
解析完毕设备树,获取了PCI HOST的基本信息。下面开始PCI总线的初始化
original_kernel_thread
->kernel_init
->do_one_initcall
->pcibios_init
->pcibios_scan_phb //遍历host_list的pci_controller
->bus = pci_create_bus(hose->parent, hose->first_busno, hose->ops, hose);
->hose->last_busno = bus->subordinate = pci_scan_child_bus(bus); //深度优先遍历PCI桥
->pci_scan_slot(bus, devfn);//下面开始扫描PCI总线下面的PCI设备,for循环里面执行 for (fn = next_fn(dev, 0); fn > 0; fn = next_fn(dev, fn))
->dev = pci_scan_single_device(bus, devfn);
->dev = pci_scan_device(bus, devfn);
->pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l)//读取PCI_VENDOR_ID,不为全0或者全F就表示存在设备
->dev = alloc_pci_dev();
->pci_setup_device(dev)
->//linux驱动模型,增加Pci设备
->pci_fixup_device
->pci_read_irq(dev);//irq保存在dev->irq域成员下
->pci_read_bases(dev, 6, PCI_ROM_ADDRESS);//区分普通和桥设备,对于普通PCI设备,分配memory空间
->for (pos = 0; pos < howmany; pos++) //探测6个BASE_ADDRESS寄存器,写全F然后取反+1,获取大小,例如[mem 0x00000000 - 0x00ffffff]
struct resource *res = &dev->resource[pos];
reg = PCI_BASE_ADDRESS_0 + (pos << 2);
pos += __pci_read_base(dev, pci_bar_unknown, res, reg);
->pci_device_add(dev, bus);
->pcibios_fixup_bus(bus);//给PCI设备分配中断号
->pcibios_setup_bus_devices(bus);
->pci_read_irq_line(dev);/* Read default IRQs and fixup if necessary */
->of_irq_map_pci(pci_dev, &oirq)
->of_irq_map_raw(ppnode, &lspec_be, 1, laddr, out_irq);//解析设备树interrupt-map,获取中断号
->virq = irq_create_of_mapping(oirq.controller, oirq.specifier,
oirq.size);
->host = irq_find_host(controller);//找到mpic
->host->ops->xlate(host, controller, intspec, intsize,
&hwirq, &type)//实际是mpic_hist_xlate
->mpic_host_xlate//翻译获取设备树的高低电平触发模式
->pci_dev->irq = virq;
->max = pci_scan_bridge(bus, dev, max, pass);
->child = pci_find_bus(pci_domain_nr(bus), secondary);
->if (!child) {
child = pci_add_new_bus(bus, dev, secondary);
->cmax = pci_scan_child_bus(child); //被递归调用,深度优先
->/* Call common code to handle resource allocation */
pcibios_resource_survey();//在函数pci_read_bases里面获取了mem空间大小,现在需要在PCI BUS域池里面给PCI设备分配PCI地址了
->pci_assign_unassigned_resources();
->list_for_each_entry(bus, &pci_root_buses, node)
pci_bus_assign_resources(bus);
->__pci_bus_assign_resources(bus, NULL);
->list_for_each_entry(dev, &bus->devices, bus_list)
__pci_bus_assign_resources(b, fail_head);
->list_for_each_entry(dev, &bus->devices, bus_list)
__pci_bus_assign_resources(b, fail_head);//递归调用__pci_bus_assign_resources(b, fail_head);
->pbus_assign_resources_sorted(bus, fail_head);
->__assign_resources_sorted(&head, fail_head);
->pci_assign_resource(list->dev, idx)
->align = pci_resource_alignment(dev, res);
->__pci_assign_resource(bus, dev, resno)
->pci_bus_alloc_resource//总线池分配资源
->pci_update_resource(dev, resno);//写到设备的mem寄存器里面
//例如BAR 0:set to [mem 0xf 80000000 - 0xf 80ffffff] PCI addr [0x80000000 - 0x80ffffff]
如下几篇文章不错,值得学习参考
PCI学习笔记
http://blog.chinaunix.net/uid-24148050-id-101021.html
PCI驱动初始化流程–基于POWERPC85xx架构的Linux内核PCI初始化
http://www.ithao123.cn/content-1305009.html
kernel hacker修炼之道之PCI subsystem(四)
http://down.51cto.com/data/652228/
Zynq设备树教程(四)
http://xilinx.eetrend.com/blog/7777
2.4 PCI总线的配置
http://blog.sina.com.cn/s/blog_6472c4cc0100qqni.html
PCIe设备发现过程
https://blog.csdn.net/yhb1047818384/article/details/71076371