linux 内核PCI驱动总结记录

news/2025/2/19 15:23:31/

1.  介绍

Peripheral ComponentInterconnect (PCI,外围设备互联)。总线由电气接口、编程接口组成。主要讨论编程接口。最常用的总线,内核支持最好的总线。ISA裸金属总线,电子爱好者偏爱。

2.  PCI的特点

是一种完整的规范,定义计算机计算机不同部分之间的通信。

 

获取、访问PCI设备。

 

对比ISA总线三个目标:

比ISA有更好的性能。

尽可能平台无关的。

简化系统添加、删除外设。

 

支持32位、64位数据总线。

对驱动编写者最相关的是接口板的自动发现。PCI设备是无跳线设备,在系统引导阶段自动配置。包括设备的配置信息等一些工作都是自动完成的,不需要任何的探测。之后,驱动编写者就能够访问设备的配置信息,以便初始化设备。

 

3.  PCI寻址

每个PCI外设由bus:device.function一个16位地址标识。bus(8位)、device(5位)、function(3位)。单个总共256个总线、每个总线最多32个设备、每个设备最多8个功能(比如声音功能)。linux为了扩展总线数量,提供domain(16位)。系统把PCI设备抽象为pci_dev结构,因此不需要访问这些二进制地址。

 

当前的工作站一般都配有2个以上的pci总线。不同PCI总线之间通过PCI桥连接(一个特殊的PCI设备)。PCI系统的整体布局是一个树状的结构。每个总线都连接上一级总线,一直到根总线0.

可以使用lspci命令查看当前系统的pci。或者在文件系统/proc/pci和/proc/bus/pci中。


外设板电路响应三种地址空间:内存、IO、配置空间。前两种地址空间在同一个PCI总线上是共享的。配置空间是物理寻址的,每次只对一个槽寻址。

内存和IO空间通常通过inb、readb等方式访问。配置空间需要通过特殊的内核函数访问配置寄存器。每个PCI槽有4个中断引脚,每个设备功能使用其中的一个。

         1个PCI总线使用32位的地址总线用于IO寻址(4G),32位的地址总线(现在设备有的支持64位)用于内存寻址。在系统启动阶段,固件初始化PCI硬件的时候,把每个区域映射到不同的地址。驱动程序不需要探测,而从配置空间读取映射的地址。

         对于每个设备功能,PCI配置空间由256字节组成(PCIE的是64KB),并且配置空间的布局是标准的。配置空间的4个字节(哪4个字节?)标识唯一的功能ID。

 

4.  引导阶段

主板上的固件(比如BIOS),读写PCI设备中的寄存器,访问配置空间。

系统引导阶段,linux内核为每个PCI地址区域申请安全的处理器地址。后续驱动可以从/sys/bus/pci/devices/*目录中读取映射的地址。

$ tree /sys/bus/pci/devices/0000:00:10.0
/sys/bus/pci/devices/0000:00:10.0
|-- class
|-- config
|-- detach_state
|-- device
|-- irq
|-- power
|  `-- state
|-- resource
|-- subsystem_device
|-- subsystem_vendor
`-- vendor

其中,config包含配置信息,resource包含分配给该设备的内存资源。irq包含了该PCI设备的中断号。


5.  配置寄存器和初始化

所有的PCI设备至少包含256字节的配置地址空间(PCIE是64KB)。其中前64字节是标志的。


PCI配置寄存器包括可选和必需两部分,必需的部分声明功能和其他字段是否可用。

PCI寄存器是小端字节序。

  • vendorID

全局性、全球性。16位标识。比如intel的0x8086.

  • deviceID

厂商定义的16位标识。通常使用vendorID+deviceID 32位标识一个设备。驱动根据该32位标识,定位到一个设备。

  • class

16位的标识,高8位标识基本类(group)。比如,以太网、令牌环网属于网络group,串行、并行属于通信group。一些驱动支持多种相同类型的设备,驱动可以根据类型区分支持的设备。

  • subsystem vendorID
  • subsystem deviceID

subsystem类型的标识,用于进一步识别设备。当一个芯片是连接到本地板载上的通用芯片时,它可能有多用用途。驱动使用subsystem标识,识别具体连接的设备。

内核标识设备ID的结构是:

struct pci_device_id {

         __u32vendor, device;               /* Vendorand device ID or PCI_ANY_ID*/

         __u32subvendor, subdevice;  /* Subsystem ID'sor PCI_ANY_ID */

         __u32class, class_mask; /*(class,subclass,prog-if) triplet */

         kernel_ulong_tdriver_data;   /* Data private to thedriver */

};

6.  MODULE_DEVICE_TABLE

通过把pci_device_id结构导出到用户空间中,使热插拔和模块加载系统知道什么模块对应什么设备。

例如:

MODULE_DEVICE_TABLE(pci, i810_ids);

具体的实现是:

extern const typeof(name)__mod_##type##__##name##_device_table              \

 __attribute__ ((unused, alias(__stringify(name))))

 

其中pci是模块名,i810_ids是pci_device_id变量名。MODULE_DEVICE_TABLE宏把例如i810_ids的变量名,起一个__mod_pci_device_table结构的别名。模块编译之后,在对应的模块ELF文件中会有相应的__mod_pci_device_table结构符号。在内核构建时,depmod搜索所有模块的类似__mod_pci_device_table结构的符号,从中解析出type和name,并取出pci_device_id数据导出到/lib/modules/KERNEL_VERSION/modules.pcimap文件中。之后内核所有模块支持的设备和模块的名字可在该文件中找到。当内核告知热插拔系统,发现一个新的设备时,热插拔系统根据modules.pcimap文件找到对应的驱动。

 

注:模块的概念。PCI是一个模块。

 

7.  PCI驱动注册

为了正确的注册到内核,PCI驱动必须创建一个结构:

struct pci_driver {

    struct list_head node;

    const char *name;

    const struct pci_device_id *id_table;  /* must be non-NULL for probe to be called*/

    int  (*probe) (struct pci_dev*dev,const struct pci_device_id*id);  /* New device inserted */

    void (*remove)(struct pci_dev*dev);  /* Device removed (NULL if not a hot-plugcapable driver) */

    int  (*suspend)(struct pci_dev*dev, pm_message_tstate); /* Device suspended */

    int  (*suspend_late)(struct pci_dev*dev, pm_message_tstate);

    int  (*resume_early)(struct pci_dev*dev);

    int  (*resume)(struct pci_dev*dev);                  /* Device woken up */

    void (*shutdown)(struct pci_dev*dev);

    int (*sriov_configure)(struct pci_dev*dev,int num_vfs);/* PF pdev */

    const struct pci_error_handlers *err_handler;

    struct device_driver    driver;

    struct pci_dynids dynids;

};

包括一些回调函数和描述PCI驱动与PCI核心对应的变量。

其中一些区域需要PCI驱动注意。

const char*name;

const struct pci_device_id*id_table;

int  (*probe) (struct pci_dev*dev,const struct pci_device_id*id);

void (*remove)(struct pci_dev*dev);

int  (*suspend)(struct pci_dev*dev, pm_message_tstate);

int  (*resume)(struct pci_dev*dev);

总的来说,一个PCI驱动结构只需要4个区域被初始化。

static struct pci_driver pci_driver = {

.name ="pci_skel",

.id_table = ids,

.probe = probe,

.remove =remove,
};

通常在模块初始化代码中,注册pci驱动。比如:

static int __init pci_skel_init(void)

{

return pci_register_driver(&pci_driver);

}

2.6更新后,在支持PCI热插拔、或CardBus系统上,PCI设备可以出现在任何时刻。

在系统运行时刻,通过写值到驱动的new_id中,指定驱动支持的新设备(原内核未认知的设备)。

当PCI驱动被卸载时,需要调用pci_unregister_driver。例如:

static void __exit pci_skel_exit(void)

{

pci_unregister_driver(&pci_driver);

}

8.  使能PCI设备

在PCI的探测函数中,在驱动访问PCI设备的任何资源之前(IO区域或资源),驱动程序必须调用函数:

int pci_enable_device(struct pci_dev *dev);

用来激活设备。

9.  访问配置空间

在驱动监测到设备之后,通常需要访问三个区域:内存、IO区域、配置空间。

访问配置空间尤其重要,因为需要通过配置空间找到内存区域映射和IO区域映射。

linux提供了一套访问配置空间的标准接口。

对驱动而言,可通过8、16、32位数据传输访问配置空间。相关的函数定义在<linux/
pci.h>
:中:

int pci_read_config_byte(struct pci_dev*dev, int where, u8 *val);
int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);

dev:访问设备的逻辑表示

where:要读取位置在配置空间中的位移

*val:读取的值

不需要考虑字节序,会自动转换。

 

int pci_write_config_byte(struct pci_dev*dev, int where, u8 val);
int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);

dev:写入设备的逻辑表示

where:要写入位置在配置空间中的位移

*val:写入的值

不需要考虑字节序,会自动转换。

 

在驱动未获得pci_dev时,可使用如上函数读写配置空间

int pci_bus_read_config_byte (structpci_bus *bus, unsigned int devfn, int
where, u8 *val);
int pci_bus_read_config_word (struct pci_bus *bus, unsigned int devfn, int
where, u16 *val);
int pci_bus_read_config_dword (struct pci_bus *bus, unsigned int devfn, int
where, u32 *val);

 

int pci_bus_write_config_byte (structpci_bus *bus, unsigned int devfn, int
where, u8 val);
int pci_bus_write_config_word (struct pci_bus *bus, unsigned int devfn, int
where, u16 val);
int pci_bus_write_config_dword (struct pci_bus *bus, unsigned int devfn, int
where, u32 val);

 

访问配置空间的最好方式是通过pci_read_系列函数,例如:

static unsigned charskel_get_revision(struct pci_dev *dev)
{

u8 revision;

pci_read_config_byte(dev,PCI_REVISION_ID, &revision);

return revision;

}

10.  访问IO和内存空间

一个PCI设备最多可实现6个IO地址区域。每个区域可以是内存或者IO地址。大多数设备在内存区域实现IO寄存器,这也是一个明智的方法。需要注意的是,和常规内存不同,IO寄存器不应该由CPU缓存,因为每次访问都可能边缘效应。为了取消这个默认设置,内存区域实现的IO寄存器,可以通过在其配置寄存器中设置“memory-is-prefetchable”。若是可预取的,CPU可缓存其内容并进行各种优化。若不是可预取的,则不能优化,因为每次访问都有边际效应,就行IO端口一样。

 

接口板(PCI设备)通过6个32位的寄存器(PCI_BASE_ADDRESS_0到PCI_BASE_ADDRESS_5)声明区域的大小和位置。所以最多实现6IO地址区域。因为PCI的IO地址空间是32位的,所以不管是内存或IO区域使用相同的配置接口是有道理的。如果设备的数据总线是64位的,那么每个区域使用两个连续的32位寄存器实现。一个PCI设备既提供32位区域又提供64位区域是有可能的。

 

内核已经把PCI设备的IO区域信息映射进了通用资源管理中。所以,不需要通过访问配置寄存器来获取IO区域信息。可以通过访问/sys/bus/pci/devices/*/resource的内容获取。但首选的方法是通过下列函数获取。

unsigned long pci_resource_start(structpci_dev *dev, int bar);

unsigned long pci_resource_end(structpci_dev *dev, int bar);

bar:指定要获取的区域(0到5)

 

unsigned long pci_resource_flags(structpci_dev *dev, int bar);

资源flag用来定义某个区域的特性。其中几个重要的标志如下:

IORESOURCE_IO

IORESOURCE_MEM

IORESOURCE_PREFETCH

IORESOURCE_READONLY(PCI资源从不设置该标志)

 

驱动程序不需要访问配置寄存器去获得这些资源信息,因为系统已经构建了这些资源信息,驱动直接使用pci_resource_系列函数获取即可。

11.  PCI中断

在linux系统启动时,已经为PCI设备分配了一个唯一的中断号,位于配置空间的第60寄存器(PCI_INTERRUPT_LINE),一个字节长度,最多256个中断号。第61个寄存器(PCI_INTERRUPT_PIN)说明PCI设备是否支持中断,如果不支持,则为0,如果支持,非0.

如果是非0的,PCI_INTERRUPT_PIN的值是中断引脚的编号()。

驱动通过下面代码读取中断号,以便使用:

result = pci_read_config_byte(dev,PCI_INTERRUPT_LINE, &myirq);

if (result) {

/* deal witherror */

}

 

12.  总结归纳

linux中:

先module初始化,内含pci初始化(对于pci设备而言)

http://www.ppmy.cn/news/274642.html

相关文章

linux驱动程序之PCI驱动程序设计

文章目录 PCI驱动描述PCI驱动注册使能PCI设备获取PCI配置寄存器基地址中断部分代码 PCI驱动描述 在linux内核中&#xff0c;PCI驱动使用 struct pic_driver 结构来描述&#xff1a; struct pci_driver {/*以上还有很多成员*///id_table 中包含了PCI设备的相关信息const struc…

PCI驱动编程

一、字符设备和块设备 Linux抽象了对硬件的处理&#xff0c;所有的硬件设备都可以像普通文件一样来看待&#xff1a;它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作&#xff0c;而驱动程序的主要任务也就是要实现这些系统调用函数。Li…

Linux设备驱动程序学习(十)——PCI驱动程序

前面介绍的是最底层的硬件控制&#xff0c;这部分将介绍高级总线架构的一些综述&#xff0c;总线由电气接口和编程接口够成。下面将重点介绍PCI总线的编程接口以及对应的内核函数。 PCI&#xff08;外围设备互联&#xff09;接口 PCI总线是当今普遍使用在桌面以及更大型计算机…

dpdk pci驱动探测

上一篇文章已经介绍了pci设备的背景知识&#xff0c; 现在我们来分析下pci设备是如何探测到支持的驱动&#xff0c;进而与驱动进行关联&#xff1b;pci与驱动的解除绑定&#xff1b;pci设备与uio设备的关联。 一、pci驱动注册 网卡驱动的注册使用了一种奇技淫巧的方法&#xf…

virtio_net 与 virtio-pci 驱动关联浅析

virtio-pci 驱动映射 virtio common_cfg resource 空间 virtio-pci 获取 comon_cfg 物理空间的函数调用如下&#xff1a; mdev->common vp_modern_map_capability(mdev, common,sizeof(struct virtio_pci_common_cfg), 4,0, sizeof(struct virtio_pci_common_cfg),NULL, …

VFIO代码分析(2)VFIO-PCI驱动1

VFIO-PCI驱动作为PCIE驱动&#xff0c;在原来的PCIE驱动卸载后&#xff0c;加载VFIO-PCI驱动&#xff0c;与内核之前扫描到的PCIE设备匹配&#xff0c;触发vfio_pci_probe()&#xff0c;进行本驱动的初始化。该驱动提供了用户态访问PCIE设备的配置空间/BAR空间/中断等资源接口&…

linux设备模型:pci驱动程序注册过程

一个具有正常探测功能的pci驱动程序应具有基本的pci_driver结构实现&#xff0c;如&#xff1a; static struct pci_driver driver_ops {.name "driver_name", // 驱动名称.id_table pci_ids, // 驱动ids结构.probe pci_probe, // 驱动探测函数.remove pci_remo…

Linux PCI驱动编写

这几天将以前为内核2.6写的驱动移植到了4.1下&#xff0c;在这里记录一下过程&#xff0c;以及从头整理一下linux下pci驱动的编写方法。   以前的驱动没有使用到linux下的probe方法&#xff0c;在4.1内核下成功编译后&#xff0c;一直无法进入中断&#xff0c;因此参考ch36x的…