技术交流

好好学习,天天向上。

0%

第十二章——PCI驱动程序

PCI介绍

PCI总线有自己的I/O空间(32位)和内存空间(32/64位),CPU访问PCI空间需要映射,硬上电自动完成将不同的外设映射到不同的CPU地址空间上。PCI设备的使用模型是这样的,外设可以抽象成一片内存或者一排连续的端口,具体而言每个功能256字节的配置内存。它们可以被排布在PCI总线地址空间上,然后映射到host CPU的地址空间里。每个PCI插槽有4个中断引脚,每个功能可以使用其中一个。每个PCI外设由domain(16位)、bus(8位)、device(5位)以及function(3位)组成的编号来标识。每个domain可以有最多256个bus,每个bus上最多可以有32个device,每个divece上最多可以有8中function。查看当前环境PCI信息可以用下面几种命令

1
2
3
lspci
cat /proc/pci
cat /proc/bus/pci

PCI设备暴露出的256字节的配置内存,前64字节是标准化的,PCI设备始终使用小端字节序。通常来说,在这256个配置寄存器中由几个只读寄存器,由厂商设定,用于识别设备

名称 描述 是否常用
vendorID 厂商标识信息,生产PCI设备的厂商需要注册
deviceID 设备标识信息,不需要注册,但是厂商需要指定
class 类型标识
subsystem vendorID 细分标识
subsystem deviceID 细分标识

驱动PCI设备的标识

驱动需要用struct pci_device_id结构体告知内核其支持的设备类型,该结构体内包含和PCI设备标识寄存器类似的字段

1
2
3
4
5
6
7
8
9
10
11
12
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 */
};

PCI_DEVICE(vendor, device) // 创建pci_device_id的宏
PCI_DEVICE_CLASS(device_class, device_class_mask)

// 下面的宏用于向内核热插拔机制注册驱动支持的设备类型,pci设备驱动的pci_device_id就用它注册
MODULE_DEVICE_TABLE(platform, xxx_device_ids)

注册设备

PCI设备的驱动注册和字符设备的驱动注册类似,参照如下接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static struct pci_driver pci_driver = {
.name = "xxx_name",
.id_table = ids,
.probe = probe,
.remove = remove,
};

const char *name; // 驱动程序名字
const struct pci_device_id *id_table; // 指向一个pci_device_id组成的数组
// 探测函数,在探测函数中,访问PCI设备的任何资源之前必须先调用 int pci_enable_device(struct pci_dev *dev);
int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);
void (*remove) (struct pci_dev *dev); // 当 struct pci_dev从系统中移除或者驱动模块被移除的时候,有PCI核心调用
int (*suspend) (struct pci_dev *dev, u32 state); // 可选的挂起函数
int (*resume) (struct pci_dev *dev); // 可选的恢复函数

static int __init pci_xxx_name_init(void)
{
return pci_register_driver(&pci_driver);
}

static void __exit pci_xxx_name_exit(void)
{
pci_unregister_driver(&pci_driver);
}

访问配置空间

当probe的时候(内核会根据注册时提交的设备ID信息安排probe),驱动需要完成对设备配置区域的读写,可以使用下面这组接口

1
2
3
4
5
6
7
8
9
#include <linux/pci.h>
// where参数是从配置空间起始位置计算的字节偏移量,返回值存于val中,有类似于PCI_REVISION_ID等宏来辅助设定
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);

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);

上面这组读写接口实际上会调用下面这组接口,当驱动不能访问pci_dev的时候也可以直接调用下面这组接口

1
2
3
4
5
6
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 devfn, int where, u16 val);
int pci_bus_write_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 val);

访问I/O和内存空间

PCI设备最多可以实现6个I/O地址区域,这6个地址区域既可以映射到PCI的I/O地址空间,也可以映射到PCI的内存地址空间,大多数还是选择后者。当I/O地址被映射到内存的时候,要注意边际作用。内核提供了辅助函数来查询某个设备6个I/O地址区域,返回的首地址可能是内存地址,也可能是I/O端口号。

1
2
3
unsigned long pci_resource_start(struct pci_dev *dev, int bar); // 返回首地址
unsigned long pci_resource_end(struct pci_dev *dev, int bar); // 返回尾部址
unsigned long pci_resource_flags(struct pci_dev *dev, int bar); // 返回有关该PCI I/O区域资源的属性