一、pci/pcie体系架构概述

  pci和pcie详细介绍参考《PCI Express体系结构导读》和https://www.cnblogs.com/YYFaGe/p/15408417.html

1.1 PCI总系体系结构

  x86架构的PCI总线结构如下所示:

  

  在soc架构的芯片中,HOST主桥则集成在soc中,soc中包含有PCI总线控制器,这个控制器就可以认为是HOST主桥。下图是freescale的PowerPC处理器,型号为MPC8584,采用soc架构,如下所示:

  

  pci体系结构主要有HOST主桥、PCI总线、PCI设备,下面介绍这些组件的作用。

  HOST主桥:在PCI体系中,有两类桥,一类是PCI主桥,另外一类是PCI桥设备。HOST主桥的基本功能是完成存储器域到pci域的转换和管理pci设备的配置空间。不同处理器对HOST主桥的处理并不相同,x86体系中,HOST主桥集成在北桥中,而soc体系的芯片将HOST主桥集成在soc中。

  PCI总线:pci设备(包过桥设备)都挂载在pci总线下面,每条pci总线可挂载32个pci设备,每个pci设备最多可包含8个功能设备,最多有256个功能设备。如将wifi和蓝牙集成在一起的pci设备就包含两个功能设备。

  PCI设备:PCI总线有三种类型设备,分别是PCI主设备、PCI从设备、PCI桥设备。PCI从设备只能被动接受来自主桥和其它PCI从设备的读写请求,而PIC主设备可以通过总线仲裁获得总线使用权。PCI设备即可作为主设备也可作为从设备,在PCI总线中的主设备和从设备统称为PCI Agent,如声卡、网卡都输入PCI Agent。PCI桥设备是特殊的PCI设备,用来扩展PCI总线,PCI桥存在使得系统大规模互联称为可能。

1.2 PCIE总线体系结构

   pcie体系结构如图1.2.1所示:

  

  

 RC是pcie总线的上游端口,只有在x86系统中才存在真正的RC,图1.2.1的RC包含了一个HOST主桥,还有pci-pci桥,在soc架构中RC一般就是pcie控制器。RC的功能与pci总线中的HOST主桥功能类似,都是完成存储器域到pci域的转换。

二、linux pci/pcie驱动概述

  从第一节中可以看出pci的体系架构和pcie的体系架构相差很大,pci使用的是总线结构连接,用桥设备扩展pci总线。pcie使用的是端到端方式连接,用switch扩展pcie总线。pci中有host主桥,而pcie中是rc。虽然pci和pcie的体系结构有差别,但对于驱动代码来说处理是一致的,linux也用同一套驱动代码来处理pci和pcie的驱动,下面就用pci驱动来指pci和pcie驱动。     linux pci驱动中重要的结构体:

  pci_dev:代表pci设备,包过桥设备

  pci_host_bridge:HOST主桥,在soc架构芯片pcie控制器中的host 主桥

  pci_bus:pci总线,pcie体系总没有pci总线的概念,但还是会用到这个。pcie是端到端连接的,pci_bus表示pcie虚拟的总线,每条总线只有一个pci设备。

  在soc中,pcie控制器就是rc,而rc中包含HOST主桥,HOST主桥下有pci根总线即pci总线0,根总线下有pci桥,pci桥下有pci总线1。HOST主桥、根总线、pci桥,、pci总线1都包含在pcie控制器中,如下图所示:

  

 

在pcie控制器初始化成功后会在sysfs中生成以下结构,以hi3559为例:

  pcie控制器(由设备树读入):/sys/devices/platform/soc/0.pcie

  pcie控制器中的HOST 主桥:/sys/devices/platform/soc/0.pcie/pci0000:00

  pcie控制器中的pci桥:/sys/devices/platform/soc/0.pcie/pci0000:00/0000:00:00.0,pci设备的命名方法:pci域号:pci总线号:pci设备号.pci功能号,具体代码见:drivers/pci/probe.c中的pci_setup_device 函数。

  pcie控制器中的pci总线0:/sys/devices/platform/soc/0.pcie/pci0000:00/pci_bus/0000:00,pci总线命名方法:pci域号:pci总线号

  pci总线1:/sys/devices/platform/soc/0.pcie/pci0000:00/0000:00:00.0/pci_bus/0000:01

  pcie设备:/sys/devices/platform/soc/0.pcie/pci0000:00/0000:00:00.0/0000:01:00.0

 在/sys/bus/pci/devices目录中会有pci桥设备和pci设备的软连接:

  

  0000:00:00.0是pcie控制器中的pci桥,0000:01:00.0是外接的pcie设备

在/proc/bus/pci/ 目录中也会有pci设备的目录

  

struct pci_host_bridge {
    struct device dev;      //主桥也是个device
    struct pci_bus *bus;        /* 主桥下面的总线 */
    struct list_head windows;    /* resource_entry */
    void (*release_fn)(struct pci_host_bridge *);
    void *release_data;
    unsigned int ignore_reset_delay:1;    /* for entire hierarchy */
    /* Resource alignment requirements */
    resource_size_t (*align_resource)(struct pci_dev *dev,
            const struct resource *res,
            resource_size_t start,
            resource_size_t size,
            resource_size_t align);
};

 

struct pci_dev {
/* 总线设备链表元素bus_list:每一个pci_dev结构除了链接到全局设备链表中外,还会通过这个成员连接到
其所属PCI总线的设备链表中。每一条PCI总线都维护一条它自己的设备链表视图,以便描述所有连接在该
PCI总线上的设备,其表头由PCI总线的pci_bus结构中的 devices成员所描述t*/
struct list_head bus_list;
/* 总线指针bus:指向这个PCI设备所在的PCI总线的pci_bus结构。因此,对于桥设备而言,bus指针将指向
桥设备的主总线(primary bus),也即指向桥设备所在的PCI总线*/
struct pci_bus *bus;
/* 指针subordinate:指向这个PCI设备所桥接的下级总线。这个指针成员仅对桥设备才有意义,而对于一般
的非桥PCI设备而言,该指针成员总是为NULL*/
struct pci_bus *subordinate;
/* 无类型指针sysdata:指向一片特定于系统的扩展数据*/
void *sysdata;
/* 指针procent:指向该PCI设备在/proc文件系统中对应的目录项*/
struct proc_dir_entry *procent;
/* devfn:这个PCI设备的设备功能号,也成为PCI逻辑设备号(0-255)。其中bit[7:3]是物理设备号(取值
范围0-31),bit[2:0]是功能号(取值范围0-7)。 */
unsigned int devfn;
/* vendor:这是一个16无符号整数,表示PCI设备的厂商ID*/
unsigned short vendor;
/*device:这是一个16无符号整数,表示PCI设备的设备ID */
unsigned short device;
/* subsystem_vendor:这是一个16无符号整数,表示PCI设备的子系统厂商ID*/
unsigned short subsystem_vendor;
/* subsystem_device:这是一个16无符号整数,表示PCI设备的子系统设备ID。*/
unsigned short subsystem_device;
/* class:32位的无符号整数,表示该PCI设备的类别,其中,bit[7:0]为编程接口,bit[15:8]为子类
别代码,bit [23:16]为基类别代码,bit[31:24]无意义。显然,class成员的低3字节刚好对应与PCI配
置空间中的类代码*/
unsigned int class;
/* hdr_type:8位符号整数,表示PCI配置空间头部的类型。其中,bit[7]=1表示这是一个多功能设备,
bit[7]=0表示这是一个单功能设备。Bit[6:0]则表示PCI配置空间头部的布局类型,值00h表示这是一
个一般PCI设备的配置空间头部,值01h表示这是一个PCI-to-PCI桥的配置空间头部,值02h表示CardBus桥
的配置空间头部*/
u8 hdr_type;
/* rom_base_reg:8位无符号整数,表示PCI配置空间中的ROM基地址寄存器在PCI配置空间中的位置。
ROM基地址寄存器在不同类型的PCI配置空间头部的位置是不一样的,对于type 0的配置空间布局,ROM基
地址寄存器的起始位置是30h,而对于PCI-to-PCI桥所用的type 1配置空间布局,ROM基地址寄存器的起始
位置是38h*/
u8 rom_base_reg;
/* 指针driver:指向这个PCI设备所对应的驱动程序定义的pci_driver结构。每一个pci设备驱动程序都必须定
义它自己的pci_driver结构来描述它自己。*/
struct pci_driver *driver;
/*dma_mask:用于DMA的总线地址掩码,一般来说,这个成员的值是0xffffffff。数据类型dma_addr_t定义在
include/asm/types.h中,在x86平台上,dma_addr_t类型就是u32类型*/
u64 dma_mask;
/* 当前操作状态 */
pci_power_t  current_state;
/* 通用的设备接口*/
 struct device dev;
/* 无符号的整数irq:表示这个PCI设备通过哪根IRQ输入线产生中断,一般为0-15之间的某个值 */
unsigned int irq;
/*表示该设备可能用到的资源,包括:I/O断口区域、设备内存地址区域以及扩展ROM地址区域。*/
struct resource resource[DEVICE_COUNT_RESOURCE];
/* 配置空间的大小 */
int cfg_size;
/* 透明 PCI 桥 */
unsigned int transparent:1;
/* 多功能设备*/
unsigned int multifunction:1;
/* 设备是主设备*/
unsigned int is_busmaster:1;
/* 设备不使用msi*/
unsigned int no_msi:1;
/* 配置空间访问形式用块的形式 */
unsigned int block_ucfg_access:1;
/* 在挂起时保存配置空间*/
u32 saved_config_space[16];
/* sysfs ROM入口的属性描述*/
struct bin_attribute *rom_attr;
/* 能显示rom 属性*/
int rom_attr_enabled;
/* 资源的sysfs文件*/
struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE];

};

三、hi3559 pcie驱动加载过程 

hi3559 的pcie控制器设备树节点为 \Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100.dtsi

  

这个pcie的节点将被系统以platform_device形式加载进内核,对应的sys下的目录为:/sys/devices/platform/soc/0.pcie

hi3559 pcie控制器驱动程序入口为: \Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\drivers\pci\hipcie\pcie.c

  

下面分析一下pcie_init的流程:

  pcie_init

    –>pci_create_root_bus           //创建代表pcie控制器的pci_host_bridge和第一条pci总线 

     –>pci_scan_child_bus         //枚举pci总线上的所有设备    

        –>pci_scan_slot         //扫描PCI 多功能设备,如果发现是单功能设备,不再继续扫描,如果发现是多功能设备,则进行8次扫描

          –>pci_scan_single_device    //扫描单个设备

            –>pci_scan_device    //通过能否读取到设备ID号来判断是否有pci设备

 

 pci_create_root_bus函数主要功能是创建pci_host_bridge和第一条pci总线,这个函数将会在/sys/devices/platform/soc/0.pcie目录下创建pci0000:00 目录,这个目录就代表pcie控制器总的HOST主桥。还会在/sys/devices/platform/soc/0.pcie/pci0000:00/pci_bus目录下创建0000:00目录,这个目录就代码第一条pci总线。

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;      //pci主桥,这里代表pcie控制器中的HOST主桥
    struct pci_bus *b, *b2;
    struct resource_entry *window, *n;
    struct resource *res;
    resource_size_t offset;
    char bus_addr[64];
    char *fmt;

    b = pci_alloc_bus(NULL);
    if (!b)
        return NULL;

    b->sysdata = sysdata;
    b->ops = ops;
    b->number = b->busn_res.start = bus;
#ifdef CONFIG_PCI_DOMAINS_GENERIC
    b->domain_nr = pci_bus_find_domain_nr(b, parent);
#endif
    b2 = pci_find_bus(pci_domain_nr(b), bus);
    if (b2) {
        /* If we already got to this bus through a different bridge, ignore it */
        dev_dbg(&b2->dev, "bus already known\n");
        goto err_out;
    }

    bridge = pci_alloc_host_bridge(b);
    if (!bridge)
        goto err_out;

    bridge->dev.parent = parent;    //父设备是设备树节点都对应的dev
    bridge->dev.release = pci_release_host_bridge_dev;
    dev_set_name(&bridge->dev, "pci%04x:%02x", pci_domain_nr(b), bus);
    error = pcibios_root_bridge_prepare(bridge);
    if (error) {
        kfree(bridge);
        goto err_out;
    }

    error = device_register(&bridge->dev);    //会在/sys/devices/platform/soc/0.pcie目录下创建pci0000:00 目录
    if (error) {
        put_device(&bridge->dev);
        goto err_out;
    }
    b->bridge = get_device(&bridge->dev);
    device_enable_async_suspend(b->bridge);
    pci_set_bus_of_node(b);
    pci_set_bus_msi_domain(b);

    if (!parent)
        set_dev_node(b->bridge, pcibus_to_node(b));

    b->dev.class = &pcibus_class;
    b->dev.parent = b->bridge;
    dev_set_name(&b->dev, "%04x:%02x", pci_domain_nr(b), bus);
    error = device_register(&b->dev);    //会在/sys/devices/platform/soc/0.pcie/pci0000:00/pci_bus目录下创建0000:00目录
    if (error)
        goto class_dev_reg_err;

    pcibios_add_bus(b);

    /* Create legacy_io and legacy_mem files for this bus */
    pci_create_legacy_files(b);

    if (parent)
        dev_info(parent, "PCI host bridge to bus %s\n", dev_name(&b->dev));
    else
        printk(KERN_INFO "PCI host bridge to bus %s\n", dev_name(&b->dev));

    /* Add initial resources to the bus */
    resource_list_for_each_entry_safe(window, n, resources) {
        list_move_tail(&window->node, &bridge->windows);
        res = window->res;
        offset = window->offset;
        if (res->flags & IORESOURCE_BUS)
            pci_bus_insert_busn_res(b, bus, res->end);
        else
            pci_bus_add_resource(b, res, 0);
        if (offset) {
            if (resource_type(res) == IORESOURCE_IO)
                fmt = " (bus address [%#06llx-%#06llx])";
            else
                fmt = " (bus address [%#010llx-%#010llx])";
            snprintf(bus_addr, sizeof(bus_addr), fmt,
                 (unsigned long long) (res->start - offset),
                 (unsigned long long) (res->end - offset));
        } else
            bus_addr[0] = '\0';
        dev_info(&b->dev, "root bus resource %pR%s\n", res, bus_addr);
    }

    down_write(&pci_bus_sem);
    list_add_tail(&b->node, &pci_root_buses);
    up_write(&pci_bus_sem);

    return b;

class_dev_reg_err:
    put_device(&bridge->dev);
    device_unregister(&bridge->dev);
err_out:
    kfree(b);
    return NULL;
}

pci_scan_child_bus函数主要功能是枚举pci总线上的所有设备,如果是桥设备则进行递归扫描。每条pci总线最多支持32个插槽,可以接32个pci设备,每个pci设备最多可以有8个功能设备。比如将蓝牙和wifi集成在一个pci设备中,则这个pci设备有两个功能设备。

unsigned int pci_scan_child_bus(struct pci_bus *bus)
{
    unsigned int devfn, pass, max = bus->busn_res.start;
    struct pci_dev *dev;

    dev_dbg(&bus->dev, "scanning bus\n");

    /* Go find them, Rover! */
    for (devfn = 0; devfn < 0x100; devfn += 8)  //扫描总线下32个插槽即32个pci设备,每个pci设备支持8个功能设备,所有devfun以8递增,pci总线用id寻址的方式方式访问pci设备的配置空间,id=总线号+设备号+功能号。devfn的0到3位表示功能号,4到7位表示设备号
        pci_scan_slot(bus, devfn);          //扫描pcie插

    /* Reserve buses for SR-IOV capability. */
    max += pci_iov_bus_range(bus);

    /*
     * After performing arch-dependent fixup of the bus, look behind
     * all PCI-to-PCI bridges on this bus.
     */
    if (!bus->is_added) {
        dev_dbg(&bus->dev, "fixups for bus\n");
        pcibios_fixup_bus(bus);
        bus->is_added = 1;
    }

    for (pass = 0; pass < 2; pass++)
        list_for_each_entry(dev, &bus->devices, bus_list) {
            if (pci_is_bridge(dev))                  //如果是桥设备则扫描桥设备下的所有设备
                max = pci_scan_bridge(bus, dev, max, pass);
        }

    /*
     * Make sure a hotplug bridge has at least the minimum requested
     * number of buses.
     */
    if (bus->self && bus->self->is_hotplug_bridge && pci_hotplug_bus_size) {
        if (max - bus->busn_res.start < pci_hotplug_bus_size - 1)
            max = bus->busn_res.start + pci_hotplug_bus_size - 1;
    }

    /*
     * We've scanned the bus and so we know all about what's on
     * the other side of any bridges that may be on this bus plus
     * any devices.
     *
     * Return how far we've got finding sub-buses.
     */
    dev_dbg(&bus->dev, "bus scan returning with max=%02x\n", max);
    return max;
}
pci_scan_slot函数主要功能是扫描多功能设备
int pci_scan_slot(struct pci_bus *bus, int devfn)
{
    unsigned fn, nr = 0;
    struct pci_dev *dev;

    if (only_one_child(bus) && (devfn > 0))
        return 0; /* Already scanned the entire slot */

    dev = pci_scan_single_device(bus, devfn);  //扫描单个功能设备
    if (!dev)
        return 0;
    if (!dev->is_added)
        nr++;

    for (fn = next_fn(bus, dev, 0); fn > 0; fn = next_fn(bus, dev, fn)) {  //如果pci设备是多功能设备,则全部扫出
        dev = pci_scan_single_device(bus, devfn + fn);
        if (dev) {
            if (!dev->is_added)
                nr++;
            dev->multifunction = 1;
        }
    }

    /* only one slot has pcie device */
    if (bus->self && nr)
        pcie_aspm_init_link_state(bus->self);

    return nr;
}

 

pci_scan_device函数主要功能是通过能否读取到设备ID号判断是否有pci设备,如果有则获取pci设备的配置空间。配置空间详情见https://www.cnblogs.com/YYFaGe/p/15408417.html 3.1节。
static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
{
    struct pci_dev *dev;
    u32 l;

    if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))  //获取pci设备的id号,总共32位,包过16位的Device ID(r)和16位的Vendor ID(r)
        return NULL;

    dev = pci_alloc_dev(bus);      //如果能获取到pci设备的ip号,则创建一个pci设备
    if (!dev)
        return NULL;

    dev->devfn = devfn;          
    dev->vendor = l & 0xffff;        //Vendor ID
    dev->device = (l >> 16) & 0xffff;    //Device ID

    pci_set_of_node(dev);

    if (pci_setup_device(dev)) {    //获取pci 设备的配置空间来填充pci_dev
        pci_bus_put(dev->bus);
        kfree(dev);
        return NULL;
    }

    return dev;
}

四、pci总线相关的启动函数

参考链接https://blog.csdn.net/qq_39376747/article/details/112723350

pci总线初始化有很多个启动入口,它们分别是:

kernel/drivers/pci/proc.c:在/proc/bus/ 目录下创建pci目录和/proc/bus/pci/device 文件

 

kernel/drivers/pci/probe.c:在/sys/class/ 目录下创建 pci_bus目录

kernel/drivers/pci/pci-driver.c:在/bus/ 目录下注册pci总线

/arch/x86/pci/init.c:该函数内部通过条件编译来确定内部函数的执行,我们可以通过.config文件得到源码定义的所有宏定义,经查得出CONFIG_PCI_DIRECT定义而CONFIG_PCI_BIOS未定义,该函数的功能是设置整个PCI配置空间的读写方法。

 

 

 

版权声明:本文为YYFaGe原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/YYFaGe/p/16184844.html