一、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技术一直停留在服务器领域的原因。
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 {
struct list_head node;
struct pci_bus *parent;
struct list_head children;
struct list_head devices;
struct pci_dev *self;
struct resource *resource[PCI_BUS_NUM_RESOURCES];
struct pci_ops *ops;
void *sysdata;
struct proc_dir_entry *procdir;
unsigned char number;
unsigned char primary;
unsigned char secondary;
unsigned char subordinate;
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 {
struct list_head bus_list;
struct pci_bus *bus;
struct pci_bus *subordinate;
void *sysdata;
struct proc_dir_entry *procent;
struct pci_slot *slot;
unsigned int devfn;
unsigned short vendor;
unsigned short device;
unsigned short subsystem_vendor;
unsigned short subsystem_device;
unsigned int class;
u8 hdr_type;
u8 rom_base_reg;
struct pci_driver *driver;
u64 dma_mask;
pci_power_t current_state;
struct device dev;
unsigned int irq;
struct resource resource[DEVICE_COUNT_RESOURCE];
int cfg_size;
unsigned int transparent:1;
unsigned int multifunction:1;
unsigned int is_busmaster:1;
unsigned int no_msi:1;
unsigned int block_ucfg_access:1;
u32 saved_config_space[16];
struct bin_attribute *rom_attr;
int rom_attr_enabled;
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 配置空间头部。
#define PCI_STD_HEADER_SIZEOF 64
#define PCI_VENDOR_ID 0x00
#define PCI_DEVICE_ID 0x02
#define PCI_COMMAND 0x04
#define PCI_COMMAND_IO 0x1
#define PCI_COMMAND_MEMORY 0x2
#define PCI_COMMAND_MASTER 0x4
#define PCI_COMMAND_SPECIAL 0x8
#define PCI_COMMAND_INVALIDATE 0x10
#define PCI_COMMAND_VGA_PALETTE 0x20
#define PCI_COMMAND_PARITY 0x40
#define PCI_COMMAND_WAIT 0x80
#define PCI_COMMAND_SERR 0x100
#define PCI_COMMAND_FAST_BACK 0x200
#define PCI_COMMAND_INTX_DISABLE 0x400
#define PCI_STATUS 0x06
#define PCI_STATUS_INTERRUPT 0x08
#define PCI_STATUS_CAP_LIST 0x10
#define PCI_STATUS_66MHZ 0x20
#define PCI_STATUS_UDF 0x40
#define PCI_STATUS_FAST_BACK 0x80
#define PCI_STATUS_PARITY 0x100
#define PCI_STATUS_DEVSEL_MASK 0x600
#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
#define PCI_STATUS_REC_TARGET_ABORT 0x1000
#define PCI_STATUS_REC_MASTER_ABORT 0x2000
#define PCI_STATUS_SIG_SYSTEM_ERROR 0x4000
#define PCI_STATUS_DETECTED_PARITY 0x8000
#define PCI_CLASS_REVISION 0x08
#define PCI_REVISION_ID 0x08
#define PCI_CLASS_PROG 0x09
#define PCI_CLASS_DEVICE 0x0a
#define PCI_CACHE_LINE_SIZE 0x0c
#define PCI_LATENCY_TIMER 0x0d
#define PCI_HEADER_TYPE 0x0e
#define PCI_HEADER_TYPE_NORMAL 0
#define PCI_HEADER_TYPE_BRIDGE 1
#define PCI_HEADER_TYPE_CARDBUS 2
#define PCI_BIST 0x0f
#define PCI_BIST_CODE_MASK 0x0f
#define PCI_BIST_START 0x40
#define PCI_BIST_CAPABLE 0x80
紧接着前 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:
上述函数只是对如下函数进行调用:
读字节 */
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 驱动部分外,其主体仍然是设备作为字符设备本身的驱动,即实现 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;
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_t state);
int (*suspend_late)(struct pci_dev *dev, pm_message_t state);
int (*resume_early)(struct pci_dev *dev);
int (*resume) (struct pci_dev *dev);
void (*shutdown) (struct pci_dev *dev);
int (*sriov_configure) (struct pci_dev *dev, int num_vfs);
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;
__u32 subvendor, subdevice;
__u32 class, class_mask;
kernel_ulong_t driver_data;
};
#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)
{
}
static void xxx_remove(struct pci_dev *pci_dev)
{
}
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));
...
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_set_master(pci_dev);
err = pci_request_regions(pci_dev, DRV_NAME);
...
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);
break;
}
}
...
...
np->base = ioremap(addr, np->register_size);
...
...
if (!nv_optimized(np))
dev->netdev_ops = &nv_netdev_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);
nv_restore_phy(dev);
nv_mgmt_release_sema(dev);
free_rings(dev);
iounmap(get_hwbase(dev));
pci_release_regions(pci_dev);
pci_disable_device(pci_dev);
free_netdev(dev);
}
static const struct pci_device_id pci_tbl[] = {
{
PCI_DEVICE(0x10DE, 0x01C3),
.driver_data = DEV_NEED_TIMERIRQ|DEV_NEED_LINKTIMER,
},
{
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);
MODULE_DEVICE_TABLE(pci, pci_tbl);
参考
- Linux设备驱动开发—PCI设备驱动
- 有关PCI、PCI-X与PCI-E的介绍
- Linux设备驱动和设备匹配过程
- PCIe学习笔记之pcie初始化枚举和资源分配流程代码分析
- PCI 驱动
- PCI总线驱动代码梳理(一)–整体框架
- PCI总线驱动代码梳理(二)–配置空间访问的设置
- PCI总线驱动代码梳理(三)–PCI设备的枚举
- Linux PCI Bus Subsystem
- PCIe学习笔记之pcie初始化枚举和资源分配流程代码分析
- 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设备驱动程序开发
- 总线特定信息
|