一:入口
我们通过分析各个模块启动的先后顺序,猜测是在如下模块开始初始化的:
通过查找引用到SI_SUB_CONFIGURE的地方:
我们注意到首先添加了一个nexus0的设备,有关这个设备的描述如下:
At the top level resides the “root” device which is the parent to hang all other devices on. For each architecture, there is typically a single child of “root” which has such things as host-to-PCI bridges, etc. attached to it. For x86, this “root” device is the “nexus” device
源文档 <http://www.freebsd.org/doc/en_US.ISO8859-1/books/arch-handbook/book.html>
这个设备添加到rootbus,本身并没有什么值得研究的;
最关键的是在configure函数中会调用到root_bus_configure, 如下:
此时root_bus上只有nexus这一个child,我们搜索nexus_probe, nexus_attach(), 如下:
我们继续看bus_generic_probe()这个函数:
对devclass上的dirvers进行遍历,并进行DEVICE_IDENTIFY操作;我们需要了解此时nexus对应的devclass都有哪些drivers,并了解DEVICE_IDENTIFY的操作。
对于第一个问题,我们还是要从nexus的源文件找答案;我们先放下这个问题先看看宏定义。
所以我们来看DEVICE_IDENTIFY的操作:
我们可以从另外一个角度推测这段代码最后都做了些什么;我们使用kgdb对系统起来的内核进行分析。
先从root_bus开始分析,其上只有nuexus0这一个虚拟的设备,如下图所示:
再看nexus0上挂载的子设备,共有五个:ram0, npx0, acpi0, legacy0, apic0.
我们再来看nexus_devclass上的driver,一共有五个,分别是:apic, legacy, ram, npx, acpi,同上面那五个驱动是对应的:
看到下面这几个驱动的声明就知道个大概了:都是挂在到nexus bus上,所以能在nexus_devclass上找到相关的driver;至于具体是如何挂在到该devclass的,要看DRIVER_MODULE宏的分析。
看到下面这几个驱动的声明就知道个大概了:都是挂在到nexus bus上,所以能在nexus_devclass上找到相关的driver;至于具体是如何挂在到该devclass的,要看DRIVER_MODULE宏的分析。
二:ACPI简介
三: ACPI层的挂载
我们先来看挂载在nexus0下的acpi0这个虚拟驱动是如何添加上的,acpi_identify函数如下:
很简单,只是添加了一个acpi0设备在nexus0上;我们接下来跳过其他几个会挂载在nexus0的驱动设备的identify分析,接下来nexus0执行完bus_generic_probe(),接下来开始执行bus_generic_attach()函数:
我们只关心nexus0上挂载的acpi0, 我们继续看device_probe_and_attach()函数的描述,这个描述很有意思,解释了很多FreeBSD驱动框架的细节:
函数内容为:
在device_probe_child中,调用parent上的devclass上的driver,并调用device_probe方法尝试去探测child这个设备;我们在这里略过,只需要知道其中会调用到的acpi_probe()函数中:接下来我们看device_attach函数(图片中只是函数的一部分,我们对略去的部分不关心):
该函数中会调用到acpi_attach()函数:
我们继续查看acpi_probe_children()这个函数,看在acpi0上都会挂载哪些设备;由于这个函数比较长,分成两部分,先看第一部分:
我们继续看AcpiWalkNamespace函数定义在哪儿:
整个函数略去分析,主要是把从BIOS读取到的设备添加到acpi0的children列表上;也就是说执行完之后再acpi0上挂载了许多需要探测到设备;
我们用devinfo命令可以查看探测完成后的树状结构:
nexus0
----acpi0
--------cpu0
------------p4tcc0
------------cpufreq0
--------pcib0
------------pci0
----------------hostb0
----------------pcib1
--------------------pci1
------------------------pcib2
----------------------------pci2
------------------------ioapic0
----------------pcib3
--------------------pci9
------------------------em0
----------------isab0
--------------------isa0
------------------------wdt0
------------------------sc0
------------------------vga0
------------------------orm0
--------acpi_sysresource0
----apic0
----ram0
我们看到大多数pci设备都是挂载到了pcib0这个节点下。我们开始分析这个节点:
四: PCI bus的挂载
在acpi_probe_children()函数中,pcib0通过AcpiWalkNamespace挂载到acpi0之后,就继续执行bus_generic_attach(),在这个函数中对每个children选择匹配的driver。
那pcib0选择的是那个driver呢?
我们先从dmesg查找线索:
我们通过查找高亮的字符串,找到如下代码:
probe之后就是attach,所以继续分析acpi_pcib_acpi_attach(FreeBSD的函数命名还是很规范的),略去acpi相关代码的分析,最后会执行到:
注意黄色高亮部分的代码,这里主动添加了一个pci0设备,与我们在devinfo看到的树状结构项符合;
最后acpi_pcib_acpi_attach又调用了bus_generic_attach(),也就是去给pci0这个设备找匹配的driver;
pci0调用的probe函数是(略去从dmesg得到的分析过程):
继续看acpi_pci_attach:
我们感兴趣的是调用到的pci_add_children这个函数:
这个函数探测挂载到pci0上的设备,其详细分析可以参考《linux源代码情景分析》中pci相关章节。
至此,我们应该对FreeBSD驱动的auto config有了一个整体的了解。
分析完之后有个问题:为什么是通过acpi0->pcib0->pci0之后才开始挂载pci总线上的设备,而不是直接通过acpi0->pci0就开始挂载pci总线上的设备?
也许是因为在acpi总线上可以挂载多个pci bridge设备。
这一节留给我们的问题是:
1 某个devclass上的driver是如何连接起来的,依据是什么(这个已经搞明白,在DRIVER_MODULE宏中会有分析);
2 kobj internel如何工作;
3 ACPICA的细节;