总线由电气接口和编程接口构成
PCI(Peripheral Component Interconnect,外围设备互联)
探测方法:“模块参数”、“自动检测IRQ编号”、PCI规范提供的方法
查看PCI:lspci、/proc/bus/pci/devices
一、PCI接口
1. 目标
获得在计算机和外设之间传输数据时更好的性能(高时钟频率),尽可能的平台无关,简化往系统中添加和删除外设的工作
2. 特性
每个PCI外设由一个总线编号、一个设备编号和一个功能编号来标识
在单个系统中插入多个总线,通过桥(bridge)来完成
IRQ线共享
PCI字节序:小头
3. 寻址
显示硬件地址:两个值(8位总线编号和8位设备及功能编号)、三个值(总线、设备、功能)、四个值(16位的域,8位的总线,5位的设备和3位的功能),以16进制显示
每个外设板的硬件电路对三种地址空间的查询进行应答:内存位置、I/O端口和配置寄存器。前两种地址空间由同一PCI总线上的所有设备共享,驱动程序以惯常的方式(inb、readb等)进行访问;而配置空间利用了物理寻址,配置事物是通过调用特定的内核函数访问配置寄存器来执行
I/O空间使用32位地址总线,内存空间使用32位或64位地址来访问,初始化PCI硬件时把每个内存和I/O地址区域映射到不同地址,可从配置空间读取这些地址(故不需探测就能访问)
配置空间中每个设备功能256字节(PCI快速设备除外,有4KB),含一个功能ID,驱动程序通过查询外设特定的ID来识别设备
与ISA相比:有配置地址空间
4. 引导阶段
PCI设备上电时硬件保持未激活状态,设备只响应配置事务。
PCI主板有能处理PCI的固件(BIOS、NVRAM、PROM)。固件通过读写PCI控制器中的寄存器,提供配置地址空间的访问。
系统引导时,固件(或内核)执行配置事务,为各个地址区域分配安全的位置(映射内存、I/O区域)
5. 配置寄存器和初始化
256字节,前64字节标准化,其余与设备相关
必需寄存器、可选寄存器
通常用三个(或五个)寄存器标志一个设备:vendorID、deviceID、class;进一步区分:subsystem vendorID、subsystem deviceID
struct pci_device_id:定义驱动程序支持的不同类型的PCI设备列表
6. MODULE_DEVICE_TABLE
pci_device_id需要导出到用户空间,便于被热插拔和模块装载系统识别
MODULE_DEVICE_TABLE(pci, i810_ids);
/*
建立指向pci_device_id的局部变量__mod_pci_device_table
在内核构建过程时,depmod程序在所有模块中搜索该符号
若找到则把模块的数据添加到 /lib/modules/KERNEL_VERSION/modules.pcimap
当内核告知热插拔系统一个新的PCI设备被发现时,热插拔系统使用modules.pcimap来寻找要装载的驱动
*/
7. 注册PCI驱动程序
struct pci_drivers结构体必须注意的字段:
const char *name;
const struct pci_device_id *id_table;
int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); 指向PCI驱动程序中探测函数的指针
void (*remove) (struct pci_dev *dev); 指向一个移除函数的指针
int (*suspend) (struct pci_dev *dev, u32 state); 可选,指向一个挂起函数的指针
int (*resume) (struct pci_dev *dev); 可选,指向一个恢复函数的指针
static struct pci_driver pci_driver = {
.name = "pci_skel",
.id_table = ids,
.probe = probe,
.remove = remove,
};
/* 初始化pci_driver结构体 */
static int __init pci_skel_init(void)
{
return pci_register_driver(&pci_driver);
}
/* 注册 */
static void __exit pci_skel_exit(void)
{
pci_unregister_driver(&pci_driver);
}
/* 卸载 */
8. 访问配置空间
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);
/*
往设备dev的配置空间读写,where是配置空间起始位置的偏移量
读时,从配置空间获得的值通过val指针返回
写时,要写入的值通过val传递
word和dword会转换字节序
*/
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);
/* 无法访问pci_dev时,用上述函数代替 */
9.访问I/O和内存空间
一个PCI设备可实现多达6个I/O地址区域,每个区域可以是内存也可以是I/O地址
标记为“内存可预取”的内存能进行各种优化
获取区域信息的接口:
unsigned long pci_resource_start(struct pci_dev *dev, int bar);
/*
返回六个PCI I/O区域之一的首地址(内存地址或I/O端口号)
该区域由bar(base address regisiter,基地址寄存器)指定,取值为0-5
*/
unsigned long pci_resource_end(struct pci_dev *dev, int bar);
/* 返回第bar个I/O区域的尾地址 */
unsigned long pci_resource_flags(struct pci_dev *dev, int bar);
/* 返回和该资源相关联的标志 */
其中资源标志有几个为:
IORESOURCE_IO、IORESOURCE_MEM
IORESOURCE_PREFETCH:可预取的
IORESOURCE_READONLY:写保护(只读)
10. PCI中断
中断号保存在配置寄存器60(PCI_INTERRUPT_LINE),允许多达256条中断线(实际受限于CPU)
处理中断的PCI特定代码只需要读取配置字节,以获取中断号
result = pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &myirq);
if (result) {
/* deal with error */
}