linux 2.6.23-PCI总线枚举源代码分析 |
|
linux 2.6.23-PCI总线枚举源代码分析
pci总线枚举是在initcalls被调用的时候进行的,do_initcalls分别调用很多init节中的的函数,调用顺序
由类似__define_initcall("0",fn,1)宏定义的参数决定。当调用到pcibios_init的时候开始进行总线初始化
pcibios_init-->pcibios_sort-->while (!list_empty(&pci_devices)) {
ln = pci_devices.next;
dev = pci_dev_g(ln);
idx = found = 0;
while (pci_bios_find_device(dev->vendor, dev->device, idx, &bus, &devfn) == PCIBIOS_SUCCESSFUL) {
idx++;
list_for_each(ln, &pci_devices) {
d = pci_dev_g(ln);//d为pci_dev
...... -->pci_bios_find_device:此为汇编代码
struct pci_bus * __devinit pcibios_scan_root(int busnum)
{
struct pci_bus *bus = NULL;
dmi_check_system(pciprobe_dmi_table);
while ((bus = pci_find_next_bus(bus)) != NULL) {
if (bus->number == busnum) {
return bus;
}
}
printk(KERN_DEBUG "PCI: Probing PCI hardware (bus %02x)\n", busnum);
return pci_scan_bus_parented(NULL, busnum, &pci_root_ops, NULL);-->|pci_bus *b = pci_create_bus(parent, bus, ops, sysdata)
} |pci_scan_child_bus(b)-->见下面代码:
unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus)
{
unsigned int devfn, pass, max = bus->secondary;
struct pci_dev *dev;
pr_debug("PCI: Scanning bus %04x:%02x\n", pci_domain_nr(bus), bus->number);
for (devfn = 0; devfn < 0x100; devfn += 8)
pci_scan_slot(bus, devfn);
pr_debug("PCI: Fixups for bus %04x:%02x\n", pci_domain_nr(bus), bus->number);
pcibios_fixup_bus(bus);
for (pass=0; pass < 2; pass++)
list_for_each_entry(dev, &bus->devices, bus_list) {
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||
dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)
max = pci_scan_bridge(bus, dev, max, pass);
}
pr_debug("PCI: Bus scan for %04x:%02x returning with max=%02x\n",
pci_domain_nr(bus), bus->number, max);
return max;
}
以上最重要的函数就是:pci_scan_slot(bus, devfn)-->dev = pci_scan_single_device(bus, devfn)-->|dev = pci_scan_device(bus, devfn)这个函数就是我一直找的建立pci_dev的函数,终于找到了,这个函数主要就是读取设备的配置空间,检查槽是否被占用,若被占用则分配一个pci_dev结构将之初始化并挂入当前的bus
|pci_device_add(dev, bus)这个函数是将上面返回的pci_dev结构挂入当前bus的设备链表,在之后的时间将要把这个Bus的所有设备挂入pci_devices全局链表
|pci_scan_msi_device(dev)这个是一个支持选项,可以将中断写入一个特定空间,然后这个空间向cpu发中断
pci_scan_bridge(bus, dev, max, pass)-->pci_scan_child_bus(child)这里是个递归的树建立过程
以上就是建立pci树的过程这些都是在pci初始化代码中建立的,每发现一个可用的pci设备就建立一个pci_dev
结构体,并且将之挂入pci_devices队列
上面是从pcibios_scan_root开始的,但是到底是谁调用的pcibios_scan_root呢?继续分析:
遍查代码后发现是pci_legacy_init调用的,但是再往前呢?很有意思,和读侦探小说一样,继续吧:注意在此
函数中有if (pcibios_scanned++)return 0;这是判断是否已经遍历过一次了,若是则不做了,直接返回,若不是
则进一步调用pcibios_scan_root,接下来分析是谁调用了pci_legacy_init,我估计马上就可以把一切联系起来了
当我遍查代码的时候发现没有一个函数显示调用它,原来他是subsys_initcall调用的内存位置时.initcall4.init
它的级别是4,它是在do_basic_setup中的do_initcalls中被调用的,看它的定义也可以:subsys_initcall(pci_legacy_init)
一切基本上结束了,它的级别是4,属于subsys范畴,接下来关于pci的就是调用级别5,6...了,他们是pcibios_init,
下面跟踪一下:
pcibios_init-->pcibios_sort-->...这不用了,最上面已经有了
另外pci_scan_bus也可以开始pci枚举过程,它和pci_scan_root差不多,它是:
static inline struct pci_bus *pci_scan_bus(int bus, struct pci_ops *ops, void *sysdata)
{
struct pci_bus *root_bus;
root_bus = pci_scan_bus_parented(NULL, bus, ops, sysdata);
if (root_bus)
pci_bus_add_devices(root_bus);
return root_bus;
}
只是它一般是在枚举过后进行fixup时候调用的,比如pci_fixup_i450nx(struct pci_dev *d)中就会调用
由此我认为在启动的时候并不是一次进行枚举,而是进行了很多次枚举,每次fixup的时候一般都要进行枚举
再看看pci_access_init的跟踪:
static __init int pci_access_init(void)
{
#ifdef CONFIG_PCI_MMCONFIG
pci_mmcfg_init();
#endif
if (raw_pci_ops)
return 0;
#ifdef CONFIG_PCI_BIOS
pci_pcbios_init();-->pci_find_bios-->check_pcibios这个函数是一个内联汇编代码
#endif
if (raw_pci_ops)
return 0;
#ifdef CONFIG_PCI_DIRECT
pci_direct_init();
#endif
return 0;
}
arch_initcall(pci_access_init);
总结一下:在start_kernel的最后一步启用函数rest_init,它内部启动内核进程init,然后调用do_initcalls
开始进行二期初始化,比如设备初始化,我这个分析中主要讨论了pci初始化,在进行设备初始化配置的时候(比
如中断号的分配)可能用到bios服务,从设备的配置空间读取中断号,这就要在pci枚举之前首先找到bios为pci服务
的地址,这个在pci_access_init中指定,然后就开始了总线枚举,完事以后pci_devices全局链表就被填充了,然后
就开始pcibios_init了,这就是一切过程。这里仅仅把设备初始化了,但是还是得需要驱动才能使设备开始工作。
这就和前些天我研究的统一的设备模型联系起来了。
终于把pci枚举的来龙去脉弄明白了,这个问题是从我看usb驱动源代码的时候引出的,当时我发现usb的主机
控制器是个pci设备,于是就一路跟下来了,其间读了irq的代码,明白了中断号的分配,中断的处理过程。在
这个分析的过程里学习了几段代码,就是在start_kernel里的处理初始参数的问题,一共有两个,一个是特权参数
的解析,一个是非特权参数的解析,初始化参数和initcalls都在内核映像的_init_begin和_init_end之间,这种安排
我今天才明白,感觉最近分析代码以后对Linux越来越清晰了,当遇到困难的时候,阅读代码是最好的方法。
看了几天Linux源代码,感觉挺好的,现在十分想知道windows是怎么实现的,可是windows的代码不会让我看的!//--------------------------------------------------------------------------------------------------------------------------------
原文地址:
阅读(1159) | 评论(0) | 转发(0) |