官术网_书友最值得收藏!

3.3 PCI核心初始化

要了解PCI子系統的初始化過程,必須先透徹分析Linux初始化調用機制是如何工作的。不失一般性,我們從PCI子系統初始化必須調用的函數pcibus_class_init開始,其代碼如程序3-1所示。

程序3-1 函數pcibus_class_init()代碼(摘自文件drivers/pci/probe.c)

pcibus_class_init()

101static int __init pcibus_class_init(void)
102{
103    return class_register(&pcibus_class);
104}
105postcore_initcall(pcibus_class_init);

這段代碼邏輯的關鍵在于兩個宏定義:__init和postcore_initcall,逐個分析。首先看來自文件include/linux/init.h的__init宏。

#define __init __section(.init.text) __cold notrace

__cold宏定義在文件include/linux/compiler-gcc4.h,__section宏和notrace宏定義在文件include/linux/ compiler.h。擴展之后,可得:

#define __init __attribute__ ((__section__(“.init.text”))) __attribute__((__cold__))
__attribute__((no_instrument_function))

這表明,使用__init定義函數時,會在函數聲明中加入一些GCC屬性。__cold__屬性的作用是將函數標記為較少使用,這樣編譯器可以考慮其長度優化,而不是速度優化。no_instrument_function屬性表示不要在這個函數入口和出口生成用于分析的檢測代碼。這里我們更感興趣的是__section屬性,它要求編譯器將這段代碼放在一個獨立的節(Section)中,節名為“.init.text”。這樣做的目的是在一個ELF節中聚集存放所有的初始化代碼,以便在初始化結束之后,釋放整個節。

因此,回到pcibus_class_init函數,第101~104行定義了實現代碼,并將這段代碼放在“.init.text”節的位置。

再看postcore_initcall宏,同樣在文件include/linux/init.h中,它直接從__define_initcall宏定義而來。

#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn
#define postcore_initcall(fn)      __define_initcall("2",fn,2)

因此,pcibus_class_init函數第105行擴展后就是:

static initcall_t __initcall_pcibus_class_init2 __attribute__((__used__)) __attribute__((__section 
__(".initcall2.init"))) = pcibus_class_init

這行代碼實際上在“.initcall2.init”節定義了一個函數指針,具有唯一的名字,指向實現代碼放在“.init.text”節的pcibus_class_init函數。

實際上,Linux初始化過程中調用的所有函數都是用類似的機制來實現的,最終構成如圖3-11所示的布局。在右邊,是將函數代碼放在對應節或者定義函數指針的宏,左邊是鏈接過程(參見文件include/ asm-generic/vmlinux.lds.h)在節前后添加的一些標簽/符號。

在構造好上面的初始化布局后,Linux內核會依次調用parse_early_param解析.init.setup節的內核參數,調用do_pre_smp_initcalls執行.initearly.init節的早期初始化調用代碼,然后調用do_initcalls順序執行.initcall#.init(包括initcallrootfs.init)節的初始化代碼。

因此,分析PCI子系統初始化流程,需要找出用###_initcall系列宏定義的函數。

對于具有同樣限定符的函數,調用順序取決于編譯順序。因此,通過根目錄下的makefile文件可以知道:arch/x86/pci下的函數將在/drivers/pci的函數前面執行。而通過/drivers/pci下的makefile文件可以知道:drivers/pci/probe.c中的函數將在drivers/pci/pci-driver.c的函數前面執行。

Linux系統啟動過程中,將根據內核編譯情況,順序調用各個初始化函數。這些順序是通過函數聲明限定符指定的PCI核心的初始化順序,如表3-4所示。

圖3-11 Linux初始化代碼使用的某些內存節

表3-4 PCI核心的初始化順序

PCI子系統的初始化主要完成以下工作。

? 初始化總線類

pcibus_class_init函數的代碼在前面已給出,它的唯一目的是初始化PCI總線類。它調用Linux驅動模型中的class_register注冊一個名字叫pci_bus的class。這將在sys/class/下創建一個pci_bus目錄。后續的PCI核心代碼將在該目錄下為每條PCI總線創建一個子目錄,目錄名格式為####:##,對應總線域編號和總線編號。

? 初始化總線類型

作為Linux驅動模型的一個主要實例,PCI核心定義了一個名字為pci的總線類型。pci_driver_init函數(參見文件drivers/pci/pci-driver.c)調用Linux驅動模型中的bus_register注冊該總線類型。正是由于PCI總線類型的存在,才會有PCI設備的鏈表和PCI驅動的鏈表,才會有PCI設備和PCI驅動之間的“綁定”。

注意:這里的pci_driver_init的函數名對應pci總線類型的驅動,和PCI設備的驅動是不同的。此外,精確理解PCI總線類型和PCI總線的含義,對理解PCI核心代碼也有著很重要的作用。

? 配置訪問方法

這里所謂的訪問方法,是指對配置空間的訪問。Linux支持多種配置訪問方法,必須在PCI子系統初始化早期根據用戶要求和系統狀況選擇一種,入口函數為pci_arch_init。對PCI配置訪問方法的分析參見本章后面部分。

? PCI總線掃描

PCI總線掃描使得Linux了解整個硬件系統的“家底”,為每個設備構建內存中數據結構,這是以后對該設備進行操作的基礎。入口函數是pcibios_scan_root,在pci_subsys_init函數調用的pci_legacy_ init函數中調用。對PCI總線掃描流程的分析參見本章后面部分。

? PCI中斷路由

PCI中斷路由的基本出發點是建立PCI硬件中斷引腳與操作系統中斷號之間的關聯,使得在引腳上出現中斷時,可以找到操作系統預設置的中斷處理函數。對于x86,PCI中斷路由的入口函數是pcibios_irq_init,在pci_subsys_init函數中被調用。對PCI中斷路由流程的分析參見本章后面部分。

? PCI資源分配

PCI資源分配的目的是將PCI設備的I/O地址空間和內存地址空間映射到Linux系統的總線空間,這樣以后對于總線空間范圍內的訪問會被自動“重定向”到PCI設備。PCI資源分配有兩種可能:(1)PCI設備的I/O空間和內存空間已經被配置好。這時候,PCI核心只需要“驗證”配置的正確性,入口函數為pcibios_resource_survey,從pci_subsys_init函數調用的pcibios_init函數中調用;(2)PCI設備的I/O空間和內存空間還沒有被配置。這時候,PCI核心結構就需要為它們分配資源,入口函數為pcibios_assign_resources。對PCI資源分配的分析參見本章后面部分。

? 初始化proc文件系統

精確地說,是初始化proc文件系統中的與PCI有關的目錄項。在文件drivers/pci/proc中,入口函數為pci_proc_init。

? 初始化sysfs文件系統

在PCI掃描過程中已經為PCI總線和PCI設備在sysfs文件系統中創建了目錄項和一些基本的屬性文件,PCI子系統初始化過程的最后,還調用pci_sysfs_init函數(參見文件drivers/pci/pci-sysfs.c)為掃描到的PCI設備創建更多的屬性文件。

需要指出的是,本書在分析時,將主要關注點集中在邏輯流程部分。Linux是一個兼容性極好的操作系統,它不僅支持各種平臺,還支持各個廠商的硬件產品,甚至是“有點小毛病”的硬件產品。例如,PCI子系統中,有很多與quirk有關的代碼,它們的處理也很有意思。不過這里不打算討論它們,請有興趣的讀者自行研讀。

主站蜘蛛池模板: 安乡县| 古丈县| 汉源县| 河曲县| 湖南省| 缙云县| 黔西县| 黔南| 阿尔山市| 邵武市| 临潭县| 汉沽区| 呼伦贝尔市| 襄樊市| 宝兴县| 顺昌县| 东辽县| 腾冲县| 衡南县| 电白县| 响水县| 稷山县| 桐梓县| 临桂县| 湖北省| 宿松县| 维西| 开封县| 石家庄市| 顺义区| 涪陵区| 郸城县| 曲水县| 长汀县| 大新县| 大连市| 南充市| 阿鲁科尔沁旗| 宜良县| 彰武县| 巫溪县|