谁有bt365体育在线网址-mobile.365-588-36563688

36563688

linux pci/pcie驱动

2026-06-21 04:56:08 作者 admin 阅读 2304
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设备的目录,使用lspci命令可以看pci设备:

00.00.0是pci桥设备,01.00.0是pci wifi设备

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总线,driver/pci/probe.c

-->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配置空间的读写方法。

相关文章