Linux PCI总线驱动-1
一、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
位和66MHz PCI
主板的难度很大。首先,使用64
位PCI
插槽需要64
位南桥芯片组支持,该南桥控制器必须可以正确处理64
位的数据。Intel
和AMD
都有64
位的南桥可提供给主板厂商,但是价格很高;其次是因为66MzPCI
槽对主板配套元件要求极高,且需要特殊的布线设计。这就是66MHzPCI
技术一直停留在服务器领域的原因。
2. PCI接口
PCI
有几种不同的接口样图:现在生产的多为通用模式的以防插错。还有64bit
统一比32bit
的宽出右边缺口的部分。
PCI 32bit
的网卡都可以查到PCI 64bit
插槽上使用。3.3v
的插到3.3v
的上,5v
的插到5v
的上。
有一些PCI
网卡同时支持32
位和64
位标准的兼容网卡,这类网卡相比前面介绍的纯64
位PCI
网卡来说,在外观上也有一个明显的区别,那就是它又多了一个缺口,有3
个缺口(下图右边第三个图)。
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
系统框图如下:
一个典型的结构是一个root port
和一个endpoint
直接组成一个点对点连接对,而Switch
可以同时连接几个endpoint
。一个root port
和一个endpoint
对就需要一个单独的PCI bus
。而PCI是在同一个总线上的设备共享同一个bus number
。过去主板上的PCI插槽都公用一个PCI bus
,而现在的PCIe插槽却连在芯片组不同的root port
上。
4.1 下图从上到下依次是PCIEX16,X1,X4
4.2 PCI-E各版本的传输速度
4.3 PCI-E不同传输通道数设备的金手指数和长度
4.4 三种接口的传输速度比较
二、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所有数据结构
1)struct pci_host_bridge
主桥数据结构,用来描述连接CPU和PCIE设备的主桥,该结构有Root bus0
成员,它也是一个设备,需要注册。
2)struct pci_dev
该结构体用来描述PCI设备,包括EP和pci to pci 桥
等设备.
Bus
:用来关联该设备在哪条bus下,
device
,version
: 枚举出来的设备配置空间的重要东西都会保存该数据结构。
3)struct pci_bus
该结构体用来描述PCI
的总线。
struct list_head devices
: 设备链表用于关联该bus下的所有设备。
children
: PCI
桥可以使当前总线得到扩展,当前总线上有几个PCI
桥,那么当前总线就会拥有几个子总线,子总线会连接到父总线的children
链表中。
ops
: 当前总线访问总线上设备配置空间的 read
、write
方法。
4)struct pci_slot
用来描述bus
下的物理插槽
5)struct pci_bus_type
这个是pci
的总线模型,和之前的pci_bus
是两码事, pci_driver
和pci_dev
就是通过该总线关联起来.
match
: 通过pci_dev
对应的字段 vendor
、subvendor
、device
、subdevice
来匹 配对应的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
。
在上图中,Bus 0
总线的 pci_bus
结构体中的 number
、primary
、secondary
都应该为 0
,因为它是通过 Host/PCI
桥引出的根总线;Bus 1
总线的 pci_bus
结构体中的 number
和 secondary
都为 1
,但是它的 primary
应该为 0
;Bus 2
总线的 pci_bus
结构体中的 number
和 secondary
都应该为 2
,而其 primary
则应该等于 1
。这 3 条总线的 subordinate
值都应该等于 2
。
系统中当前存在的所有根总线都通过其 pci_bus
结构体中的 node
成员链接成一条全局的根总线链表,其表头由 list
类型的全局变量 pci_root_buses
来描述。而根总线下面的所有下级总线则都通过其 pci_bus
结构体中的 node
成员链接到其父总线的 children
链表中。这样,通过这两种 PCI
总线链表,Linux 内核就将所有的 pci_bus
结构体以一种倒置树的方式组织起来。假定对于如图 所示的多根 PCI
总线体系结构:
它所对应的总线链表结构将如图所示。
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? */
紧接着前 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
个字节(0x18
~0x39
)则被用来配置桥设备的主、次编号以及地址过滤窗口等信息。
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_driver
和 platform_driver
、i2c_driver
、usb_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_driver
的 probe()
函数要完成 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导出到用户空间*/
参考
- Linux设备驱动开发—PCI设备驱动
- 有关PCI、PCI-X与PCI-E的介绍
- Linux设备驱动和设备匹配过程
- PCIe学习笔记之pcie初始化枚举和资源分配流程代码分析
- PCI 驱动
- PCI总线驱动代码梳理(一)–整体框架
- PCI总线驱动代码梳理(二)–配置空间访问的设置
- PCI总线驱动代码梳理(三)–PCI设备的枚举
- Linux PCI Bus Subsystem
- Linux设备驱动和设备匹配过程
- PCIe学习笔记之pcie初始化枚举和资源分配流程代码分析
- PCIE 之linux驱动分析
- 慢慢欣赏linux PCI-PCIE初始化总结
- PCIe学习笔记之pcie结构和配置空间
- Linux内核笔记之PCIe hotplug介绍及代码分析
- PCIe学习笔记
- 第十二章——PCI驱动程序
- 第二章——体系结构概述
- Linux源码阅读——PCI总线驱动代码(三)PCI设备枚举过程
- Linux下PCI设备驱动程序开发
- PCI配置空间简介
- 【PCIe】配置空间
- 深入PCI与PCIe之二:软件篇
- PCIe扫盲——BDF与配置空间/配置空间的读写机制/Type0 & Type1 型配置请求
- Linux下PCI设备驱动程序开发
- 总线特定信息