Linux PCI总线驱动-1

Linux PCI总线驱动-1

    • 一、PCI总线介绍
      • 1. PCI介绍
      • 2. PCI接口
      • 3. PCI-X介绍
      • 4. PCI-E介绍
        • 4.1 下图从上到下依次是PCIEX16,X1,X4
        • 4.2 PCI-E各版本的传输速度
        • 4.3 PCI-E不同传输通道数设备的金手指数和长度
        • 4.4 三种接口的传输速度比较
    • 二、pci设备基础知识
      • 2.1 内核中的pci活动
        • 2.1.1 枚举
        • 2.1.2 配置
        • 2.1.3 pci初始化流程
        • 2.1.4 关于入口
    • 三、PCI总线与配置空间
      • 3.1 PCIE所有数据结构
      • 3.2 PCI 总线描述:pci_bus
      • 3.3 pci设备描述符:pci_dev
      • 3.4 PCI配置空间
      • 3.5 PCI DMA 相关API
      • 3.6 PCI 设备驱动其他常用 API
    • 四、PCI设备驱动结构
      • 4.1 pci_driver结构体
      • 4.2 PCI设备驱动的组成
      • 4.3 驱动实例
    • 参考

一、PCI总线介绍

1. PCI介绍

外设互联标准(或称个人电脑接口,Personal Computer Interface),实际应用中简称PCI(Peripheral Component Interconnect),是一种连接电子计算机主板和外部设备的总线标准。一般PCI设备可分为两种形式:直接布放在主板上的集成电路,在PCI规范中称作”平面设备“,另一种是安装在插槽中的扩展卡。

PCI bus常见于现代的个人计算机中,已经取代ISA和VESA局部总线,成为标准扩展总线。PCI总线最终将被PCI Express或者更先进的技术取代。

PCI是并行基于总线控制,所有设备共同分享单向32bit/64bit并行总线(半双工)。如果有多个PCI设备共用总线,他们将共享总的传输速率。

PCI标准于1993年7月被Intel发明,每个接口最多连接1个设备,可以工作在33MHz和66MHz(工作时电压33MHz为5V,66MHz为3V),2004年被PCI Express替代。

PCI插槽可以插很多类型的卡,包括网卡、声卡、调制解调器(内置Modem)、电视卡、磁盘控制器(RAID卡)、视频采集卡、IDE接口卡、IEEE1394卡、USB卡和串行等,原本也可以插显卡,但很快PCI的带宽不足以支持显卡的性能。PCI插槽通过插不同的卡几乎可以实现所有的外接功能。后来显卡使用AGP插槽,现在已被PCI Express插槽取代。

PCI接口分32bit和64bit两种。早期的PCI(PCI2.1标准)工作在32bit、33.33MHz、5V下,最大传输速度133MB/s(33.33MHz * 32bit / 8bit/byte = 133MB/s),后来又出现了PCI2.2 2.3等标准。现在PCI有32bit和64bit两种,32bit的一般用在PC上,64bit的一般应用于服务器上,64bit的要比32bit的长一些。32bit和64bit都有5v和3.3v电压两种,5v电压的是PCI2.1标准工作在33MHz,3.3v电压的是PCI2.2标准工作在66MHz的时钟频率上。频率或者位宽增加都会增加传输速率,实现也是通过这两个指标来实现的。

在PC上,64位PCI还没有成为主流。原因在于制造64位和66MHzPCI主板的难度很大。首先,使用64位PCI插槽需要64位南桥芯片组支持,该南桥控制器必须可以正确处理64位的数据。Intel和AMD都有64位的南桥可提供给主板厂商,但是价格很高;其次是因为66MzPCI槽对主板配套元件要求极高,且需要特殊的布线设计。这就是66MHzPCI技术一直停留在服务器领域的原因。
Linux PCI总线驱动-1_第1张图片

2. PCI接口

PCI有几种不同的接口样图:现在生产的多为通用模式的以防插错。还有64bit统一比32bit的宽出右边缺口的部分。

PCI 32bit的网卡都可以查到PCI 64bit插槽上使用。3.3v的插到3.3v的上,5v的插到5v的上。

有一些PCI网卡同时支持32位和64位标准的兼容网卡,这类网卡相比前面介绍的纯64位PCI网卡来说,在外观上也有一个明显的区别,那就是它又多了一个缺口,有3个缺口(下图右边第三个图)。

Linux PCI总线驱动-1_第2张图片

3. PCI-X介绍

PCI-X是传统PCI总线的改版,有更高的带宽。PCI-X插槽类型基本于64bit的PCI插槽相同。

PCI-X于1998年被IBM、HP和Compaq发明,64bit位宽,传输方式并发,2004年被新出的PCI Express替代。PCI-X多用于服务器上,不过也是昙花一现。

4. PCI-E介绍

PCI Express是INTEL提出的新一代的总线接口,PCI Express采用了目前业内流行的点对点串行连接,比起PCI以及更早期的计算机总线的共享并行架构,每个设备都有自己的专用连接,不需要向整个总线请求带宽,而且可以把数据传输率提高到一个很高的频率,达到PCI所不能提供的高带宽。相对于传统PCI总线在单一时间周期内只能实现单向传输,PCI Express的双单工连接能提供更高的传输速率和质量。PCI-E插槽是可以向下兼容的,比如PCI-E 16X插槽可以插8X、4X、1X的卡。现在的服务器一般都会提供多个8X、4X的接口,已取代以前的PCI-X接口。

PCI是总线结构,而PCIe是点对点结构。一个典型的PCIe系统框图如下:

Linux PCI总线驱动-1_第3张图片
一个典型的结构是一个root port和一个endpoint直接组成一个点对点连接对,而Switch可以同时连接几个endpoint。一个root port和一个endpoint对就需要一个单独的PCI bus。而PCI是在同一个总线上的设备共享同一个bus number。过去主板上的PCI插槽都公用一个PCI bus,而现在的PCIe插槽却连在芯片组不同的root port上。

4.1 下图从上到下依次是PCIEX16,X1,X4

Linux PCI总线驱动-1_第4张图片

4.2 PCI-E各版本的传输速度

Linux PCI总线驱动-1_第5张图片

4.3 PCI-E不同传输通道数设备的金手指数和长度

Linux PCI总线驱动-1_第6张图片

4.4 三种接口的传输速度比较

Linux PCI总线驱动-1_第7张图片

二、pci设备基础知识

Linux PCI设备驱动实际包括Linux PCI设备驱动和设备本身驱动两部分。PCI(Periheral Component Interconnect)有三种地址空间:PCI I/O空间、PCI内存地址空间和PCI配置空间。其中,PCI I/O空间和PCI内存地址空间由设备驱动程序使用,而PCI配置空间由Linux PCI初始化代码使用,用于配置PCI设备,比如中断号以及I/O或内存基地址。

2.1 内核中的pci活动

Linux内核主要就做了对PCI设备的枚举和配置;在Linux内核初始化时完成的。
对于PCI总线,有一个叫做PCI桥的设备用来将父总线与子总线连接。作为一种特殊的PCI设备,PCI桥主要包括以下三种:

  • HOST/PCI桥: 用于连接 CPU 与 PCI 根总线,第 1 个根总线的编号为 0。在 PC 中,内存控制器也通常被集成到 Host/PCI 桥设备芯片中,因此,Host/PCI 桥通常也被称为“北桥芯片组(North Bridge Chipset)”。
  • PCI/ISA桥:用于连接旧的 ISA 总线。通常,PCI 中的类似 i8359A 中断控制器这样的设备也会被集成到 PCI/ISA 桥设备中,因此,PCI/ISA 桥通常也被称为“南桥芯片组(SouthBridge Chipset)”。
  • PCI-to-PCI桥: 用于连接 PCI 主总线(primary bus)与次总线(secondary bus)。PCI 桥所处的 PCI 总线称为“主总线”(即次总线的父总线),桥设备所连接的 PCI 总线称为“次总线”(即主总线的子总线)。

2.1.1 枚举

从Host/PCI桥开始进行探测和扫描,逐个“枚举”连接在第一条PCI总线上的所有设备并记录在案。如果其中的某个设备是PCI-PCI桥,则又进一步再探测和扫描连在这个桥上的次级PCI总线。就这样递归下去,直到穷尽系统中的所有PCI设备。其结果,是在内存中建立起一棵代表着这些PCI总线和设备的PCI树。

每个PCI设备(包括PCI桥设备)都由一个pci_dev结构体来表示,而每条PCI总线则由pci_bus结构来表示。

2.1.2 配置

PCI设备中一般都带有一些RAM和ROM 空间,通常的控制/状态寄存器和数据寄存器也往往以RAM区间的形式出现,而这些区间的地址在设备内部一般都是从0开始编址的,那么当总线上挂接了多个设备时,对这些空间的访问就会产生冲突。所以,这些地址都要先映射到系统总线上,再进一步映射到内核的虚拟地址空间。

配置就是通过对PCI配置空间的寄存器进行操作从而完成地址的映射。

2.1.3 pci初始化流程

pci这块代码在两个地方,一个是driver/pci另一个是arch/×××/pci中。

2.1.4 关于入口

pci系统中入口函数处理subsys_initcall之外,还有arch_initcall,postcore_initcall等等。

pci系统的初始化工作有内核来完成,在drivers/pci/probe.c文件中,调用postcore_initcall(pcibus_class_init);函数,在sys/class/下创建一个pci_bus目录。

drivers/pci/pci-driver.c文件,postcore_initcall(pci_driver_init); 注册pci总线,并在/sys/bus/下创建了一个pci目录

arch/xxx/pci/init.c文件中

arch_initcall(pci_arch_init);体系架构相关,对于64 bit x86来说使用CONFIG_PCI_DIRECT的方式进行访问PCI配置空间。在内核编译时候可以指定。

drivers/pci/slot.c文件中,subsys_initcall(pci_slot_init); 创建/sys/bus/slots文件。

三、PCI总线与配置空间

3.1 PCIE所有数据结构

Linux PCI总线驱动-1_第8张图片

1)struct pci_host_bridge 主桥数据结构,用来描述连接CPU和PCIE设备的主桥,该结构有Root bus0成员,它也是一个设备,需要注册。

2)struct pci_dev该结构体用来描述PCI设备,包括EP和pci to pci 桥等设备.

Bus:用来关联该设备在哪条bus下,

deviceversion: 枚举出来的设备配置空间的重要东西都会保存该数据结构。

3)struct pci_bus 该结构体用来描述PCI的总线。

struct list_head devices: 设备链表用于关联该bus下的所有设备。

children: PCI桥可以使当前总线得到扩展,当前总线上有几个PCI桥,那么当前总线就会拥有几个子总线,子总线会连接到父总线的children链表中。

ops: 当前总线访问总线上设备配置空间的 readwrite 方法。

4)struct pci_slot 用来描述bus下的物理插槽

5)struct pci_bus_type 这个是pci的总线模型,和之前的pci_bus是两码事, pci_driverpci_dev就是通过该总线关联起来.

match: 通过pci_dev 对应的字段 vendorsubvendordevicesubdevice 来匹 配对应的pci_driver

所以大致的流程是内核启动之后,通过PCI_BUS之间的关系枚举出来所有设备,并为每一个设备创建一个PCI_DEV的数据结构,将配置空间的信息填入PCI_DEV之后,注册到总线PCI_BUS_TYPE. 然后我们编写的pci_driver中,填写了此驱动适配的设备号版本号等,在一个pci_device_id的数据结构中,同样也注册到pci_bus_type的总线中,如果信息一致则执行驱动probe函数。

3.2 PCI 总线描述:pci_bus

在 Linux 系统中,PCI 总线用 pci_bus 来描述,这个结构体记录了本 PCI 总线的信息以及本PCI 总线的父总线、子总线、桥设备信息等。
./include/linux/pci.h

struct pci_bus {
	/* 链表元素node:对于PCI根总线而言,其pci_bus结构通过node成员链接到本节一开始所述的根总线链表中,根总线链表
	的表头由一个list_head类型的全局变量pci_root_buses所描述。而对于非根pci总线,其pci_bus结构通过node成员链接
	到其父总线的子总线链表children中*/
	struct list_head node;
	/*parent指针:指向该pci总线的父总线,即pci桥所在的那条总线*/
	struct pci_bus *parent;
	/*children指针:描述了这条PCI总线的子总线链表的表头。这条PCI总线的所有子总线都通过上述的node链表元素链接成一
	条子总线链表,而该链表的表头就由父总线的children指针所描述*/
	struct list_head children;
	/* list of devices on this bus */
	struct list_head devices;
	/* devices链表头:描述了这条PCI总线的逻辑设备链表的表头。除了链接在全局PCI设备链表中之外,每一个PCI逻辑设备也
	通过其 pci_dev结构中的bus_list成员链入其所在PCI总线的局部设备链表中,而这个局部的总线设备链表的表头就由
	pci_bus结构中的 devices成员所描述*/
	struct pci_dev *self;
	/* 资源指针数组:指向应路由到这条pci总线的地址空间资源,通常是指向对应桥设备的pci_dev结构中的资源数组resource[10:7]*/
	struct resource *resource[PCI_BUS_NUM_RESOURCES];
	/* 指针ops:指向一个pci_ops结构,表示这条pci总线所使用的配置空间访问函数*/
	struct pci_ops *ops;
	/* 无类型指针sysdata:指向系统特定的扩展数据*/
	void *sysdata;
	/* 指针procdir:指向该PCI总线在/proc文件系统中对应的目录项*/
	struct proc_dir_entry *procdir;
	/* number:这条PCI总线的总线编号(bus number),取值范围0-255 */
	 unsigned char number;
	/* primary:表示引出这条PCI总线的“桥设备的主总线”(也即桥设备所在的PCI总线)编号,取值范围0-255*/
	unsigned char primary;/* secondary:表示引出这条PCI总线的桥设备的次总线号,因此secondary成员总是等于number成员的值。取值范围0-255*/
	unsigned char secondary;
	/* subordinate:这条PCI总线的下属PCI总线(Subordinate pci bus)的总线编号最大值,它应该等于引出这条PCI总线的桥设备的subordinate值*/
	unsigned char subordinate;
	/* name[48]:这条PCI总线的名字字符串*/
	char name[48];
	unsigned short  bridge_ctl;
	unsigned short  pad2;
	struct device *bridge;
	struct device class_dev;
	struct bin_attribute *legacy_io;
	struct bin_attribute *legacy_mem;
};

假定一个如图 所示的 PCI 总线系统,根总线 0 上有一个 PCI 桥,它引出子总线 Bus 1,Bus 1 上又有一个 PCI 桥引出 Bus 2。

Linux PCI总线驱动-1_第9张图片

在上图中,Bus 0 总线的 pci_bus 结构体中的 numberprimarysecondary 都应该为 0,因为它是通过 Host/PCI 桥引出的根总线;Bus 1 总线的 pci_bus 结构体中的 number secondary 都为 1,但是它的 primary 应该为 0;Bus 2 总线的 pci_bus 结构体中的 numbersecondary 都应该为 2,而其 primary 则应该等于 1。这 3 条总线的 subordinate 值都应该等于 2。

系统中当前存在的所有根总线都通过其 pci_bus 结构体中的 node 成员链接成一条全局的根总线链表,其表头由 list 类型的全局变量 pci_root_buses 来描述。而根总线下面的所有下级总线则都通过其 pci_bus 结构体中的 node 成员链接到其父总线的 children 链表中。这样,通过这两种 PCI总线链表,Linux 内核就将所有的 pci_bus 结构体以一种倒置树的方式组织起来。假定对于如图 所示的多根 PCI 总线体系结构:
Linux PCI总线驱动-1_第10张图片
它所对应的总线链表结构将如图所示。
Linux PCI总线驱动-1_第11张图片

3.3 pci设备描述符:pci_dev

Linux 系统中,所有种类的 PCI 设备都可以用 pci_dev 结构体来描述,由于一个 PCI 接口卡上可能包含多个功能模块,每个功能被当作一个独立的逻辑设备,因此,每一个 PCI 功能,即PCI 逻辑设备都惟一地对应一个 pci_dev 设备描述符:

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; /* 这个 PCI 设备所在的 PCI 总线的 pci_bus 结构 */
/* 指针subordinate:指向这个PCI设备所桥接的下级总线。这个指针成员仅对桥设备才有意义,而对于一般
的非桥PCI设备而言,该指针成员总是为NULL*/
struct pci_bus *subordinate; /* 指向这个 PCI 设备所桥接的下级总线 */
/* 无类型指针sysdata:指向一片特定于系统的扩展数据*/
void *sysdata; /* 指向一片特定于系统的扩展数据 */
/* 指针procent:指向该PCI设备在/proc文件系统中对应的目录项*/
struct proc_dir_entry *procent; /* 该 PCI 设备在/proc/bus/pci 中对应的目录项 */
struct pci_slot *slot; /* 设备位于的物理插槽 */
/* devfn:这个PCI设备的设备功能号,也成为PCI逻辑设备号(0-255)。其中bit[7:3]是物理设备号(取值
范围0-31),bit[2:0]是功能号(取值范围0-7)。 */
unsigned int devfn; /* 这个 PCI 设备的设备功能号 */
/* 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];

};

PS:

同一条总线上的设备通过struct list_head bus_list组成这条总线的设备链表;
表头则是pci_bus结构体中的struct list_head devices成员。

3.4 PCI配置空间

对于PCI配置空间的讲解专门放到单独一篇文章中进行说明。

  • PCI 有 3 种地址空间:PCI I/O 空间、PCI 内存地址空间和 PCI 配置空间。
  • CPU 可以访问所有的地址空间,其中 PCI I/O 空间和 PCI 内存地址空间由设备驱动程序使用,PCI 配置空间由内核中PCI初始化时使用。
  • PCI 支持自动配置设备,与旧的 ISA 驱动程序不一样,PCI 驱动程序不需要实现复杂的检测逻辑。
  • 启动时,BIOS 或内核自身会遍历 PCI 总线并分配资源,如中断优先级和 I/O 基址。设备驱动程序通过 PCI 配置空间来找到资源分配情况。
  • PCI 规范定义了 3 种类型的 PCI 配置空间头部,其中 type 0 用于标准的 PCI 设备,type 1 用
    于 PCI 桥,type 2 用于 PCI CardBus 桥。

不管是哪一种类型的配置空间头部,其前 16 个字节的格式都是相同的,./include/uapi/linux/pci_regs.h文件中定义了 PCI 配置空间头部。

/*
 * Under PCI, each device has 256 bytes of configuration address space,
 * of which the first 64 bytes are standardized as follows:
 */
#define PCI_STD_HEADER_SIZEOF	64
#define PCI_VENDOR_ID		0x00	/* 16 bits *//* 16 位厂商 ID */
#define PCI_DEVICE_ID		0x02	/* 16 bits *//* 16 位设备 ID */

/* PCI 命令寄存器 */
#define PCI_COMMAND		0x04	/* 16 bits */
#define  PCI_COMMAND_IO		0x1	/* Enable response in I/O space *//* 使能设备响应对 I/O 空间的访问 */
#define  PCI_COMMAND_MEMORY	0x2	/* Enable response in Memory space *//* 使能设备响应对存储空间的访问 */
#define  PCI_COMMAND_MASTER	0x4	/* Enable bus mastering *//* 使能总线主模式 */
#define  PCI_COMMAND_SPECIAL	0x8	/* Enable response to special cycles *//* 使能设备响应特殊周期 */
#define  PCI_COMMAND_INVALIDATE	0x10	/* Use memory write and invalidate */ /*使用 PCI 内存写无效事务 */
#define  PCI_COMMAND_VGA_PALETTE 0x20	/* Enable palette snooping *//* 使能 VGA 调色板侦测 */
#define  PCI_COMMAND_PARITY	0x40	/* Enable parity checking *//* 使能奇偶校验 */
#define  PCI_COMMAND_WAIT	0x80	/* Enable address/data stepping *//* 使能地址/数据步进 */
#define  PCI_COMMAND_SERR	0x100	/* Enable SERR *//* 使能 SERR */
#define  PCI_COMMAND_FAST_BACK	0x200	/* Enable back-to-back writes *//* 使能背靠背写 */
#define  PCI_COMMAND_INTX_DISABLE 0x400 /* INTx Emulation Disable */ /* 禁止中断竞争*/

/* PCI 状态寄存器 */
#define PCI_STATUS		0x06	/* 16 bits */
#define  PCI_STATUS_INTERRUPT	0x08	/* Interrupt status */
#define  PCI_STATUS_CAP_LIST	0x10	/* Support Capability List *//* 支持的能力列表 */
#define  PCI_STATUS_66MHZ	0x20	/* Support 66 MHz PCI 2.1 bus *//* 支持 PCI 2.1 66MHz */
#define  PCI_STATUS_UDF		0x40	/* Support User Definable Features [obsolete] *//* 支持用户定义的特征 */
#define  PCI_STATUS_FAST_BACK	0x80	/* Accept fast-back to back *//* 快速背靠背操作 */
#define  PCI_STATUS_PARITY	0x100	/* Detected parity error *//* 侦测到奇偶校验错 */
#define  PCI_STATUS_DEVSEL_MASK	0x600	/* DEVSEL timing *//* DEVSEL 定时 */
#define  PCI_STATUS_DEVSEL_FAST		0x000
#define  PCI_STATUS_DEVSEL_MEDIUM	0x200
#define  PCI_STATUS_DEVSEL_SLOW		0x400
#define  PCI_STATUS_SIG_TARGET_ABORT	0x800 /* Set on target abort */ /* 目标设备异常 */
#define  PCI_STATUS_REC_TARGET_ABORT	0x1000 /* Master ack of " */ /* 主设备确认 */
#define  PCI_STATUS_REC_MASTER_ABORT	0x2000 /* Set on master abort *//* 主设备异常 */
#define  PCI_STATUS_SIG_SYSTEM_ERROR	0x4000 /* Set when we drive SERR */ /* 驱动了 SERR */
#define  PCI_STATUS_DETECTED_PARITY	0x8000 /* Set on parity error */ /* 奇偶校验错 */


/* 类代码寄存器和修订版本寄存器 */
#define PCI_CLASS_REVISION	0x08	/* High 24 bits are class, low 8 revision *//* 高 24 位为类码,低 8 位为修订版本 */
#define PCI_REVISION_ID		0x08	/* Revision ID *//* 修订号 */
#define PCI_CLASS_PROG		0x09	/* Reg. Level Programming Interface *//* 编程接口 */
#define PCI_CLASS_DEVICE	0x0a	/* Device class *//* 设备类 */

#define PCI_CACHE_LINE_SIZE	0x0c	/* 8 bits */
#define PCI_LATENCY_TIMER	0x0d	/* 8 bits */

/* PCI 头类型 */
#define PCI_HEADER_TYPE		0x0e	/* 8 bits *//* 8 位头类型 */
#define  PCI_HEADER_TYPE_NORMAL		0
#define  PCI_HEADER_TYPE_BRIDGE		1
#define  PCI_HEADER_TYPE_CARDBUS	2

 /* 表示配置空间头部中的 Built-In Self-Test 寄存器在配置空间中的字节地址索引 */
#define PCI_BIST		0x0f	/* 8 bits */
#define  PCI_BIST_CODE_MASK	0x0f	/* Return result *//* 完成代码 */
#define  PCI_BIST_START		0x40	/* 1 to start BIST, 2 secs or less *//* 用于启动 BIST*/
#define  PCI_BIST_CAPABLE	0x80	/* 1 if BIST capable *//* 设备是否支持 BIST? */

Linux PCI总线驱动-1_第12张图片
紧接着前 16 个字节的寄存器为基地址寄存器 0~基地址寄存器 5,其中,PCI_BASE_ADDRESS_2~5 仅对标准 PCI 设备的 0 类型配置空间头部有意义,而 PCI_BASE_ADDRESS_0~1 则适用于0 类型和 1 类型配置空间头部。

基地址寄存器中的 bit[0]的值决定了这个基地址寄存器所指定的地址范围是在 I/O 空间还是在内存映射空间内进行译码。当基地址寄存器所指定的地址范围位于内存映射空间中时,其 bit[2∶1]表示内存地址的类型,bit[3]表示内存范围是否为可预取(Prefetchable)的内存。

1 类型配置空间头部适用于 PCI-PCI 桥设备,其基地址寄存器 0 与基地址寄存器 1 可以用来指定桥设备本身可能要用到的地址范围,而后 40 个字节(0x180x39)则被用来配置桥设备的主、次编号以及地址过滤窗口等信息。

pci_bus 结构体中的 pci_ops 类型成员指针 ops 指向该 PCI 总线所使用的配置空间访问操作的具体实现。

struct pci_ops {
	int (*add_bus)(struct pci_bus *bus);
	void (*remove_bus)(struct pci_bus *bus);
	void __iomem *(*map_bus)(struct pci_bus *bus, unsigned int devfn, int where);
	int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);/* 读配置空间 */
	int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val); /* 写配置空间 */
};

read()write()成员函数中的 size 表示访问的是字节、2 字节还是 4 字节,对于 write()而言,val 是要写入的值;对于 read()而言,val 是要返回的读取到的值的指针。通过 bus 参数的成员以及devfn 可以定位相应 PCI 总线上相应 PCI 逻辑设备的配置空间。在 Linux 设备驱动中,可用如下一组函数来访问配置空间:

int pci_bus_read_config_byte(struct pci_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(struct pci_bus *bus, unsigned int devfn, int where, u8 val);
int pci_bus_write_config_word(struct pci_bus *bus, unsigned int devf, int where, u16 val);
int pci_bus_write_config_dword(struct pci_bus *bus, unsigned int devfn, int where, u32 val);
————————————————
版权声明:本文为CSDN博主「xdtp」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tpmamba/article/details/111389986

上述函数只是对如下函数进行调用:

读字节 */
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 (struct pci_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); /* 写双字 */

查看/proc/sysfs文件系统下的PCI总线、设备和驱动

tree /proc/bus/pci
tree /sys/bus/pci
uos@uos-PC:~$ tree /proc/bus/pci
/proc/bus/pci
├── 0000:00
│   ├── 00.0
│   ├── 04.0
│   ├── 04.1
│   ├── 05.0
│   ├── 05.1
│   ├── 07.0
│   ├── 08.0
│   ├── 08.1
│   ├── 08.2
│   ├── 0a.0
│   ├── 0b.0
│   ├── 0d.0
│   ├── 0f.0
│   ├── 13.0
│   ├── 16.0
│   └── 17.0
├── 0000:01
│   └── 00.0
├── 0000:02
│   └── 00.0
├── 0000:03
│   └── 00.0
├── 0000:04
│   ├── 00.0
│   └── 00.1
├── 0000:05
│   └── 00.0
└── devices

6 directories, 23 files
uos@uos-PC:~$ tree /sys/bus/pci
/sys/bus/pci
├── devices
│   ├── 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0
│   ├── 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0
│   ├── 0000:00:04.1 -> ../../../devices/pci0000:00/0000:00:04.1
│   ├── 0000:00:05.0 -> ../../../devices/pci0000:00/0000:00:05.0
│   ├── 0000:00:05.1 -> ../../../devices/pci0000:00/0000:00:05.1
│   ├── 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0
│   ├── 0000:00:08.0 -> ../../../devices/pci0000:00/0000:00:08.0
│   ├── 0000:00:08.1 -> ../../../devices/pci0000:00/0000:00:08.1
│   ├── 0000:00:08.2 -> ../../../devices/pci0000:00/0000:00:08.2
│   ├── 0000:00:0a.0 -> ../../../devices/pci0000:00/0000:00:0a.0
│   ├── 0000:00:0b.0 -> ../../../devices/pci0000:00/0000:00:0b.0
│   ├── 0000:00:0d.0 -> ../../../devices/pci0000:00/0000:00:0d.0
│   ├── 0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0
│   ├── 0000:00:13.0 -> ../../../devices/pci0000:00/0000:00:13.0
│   ├── 0000:00:16.0 -> ../../../devices/pci0000:00/0000:00:16.0
│   ├── 0000:00:17.0 -> ../../../devices/pci0000:00/0000:00:17.0
│   ├── 0000:01:00.0 -> ../../../devices/pci0000:00/0000:00:0a.0/0000:01:00.0
│   ├── 0000:02:00.0 -> ../../../devices/pci0000:00/0000:00:0b.0/0000:02:00.0
│   ├── 0000:03:00.0 -> ../../../devices/pci0000:00/0000:00:0d.0/0000:03:00.0
│   ├── 0000:04:00.0 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0
│   ├── 0000:04:00.1 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.1
│   └── 0000:05:00.0 -> ../../../devices/pci0000:00/0000:00:13.0/0000:05:00.0
├── drivers
│   ├── ahci
│   │   ├── 0000:00:08.0 -> ../../../../devices/pci0000:00/0000:00:08.0
│   │   ├── 0000:00:08.1 -> ../../../../devices/pci0000:00/0000:00:08.1
│   │   ├── 0000:00:08.2 -> ../../../../devices/pci0000:00/0000:00:08.2
│   │   ├── 0000:03:00.0 -> ../../../../devices/pci0000:00/0000:00:0d.0/0000:03:00.0
│   │   ├── bind
│   │   ├── module -> ../../../../module/ahci
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── amdgpu
│   │   ├── 0000:04:00.0 -> ../../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0
│   │   ├── bind
│   │   ├── module -> ../../../../module/amdgpu
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── ast
│   │   ├── bind
│   │   ├── module -> ../../../../module/ast
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── cavium_ptp
│   │   ├── bind
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── ehci-pci
│   │   ├── 0000:00:04.1 -> ../../../../devices/pci0000:00/0000:00:04.1
│   │   ├── 0000:00:05.1 -> ../../../../devices/pci0000:00/0000:00:05.1
│   │   ├── bind
│   │   ├── module -> ../../../../module/ehci_pci
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── loongson-audio
│   │   ├── 0000:00:07.0 -> ../../../../devices/pci0000:00/0000:00:07.0
│   │   ├── bind
│   │   ├── module -> ../../../../module/snd_hda_loongson
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── ls-spi-pci
│   │   ├── 0000:00:16.0 -> ../../../../devices/pci0000:00/0000:00:16.0
│   │   ├── bind
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── megaraid_legacy
│   │   ├── bind
│   │   ├── module -> ../../../../module/megaraid
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── megaraid_sas
│   │   ├── bind
│   │   ├── dbg_lvl
│   │   ├── module -> ../../../../module/megaraid_sas
│   │   ├── new_id
│   │   ├── release_date
│   │   ├── remove_id
│   │   ├── support_device_change
│   │   ├── support_nvme_encapsulation
│   │   ├── support_poll_for_event
│   │   ├── uevent
│   │   ├── unbind
│   │   └── version
│   ├── mpt3sas
│   │   ├── bind
│   │   ├── module -> ../../../../module/mpt3sas
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── mvumi
│   │   ├── bind
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── nvme
│   │   ├── 0000:05:00.0 -> ../../../../devices/pci0000:00/0000:00:13.0/0000:05:00.0
│   │   ├── bind
│   │   ├── module -> ../../../../module/nvme
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── ohci-pci
│   │   ├── 0000:00:04.0 -> ../../../../devices/pci0000:00/0000:00:04.0
│   │   ├── 0000:00:05.0 -> ../../../../devices/pci0000:00/0000:00:05.0
│   │   ├── bind
│   │   ├── module -> ../../../../module/ohci_pci
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── pcieport
│   │   ├── 0000:00:0a.0 -> ../../../../devices/pci0000:00/0000:00:0a.0
│   │   ├── 0000:00:0b.0 -> ../../../../devices/pci0000:00/0000:00:0b.0
│   │   ├── 0000:00:0d.0 -> ../../../../devices/pci0000:00/0000:00:0d.0
│   │   ├── 0000:00:0f.0 -> ../../../../devices/pci0000:00/0000:00:0f.0
│   │   ├── 0000:00:13.0 -> ../../../../devices/pci0000:00/0000:00:13.0
│   │   ├── bind
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── r8168
│   │   ├── 0000:02:00.0 -> ../../../../devices/pci0000:00/0000:00:0b.0/0000:02:00.0
│   │   ├── bind
│   │   ├── module -> ../../../../module/r8168
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── radeon
│   │   ├── bind
│   │   ├── module -> ../../../../module/radeon
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── radeonfb
│   │   ├── bind
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── serial
│   │   ├── bind
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── shpchp
│   │   ├── bind
│   │   ├── module -> ../../../../module/shpchp
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── smifb
│   │   ├── bind
│   │   ├── module -> ../../../../module/smifb
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── snd_hda_intel
│   │   ├── 0000:04:00.1 -> ../../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.1
│   │   ├── bind
│   │   ├── module -> ../../../../module/snd_hda_intel
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── stmmaceth
│   │   ├── bind
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   ├── tsi721
│   │   ├── bind
│   │   ├── module -> ../../../../module/tsi721_mport
│   │   ├── new_id
│   │   ├── remove_id
│   │   ├── uevent
│   │   └── unbind
│   └── xhci_hcd
│       ├── 0000:01:00.0 -> ../../../../devices/pci0000:00/0000:00:0a.0/0000:01:00.0
│       ├── bind
│       ├── module -> ../../../../module/xhci_pci
│       ├── new_id
│       ├── remove_id
│       ├── uevent
│       └── unbind
├── drivers_autoprobe
├── drivers_probe
├── rescan
├── resource_alignment
├── slots
└── uevent

86 directories, 131 files

3.5 PCI DMA 相关API

内核中定义了一组专门针对 PCI 设备的 DMA 操作接口,这些 API 的原型和作用与通用 DMA API 非常相似,主要包括如下。

  • 设置 DMA 缓冲区掩码。
    int pci_set_dma_mask(struct pci_dev *dev, u64 mask);
    
  • 一致性 DMA 缓冲区分配/释放。
    void *pci_alloc_consistent(struct pci_dev *pdev, size_t size, dma_addr_t *dma_handle);
    void pci_free_consistent(struct pci_dev *hwdev, size_t size, void *vaddr, dma_addr_t dma_handle);
    
  • 流式 DMA 缓冲区映射/去映射。
dma_addr_t pci_map_single(struct pci_dev *pdev, void *ptr, size_t size, int direction);

int pci_map_sg(struct pci_dev *pdev,struct scatterlist *sgl,int num_entries, int direction);

void pci_unmap_single(struct pci_dev *pdev, dma_addr_t dma_addr, size_t size, int direction);

void pci_unmap_sg(struct pci_dev *pdev, struct scatterlist *sg, int nents, int direction);

这些 API 的用法与dma_alloc_consistent()dma_map_single()dma_map_sg()相似,只是以 pci_开头的 API 用于 PCI 设备驱动。

3.6 PCI 设备驱动其他常用 API

除了 DMA API 外,在 PCI 设备驱动中其他常用的函数(或宏)如下所示。

  • 获取 I/O 或内存资源。

    #define pci_resource_start(dev,bar) ((dev)!resource[(bar)].start)
    #define pci_resource_end(dev,bar) ((dev)!resource[(bar)].end)
    #define pci_resource_flags(dev,bar) ((dev)!resource[(bar)].flags)
    #define pci_resource_len(dev,bar) \
     				((pci_resource_start((dev),(bar)) == 0 && \
     				pci_resource_end((dev),(bar)) == \
    				pci_resource_start((dev),(bar))) ? 0 : \
     													\
     				(pci_resource_end((dev),(bar)) - \
     				pci_resource_start((dev),(bar)) + 1))
    
  • 申请/释放 I/O 或内存资源。

    int pci_request_regions(struct pci_dev *pdev, const char *res_name);
    void pci_release_regions(struct pci_dev *pdev);
    
  • 获取/设置驱动私有数据。

    void *pci_get_drvdata (struct pci_dev *pdev);
    void pci_set_drvdata (struct pci_dev *pdev, void *data);
    
  • 使能/禁止 PCI 设备。

    int pci_enable_device(struct pci_dev *dev);
    void pci_disable_device(struct pci_dev *dev);
    
  • 设置为总线主 DMA。

    void pci_set_master(struct pci_dev *dev);
    
  • 寻找指定总线指定槽位的 PCI 设备。

    struct pci_dev *pci_find_slot (unsigned int bus, unsigned int devfn);
    
  • 设置 PCI 能量管理状态(0=D0 … 3=D3)。

    int pci_set_power_state(struct pci_dev *dev, pci_power_t state);
    
  • 在设备的能力表中找出指定的能力。

    int pci_find_capability (struct pci_dev *dev, int cap);
    
  • 启用设备内存写无效事务。

    int pci_set_mwi(struct pci_dev *dev);
    
  • 禁用设备内存写无效事务。

    void pci_clear_mwi(struct pci_dev *dev);
    

四、PCI设备驱动结构

4.1 pci_driver结构体

从本质上讲 PCI 只是一种总线,具体的 PCI 设备可以是字符设备、网络设备、USB 主机控制器等,因此,一个通过 PCI 总线与系统连接的设备的驱动至少包含以下两部分内容。

  • PCI 驱动。
  • 设备本身的驱动。

PCI 驱动只是为了辅助设备本身的驱动,它不是目的,而是手段。例如,对于通过 PCI 总线与系统连接的字符设备,则驱动中除了要实现 PCI 驱动部分外,其主体仍然是设备作为字符设备本身的驱动,即实现 file_operations 成员函数并注册 cdev。分析 Linux 内核可知,在/drivers/block//drivers/atm/ /drivers/char//drivers/i2c//drivers/ieee1394//drivers/media//drivers/mtd//drivers/net//drivers/serial//drivers/video//sound/等目录中均广泛分布着 PCI 设备驱动。

在 Linux 内核中,用 pci_driver 结构体来定义 PCI 驱动,该结构体中包含了 PCI 设备的探测/移除、挂起/恢复等函数。pci_driverplatform_driveri2c_driverusb_driver 的地位非常相似,都是起到挂接总线的作用。

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 */ /*不能为 NULL,以便 probe 函数调用*/

	/* 新设备添加 */
	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-plug capable driver) */ /* 设备移出 */
	int  (*suspend)(struct pci_dev *dev, pm_message_t state);	/* Device suspended *//* 设备挂起 */
	int  (*suspend_late)(struct pci_dev *dev, pm_message_t state);
	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); /* On PF */
	const struct pci_error_handlers *err_handler;
	const struct attribute_group **groups;
	struct device_driver	driver;
	struct pci_dynids	dynids;
};

#define	to_pci_driver(drv) container_of(drv, struct pci_driver, driver)
注册/注销PCI驱动
int pci_register_driver(struct pci_driver *drv);
void pci_unregister_driver(struct pci_driver *drv);

使能/禁止PCI设备
int pci_enable_device(struct pci_dev *dev);
void pci_disable_device(struct pci_dev *dev);

申请/释放I/O或内存资源
int pci_request_regions(struct pci_dev *dev, const char *res_name);
void pci_release_regions(struct pci_dev *dev);

获取I/O或内存资源
#define pci_resource_start(dev, bar)	((dev)->resource[(bar)].start)
#define pci_resource_end(dev, bar)	((dev)->resource[(bar)].end)
#define pci_resource_flags(dev, bar)	((dev)->resource[(bar)].flags)
#define pci_resource_len(dev,bar) \
	((pci_resource_start((dev), (bar)) == 0 &&	\
	  pci_resource_end((dev), (bar)) ==		\
	  pci_resource_start((dev), (bar))) ? 0 :	\
							\
	 (pci_resource_end((dev), (bar)) -		\
	  pci_resource_start((dev), (bar)) + 1))

设置/获取驱动私有数据
void pci_set_drvdata(struct pci_dev *pdev, void *data);
void *pci_get_drvdata(struct pci_dev *pdev);

设置/清除为总线主DMA
void pci_set_master(struct pci_dev *dev);
void pci_clear_master(struct pci_dev *dev);

在设备的能力表中找出指定的capability
int pci_find_capability(struct pci_dev *dev, int cap);

设置/清除内存写无效事务
int pci_set_mwi(struct pci_dev *dev);
void pci_clear_mwi(struct pci_dev *dev);

pci_driver的probe函数
	func: 完成PCI设备的初始化及设备本身(字符、tty、网络等)的驱动的注册。	

pci_device_id结构体
	struct pci_device_id {
		__u32 vendor, device;		/* Vendor and device ID or PCI_ANY_ID*/
		__u32 subvendor, subdevice;	/* Subsystem ID's or PCI_ANY_ID */
		__u32 class, class_mask;	/* (class,subclass,prog-if) triplet */
		kernel_ulong_t driver_data;	/* Data private to the driver */
	};
#define PCI_DEVICE(vend,dev) \
	.vendor = (vend), .device = (dev), \
	.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID

pci_driverprobe()函数要完成 PCI 设备的初始化及其设备本身身份(字符、TTY、网络等)驱动的注册。当 Linux 内核启动并完成对所有 PCI 设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有 PCI 设备的拓扑结构,probe()函数将负责硬件的探测工作并保存配置信息。

4.2 PCI设备驱动的组成

static int xxx_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
{
	/*pci设备相关结构的初始化*/
	/*初始化设备本身(字符、tty、网络等)*/
	/*注册设备本身*/
}
static void xxx_remove(struct pci_dev *pci_dev)
{
	/*注销设备本身*/
	/*PCI设备相关结构的释放*/
}
static struct pci_driver xxx_pci_driver = {
			.name		= DRV_NAME,
			.id_table	= pci_tbl,
			.probe		= xxx_probe,
			.remove		= xxx_remove,
			...
		};
xxx_init
	pci_register_driver(&xxx_pci_driver);
xxx_exit
	pci_unregister_driver(&xxx_pci_driver);

pci总线就像工作单位、PCI设备需要向“工作单位”注册并接收其管理(probe/remove/resume/...);
但PCI设备又本身可能是个保安、工程师、经理等,故有其自己的主体工作(字符/tty/网络等驱动程序)。

4.3 驱动实例

************************************************
NVIDIA nForce媒体访问控制器的以太网驱动程序
(drivers/net/ethernet/nvidia/forcedeth.c)
************************************************
static const struct net_device_ops nv_netdev_ops = {
	.ndo_open		= nv_open,
	.ndo_stop		= nv_close,
	.ndo_get_stats64	= nv_get_stats64,
	.ndo_start_xmit		= nv_start_xmit,
	...
	...
};
static int nv_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
{
	struct net_device *dev;
	struct fe_priv *np;
	unsigned long addr;
	u8 __iomem *base;
	...

	dev = alloc_etherdev(sizeof(struct fe_priv));			/*分配etherdev设备*/
	...
	np = netdev_priv(dev);
	np->dev = dev;
	np->pci_dev = pci_dev;
	...
	SET_NETDEV_DEV(dev, &pci_dev->dev);
	...

	err = pci_enable_device(pci_dev);							/*使能PCI设备*/				
	...
	pci_set_master(pci_dev);									/*设置为总线主DMA*/

	err = pci_request_regions(pci_dev, DRV_NAME);				/*申请PCI I/O和内存资源*/
	...
	addr = 0;
	for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
		if (pci_resource_flags(pci_dev, i) & IORESOURCE_MEM &&
				pci_resource_len(pci_dev, i) >= np->register_size) {
			addr = pci_resource_start(pci_dev, i);				/*获取I/O或内存基址*/
			break;
		}
	}
	...
	...
	np->base = ioremap(addr, np->register_size);				/*ioremap I/O区域*/
	...
	...
	if (!nv_optimized(np))
		dev->netdev_ops = &nv_netdev_ops;							/*指定net_device_ops结构体*/
	else
		dev->netdev_ops = &nv_netdev_ops_optimized;

	netif_napi_add(dev, &np->napi, nv_napi_poll, RX_WORK_PER_LOOP);
	dev->ethtool_ops = &ops;
	dev->watchdog_timeo = NV_WATCHDOG_TIMEO;

	pci_set_drvdata(pci_dev, dev);								/*获取驱动私有数据*/
	...
	...
	err = register_netdev(dev);								/*注册网络设备*/
	...
	...
	return 0;
	...
}
static void nv_remove(struct pci_dev *pci_dev)
{
	struct net_device *dev = pci_get_drvdata(pci_dev);

	unregister_netdev(dev);							/*注销网络设备*/

	nv_restore_mac_addr(pci_dev);

	/* restore any phy related changes */
	nv_restore_phy(dev);

	nv_mgmt_release_sema(dev);

	/* free all structures */
	free_rings(dev);
	iounmap(get_hwbase(dev));
	pci_release_regions(pci_dev);						/*释放I/O或内存资源*/		
	pci_disable_device(pci_dev);						/*禁止PCI设备*/
	free_netdev(dev);								/*释放网络设备*/
}
static const struct pci_device_id pci_tbl[] = {
	{	/* nForce Ethernet Controller */
		PCI_DEVICE(0x10DE, 0x01C3),								/*宏定义见附录*/								
		.driver_data = DEV_NEED_TIMERIRQ|DEV_NEED_LINKTIMER,
	},
	{	/* nForce2 Ethernet Controller */
		PCI_DEVICE(0x10DE, 0x0066),
		.driver_data = DEV_NEED_TIMERIRQ|DEV_NEED_LINKTIMER,
	},
	...
	...
	{0,},
};
static struct pci_driver forcedeth_pci_driver = {
	.name		= DRV_NAME,
	.id_table	= pci_tbl,
	.probe		= nv_probe,
	.remove		= nv_remove,
	...
};
module_pci_driver(forcedeth_pci_driver);		/*该函数实际已经包含了pci_register_driver和pci_register_driver*/
MODULE_DEVICE_TABLE(pci, pci_tbl);				/*使用该宏将pci_tbl导出到用户空间*/

参考

  1. Linux设备驱动开发—PCI设备驱动
  2. 有关PCI、PCI-X与PCI-E的介绍
  3. Linux设备驱动和设备匹配过程
  4. PCIe学习笔记之pcie初始化枚举和资源分配流程代码分析
  5. PCI 驱动
  6. PCI总线驱动代码梳理(一)–整体框架
  7. PCI总线驱动代码梳理(二)–配置空间访问的设置
  8. PCI总线驱动代码梳理(三)–PCI设备的枚举
  9. Linux PCI Bus Subsystem
  10. Linux设备驱动和设备匹配过程
  11. PCIe学习笔记之pcie初始化枚举和资源分配流程代码分析
  12. PCIE 之linux驱动分析
  13. 慢慢欣赏linux PCI-PCIE初始化总结
  14. PCIe学习笔记之pcie结构和配置空间
  15. Linux内核笔记之PCIe hotplug介绍及代码分析
  16. PCIe学习笔记
  17. 第十二章——PCI驱动程序
  18. 第二章——体系结构概述
  19. Linux源码阅读——PCI总线驱动代码(三)PCI设备枚举过程
  20. Linux下PCI设备驱动程序开发
  21. PCI配置空间简介
  22. 【PCIe】配置空间
  23. 深入PCI与PCIe之二:软件篇
  24. PCIe扫盲——BDF与配置空间/配置空间的读写机制/Type0 & Type1 型配置请求
  25. Linux下PCI设备驱动程序开发
  26. 总线特定信息

你可能感兴趣的:(Linux内核驱动,linux,单片机,运维)