linux pci/pcie驱动
一、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配置空间的读写方法。