精华内容
下载资源
问答
  • 在《Linux内核修炼之道》里,文章作者对如何...并且该作者还在《Linux那些事儿 之 我是PCI》系列文章中对X86架构下的PCI总线和设备驱动做了很详细的分析。由于工作需要,我在此基础上分析了ARM体系结构的PCI总线驱...

    转载:https://blog.csdn.net/eydwyz/article/details/72285858

    《Linux内核修炼之道》里,文章作者对如何使用Kconfig和Makefile定位内核源码有精彩的阐述。并且该作者还在《Linux那些事儿 之 我是PCI》系列文章中对X86架构下的PCI总线和设备驱动做了很详细的分析。由于工作需要,我在此基础上分析了ARM体系结构的PCI总线驱动,并把心得和笔记整理一下,或许对日后解析其他子系统有所帮助。以下涉及到的Linux内核源码均为2.6.23版本。

    PCI是一种总线标准。一般已经形成xxx标准的东西,尤其是总线这样可以被叫做“子系统”的东西,在Linux内核里基本上都已经得到了很好的支持。内核一般把PCI总线这类子系统的驱动分成三层:总线驱动、协议、设备驱动。其中Linux内核早已把协议层实现完毕,最大程度地精简了总线驱动和设备驱动这两层与硬件密切相关的工作。

    PCI设备驱动相关知识在《Linux设备驱动程序 第三版》中已经有了详细介绍。在这一层里工作的程序员不必知道PCI总线驱动的实现细节,他们只需要利用内核提供的PCI设备驱动相关接口,就可以成功地注册、配置、控制和卸载任何PCI设备,无论那个设备究竟是一个大容量存储器、一个USB HUB、还是一个视频采集卡。正因为有了那些内核接口,设备驱动层的程序员们才能把大量的精力用在实现具体设备的功能的完善和性能的优化上。假如此时你正在为你的笔记本电脑编写一个PCI设备的驱动(现在的笔记本都有PCIE热插拔的插槽,所以有这样的设备是很可能的~),那么你就正在PCI设备驱动层上工作着。

    当然,对于设备驱动层的程序员来说,一切的前提是这块开发板上的PCI总线驱动已经有人写好了,因为如果没有底层总线驱动的支持,协议层和设备驱动层写得再精彩也只是空中楼阁。可是如果开发板上并没有PCI总线驱动怎么办?要想在一个裸体开发板上使用某个PCI设备,很明显,写驱动的程序员就必须先把PCI的总线驱动给写了,然后再写设备的驱动。

    相比设备驱动,总线驱动显得更加神秘和高深莫测,因为它总是跟CPU体系结构、跟具体的芯片或芯片组密切相关,它的工作非常抽象,而不像设备驱动那么具体和显而易见。不论如何,PCI总线也是一种硬件设备,处理器访问和控制任何设备的方式,无非就是探测某些管脚的电平的高低,以及把某些管脚的电平拉高或拉低(先不管这个过程中间可能经历的千山万水)。再抽象一点,其实就是CPU向设备上的某个寄存器读和写,也就是访问传说中的“I/O端口和I/O内存”(有关I/O端口和I/O内存的知识,参考《Linux设备驱动程序》“第九章 与硬件通信”)。芯片手册上通常会用很多篇幅来介绍如何访问寄存器,以及寄存器与设备功能之间的关系。

    注:中断也是设备控制的重要方式,但并非所有设备都需要中断。

    不过就算知道了有关硬件I/O的知识,如果不知道PCI驱动的架构,不知道Linux内核应该在何时何地与芯片手册上介绍的寄存器发生关系,也不可能写出PCI总线的驱动来。既然Linux内核已经把PCI子系统的架子搭了起来,并且还派了一些内核接口来跑龙套,那我们写总线驱动的程序员作为主角总要知道自己应该唱哪一出戏。

    话说Linux内核里有一帮子专业龙套。最著名的要算module_init()宏,它甚至作为半个主角出现在Helloworld模块那为数不多的几行代码里。不过它也只是__define_initcall()的若干标准Pose之一。这个叫做__define_initcall()的宏,套上不同的行头摆出不同的Pose,几乎出现在Linux内核的每个模块里。周杰伦看完内核代码也不禁感叹“你出现在我诗的每一页”。有关__define_initcall()的定义,都在linux/init.h文件中。

    #define __define_initcall(level,fn,id) /
        static initcall_t __initcall_##fn##id __attribute_used__ /
        __attribute__((__section__(".initcall" level ".init"))) = fn

    #define pure_initcall(fn)               __define_initcall("0",fn,0)

    #define core_initcall(fn)               __define_initcall("1",fn,1)
    #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
    #define postcore_initcall(fn)           __define_initcall("2",fn,2)
    #define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)
    #define arch_initcall(fn)               __define_initcall("3",fn,3)
    #define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
    #define subsys_initcall(fn)             __define_initcall("4",fn,4)
    #define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)
    #define fs_initcall(fn)                 __define_initcall("5",fn,5)
    #define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
    #define rootfs_initcall(fn)             __define_initcall("rootfs",fn,rootfs)
    #define device_initcall(fn)             __define_initcall("6",fn,6)
    #define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)
    #define late_initcall(fn)               __define_initcall("7",fn,7)
    #define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)

    #define __initcall(fn)                  device_initcall(fn)

    #define module_init(x)                  __initcall(x);

     

    __define_initcall()宏的作用,其实是告诉编译器在编译的时候,把它所修饰的函数按照已经定义好的顺序放入程序段中。而内核在初始化的过程中,会调用到init/main.c中的一个名为do_initcalls()的函数,在那里,所有的被__define_initcall()修饰的函数会严格地按照标准顺序执行一遍。你懂的,这就是传说中的模块入口。其实模块本没有入口,但是内核的模块多了,就有了模块的入口;写模块时重启系统的次数多了,就有了模块的动态加载。

     

    终于要说到PCI子系统框架的入口了。说起PCI框架入口,不得不佩服Linux内核的模块化布局。有关PCI子系统的源码,实际上只会出现在两个地方,一个是drivers/pci路径下,另一个是arch里的相关路径下。《我是PCI》文章作者已经把i386架构的PCI入口贴了出来,这里抄袭如下:

    文件 函数 入口 内存位置
    arch/i386/pci/acpi.c pci_acpi_init subsys_initcall .initcall4.init
    arch/i386/pci/ common.c

    pcibios_init

    subsys_initcall

    .initcall4.init

    arch/i386/pci/i386.c

    pcibios_assign_resources

    fs_initcall

    .initcall5.init

    arch/i386/pci/ legacy.c pci_legacy_init subsys_initcall .initcall4.init
    drivers/pci/pci-acpi.c acpi_pci_init arch_initcall .initcall3.init
    drivers/pci/pci- driver.c pci_driver_init postcore_initcall .initcall2.init
    drivers/pci/pci- sysfs.c pci_sysfs_init late_initcall .initcall7.init
    drivers/pci/pci.c pci_init device_initcall .initcall6.init
    drivers/pci/probe.c pcibus_class_init postcore_initcall .initcall2.init
    drivers/pci/proc.c pci_proc_init __initcall .initcall6.init
    arch/i386/pci/init.c pci_access_init arch_initcall .initcall3.init

     

    PCI毕竟是Intel抻头搞出来的,所以i386架构自然是先吃到了螃蟹。不过到底还是第一次吃,所以吃得并不风雅,反而显得冗余罗嗦,光是在arch/i386/pci路径下,就有好几个入口函数。

     

    后来者之所以能够居上,通常是既借鉴了前人的经验,又总结了前人的教训。想来Arm对PCI的支持要比X86晚一些,所以Arm架构下PCI子系统的initcall函数要少一些——通常来说,arch/arm/mach-xxx/路径下基本上只有一个用subsys_initcall()修饰的PCI子系统相关的初始化函数。不过PCI子系统在Arm下的初始化逻辑也要复杂一点,因为X86有bios帮它打杂,而Arm的一切都要靠内核自力更生。

     

    《我是PCI》文章里用了很长篇幅介绍如何确定这些initcall函数的顺序。其实只有三点:

    第一,先看它们在内存中的位置,位置靠前的先被执行。所以被postcore_initcall()修饰的函数就一定会在被subsys_initcall()修饰的函数之前执行。

    第二,如果两个函数被放在相同的内存区里,则谁先被编译谁就先执行。而gcc的规则是“The order of files in $(obj-y) is significant.”,因此哪个函数所在的编译单元写在前面,谁就先执行。

    第三,如果两个函数并不在同一个Makefile中,那么哪个Makefile先被调用,哪个函数就先被编译,因此也就会先被执行。

     

    有了这几条判断依据,自然可以给出一个准确的initcall函数执行顺序(不包含i386的函数)。
    pcibus_class_init()
    pci_driver_init()
    acpi_pci_init()
    /* mach_spec_pci_init() */
    pci_init()
    pci_proc_init()
    pci_sysfs_init()

    从理论上讲,真正落实了如何与PCI总线通信的初始化工作的函数,就是注释的这一行。只不过,为各种类型的板子开发底层驱动的大侠们会把这个函数起成各种各样的名字(一般来说,叫“xxx_pci_init()”的比较常见)。所以我也只是随便给它起个一目了然的名字而已。

     

    前面说过,Arm不像X86那样有bios给她当助理,所以在X86下可以由bios完成的工作,在Arm下都得由内核源码自己解决。有好奇心的童鞋现在一定想知道到底bios帮X86搞定了些什么事情,既然有好奇心,不妨自己搜一下CONFIG_PCI_BIOS这个宏一探究竟。Arm虽然没有bios,但Arm上的工程师也不想因为搞特殊化而打乱X86上已经正常运转的PCI总线驱动的初始化流程。Arm工程师发挥温州人的精神,自己伪造了一个软bios,于是arch/arm/kernel/bios32.c源码就应运而生。与之相对的头文件是include/asm-arm/mach/pci.h,它包含着简单而重要的bios32.c源码的外部接口:

    struct pci_sys_data;
    struct pci_bus;

    struct hw_pci {
        struct list_head buses;
        int        nr_controllers;
        int        (*setup)(int nr, struct pci_sys_data *);
        struct pci_bus *(*scan)(int nr, struct pci_sys_data *);
        void        (*preinit)(void);
        void        (*postinit)(void);
        u8        (*swizzle)(struct pci_dev *dev, u8 *pin);
        int        (*map_irq)(struct pci_dev *dev, u8 slot, u8 pin);
    };

     

    void __init pci_common_init(struct hw_pci *hw)
    {
        struct pci_sys_data *sys;

        INIT_LIST_HEAD(&hw->buses);

        if (hw->preinit)
            hw->preinit();
        pcibios_init_hw(hw);
        if (hw->postinit)
            hw->postinit();

        pci_fixup_irqs(pcibios_swizzle, pcibios_map_irq);

        list_for_each_entry(sys, &hw->buses, node) {
            struct pci_bus *bus = sys->bus;

            if (!use_firmware) {
                /*
                 * Size the bridge windows.
                 */
                pci_bus_size_bridges(bus);

                /*
                 * Assign resources.
                 */
                pci_bus_assign_resources(bus);
            }

            /*
             * Tell drivers about devices found.
             */
            pci_bus_add_devices(bus);
        }
    }
     

    /*
     * Per-controller structure
     */
    struct pci_sys_data {
        struct list_head node;
        int        busnr;        /* primary bus number            */
        u64        mem_offset;    /* bus->cpu memory mapping offset    */
        unsigned long    io_offset;    /* bus->cpu IO mapping offset        */
        struct pci_bus    *bus;        /* PCI bus                */
        struct resource *resource[3];    /* Primary PCI bus resources        */
                        /* Bridge swizzling            */
        u8        (*swizzle)(struct pci_dev *, u8 *);
                        /* IRQ mapping                */
        int        (*map_irq)(struct pci_dev *, u8, u8);
        struct hw_pci    *hw;
    };

    /*
     * This is the standard PCI-PCI bridge swizzling algorithm.
     */
    u8 pci_std_swizzle(struct pci_dev *dev, u8 *pinp);

    /*
     * Call this with your hw_pci struct to initialise the PCI system.
     */
    void pci_common_init(struct hw_pci *);

    既然这个头文件是在include/asm-arm路径下,就说明你不能够期待这里的接口也会出现在i386里,若真的有雷同,也绝对纯属巧合,或者只不过是程序员为了提示Arm上的某个接口与i386上的某个接口有着对应关系。所以从现在开始分析代码,就只能捡arch/arm路径下的代码来看了。你可以随便看看arch路径下的任何mach的pci初始化相关的subsys_initcall函数(通常名叫xxx_pci_init()函数),你会发现一个共同的特点就是,它们基本上只是调用了pci_common_init(struct hw_pci *)这个函数。而这个函数又早已经被维护arch体系结构的大侠写好了。调用一个已经被实现的内核接口——难道一切就这样简单地结束了吗?想得美。那不是还有一个参数么。一看参数恍然大悟,这又是一个Linux内核搭台,我们来唱戏的模式。

    我们先来看看struct hw_pci这个结构体。.buses是一个链表,链表中的每个节点指向一个PCI子总线的私有数据(也就是struct pci_sys_data结构体)。.nr_controllers指出PCI总线有几个控制器,这通常要看芯片手册才能确定。下面的六个函数指针,都是pci_common_init()所需要的回调函数。pci_common_init()函数无疑是Arm体系下PCI子系统总线驱动初始化的剧本,我们虽然是主角,但也只能在固定的时间地点大背景下自我发挥。下面我们就先熟悉一下这个剧本吧。pci_common_init()函数就在arch/arm/kernel/bios32.c源码中。

    void __init pci_common_init(struct hw_pci *hw)
    {
        struct pci_sys_data *sys;

        INIT_LIST_HEAD(&hw->buses);

        if (hw->preinit)
            hw->preinit();
        pcibios_init_hw(hw);
        if (hw->postinit)
            hw->postinit();

        pci_fixup_irqs(pcibios_swizzle, pcibios_map_irq);

        list_for_each_entry(sys, &hw->buses, node) {
            struct pci_bus *bus = sys->bus;

            if (!use_firmware) {
                /*
                 * Size the bridge windows.
                 */
                pci_bus_size_bridges(bus);

                /*
                 * Assign resources.
                 */
                pci_bus_assign_resources(bus);
            }

            /*
             * Tell drivers about devices found.
             */
            pci_bus_add_devices(bus);
        }
    }

     

    剧情梗概:

    569行,pcibios_init_hw()函数初始化每一个controller,初始化它并且递归地枚举出它的子总线。在这个过程中会调用到.setup和.scan回调函数。如果需要,可以实现.preinit和.postinit这两个回调函数,它们俩会在pcibios_init_hw()函数执行之前和之后被调用。

    573行,设置设备的irq号。按理说,根据《Linux设备驱动程序》里的介绍,PCI设备的irq号不是已经在配置空间里明确指定了吗,为什么还要多此一举呢?配置空间里的确是指定了,但是考虑到一个PCI设备有可能经过很多个级联的PCI-PCI桥才连接到主桥上,所以它到底使用哪个中断号还真是说不准啊。Linux内核眼睛里是揉不得沙子的,她说不准的事,一定会让我们来说准。.swizzle和.map_irq两个回调函数就是用来干这个的。值得一提的是,pci_fixup_irqs()函数中会使用pci_read_config_xxx()和pci_write_config_xxx()这些PCI子系统的内核接口,这说明在此之前内核必须已经知道如何访问PCI设备的配置空间。显然,聪明的你一定猜到,这件事也是在569行里面做的,至于究竟怎么做,后面会说到。

    575-594行,遍历每个子总线,初始化子总线所在的PCI桥设备,然后把这条子总线上的设备(在569行里已经被枚举出来了)加入一个全局的设备列表中去(同时也会加入到子总线的设备列表中去)。 

     

    代码看到这里,已经敞亮了很多。我们可以确定的是.setup、.preinit、.postinit、.scan、.swizzle和.map_irq这六个回调函数就是Linux内核PCI总线驱动跟芯片手册发生关系的地方!

     

    .setup、.preinit、.postinit这三个函数跟芯片手册密切相关。通常芯片手册会详细介绍PCI总线初始化时读写哪些寄存器以及具体步骤。如果controller只有一个,那么很可能就不需要实现.preinit、.postinit这两个函数了,但.setup函数必须实现。

     

    另一个跟芯片手册密切相关而且必须实现的回调函数是.map_irq函数。它使device、slot和pin三个元素与一个irq中断号关联起来。

     

    至于.swizzle函数,bios32.c源码中已经实现了一个名叫pci_std_swizzle()的通用函数,如果我们拿到的芯片手册上对swizzle这件事情没有特殊说明,就可以直接使用这个现成的函数。如果我们能够确定板子上根本不会有PCI子总线(根本没有PCI-PCI桥这种设备,很多嵌入式开发板上就没有这种设备),那么.swizzle函数就可以省略。

     

    回调函数.scan是我们必须实现的。它的作用是,从主总线开始扫描总线上的PCI设备。一旦发现PCI-PCI桥,就初始化一条子总线,并且继续扫描子总线上的设备。就这样递归地扫描下去,直到形成一棵完整的PCI设备树,包含主总线和所有子总线上挂接的设备。整个过程听起来好像很复杂,事实上也确实如此。不过由于任何芯片的PCI总线的scan都是这个过程,因此Linux内核再次为我们准备好了一个通用函数:

    static struct pci_bus *pci_scan_bus(int bus, struct pci_ops *ops, void *sysdata);

    这个函数只是向我们额外索取一个struct pci_ops结构体,这个结构体只有.read和.write两个回调函数指针,作用是读取和写入PCI设备的配置空间——又是一个典型的内核搭台,我们唱戏的模式。

     

    也就是说,只要我们实现了PCI设备配置空间的访问操作,则.scan的一切就尽在掌握。如果你有兴趣进入pci_scan_bus()简单地追踪一下,就可以知道这个ops参数通过pci_scan_bus_parented()-->pci_create_bus()的途径传递给主总线,然后又通过pci_scan_bus_parented()-->pci_scan_child_bus()-->pci_scan_bridge()-->pci_add_new_bus()-->pci_alloc_child_bus()的途径传递给了子总线。于是,PCI子系统中的所有子总线都知道如何访问设备的配置空间,那么PCI总线驱动初始化之后,PCI总线协议层向上提供的内核接口也就都可以正常运转了。因为这些协议说到底,都是在访问配置空间、I/O端口和I/O内存空间,而后两者又可以被CPU直接访问到。最后,访问PCI设备配置空间的方法,是与芯片手册密切相关的。

     

    说明了.scan的流程和PCI设备树的概念,就可以解释一个名词了。前面好几次提到一个叫做controller的东西,却没有解释它到底是个什么东西。其实这个东西的全名叫做Host Controller,主控制器。这么说吧,每棵PCI设备树都是一棵单根树,而树根就是一个Host Controller。如果一块板子上有两个PCI Host Controller怎么办?很简单,那就有两棵独立的PCI设备树。那么Host Controller究竟是干啥的呢?前面也提到,编写PCI总线驱动,关键就是找到PCI子系统框架中与芯片手册发生关系的地方。OK,我们最终找到了,就是初始化、映射中断和访问配置空间。那么到底这个所谓的“关系”是怎么发生的呢?前面介绍过有关硬件I/O的知识,我们知道与硬件发生关系,靠的不是潜规则,而是读写硬件上的寄存器。你可以这样理解,这个Host Controller,就是一大片控制寄存器,CPU通过对它的读写,来控制PCI总线。所以,如果一块板子上有两个PCI Host Controller,那就有两条PCI主总线,它们俩绝对可以互不关心彼此的存在,此所谓相濡以沫不如相忘于江湖。当然,你也可能希望只用一个subsys_initcall就初始化两条主总线,struct hw_pci允许你这么干,因为它提供了.nr_controllers这个成员变量,你可以把它设置为2,或者更大值,你有多少个controller,你的.setup和.scan回调函数就会被调用多少次。

     

    总结

     

    1,Arm体系结构一般只需要一个被subsys_initcall()修饰的初始化函数。该函数的主体是pci_common_init()函数。

    2,pci_common_init()函数需要一个已被初始化了的struct hw_pci结构体的指针。该结构体包含了PCI子系统的基本信息和回调函数。

    3,在struct hw_pci结构体中,.setup()、.preinit()、.postinit()、.map_irq()等回调函数与芯片手册密切相关。不过.preinit()和.postinit()函数有时可以不实现。

    4,在struct hw_pci结构体中,.swizzle函数已经有通用的实现。.scan函数的主体应为pci_scan_bus()函数。

    5,pci_scan_bus()函数需要一个已被初始化了的struct pci_ops结构体的指针。该结构体包含了PCI设备配置空间的读取和写入函数。

    6,读写配置空间与芯片手册密切相关。

    关于PCIE总线驱动

    PCIE在协议层完全兼容PCI。从PCIE子系统的协议层来看,新增加的功能(在drivers/pci/pcie路径下)全部通过访问设备的配置空间、I/O端口和I/O内存实现,没有为底层总线驱动添加任何负担。因此PCIE子系统与PCI子系统在总线驱动方面也没有本质不同,都沿用了PCI子系统的初始化过程,并需要程序员实现设备配置空间的读写函数。
     

    展开全文
  • PCI 总线初始化

    千次阅读 2017-05-16 14:50:48
    之 我是PCI》系列文章中对X86架构下的PCI总线和设备驱动做了很详细的分析。由于工作需要,我在此基础上分析了ARM体系结构的PCI总线驱动,并把心得和笔记整理一下,或许对日后解析其他子系统有所帮助。以下涉及到的...

    《Linux内核修炼之道》里,文章作者对如何使用Kconfig和Makefile定位内核源码有精彩的阐述。并且该作者还在《Linux那些事儿 之 我是PCI》系列文章中对X86架构下的PCI总线和设备驱动做了很详细的分析。由于工作需要,我在此基础上分析了ARM体系结构的PCI总线驱动,并把心得和笔记整理一下,或许对日后解析其他子系统有所帮助。以下涉及到的Linux内核源码均为2.6.23版本。

     

    PCI是一种总线标准。一般已经形成xxx标准的东西,尤其是总线这样可以被叫做“子系统”的东西,在Linux内核里基本上都已经得到了很好的支持。内核一般把PCI总线这类子系统的驱动分成三层:总线驱动、协议、设备驱动。其中Linux内核早已把协议层实现完毕,最大程度地精简了总线驱动和设备驱动这两层与硬件密切相关的工作。

     

    PCI设备驱动相关知识在《Linux设备驱动程序 第三版》中已经有了详细介绍。在这一层里工作的程序员不必知道PCI总线驱动的实现细节,他们只需要利用内核提供的PCI设备驱动相关接口,就可以成功地注册、配置、控制和卸载任何PCI设备,无论那个设备究竟是一个大容量存储器、一个USB HUB、还是一个视频采集卡。正因为有了那些内核接口,设备驱动层的程序员们才能把大量的精力用在实现具体设备的功能的完善和性能的优化上。假如此时你正在为你的笔记本电脑编写一个PCI设备的驱动(现在的笔记本都有PCIE热插拔的插槽,所以有这样的设备是很可能的~),那么你就正在PCI设备驱动层上工作着。

     

    当然,对于设备驱动层的程序员来说,一切的前提是这块开发板上的PCI总线驱动已经有人写好了,因为如果没有底层总线驱动的支持,协议层和设备驱动层写得再精彩也只是空中楼阁。可是如果开发板上并没有PCI总线驱动怎么办?要想在一个裸体开发板上使用某个PCI设备,很明显,写驱动的程序员就必须先把PCI的总线驱动给写了,然后再写设备的驱动。

     

    相比设备驱动,总线驱动显得更加神秘和高深莫测,因为它总是跟CPU体系结构、跟具体的芯片或芯片组密切相关,它的工作非常抽象,而不像设备驱动那么具体和显而易见。不论如何,PCI总线也是一种硬件设备,处理器访问和控制任何设备的方式,无非就是探测某些管脚的电平的高低,以及把某些管脚的电平拉高或拉低(先不管这个过程中间可能经历的千山万水)。再抽象一点,其实就是CPU向设备上的某个寄存器读和写,也就是访问传说中的“I/O端口和I/O内存”(有关I/O端口和I/O内存的知识,参考《Linux设备驱动程序》“第九章 与硬件通信”)。芯片手册上通常会用很多篇幅来介绍如何访问寄存器,以及寄存器与设备功能之间的关系。

    注:中断也是设备控制的重要方式,但并非所有设备都需要中断。

     

    不过就算知道了有关硬件I/O的知识,如果不知道PCI驱动的架构,不知道Linux内核应该在何时何地与芯片手册上介绍的寄存器发生关系,也不可能写出PCI总线的驱动来。既然Linux内核已经把PCI子系统的架子搭了起来,并且还派了一些内核接口来跑龙套,那我们写总线驱动的程序员作为主角总要知道自己应该唱哪一出戏。

     

    话说Linux内核里有一帮子专业龙套。最著名的要算module_init()宏,它甚至作为半个主角出现在Helloworld模块那为数不多的几行代码里。不过它也只是__define_initcall()的若干标准Pose之一。这个叫做__define_initcall()的宏,套上不同的行头摆出不同的Pose,几乎出现在Linux内核的每个模块里。周杰伦看完内核代码也不禁感叹“你出现在我诗的每一页”。有关__define_initcall()的定义,都在linux/init.h文件中。

     

    1. #define __define_initcall(level,fn,id) /  
    2.     static initcall_t __initcall_##fn##id __attribute_used__ /  
    3.     __attribute__((__section__(".initcall" level ".init"))) = fn  
    4.   
    5. #define pure_initcall(fn)               __define_initcall("0",fn,0)  
    6.   
    7. #define core_initcall(fn)               __define_initcall("1",fn,1)  
    8. #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)  
    9. #define postcore_initcall(fn)           __define_initcall("2",fn,2)  
    10. #define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)  
    11. #define arch_initcall(fn)               __define_initcall("3",fn,3)  
    12. #define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)  
    13. #define subsys_initcall(fn)             __define_initcall("4",fn,4)  
    14. #define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)  
    15. #define fs_initcall(fn)                 __define_initcall("5",fn,5)  
    16. #define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)  
    17. #define rootfs_initcall(fn)             __define_initcall("rootfs",fn,rootfs)  
    18. #define device_initcall(fn)             __define_initcall("6",fn,6)  
    19. #define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)  
    20. #define late_initcall(fn)               __define_initcall("7",fn,7)  
    21. #define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)  
    22.   
    23. #define __initcall(fn)                  device_initcall(fn)  
    24.   
    25. #define module_init(x)                  __initcall(x);  

     

    __define_initcall()宏的作用,其实是告诉编译器在编译的时候,把它所修饰的函数按照已经定义好的顺序放入程序段中。而内核在初始化的过程中,会调用到init/main.c中的一个名为do_initcalls()的函数,在那里,所有的被__define_initcall()修饰的函数会严格地按照标准顺序执行一遍。你懂的,这就是传说中的模块入口。其实模块本没有入口,但是内核的模块多了,就有了模块的入口;写模块时重启系统的次数多了,就有了模块的动态加载。

     

    终于要说到PCI子系统框架的入口了。说起PCI框架入口,不得不佩服Linux内核的模块化布局。有关PCI子系统的源码,实际上只会出现在两个地方,一个是drivers/pci路径下,另一个是arch里的相关路径下。《我是PCI》文章作者已经把i386架构的PCI入口贴了出来,这里抄袭如下:


    文件 函数 入口 内存位置
    arch/i386/pci/acpi.c pci_acpi_init subsys_initcall .initcall4.init
    arch/i386/pci/ common.c
    pcibios_init
    subsys_initcall
    .initcall4.init
    arch/i386/pci/i386.c
    pcibios_assign_resources
    fs_initcall
    .initcall5.init
    arch/i386/pci/ legacy.c pci_legacy_init subsys_initcall .initcall4.init
    drivers/pci/pci-acpi.c acpi_pci_init arch_initcall .initcall3.init
    drivers/pci/pci- driver.c pci_driver_init postcore_initcall .initcall2.init
    drivers/pci/pci- sysfs.c pci_sysfs_init late_initcall .initcall7.init
    drivers/pci/pci.c pci_init device_initcall .initcall6.init
    drivers/pci/probe.c pcibus_class_init postcore_initcall .initcall2.init
    drivers/pci/proc.c pci_proc_init __initcall .initcall6.init
    arch/i386/pci/init.c pci_access_init arch_initcall .initcall3.init
     

    PCI毕竟是Intel抻头搞出来的,所以i386架构自然是先吃到了螃蟹。不过到底还是第一次吃,所以吃得并不风雅,反而显得冗余罗嗦,光是在arch/i386/pci路径下,就有好几个入口函数。

     

    后来者之所以能够居上,通常是既借鉴了前人的经验,又总结了前人的教训。想来Arm对PCI的支持要比X86晚一些,所以Arm架构下PCI子系统的initcall函数要少一些——通常来说,arch/arm/mach-xxx/路径下基本上只有一个用subsys_initcall()修饰的PCI子系统相关的初始化函数。不过PCI子系统在Arm下的初始化逻辑也要复杂一点,因为X86有bios帮它打杂,而Arm的一切都要靠内核自力更生。

     

    《我是PCI》文章里用了很长篇幅介绍如何确定这些initcall函数的顺序。其实只有三点:

    第一,先看它们在内存中的位置,位置靠前的先被执行。所以被postcore_initcall()修饰的函数就一定会在被subsys_initcall()修饰的函数之前执行。

    第二,如果两个函数被放在相同的内存区里,则谁先被编译谁就先执行。而gcc的规则是“The order of files in $(obj-y) is significant.”,因此哪个函数所在的编译单元写在前面,谁就先执行。

    第三,如果两个函数并不在同一个Makefile中,那么哪个Makefile先被调用,哪个函数就先被编译,因此也就会先被执行。

     

    有了这几条判断依据,自然可以给出一个准确的initcall函数执行顺序(不包含i386的函数)。

    1. pcibus_class_init()  
    2. pci_driver_init()  
    3. acpi_pci_init()  
    4. /* mach_spec_pci_init() */  
    5. pci_init()  
    6. pci_proc_init()  
    7. pci_sysfs_init()  

     

    从理论上讲,真正落实了如何与PCI总线通信的初始化工作的函数,就是注释的这一行。只不过,为各种类型的板子开发底层驱动的大侠们会把这个函数起成各种各样的名字(一般来说,叫“xxx_pci_init()”的比较常见)。所以我也只是随便给它起个一目了然的名字而已。

     

    前面说过,Arm不像X86那样有bios给她当助理,所以在X86下可以由bios完成的工作,在Arm下都得由内核源码自己解决。有好奇心的童鞋现在一定想知道到底bios帮X86搞定了些什么事情,既然有好奇心,不妨自己搜一下CONFIG_PCI_BIOS这个宏一探究竟。Arm虽然没有bios,但Arm上的工程师也不想因为搞特殊化而打乱X86上已经正常运转的PCI总线驱动的初始化流程。Arm工程师发挥温州人的精神,自己伪造了一个软bios,于是arch/arm/kernel/bios32.c源码就应运而生。与之相对的头文件是include/asm-arm/mach/pci.h,它包含着简单而重要的bios32.c源码的外部接口:

     

    1. struct pci_sys_data;  
    2. struct pci_bus;  
    3.   
    4. struct hw_pci {  
    5.     struct list_head buses;  
    6.     int     nr_controllers;  
    7.     int     (*setup)(int nr, struct pci_sys_data *);  
    8.     struct pci_bus *(*scan)(int nr, struct pci_sys_data *);  
    9.     void        (*preinit)(void);  
    10.     void        (*postinit)(void);  
    11.     u8      (*swizzle)(struct pci_dev *dev, u8 *pin);  
    12.     int     (*map_irq)(struct pci_dev *dev, u8 slot, u8 pin);  
    13. };  
    14.   
    15. /* 
    16.  * Per-controller structure 
    17.  */  
    18. struct pci_sys_data {  
    19.     struct list_head node;  
    20.     int     busnr;      /* primary bus number           */  
    21.     u64     mem_offset; /* bus->cpu memory mapping offset    */  
    22.     unsigned long   io_offset;  /* bus->cpu IO mapping offset        */  
    23.     struct pci_bus  *bus;       /* PCI bus              */  
    24.     struct resource *resource[3];   /* Primary PCI bus resources        */  
    25.                     /* Bridge swizzling         */  
    26.     u8      (*swizzle)(struct pci_dev *, u8 *);  
    27.                     /* IRQ mapping              */  
    28.     int     (*map_irq)(struct pci_dev *, u8, u8);  
    29.     struct hw_pci   *hw;  
    30. };  
    31.   
    32. /* 
    33.  * This is the standard PCI-PCI bridge swizzling algorithm. 
    34.  */  
    35. u8 pci_std_swizzle(struct pci_dev *dev, u8 *pinp);  
    36.   
    37. /* 
    38.  * Call this with your hw_pci struct to initialise the PCI system. 
    39.  */  
    40. void pci_common_init(struct hw_pci *);  

     

    既然这个头文件是在include/asm-arm路径下,就说明你不能够期待这里的接口也会出现在i386里,若真的有雷同,也绝对纯属巧合,或者只不过是程序员为了提示Arm上的某个接口与i386上的某个接口有着对应关系。所以从现在开始分析代码,就只能捡arch/arm路径下的代码来看了。你可以随便看看arch路径下的任何mach的pci初始化相关的subsys_initcall函数(通常名叫xxx_pci_init()函数),你会发现一个共同的特点就是,它们基本上只是调用了pci_common_init(struct hw_pci *)这个函数。而这个函数又早已经被维护arch体系结构的大侠写好了。调用一个已经被实现的内核接口——难道一切就这样简单地结束了吗?想得美。那不是还有一个参数么。一看参数恍然大悟,这又是一个Linux内核搭台,我们来唱戏的模式。

     

    我们先来看看struct hw_pci这个结构体。.buses是一个链表,链表中的每个节点指向一个PCI子总线的私有数据(也就是struct pci_sys_data结构体)。.nr_controllers指出PCI总线有几个控制器,这通常要看芯片手册才能确定。下面的六个函数指针,都是pci_common_init()所需要的回调函数。pci_common_init()函数无疑是Arm体系下PCI子系统总线驱动初始化的剧本,我们虽然是主角,但也只能在固定的时间地点大背景下自我发挥。下面我们就先熟悉一下这个剧本吧。pci_common_init()函数就在arch/arm/kernel/bios32.c源码中。

     

    1. void __init pci_common_init(struct hw_pci *hw)  
    2. {  
    3.     struct pci_sys_data *sys;  
    4.   
    5.     INIT_LIST_HEAD(&hw->buses);  
    6.   
    7.     if (hw->preinit)  
    8.         hw->preinit();  
    9.     pcibios_init_hw(hw);  
    10.     if (hw->postinit)  
    11.         hw->postinit();  
    12.   
    13.     pci_fixup_irqs(pcibios_swizzle, pcibios_map_irq);  
    14.   
    15.     list_for_each_entry(sys, &hw->buses, node) {  
    16.         struct pci_bus *bus = sys->bus;  
    17.   
    18.         if (!use_firmware) {  
    19.             /* 
    20.              * Size the bridge windows. 
    21.              */  
    22.             pci_bus_size_bridges(bus);  
    23.   
    24.             /* 
    25.              * Assign resources. 
    26.              */  
    27.             pci_bus_assign_resources(bus);  
    28.         }  
    29.   
    30.         /* 
    31.          * Tell drivers about devices found. 
    32.          */  
    33.         pci_bus_add_devices(bus);  
    34.     }  
    35. }  

     

    剧情梗概:

    569行,pcibios_init_hw()函数初始化每一个controller,初始化它并且递归地枚举出它的子总线。在这个过程中会调用到.setup和.scan回调函数。如果需要,可以实现.preinit和.postinit这两个回调函数,它们俩会在pcibios_init_hw()函数执行之前和之后被调用。

    573行,设置设备的irq号。按理说,根据《Linux设备驱动程序》里的介绍,PCI设备的irq号不是已经在配置空间里明确指定了吗,为什么还要多此一举呢?配置空间里的确是指定了,但是考虑到一个PCI设备有可能经过很多个级联的PCI-PCI桥才连接到主桥上,所以它到底使用哪个中断号还真是说不准啊。Linux内核眼睛里是揉不得沙子的,她说不准的事,一定会让我们来说准。.swizzle和.map_irq两个回调函数就是用来干这个的。值得一提的是,pci_fixup_irqs()函数中会使用pci_read_config_xxx()和pci_write_config_xxx()这些PCI子系统的内核接口,这说明在此之前内核必须已经知道如何访问PCI设备的配置空间。显然,聪明的你一定猜到,这件事也是在569行里面做的,至于究竟怎么做,后面会说到。

    575-594行,遍历每个子总线,初始化子总线所在的PCI桥设备,然后把这条子总线上的设备(在569行里已经被枚举出来了)加入一个全局的设备列表中去(同时也会加入到子总线的设备列表中去)。 

     

    代码看到这里,已经敞亮了很多。我们可以确定的是.setup、.preinit、.postinit、.scan、.swizzle和.map_irq这六个回调函数就是Linux内核PCI总线驱动跟芯片手册发生关系的地方!

     

    .setup、.preinit、.postinit这三个函数跟芯片手册密切相关。通常芯片手册会详细介绍PCI总线初始化时读写哪些寄存器以及具体步骤。如果controller只有一个,那么很可能就不需要实现.preinit、.postinit这两个函数了,但.setup函数必须实现。

     

    另一个跟芯片手册密切相关而且必须实现的回调函数是.map_irq函数。它使device、slot和pin三个元素与一个irq中断号关联起来。

     

    至于.swizzle函数,bios32.c源码中已经实现了一个名叫pci_std_swizzle()的通用函数,如果我们拿到的芯片手册上对swizzle这件事情没有特殊说明,就可以直接使用这个现成的函数。如果我们能够确定板子上根本不会有PCI子总线(根本没有PCI-PCI桥这种设备,很多嵌入式开发板上就没有这种设备),那么.swizzle函数就可以省略。

     

    回调函数.scan是我们必须实现的。它的作用是,从主总线开始扫描总线上的PCI设备。一旦发现PCI-PCI桥,就初始化一条子总线,并且继续扫描子总线上的设备。就这样递归地扫描下去,直到形成一棵完整的PCI设备树,包含主总线和所有子总线上挂接的设备。整个过程听起来好像很复杂,事实上也确实如此。不过由于任何芯片的PCI总线的scan都是这个过程,因此Linux内核再次为我们准备好了一个通用函数:

    static struct pci_bus *pci_scan_bus(int bus, struct pci_ops *ops, void *sysdata);

    这个函数只是向我们额外索取一个struct pci_ops结构体,这个结构体只有.read和.write两个回调函数指针,作用是读取和写入PCI设备的配置空间——又是一个典型的内核搭台,我们唱戏的模式。

     

    也就是说,只要我们实现了PCI设备配置空间的访问操作,则.scan的一切就尽在掌握。如果你有兴趣进入pci_scan_bus()简单地追踪一下,就可以知道这个ops参数通过pci_scan_bus_parented()-->pci_create_bus()的途径传递给主总线,然后又通过pci_scan_bus_parented()-->pci_scan_child_bus()-->pci_scan_bridge()-->pci_add_new_bus()-->pci_alloc_child_bus()的途径传递给了子总线。于是,PCI子系统中的所有子总线都知道如何访问设备的配置空间,那么PCI总线驱动初始化之后,PCI总线协议层向上提供的内核接口也就都可以正常运转了。因为这些协议说到底,都是在访问配置空间、I/O端口和I/O内存空间,而后两者又可以被CPU直接访问到。最后,访问PCI设备配置空间的方法,是与芯片手册密切相关的。

     

    说明了.scan的流程和PCI设备树的概念,就可以解释一个名词了。前面好几次提到一个叫做controller的东西,却没有解释它到底是个什么东西。其实这个东西的全名叫做Host Controller,主控制器。这么说吧,每棵PCI设备树都是一棵单根树,而树根就是一个Host Controller。如果一块板子上有两个PCI Host Controller怎么办?很简单,那就有两棵独立的PCI设备树。那么Host Controller究竟是干啥的呢?前面也提到,编写PCI总线驱动,关键就是找到PCI子系统框架中与芯片手册发生关系的地方。OK,我们最终找到了,就是初始化、映射中断和访问配置空间。那么到底这个所谓的“关系”是怎么发生的呢?前面介绍过有关硬件I/O的知识,我们知道与硬件发生关系,靠的不是潜规则,而是读写硬件上的寄存器。你可以这样理解,这个Host Controller,就是一大片控制寄存器,CPU通过对它的读写,来控制PCI总线。所以,如果一块板子上有两个PCI Host Controller,那就有两条PCI主总线,它们俩绝对可以互不关心彼此的存在,此所谓相濡以沫不如相忘于江湖。当然,你也可能希望只用一个subsys_initcall就初始化两条主总线,struct hw_pci允许你这么干,因为它提供了.nr_controllers这个成员变量,你可以把它设置为2,或者更大值,你有多少个controller,你的.setup和.scan回调函数就会被调用多少次。

     

    总结

     

    1,Arm体系结构一般只需要一个被subsys_initcall()修饰的初始化函数。该函数的主体是pci_common_init()函数。

    2,pci_common_init()函数需要一个已被初始化了的struct hw_pci结构体的指针。该结构体包含了PCI子系统的基本信息和回调函数。

    3,在struct hw_pci结构体中,.setup()、.preinit()、.postinit()、.map_irq()等回调函数与芯片手册密切相关。不过.preinit()和.postinit()函数有时可以不实现。

    4,在struct hw_pci结构体中,.swizzle函数已经有通用的实现。.scan函数的主体应为pci_scan_bus()函数。

    5,pci_scan_bus()函数需要一个已被初始化了的struct pci_ops结构体的指针。该结构体包含了PCI设备配置空间的读取和写入函数。

    6,读写配置空间与芯片手册密切相关。

     

    关于PCIE总线驱动

     

    PCIE在协议层完全兼容PCI。从PCIE子系统的协议层来看,新增加的功能(在drivers/pci/pcie路径下)全部通过访问设备的配置空间、I/O端口和I/O内存实现,没有为底层总线驱动添加任何负担。因此PCIE子系统与PCI子系统在总线驱动方面也没有本质不同,都沿用了PCI子系统的初始化过程,并需要程序员实现设备配置空间的读写函数。

    展开全文
  • Linux设备驱动——PCI总线初始化

    千次阅读 2019-07-24 11:16:18
    目录 一、概述 二、初始化分析 2.1pcibus_class_init 2.2pci_driver_init 2.3acpi_pci_init ...2.3.1 基于ACPI的PCI设备枚举过程 ...2.4 pci_arch_init ...2.5pci_slot_init ...pci初始化涉及很多函数,这...

    目录

    一、概述

    二、初始化分析

    2.1 pcibus_class_init

    2.2 pci_driver_init

    2.3 acpi_pci_init

    2.3.1 基于ACPI的PCI设备枚举过程

    2.4 pci_arch_init

    2.5 pci_slot_init

    2.6 pci_subsys_init

    三、参考


    一、概述

    二、初始化分析

    pci初始化涉及很多函数,这里根据下面的启动顺序来分析:

    [root@localhost 3.10.0-862.el7.x86_64]# cat System.map | grep pci | grep initcall
    ffffffff81ef23a0 t __initcall_pcibus_class_init2
    ffffffff81ef23a8 t __initcall_pci_driver_init2
    ffffffff81ef2450 t __initcall_acpi_pci_init3
    ffffffff81ef2478 t __initcall_pci_arch_init3
    ffffffff81ef2580 t __initcall_pci_slot_init4
    ffffffff81ef2678 t __initcall_pci_subsys_init4
    ffffffff81ef27c0 t __initcall_pcibios_assign_resources5
    ffffffff81ef27f8 t __initcall_pci_apply_final_quirks5s
    ffffffff81ef2808 t __initcall_pci_iommu_initrootfs
    ffffffff81ef2d30 t __initcall_pci_proc_init6
    ffffffff81ef2d38 t __initcall_pcie_portdrv_init6
    ffffffff81ef2d48 t __initcall_pcie_pme_service_init6
    ffffffff81ef2d58 t __initcall_pci_hotplug_init6
    ffffffff81ef2d60 t __initcall_pcied_init6
    ffffffff81ef2d68 t __initcall_pci_stub_init6
    ffffffff81ef2e08 t __initcall_platform_pci_module_init6
    ffffffff81ef2e30 t __initcall_serial_pci_driver_init6
    ffffffff81ef2ec8 t __initcall_intel_lpss_pci_driver_init6
    ffffffff81ef2f28 t __initcall_ehci_pci_init6
    ffffffff81ef2f38 t __initcall_ohci_pci_init6
    ffffffff81ef2f50 t __initcall_xhci_pci_init6
    ffffffff81ef3280 t __initcall_pci_resource_alignment_sysfs_init7
    ffffffff81ef3288 t __initcall_pci_sysfs_init7
    ffffffff81ef32d0 t __initcall_pci_mmcfg_late_insert_resources7
    

    2.1 pcibus_class_init

    [driver/pci/probe.c]

    static struct class pcibus_class = {
    	.name		= "pci_bus",
    	.dev_release	= &release_pcibus_dev,
    	.dev_groups	= pcibus_groups,
    };
    
    static int __init pcibus_class_init(void)
    {
    	return class_register(&pcibus_class);
    }

    2.2 pci_driver_init

    [driver/pci/pci-driver.c]

    static int __init pci_driver_init(void)
    {
    	int ret;
    
    	ret = bus_register(&pci_bus_type);
    	if (ret)
    		return ret;
    
    #ifdef CONFIG_PCIEPORTBUS
    	ret = bus_register(&pcie_port_bus_type);
    	if (ret)
    		return ret;
    #endif
    
    	return 0;
    }

    PCI bus的注册,不多说,基本的驱动模型,这里仅列一下pci 的bus_type:

    struct bus_type pci_bus_type = {
    	.name		= "pci",
    	.match		= pci_bus_match,
    	.uevent		= pci_uevent,
    	.probe		= pci_device_probe,
    	.remove		= pci_device_remove,
    	.shutdown	= pci_device_shutdown,
    	.dev_groups	= pci_dev_groups,
    	.bus_groups	= pci_bus_groups,
    	.drv_groups	= pci_drv_groups,
    	.pm		= PCI_PM_OPS_PTR,
    	.num_vf		= pci_bus_num_vf,
    	.force_dma	= true,
    };

    2.3 acpi_pci_init

    这里有一个ACPI(Advanced Configuration and Power Interface),我的理解是ACPI提供了电源、硬件和固件的接口。这里只关注软件角度的ACPI的结构——在屏蔽了硬件细节的同时,提供了一系列系统资源,包括:

    • ACPI寄存器
    • ACPI BIOS
    • ACPI Tables

    2.3.1 基于ACPI的PCI设备枚举过程

    整个ACPI的初始化在:

    Bus.c (drivers\acpi):static int __init acpi_init(void)

    这是一个subsys_initcall(4)级别的初始化

    这里关注和PCI有关的初始化acpi_init->acpi_scan_init->acpi_pci_root_init / acpi_bus_scan

    static struct acpi_scan_handler pci_root_handler = {
    	.ids = root_device_ids,
    	.attach = acpi_pci_root_add,
    	.detach = acpi_pci_root_remove,
    	.hotplug = {
    		.enabled = true,
    		.scan_dependent = acpi_pci_root_scan_dependent,
    	},
    };
    
    void __init acpi_pci_root_init(void)
    {
    	acpi_hest_init();
    	if (acpi_pci_disabled)
    		return;
    
    	pci_acpi_crs_quirks();
    	acpi_scan_add_handler_with_hotplug(&pci_root_handler, "pci_root");
    }
    

    接下来:

    acpi_bus_scan(ACPI_ROOT_OBJECT)
    ->acpi_bus_attach(device);
      ->acpi_scan_attach_handler(device);
    static int acpi_scan_attach_handler(struct acpi_device *device)
    {
    	struct acpi_hardware_id *hwid;
    	int ret = 0;
    
    	list_for_each_entry(hwid, &device->pnp.ids, list) {
    		const struct acpi_device_id *devid;
    		struct acpi_scan_handler *handler;
    
    		handler = acpi_scan_match_handler(hwid->id, &devid);
    		if (handler) {
    			if (!handler->attach) {
    				device->pnp.type.platform_id = 0;
    				continue;
    			}
    			device->handler = handler;
    			ret = handler->attach(device, devid);
    			if (ret > 0)
    				break;
    
    			device->handler = NULL;
    			if (ret < 0)
    				break;
    		}
    	}
    
    	return ret;
    }

    遍历的链表就是上面注册的,可以看到先acpi_scan_match_handler,就是分别通过match和id进行匹配,如果匹配成功了就执行handler的attach函数,这就对应到了上面的acpi_pci_root_add函数,函数比较长,分段来看:

    static int acpi_pci_root_add(struct acpi_device *device,
    			     const struct acpi_device_id *not_used)
    {
    	unsigned long long segment, bus;
    	acpi_status status;
    	int result;
    	struct acpi_pci_root *root;
    	acpi_handle handle = device->handle;
    	int no_aspm = 0;
    	bool hotadd = system_state == SYSTEM_RUNNING;
    
    	root = kzalloc(sizeof(struct acpi_pci_root), GFP_KERNEL);
    	if (!root)
    		return -ENOMEM;
    
    	segment = 0;
    	status = acpi_evaluate_integer(handle, METHOD_NAME__SEG, NULL,
    				       &segment);
        ...
    
    	/* Check _CRS first, then _BBN.  If no _BBN, default to zero. */
    	root->secondary.flags = IORESOURCE_BUS;
    	status = try_get_root_bridge_busnr(handle, &root->secondary);
        ...
    
    	root->device = device;
    	root->segment = segment & 0xFFFF;
    	strcpy(acpi_device_name(device), ACPI_PCI_ROOT_DEVICE_NAME);
    	strcpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS);
    	device->driver_data = root;
    
    	if (hotadd && dmar_device_add(handle)) {
    		result = -ENXIO;
    		goto end;
    	}
    
    	pr_info(PREFIX "%s [%s] (domain %04x %pR)\n",
    	       acpi_device_name(device), acpi_device_bid(device),
    	       root->segment, &root->secondary);
    
    	root->mcfg_addr = acpi_pci_root_get_mcfg_addr(handle);
    
    	negotiate_os_control(root, &no_aspm);
        ...
    }

    上述过程就是分配一个acpi_pci_root,并对其进行初始化,一般情况下仅含有一个HOST桥。

    struct acpi_pci_root {
    	struct acpi_device * device;
    	struct pci_bus *bus;
    	u16 segment;
    	struct resource secondary;	/* downstream bus range */
    
    	u32 osc_support_set;	/* _OSC state of support bits */
    	u32 osc_control_set;	/* _OSC state of control bits */
    	phys_addr_t mcfg_addr;
    };
    • 通过ACPI表获取HOST主桥的segment和bus号

    看一下填充后在虚拟机上的打印信息:

    [    0.212357] ACPI: PCI Root Bridge [PCI0] (domain 0000 [bus 00-7f])

    来看下后半部分:

    /*
    	 * Scan the Root Bridge
    	 * --------------------
    	 * Must do this prior to any attempt to bind the root device, as the
    	 * PCI namespace does not get created until this call is made (and
    	 * thus the root bridge's pci_dev does not exist).
    	 */
    	root->bus = pci_acpi_scan_root(root);
    
    	if (no_aspm)
    		pcie_no_aspm();
    
    	pci_acpi_add_bus_pm_notifier(device);
    	device_set_wakeup_capable(root->bus->bridge, device->wakeup.flags.valid);
    
    	if (hotadd) {
    		pcibios_resource_survey_bus(root->bus);
    		pci_assign_unassigned_root_bus_resources(root->bus);
    		/*
    		 * This is only called for the hotadd case. For the boot-time
    		 * case, we need to wait until after PCI initialization in
    		 * order to deal with IOAPICs mapped in on a PCI BAR.
    		 *
    		 * This is currently x86-specific, because acpi_ioapic_add()
    		 * is an empty function without CONFIG_ACPI_HOTPLUG_IOAPIC.
    		 * And CONFIG_ACPI_HOTPLUG_IOAPIC depends on CONFIG_X86_IO_APIC
    		 * (see drivers/acpi/Kconfig).
    		 */
    		acpi_ioapic_add(root->device->handle);
    	}
    
    	pci_lock_rescan_remove();
    	pci_bus_add_devices(root->bus);
    	pci_unlock_rescan_remove();
    	return 1;
    

    pci_acpi_scan_root枚举PCI设备

    [arch/x86/pci/acpi.c]

    struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
    {
    	int domain = root->segment;
    	int busnum = root->secondary.start;
    	int node = pci_acpi_root_get_node(root);
    	struct pci_bus *bus;
    
    	bus = pci_find_bus(domain, busnum);
    	if (bus) {
            ...
    		memcpy(bus->sysdata, &sd, sizeof(sd));
    	} else {
    		struct pci_root_info *info;
    
    		info = kzalloc_node(sizeof(*info), GFP_KERNEL, node);
    		if (!info)
    			dev_err(&root->device->dev,
    				"pci_bus %04x:%02x: ignored (out of memory)\n",
    				domain, busnum);
    		else {
    			info->sd.domain = domain;
    			info->sd.node = node;
    			info->sd.companion = root->device;
    			bus = acpi_pci_root_create(root, &acpi_pci_root_ops,
    						   &info->common, &info->sd);
    		}
    	}
    
        ...
    
    	return bus;
    }

    通过pci_find_bus查找HOST Bridge对应的segment,bus num有没有被注册,如果注册了就更新一下信息,没有注册则调用acpi_pci_root_create创建,该函数中有两个比较重要,一个是pci_create_root_bus

    struct pci_bus *pci_create_root_bus(struct device *parent, int bus,
    		struct pci_ops *ops, void *sysdata, struct list_head *resources)
    {
    	int error;
    	struct pci_host_bridge *bridge;
    
    	bridge = pci_alloc_host_bridge(0);
    	if (!bridge)
    		return NULL;
    
    	bridge->dev.parent = parent;
    
    	list_splice_init(resources, &bridge->windows);
    	bridge->sysdata = sysdata;
    	bridge->busnr = bus;
    	bridge->ops = ops;
    
    	error = pci_register_host_bridge(bridge);
    	if (error < 0)
    		goto err_out;
    
    	return bridge->bus;
    
    err_out:
    	kfree(bridge);
    	return NULL;
    }
    • 分配一个主桥结构,就是一个device,其parent为NULL,为PCI设备的顶级顶点
    • 接下来注册主桥
    static int pci_register_host_bridge(struct pci_host_bridge *bridge)
    {
        ...
    
    	bus = pci_alloc_bus(NULL);
    
    	bridge->bus = bus;
    
    	/* Temporarily move resources off the list */
    	list_splice_init(&bridge->windows, &resources);
    	bus->sysdata = bridge->sysdata;
    	bus->msi = bridge->msi;
    	bus->ops = bridge->ops;
    	bus->number = bus->busn_res.start = bridge->busnr;  //注意这
    
        ...
    
    	dev_set_name(&bridge->dev, "pci%04x:%02x", pci_domain_nr(bus),
    		     bridge->busnr);
    
    	err = device_register(&bridge->dev);
    
    	bus->bridge = get_device(&bridge->dev);
    
    	bus->dev.class = &pcibus_class;
    	bus->dev.parent = bus->bridge;
    
    	dev_set_name(&bus->dev, "%04x:%02x", pci_domain_nr(bus), bus->number);
    	name = dev_name(&bus->dev);
    
    	err = device_register(&bus->dev);
    
    	pcibios_add_bus(bus);
    
    	/* Create legacy_io and legacy_mem files for this bus */
    	pci_create_legacy_files(bus);
    
    	down_write(&pci_bus_sem);
    	list_add_tail(&bus->node, &pci_root_buses);
    	up_write(&pci_bus_sem);
    
    	return 0;
    
    unregister:
    	put_device(&bridge->dev);
    	device_unregister(&bridge->dev);
    
    free:
    	kfree(bus);
    	return err;
    }

    一个主桥下面新建了一个pci_bus, 其也对应一个设备,这两个设备都注册到系统中,注意他们的名字

    dev_set_name(&bridge->dev, "pci%04x:%02x", pci_domain_nr(bus), bridge->busnr);
    dev_set_name(&bus->dev, "%04x:%02x", pci_domain_nr(bus), bus->number);

    下图显示了注册后的PCI设备分级情况,其他的PCI设备都挂在主桥下

    [root@localhost /]# ls /sys/devices/pci0000:00
    0000:00:00.0  0000:00:07.7  0000:00:15.1  0000:00:15.6  0000:00:16.3  0000:00:17.0  0000:00:17.5  0000:00:18.2  0000:00:18.7
    0000:00:01.0  0000:00:0f.0  0000:00:15.2  0000:00:15.7  0000:00:16.4  0000:00:17.1  0000:00:17.6  0000:00:18.3  firmware_node
    0000:00:07.0  0000:00:10.0  0000:00:15.3  0000:00:16.0  0000:00:16.5  0000:00:17.2  0000:00:17.7  0000:00:18.4  pci_bus
    0000:00:07.1  0000:00:11.0  0000:00:15.4  0000:00:16.1  0000:00:16.6  0000:00:17.3  0000:00:18.0  0000:00:18.5  power
    0000:00:07.3  0000:00:15.0  0000:00:15.5  0000:00:16.2  0000:00:16.7  0000:00:17.4  0000:00:18.1  0000:00:18.6  uevent
     

    我画了个示意图,pci桥本身也是一个设备和其下的pci device一样都挂在同一总线上:

    pci_scan_child_bus包含整个的枚举过程,简单而言就是一个DFS,这个过程最终确定了每级PCI桥的bus范围:[secondary, subordinate]

    pci_scan_child_bus(bus)

        scan each slot on current bus
            scan each function on current slot
                if (function exist)           //vendor id & device id
                    alloc pci_dev
                    setup pci_dev            //header type:0 for PCI agent, 1 for PCI bridge. multi-function or single-function device

         

          max = current bus start busnr
           for each bridge on current bus
             if bridge not scaned                                                     // secondary = subordinate = 0
                 set bridge PCI_PRIMARY_BUS register                 // primary=busnr, secondary=primary+1, subordinate=0xFF
                    max = pci_scan_child_bus(next(bus))                         //    next busnr start with max+1
                    set bridge PCI_SUBORDINATE_BUS register     // subordinate = max  

    下面来看一下函数的实现:

    static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus,
    					      unsigned int available_buses)
    {
    	/* Go find them, Rover! */
    	for (devfn = 0; devfn < 256; devfn += 8) {
    		nr_devs = pci_scan_slot(bus, devfn);
        ...
        
        
        for_each_pci_bridge(dev, bus) {
    	unsigned int buses = 0;
    	if (!hotplug_bridges && normal_bridges == 1) {
            ...
    	} else if (dev->is_hotplug_bridge) {
          ...
    
    	}
    
    	cmax = max;
    	max = pci_scan_bridge_extend(bus, dev, cmax, buses, 1);
    	used_buses += max - cmax;
    }

    pci_scan_slot->pci_scan_single_device

    struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
    {
    	struct pci_dev *dev;
    
    	dev = pci_get_slot(bus, devfn);
    	if (dev) {
    		pci_dev_put(dev);
    		return dev;
    	}
    
    	dev = pci_scan_device(bus, devfn);
    	if (!dev)
    		return NULL;
    
    	pci_device_add(dev, bus);
    
    	return dev;
    }
    • 这个函数就是分配,配置pci_dev,并将其加入设备模型

    接下来就是扫描PCI桥的递归过程了:

    static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
    				  int max, unsigned int available_buses,
    				  int pass)
    {
        ...
    	pci_read_config_dword(dev, PCI_PRIMARY_BUS, &buses);
    	primary = buses & 0xFF;
    	secondary = (buses >> 8) & 0xFF;
    	subordinate = (buses >> 16) & 0xFF;
    
    	if ((secondary || subordinate) && !pcibios_assign_all_busses() &&
    	    !is_cardbus && !broken) {
    		unsigned int cmax;
        ...
    	} else {
            ...
    
    		/* Clear errors */
    		pci_write_config_word(dev, PCI_STATUS, 0xffff);
    
    		child = pci_find_bus(pci_domain_nr(bus), max+1);
    		if (!child) {
    			child = pci_add_new_bus(bus, dev, max+1);
    			if (!child)
    				goto out;
    			pci_bus_insert_busn_res(child, max+1,
    						bus->busn_res.end);
    		}
    		max++;
    		if (available_buses)
    			available_buses--;
    
    		buses = (buses & 0xff000000)
    		      | ((unsigned int)(child->primary)     <<  0)
    		      | ((unsigned int)(child->busn_res.start)   <<  8)
    		      | ((unsigned int)(child->busn_res.end) << 16);
    
    
    		/* We need to blast all three values with a single write */
    		pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses);
    
    		if (!is_cardbus) {
    			child->bridge_ctl = bctl;
    			max = pci_scan_child_bus_extend(child, available_buses);
    		} else {
                ...
    		}
    
    		/* Set subordinate bus number to its real value */
    		pci_bus_update_busn_res_end(child, max);
    		pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, max);
    	}
    
    	...
    }
    

     下面的图展示了一个HOST Bridge 下一个pci dev和一个pci桥的数据结构情况:

    • 一个Host Bridge是一个设备(但不是PCI设备),
    • 对于一个pci agent来说,使用pci_dev来描述
    • 每个PCI桥对应一个pci_bus结构和一个pci_dev

    2.4 pci_arch_init

    [arch/x86/pci/init.c]

    static __init int pci_arch_init(void)
    {
    #ifdef CONFIG_PCI_DIRECT
    	int type = 0;
    
    	type = pci_direct_probe();
    #endif
    
    	if (!(pci_probe & PCI_PROBE_NOEARLY))
    		pci_mmcfg_early_init();
    
    	if (x86_init.pci.arch_init && !x86_init.pci.arch_init())
    		return 0;
    
    #ifdef CONFIG_PCI_BIOS
    	pci_pcbios_init();
    #endif
    	/*
    	 * don't check for raw_pci_ops here because we want pcbios as last
    	 * fallback, yet it's needed to run first to set pcibios_last_bus
    	 * in case legacy PCI probing is used. otherwise detecting peer busses
    	 * fails.
    	 */
    #ifdef CONFIG_PCI_DIRECT
    	pci_direct_init(type);
    #endif
        ...
    
    	return 0;
    }

    CONFIG_PCI_BIOS表示使用BIOS枚举结果

    CONFIG_PCI_DIRECT表示由OS重新枚举

    我的系统上:

    [root@localhost 3.10.0-862.el7.x86_64]# cat .config  | grep CONFIG_PCI_BIOS
    [root@localhost 3.10.0-862.el7.x86_64]# cat .config  | grep CONFIG_PCI_DIRECT
    CONFIG_PCI_DIRECT=y
    

    这里分析CONFIG_PCI_DIRECT

    int __init pci_direct_probe(void)
    {
    	if ((pci_probe & PCI_PROBE_CONF1) == 0)
    		goto type2;
    	if (!request_region(0xCF8, 8, "PCI conf1"))
    		goto type2;
    
    	if (pci_check_type1()) {
    		raw_pci_ops = &pci_direct_conf1;
    		port_cf9_safe = true;
    		return 1;
    	}
        ...
    }

    这里

    unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 |
    				PCI_PROBE_MMCONF;

    所以使用type1,申请了0xCF8, 0xCFC两个I/O port资源,使用这种方式访问PCI配置空间:

    const struct pci_raw_ops pci_direct_conf1 = {
    	.read =		pci_conf1_read,
    	.write =	pci_conf1_write,
    };

    2.5 pci_slot_init

    [driver/pci/slot.c]

    static int pci_slot_init(void)
    {
    	struct kset *pci_bus_kset;
    
    	pci_bus_kset = bus_get_kset(&pci_bus_type);
    	pci_slots_kset = kset_create_and_add("slots", NULL,
    						&pci_bus_kset->kobj);
    	if (!pci_slots_kset) {
    		printk(KERN_ERR "PCI: Slot initialization failure\n");
    		return -ENOMEM;
    	}
    	return 0;
    }

    2.6 pci_subsys_init

    static int __init pci_subsys_init(void)
    {
    	/*
    	 * The init function returns an non zero value when
    	 * pci_legacy_init should be invoked.
    	 */
    	if (x86_init.pci.init()) {
    		if (pci_legacy_init()) {
    			pr_info("PCI: System does not support PCI\n");
    			return -ENODEV;
    		}
    	}
    
    	pcibios_fixup_peer_bridges();
    	x86_init.pci.init_irq();
    	pcibios_init();
    
    	return 0;
    }

    这里init_irq是:

    Boot.c (arch\x86\kernel\acpi):		x86_init.pci.init = pci_acpi_init;
    int __init pci_acpi_init(void)
    {
    	struct pci_dev *dev = NULL;
    
    	if (acpi_noirq)
    		return -ENODEV;
    
    	printk(KERN_INFO "PCI: Using ACPI for IRQ routing\n");
    	acpi_irq_penalty_init();
    	pcibios_enable_irq = acpi_pci_irq_enable;
    	pcibios_disable_irq = acpi_pci_irq_disable;
    	x86_init.pci.init_irq = x86_init_noop;
    
    	if (pci_routeirq) {
    		/*
    		 * PCI IRQ routing is set up by pci_enable_device(), but we
    		 * also do it here in case there are still broken drivers that
    		 * don't use pci_enable_device().
    		 */
    		printk(KERN_INFO "PCI: Routing PCI interrupts for all devices because \"pci=routeirq\" specified\n");
    		for_each_pci_dev(dev)
    			acpi_pci_irq_enable(dev);
    	}
    
    	return 0;
    }
    

    系统中输出“PCI: Using ACPI for IRQ routing”,因此不会执行pci_legacy_init

    三、参考

    【1】https://blog.csdn.net/lizuobin2/article/details/51828594

     

     

     

     

    展开全文
  • 在《Linux设备模型初始化》一节介绍了由__define_initcall引入的几个初始化宏,PCI初始化过程就是由这些宏控制的,下面我们介绍PCI初始化。 首先就是优先级最高的postcore_initcall(pcibus_class_init),pcibus_...

    在《Linux设备模型初始化》一节介绍了由__define_initcall引入的几个初始化宏,PCI初始化过程就是由这些宏控制的,下面我们介绍PCI的初始化。

    首先就是优先级最高的postcore_initcall(pcibus_class_init),pcibus_class_init函数是PCI子系统第一个执行的初始化函数。在sys/class中注册pci_bus目录。

    static int __init pcibus_class_init(void)
    {
    	return class_register(&pcibus_class);
    }
    

    pcibus_class定义如下

    static struct class pcibus_class = {
    	.name		= "pci_bus",
    	.release	= &release_pcibus_dev,
    };
    

    pcibus_class_init就是将pcibus_class注册到/sys/class目录下,创建/sys/class/pci_bus目录

    然后subsys_initcall(pci_legacy_init)决定接下来会调用pci_legacy_init

    static int __init pci_legacy_init(void)
    	if (pcibios_scanned++)
    		return 0;
    	pci_root_bus = pcibios_scan_root(0);
    		//最开始的时候,pci_root_buses为空,所以pci_find_next_bus返回空
    		while ((bus = pci_find_next_bus(bus)) != NULL)
    			///* 如果现有的根总线编号与传参数相等,说明已经扫描过该根总线,退出 */
    			if (bus->number == busnum) {
    				/* Already scanned */
    				return bus;
    			}
    		return pci_scan_bus(busnum, &pci_root_ops, NULL); //busnum=0
    			return pci_scan_bus_parented(NULL, bus, ops, sysdata);
    

    首先增加pcibios_scanned的值,如果增加前的值不为0,就说明已经初始胡过一次了,就直接退出,否则调用pcibios_scan_root从0号pci线去扫描总线,具体的扫描操作是由pci_scan_bus_parented执行的。

    struct pci_bus * __devinit pci_scan_bus_parented(struct device *parent, int bus, struct pci_ops *ops, void *sysdata)
    {
    	/* 为根总线分配一个pci_bus描述符 */
    	b = pci_alloc_bus();
    	dev = kmalloc(sizeof(*dev), GFP_KERNEL);
    
    	b->sysdata = sysdata;
    	b->ops = ops;
    
    	/* pci_find_bus查找成功,说明通过另外一个桥设备可以到达总线,这样放弃扫描 */
    	if (pci_find_bus(pci_domain_nr(b), bus)) 
    		goto err_out;
    		
    	/* 将根总线链接入pci_root_buses链表 */
    	list_add_tail(&b->node, &pci_root_buses);
    
    	/* 初始化设备,并注册到系统。 */
    	memset(dev, 0, sizeof(*dev));
    	dev->parent = parent;
    	dev->release = pci_release_bus_bridge_dev;
    	sprintf(dev->bus_id, "pci%04x:%02x", pci_domain_nr(b), bus);
    	// 如果dev的父设备为空,所以会把dev注册到/dev/devices目录下,否则就会注册到父设备目录下,对于根总线,就是创建/sys/devices/pci0000:00目录
    	device_register(dev);
    	b->bridge = get_device(dev);
    
    	/* 初始化类设备描述符,并添加到系统 */
    	b->class_dev.class = &pcibus_class;
    	sprintf(b->class_dev.class_id, "%04x:%02x", pci_domain_nr(b), bus);
    	// 将类设备注册到/sys/class/pci_bus/目录下,也就是创建/sys/class/pci_bus/0000:00目录
        class_device_register(&b->class_dev);
    	/* 
    		在PCI总线目录下创建属性文件,关于class_device_attr_cpuaffinity的定义
    		参考CLASS_DEVICE_ATTR(cpuaffinity, S_IRUGO, pci_bus_show_cpuaffinity, NULL);
    	*/
    	class_device_create_file(&b->class_dev, &class_device_attr_cpuaffinity);
    
    	/* Create legacy_io and legacy_mem files for this bus */
    	/* 在PCI总线目录下创建二进制属性文件 */
    	pci_create_legacy_files(b);
    
    	// 在类设备目录下创建名为“bridge”的链接文件,链接到对应的桥设备目录下
    	sysfs_create_link(&b->class_dev.kobj, &b->bridge->kobj, "bridge");
    
    	/* 初始化总线与次总线编号 */
    	b->number = b->secondary = bus;
    	/* 设置端口和内存资源,以后分配资源时使用 */
    	b->resource[0] = &ioport_resource;
    	b->resource[1] = &iomem_resource;
    
    	/* 扫描根总线 */
    	b->subordinate = pci_scan_child_bus(b);
    
    	pci_bus_add_devices(b);
    
    	return b;
    

    pci_scan_bus_parented的主要内容如下:
    (1)Linux把总线也看做设备,所以将代表根总线的设备注册到/dev/devices目录下,创建创建/sys/devices/pci0000:00目录。
    (2)设置根总线的桥设备就是当前总线设备。
    (3) 将代表根总线的类设备注册到/sys/class/pci_bus/目录下,也就是创建/sys/class/pci_bus/0000:00目录。
    (4)在类设备目录下创建名为“bridge”的链接文件,链接到对应的桥设备目录下。
    (5)调用pci_scan_child_bus去扫描根总线。
    (6)调用pci_bus_add_devices将扫描到的设备插入到全局PCI设备列表中,并加入到sysfs和procfs中。

    unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus)
    {
    	/**
    	 * 扫描当前PCI总线的所有设备,如果设备是PCI总线,会递归调用本函数。
    	 * 并将设备加入到对应总线的设备队列中。
    	 * 每个PCI总线最多有32个设备,每个设备最多有8个function,因此循环0x100次。
    	 */
    	for (devfn = 0; devfn < 0x100; devfn += 8)
    		pci_scan_slot(bus, devfn);
    
    	/**
    	 * pcibios_fixup_bus的主要目的是为一些PCI设备中的errata提供work-around.
    	 * 同时还会调用一个重要的函数pci_read_bridge_base函数。
    	 * pci_read_bridge_base函数会根据寄存存器的值初始化PCI所管理的地址空间。
    	 */
    	pcibios_fixup_bus(bus);
    	/**
    	 * 调用pci_scan_bridge函数处理当前PCI总线上所挂接的PCI桥。
    	 */
    	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)
    			    /**
    			     * pass为0处理"已经完成枚举"的PCI桥。
    			     * pass为1处理"尚未完成枚举"的PCI桥。
    			     * 对x86来说,BIOS已经预先对总线进行枚举。	
    			     */
    				max = pci_scan_bridge(bus, dev, max, pass);
    		}
    	return max;
    }	
    

    pci_scan_child_bus首先调用pci_scan_slot去扫描挂载到该当前总线的上的设备,然后调用pci_scan_bridge去递归的扫描桥接的其他PCI总线。

    int __devinit pci_scan_slot(struct pci_bus *bus, int devfn)
    	/**
    	 * 每个PCI设备最多可能有8个function。
    	 */
    	for (func = 0; func < 8; func++, devfn++) {
    		struct pci_dev *dev;
    
    		/**
    		 * 配置设备。
    		 * pci_scan_single_device调用pci_scan_device对PCI设备的配置寄存器进行读写操作,侧重于对PCI设备进行硬件层面的初始化操作。
    		 * 调用pci_device_add进行软件方面的初始化。
    		 */
    		dev = pci_scan_single_device(bus, devfn);
    			dev = pci_scan_device(bus, devfn);
    			pci_fixup_device(pci_fixup_header, dev);
    			/* 将设备添加到全局链表,以及总线设备链表 */
    			INIT_LIST_HEAD(&dev->global_list);
    			list_add_tail(&dev->bus_list, &bus->devices);
    		nr++;
    	return nr;
    

    pci_scan_slot会调用pci_scan_single_device去扫描该总线上功能号为devfn的设备,pci_scan_single_device会继续调用pci_scan_device去扫描设备,然后将扫描到的设备利用bus_list添加到bus->devices的链表上。

    所以最终扫描指定功能号的设备是否存在就是由pci_scan_device函数去完成的,接下来我们看一下该函数

    static struct pci_dev * __devinit
    pci_scan_device(struct pci_bus *bus, int devfn)
    {
    	/* 读取总线上给定设备/功能号的配置空间中厂商ID和设备ID */
    	if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
    		return NULL;
    
    	/* 某些主板会返回全0或全1表示设备不存在 */
    	if (l == 0xffffffff || l == 0x00000000 ||
    	    l == 0x0000ffff || l == 0xffff0000)
    		return NULL;
    
    	/**
    	 * 读取VENDOR_ID和HEADER_TYPE寄存器,并对设备进行初始化。
    	 */
    	while (l == 0xffff0001) {/* 0xffff0001状态表示配置重试状态 */
    		/* 读取配置空间失败,退出 */
    		if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
    			return NULL;
    	}
    
    	/* 厂商和设备ID读取成功,这里再读取配置头 */
    	if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type))
    		return NULL;
    
    	/**
    	 * 分配pci_dev并对其进行初始化。
    	 */
    	dev = kmalloc(sizeof(struct pci_dev), GFP_KERNEL);
    	if (!dev)
    		return NULL;
    
    	memset(dev, 0, sizeof(struct pci_dev));
    	dev->bus = bus;
    	dev->sysdata = bus->sysdata;
    	dev->dev.parent = bus->bridge;
    	dev->dev.bus = &pci_bus_type;
    	dev->devfn = devfn;
    	dev->hdr_type = hdr_type & 0x7f;
    	dev->multifunction = !!(hdr_type & 0x80);
    	dev->vendor = l & 0xffff;
    	dev->device = (l >> 16) & 0xffff;
    	dev->cfg_size = pci_cfg_space_size(dev);
    
    	dev->dma_mask = 0xffffffff;
    	/**
    	 * pci_setup_device对设备进行真正的初始化,在该函数里会设备pci设备的名字,例如0000:00:00:0
    	 */
    	if (pci_setup_device(dev) < 0) {
    		kfree(dev);
    		return NULL;
    	}
    	/* 初始化PCI设备的内嵌设备描述符 */
    	device_initialize(&dev->dev);
    		kobj_set_kset_s(dev, devices_subsys);
    	dev->dev.release = pci_release_dev;
    	pci_dev_get(dev);
    
    	pci_name_device(dev);
    
    	dev->dev.dma_mask = &dev->dma_mask;
    	dev->dev.coherent_dma_mask = 0xffffffffull;
    
    	return dev;
    

    PCI总线扫描结束后,即pci_scan_child_bus调用结束后,会执行pci_bus_add_devices函数去注册扫描到的设备。

    void __devinit pci_bus_add_devices(struct pci_bus *bus)
    {
    	// 遍历该总线上的设备
    	list_for_each_entry(dev, &bus->devices, bus_list) {
    		if (!list_empty(&dev->global_list))
    			continue;
    		/**
    		 * 将当前PCI总线上的所有PCI设备相关信息加入到proc和sysfs文件系统中。
    		 */
    		pci_bus_add_device(dev);
    			// PCI设备被注册到/sys/devices/pci0000:00目录下
    			device_add(&dev->dev);
    			list_add_tail(&dev->global_list, &pci_devices);
    			pci_proc_attach_device(dev);
    			pci_create_sysfs_dev_files(dev);
    	}
    
    	list_for_each_entry(dev, &bus->devices, bus_list) {
    
    		if (dev->subordinate && list_empty(&dev->subordinate->node)) {
    			spin_lock(&pci_bus_lock);
    			list_add_tail(&dev->subordinate->node, &dev->bus->children);
    			spin_unlock(&pci_bus_lock);
    			/**
    			 * 递归调用,将PCI总线上的所有PCI子桥加入proc和sysfs文件系统。
    			 */
    			pci_bus_add_devices(dev->subordinate);
    
    			sysfs_create_link(&dev->subordinate->class_dev.kobj, &dev->dev.kobj, "bridge");
    		}
    	}
    
    展开全文
  • Linux PCI总线

    2013-07-06 20:43:38
    PCI总线具有三个非常显著的优点: 1、在计算机和外设间传输数据时具有更好的性能 2、能够尽量独立于具体的平台 3、可以方便地实现即插即用   体系结构: 从结构上,PCI总线是一种不依附于某个具体处理器的局部总线...
  • Linux启动过程中pci总线初始化主要包括2部分,pci控制器的注册和pci设备的枚举,pci总线和其他总线一个很重要的区别就是pci总线的枚举,在启动过程中遍历pci总线树上所有可能的dev func,记录下所有存在的设备的...
  • 基于Linux PCI总线驱动模型的网卡驱动分析 总线概念: 总线是一种传输信号的信道;总线是连接一个或多个导体的电气连线。总线 由电气接口和编程接口组成,我们重点关注编程接口。 PCI(Peripheral ...
  • 慢慢欣赏linux PCI-PCIE初始化总结

    千次阅读 2019-03-24 18:42:54
    PCI-PCIE初始化流程主要分成如下几个部分: 第一部分:解析设备树,获取控制器地址资源,CPU-MEM域地址资源,PCIE域地址资源以及两者的关系,中断资源等信息 #address-cells=<0x2>//父域地址长度 #size_...
  • 慢慢欣赏linux PCI-PCIE初始化学习

    千次阅读 2017-05-30 10:53:57
    powerpc的PCI/PCIE控制器枚举PCI设备实在内核里面实现的,依赖的资源主要是设备树。 设备数提供了PCI/PCIE池的空间基地址/大小,用于分配PCI/PCIE的memory空间; 如果是传统PCI设备,设备树还提供了PCI设备的...
  • PCI设备初始化

    2020-01-07 13:32:21
    访问PCI设备 我们知道CPU和网卡是通过PCI总线相连的,CPU可以直接访问系统内存(虚拟地址),也可以通过映射间接访问总线地址,那CPU怎么访问网卡的存储...系统初始化时扫描PCI总线上的各个PCI设备(包括网卡),...
  • Linux下的PCI总线驱动

    2017-12-07 15:09:31
    1. PCI总线的特点: (1)速度上快,时钟频率提高到33M,而且还为进一步把时钟频率提高到66MHZ、总线带宽提高到64位留下了余地。(2)对于地址的分配和设置,系统软件课自动设置,每块外设通过某种途径告诉系统该...
  • 并且该作者还在《Linux那些事儿 之 我是PCI》系列文章中对X86架构下的PCI总线和设备驱动做了很详细的分析。由于工作需要,我在此基础上分析了ARM体系结构的PCI总线驱动,并把心得和笔记整理一下,或许对日后解析其他...
  • linuxPCI总线驱动

    千次阅读 2016-09-07 15:35:50
    1. PCI总线的特点: (1)速度快,时钟频率提高到33M,而且还为进一步把时钟频率提高到66MHZ、总线带宽提高到64位留下了余地。(2)对于地址的分配和设置,系统软件课自动设置,每块外设通过某种途径告诉系统该外...
  • 文章目录总线概念PCI总线PCI总线体系结构PCI设备寻址PCI寻址配置寄存器 总线概念 总线是一种传输信号的信道;总线是连接一个或多个半导体的电气连线。总线由电气接口和编程接口组成,对于软件设计人员来说,重点关注...
  • linux驱动之PCI总线

    2015-09-22 15:34:00
    PCI总线概述   随着通用处理器和嵌入式技术的迅猛发展,越来越多的电子设备需要由处理器...目前大多数CPU和外部设备都会提供PCI总线的接口,PCI总线已成为计算机系统中一种应用广泛、通用的总线标准[1]。Lin
  • PCI设备初始化(一)

    万次阅读 2018-04-02 15:29:40
    访问PCI设备 我们知道CPU和网卡是通过PCI总线相连的,CPU可以直接访问系统内存(虚拟地址),也可以通过映射间接访问总线地址,那CPU怎么访问网卡的存储空间呢?...系统初始化时扫描PCI总线上的各个PCI设备(包括网...
  • Linux设备驱动之——PCI 总线

    千次阅读 2013-12-24 11:10:17
    PCI总线概述   随着通用处理器和嵌入式技术的迅猛发展,越来越多的电子设备需要由处理器控制。目前大多数CPU和外部设备都会提供PCI总线的接口,PCI总线已成为计算机系统中一种应用广泛、通用的总线标准[1]。...
  • PCI设备初始化(二)

    千次阅读 2018-04-02 16:30:30
    主要数据结构 pci_bus struct pci_bus { struct list_head node; /* node in list of buses */ // 同级PCI总线链表 struct pci_bus *parent; /* parent bus this bridge is on */ // 上级PCI总线 str...
  • Linux内核工程导论——总线Linux PCI

    千次阅读 2015-08-30 19:42:05
    PCI PCI系列总线介绍  首先,PCI是一种总线,...没有设备PCI总线的存在无意义,没有PCI总线的支持,PCI设备无法发挥作用(就得使用其他总线)。  众多设备本身没有从属关系,如果放由他们任意接入系统,就会对可怜的

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,132
精华内容 4,452
关键字:

linuxpci总线初始化

linux 订阅