一,PCI总线:
1,PCI总线是系统总线;Host-Briage(PCI总线0)——PCI设备,PCI-PCI briage ——PCI总线1(图片来自精通Linux设备驱动程序开发)
Host-Briage 集成在北条芯片组中,PCI-PCI-Briage是透明设备,PCI设备通过Host-Briage与CPU通信,CPU将信息传递给Host-Briage,然后Host-Briage将信息传递给PCI设备,所以PCI总线与体系结构无关,PCI总线上链接各种PCI控制器(PCI设备),与北条芯片链接的PCI总线是编号为0是主PCI总线;
如下图:PCI总线结构图(图片来自网络:http://blog.sina.com.cn/s/blog_6472c4cc0100qbx5.html)
PCI设备有三类:主设备,从设备,桥设备,每个设备同一时刻只能是一种主设备或从设备,主设备能够取得总线控制权;PCI总线是多主的。
2,PCI设备信息查看及寻址方式:
系统中的PCI设备信息可以从/proc/bus/pci/device , /sys/bus/pci/device 文件系统中查看;
如下截图:
其中设备信息的表示格式为: 总线域(16位):总线编号(8位):设备编号(5位):功能编号(3位)
由以上信息可以知道:每个总线域最多有256个PCI总线,每个总线最多有32个PCI设备,每个设备最多有8个功能;
PCI设备配置寄存器地址表示:1位使能位:7位保留位:8位总线编号:5位设备编号:3位功能编号:6位配置寄存器偏移量:2位标志位
访问PCI设备配置寄存器地址表示如图:
31 | 30-24 | 23-16 | 15-11 | 10-8 | 7-2 | 1 | 0 |
使能位 | 保留位 | 8位总线编号 | 5位设备编号 | 3位功能编号 | 6位寄存器偏移量 | 0 | 0 |
使能位决定是否允许访问配置空间;
第一位和第二位表示每次读取4字节;
3,PCI设备配置空间结构,如下图(图片来自Linux设备驱动程序第三版):
PCI规范规定PCI配置空间大小位256字节,所以每个配置寄存器的位宽为8位,前64字节格式是固定的标准化的,剩下的由厂商决定;
如上图前64字节配置空间前面四个字节为VendorId , DeviceId;
偏移量为0x9的寄存器3个字节保存了ClassCode;
偏移量为0xe的寄存器1个字节保存了HeaderType:0表示PCI设备,1表示PCI Briage;
偏移量为0x10的寄存器之后每4个字节为一个BAR寄存器(IO内存或IO端口空间基地址寄存器),一共6个BAR寄存器;
每个BAR的地址格式如下图(图片来自网络):
如果BAR的地址映射的是IO地址空间:最低位为0,第二位预留,从第三位开始表示为地址值;
如果BAR的地址映射的是内存地址空间:最低位为1;第二位和第三位表示体系架构,00表示32位架构,10表示64位架构;第三位表示是否可预取,对于具有边际效应的寄存器该值设为不可预取,比如某些控制寄存器;
地址最后一位表示为IO映射或内存映射:如果为1则为IO映射,如果为0则为内存映射;
前64字节空间中剩下的寄存器中保存了subVendorId,subDeviceId,IRQ LINE,IRQ PIN等等;
struct pci_device_id;
Linux 将配置空间中的venderId,deviceId,classcode,subvendorId,subdeviceId,class_mask,driver_data(kernel_ulong_t,不是必须的)
抽象为struct pci_device_id结构体,PCI驱动程序用该结构体告诉内核,本身支持什么样的PCI设备列表;
MODULE_DEVICE_TABLE(pci,ids);
使用该宏将pci驱动程序支持的设备列表导出到用户空间,供热插拔系统为设备查找驱动程序使用;
在编译模块的时候编译系统会抽取该宏数据并导出到用户空间;
4,Linux PCI设备配置空间访问:
在上面介绍的PCI规范的配置空间结构基础上,再介绍Linux 访问PCI配置空间的api操作函数:
头文件:<linux/pci.h>
读取配置空间:
int pci_read_config_byte(struct pci_dev *dev,int where,u8 *val);
int pci_read_config_word(struct pci_dev *dev,int where,u16 *val);
int pci_read_config_dword(struct pci_dev *dev,int where,u32 *val);
写入配置空间:
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);
struct pci_device 为PCI设备结构,int where 为寄存器相对配置空间起始地址的偏移量,val保存读取的值或写入的值;
参数where通常传入pci.h中定义的寄存器偏移量的符号,例如:PCI_VERSION_ID,PCI_INTERRUPT_LINE;
通过Linux resource来获取PCI设备配置空间信息:
在Linux 系统中将设备地址映射的IO端口空间或IO内存空间均抽象为资源来管理,用struct resource 数据结构来表示;
因此PCI驱动程序获取PCI设备配置空间信息无需访问底层的PCI设备配置空间,而直接通过Linux资源管理来获取配置信息:
操作如下:
unsigned long pci_resource_start(struct pci_dev *dev,int bar);
该函数返回编号为bar的寄存器映射的区域范围的起始地址,参数bar为寄存器编号(0~5);
unsigned long pci_resource_end(struct pci_dev *dev,int bar);
该函数返回编号为bar的寄存器映射的区域的结束地址,bar的编号范围(0~5);
unsigned long pci_resource_flags(struct pci_dev *dev,int bar);
该函数返回编号为bar的寄存器映射的区域标志,这些标志定义在<linux/ioport.h>中,重要的有以下几个:
IORESOURCE_IO:映射到IO端口空间;
IORESOURCE_MEM:映射到内存空间;
IORESOURCE_PREFETCH:表示该区域是否为可预取的;
IORESOURCE_READONLY:表示该区域只读;
5,Linux PCI设备驱动程序结构表示:
Linux用struct pci_driver 结构体表示驱动程序,该结构体是PCI设备与PCI设备驱动程序的联系桥梁,通过该结构体可以查询到驱动程序的设备并初始化设备;
比较重要的字段如下:
struct pci_driver{
char *name;
struct pci_device_id *ids;
int (*probe)(struct pci_dev *dev,struct pci_device_id *id);
void (*remove)(struct pci_dev *dev);
int (*suspend)(struct pci_dev *dev,u32 state);
int (*resume)(struct pci_dev *dev);
... ...
};
6,Linux PCI设备驱动程序探测及注册:
int pci_probe(struct pci_dev *dev,struct pci_device_id *id){
struct pci_privdata *data=NULL;
struct resource *resource=NULL;
if(pci_device_is_present(dev)){
//1,不支持pci总线;
}
if(pci_enable_device(dev)){
//2,使能设备;
}
//3,从配置空间获取信息;
data=kmalloc(sizeof(struct pci_privdata),GFP_KERNEL);
data->phy_addr=pci_resource_start(dev,1);
data->size =pci_resource_len(dev,1);
data->flags =pci_resource_flags(dev,1);
if(data->flags != IORESOURCE_MEM){
//4,判断是否为内存空间或IO端口空间;
}
//resource=request_mem_region(data->phy_addr,data->size,NAME);
resource=pci_request_region(data->phy_addr,data->size,NAME);
if(!resource){
//5,独占设备;
}
//6,io内存映射,映射到内核虚拟地址空间;
data->addr=ioremap(data->phy_addr,data->size);
//7,设置PCI设备私有数据;
pci_set_drvdata(dev,data);
//8,设置PCI设备为DMA主设备;
pci_set_master(dev);
return0;
}
int pci_remove(struct pci_dev *dev){
//与probe函数相反的顺序将probe函数中的资源都释放掉;
return 0;
}
//在驱动模块中注册驱动数据结构;
static int __init pci_module_init(void){
return pci_register_driver(&pci_driver);
}