Peripheral ComponentInterconnect (PCI,外围设备互联)。总线由电气接口、编程接口组成。主要讨论编程接口。最常用的总线,内核支持最好的总线。ISA裸金属总线,电子爱好者偏爱。
是一种完整的规范,定义计算机计算机不同部分之间的通信。
获取、访问PCI设备。
对比ISA总线三个目标:
比ISA有更好的性能。
尽可能平台无关的。
简化系统添加、删除外设。
支持32位、64位数据总线。
对驱动编写者最相关的是接口板的自动发现。PCI设备是无跳线设备,在系统引导阶段自动配置。包括设备的配置信息等一些工作都是自动完成的,不需要任何的探测。之后,驱动编写者就能够访问设备的配置信息,以便初始化设备。
每个PCI外设由bus:device.function一个16位地址标识。bus(8位)、device(5位)、function(3位)。单个总共256个总线、每个总线最多32个设备、每个设备最多8个功能(比如声音功能)。linux为了扩展总线数量,提供domain(16位)。系统把PCI设备抽象为pci_dev结构,因此不需要访问这些二进制地址。
当前的工作站一般都配有2个以上的pci总线。不同PCI总线之间通过PCI桥连接(一个特殊的PCI设备)。PCI系统的整体布局是一个树状的结构。每个总线都连接上一级总线,一直到根总线0.
可以使用lspci命令查看当前系统的pci。或者在文件系统/proc/pci和/proc/bus/pci中。
外设板电路响应三种地址空间:内存、IO、配置空间。前两种地址空间在同一个PCI总线上是共享的。配置空间是物理寻址的,每次只对一个槽寻址。
内存和IO空间通常通过inb、readb等方式访问。配置空间需要通过特殊的内核函数访问配置寄存器。每个PCI槽有4个中断引脚,每个设备功能使用其中的一个。
1个PCI总线使用32位的地址总线用于IO寻址(4G),32位的地址总线(现在设备有的支持64位)用于内存寻址。在系统启动阶段,固件初始化PCI硬件的时候,把每个区域映射到不同的地址。驱动程序不需要探测,而从配置空间读取映射的地址。
对于每个设备功能,PCI配置空间由256字节组成(PCIE的是64KB),并且配置空间的布局是标准的。配置空间的4个字节(哪4个字节?)标识唯一的功能ID。
主板上的固件(比如BIOS),读写PCI设备中的寄存器,访问配置空间。
系统引导阶段,linux内核为每个PCI地址区域申请安全的处理器地址。后续驱动可以从/sys/bus/pci/devices/*目录中读取映射的地址。
$ tree /sys/bus/pci/devices/0000:00:10.0
/sys/bus/pci/devices/0000:00:10.0
|-- class
|-- config
|-- detach_state
|-- device
|-- irq
|-- power
| `-- state
|-- resource
|-- subsystem_device
|-- subsystem_vendor
`-- vendor
PCI配置寄存器包括可选和必需两部分,必需的部分声明功能和其他字段是否可用。
PCI寄存器是小端字节序。
全局性、全球性。16位标识。比如intel的0x8086.
厂商定义的16位标识。通常使用vendorID+deviceID 32位标识一个设备。驱动根据该32位标识,定位到一个设备。
16位的标识,高8位标识基本类(group)。比如,以太网、令牌环网属于网络group,串行、并行属于通信group。一些驱动支持多种相同类型的设备,驱动可以根据类型区分支持的设备。
subsystem类型的标识,用于进一步识别设备。当一个芯片是连接到本地板载上的通用芯片时,它可能有多用用途。驱动使用subsystem标识,识别具体连接的设备。
内核标识设备ID的结构是:
struct pci_device_id {
__u32vendor, device; /* Vendorand device ID or PCI_ANY_ID*/
__u32subvendor, subdevice; /* Subsystem ID'sor PCI_ANY_ID */
__u32class, class_mask; /*(class,subclass,prog-if) triplet */
kernel_ulong_tdriver_data; /* Data private to thedriver */
};通过把pci_device_id结构导出到用户空间中,使热插拔和模块加载系统知道什么模块对应什么设备。
例如:
MODULE_DEVICE_TABLE(pci, i810_ids);
具体的实现是:
extern const typeof(name)__mod_##type##__##name##_device_table \
__attribute__ ((unused, alias(__stringify(name))))
其中pci是模块名,i810_ids是pci_device_id变量名。MODULE_DEVICE_TABLE宏把例如i810_ids的变量名,起一个__mod_pci_device_table结构的别名。模块编译之后,在对应的模块ELF文件中会有相应的__mod_pci_device_table结构符号。在内核构建时,depmod搜索所有模块的类似__mod_pci_device_table结构的符号,从中解析出type和name,并取出pci_device_id数据导出到/lib/modules/KERNEL_VERSION/modules.pcimap文件中。之后内核所有模块支持的设备和模块的名字可在该文件中找到。当内核告知热插拔系统,发现一个新的设备时,热插拔系统根据modules.pcimap文件找到对应的驱动。
注:模块的概念。PCI是一个模块。
为了正确的注册到内核,PCI驱动必须创建一个结构:
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*/
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-plugcapable driver) */
int (*suspend)(struct pci_dev*dev, pm_message_tstate); /* Device suspended */
int (*suspend_late)(struct pci_dev*dev, pm_message_tstate);
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);/* PF pdev */
const struct pci_error_handlers *err_handler;
struct device_driver driver;
struct pci_dynids dynids;
};
包括一些回调函数和描述PCI驱动与PCI核心对应的变量。
其中一些区域需要PCI驱动注意。
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_tstate);
int (*resume)(struct pci_dev*dev);
总的来说,一个PCI驱动结构只需要4个区域被初始化。
static struct pci_driver pci_driver = {
.name ="pci_skel",
.id_table = ids,
.probe = probe,
.remove =remove,
};
通常在模块初始化代码中,注册pci驱动。比如:
static int __init pci_skel_init(void)
{
return pci_register_driver(&pci_driver);
}
2.6更新后,在支持PCI热插拔、或CardBus系统上,PCI设备可以出现在任何时刻。
在系统运行时刻,通过写值到驱动的new_id中,指定驱动支持的新设备(原内核未认知的设备)。
当PCI驱动被卸载时,需要调用pci_unregister_driver。例如:
static void __exit pci_skel_exit(void)
{
pci_unregister_driver(&pci_driver);
}
在PCI的探测函数中,在驱动访问PCI设备的任何资源之前(IO区域或资源),驱动程序必须调用函数:
int pci_enable_device(struct pci_dev *dev);
用来激活设备。
在驱动监测到设备之后,通常需要访问三个区域:内存、IO区域、配置空间。
访问配置空间尤其重要,因为需要通过配置空间找到内存区域映射和IO区域映射。
linux提供了一套访问配置空间的标准接口。
对驱动而言,可通过8、16、32位数据传输访问配置空间。相关的函数定义在
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);
dev:访问设备的逻辑表示
where:要读取位置在配置空间中的位移
*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:写入的值
不需要考虑字节序,会自动转换。
在驱动未获得pci_dev时,可使用如上函数读写配置空间
int pci_bus_read_config_byte (structpci_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 (structpci_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_read_系列函数,例如:
static unsigned charskel_get_revision(struct pci_dev *dev)
{
u8 revision;
pci_read_config_byte(dev,PCI_REVISION_ID, &revision);
return revision;
}
一个PCI设备最多可实现6个IO地址区域。每个区域可以是内存或者IO地址。大多数设备在内存区域实现IO寄存器,这也是一个明智的方法。需要注意的是,和常规内存不同,IO寄存器不应该由CPU缓存,因为每次访问都可能边缘效应。为了取消这个默认设置,内存区域实现的IO寄存器,可以通过在其配置寄存器中设置“memory-is-prefetchable”。若是可预取的,CPU可缓存其内容并进行各种优化。若不是可预取的,则不能优化,因为每次访问都有边际效应,就行IO端口一样。
接口板(PCI设备)通过6个32位的寄存器(PCI_BASE_ADDRESS_0到PCI_BASE_ADDRESS_5)声明区域的大小和位置。所以最多实现6个IO地址区域。因为PCI的IO地址空间是32位的,所以不管是内存或IO区域使用相同的配置接口是有道理的。如果设备的数据总线是64位的,那么每个区域使用两个连续的32位寄存器实现。一个PCI设备既提供32位区域又提供64位区域是有可能的。
内核已经把PCI设备的IO区域信息映射进了通用资源管理中。所以,不需要通过访问配置寄存器来获取IO区域信息。可以通过访问/sys/bus/pci/devices/*/resource的内容获取。但首选的方法是通过下列函数获取。
unsigned long pci_resource_start(structpci_dev *dev, int bar);
unsigned long pci_resource_end(structpci_dev *dev, int bar);
bar:指定要获取的区域(0到5)
unsigned long pci_resource_flags(structpci_dev *dev, int bar);
资源flag用来定义某个区域的特性。其中几个重要的标志如下:
IORESOURCE_IO
IORESOURCE_MEM
IORESOURCE_PREFETCH
IORESOURCE_READONLY(PCI资源从不设置该标志)
驱动程序不需要访问配置寄存器去获得这些资源信息,因为系统已经构建了这些资源信息,驱动直接使用pci_resource_系列函数获取即可。
在linux系统启动时,已经为PCI设备分配了一个唯一的中断号,位于配置空间的第60寄存器(PCI_INTERRUPT_LINE),一个字节长度,最多256个中断号。第61个寄存器(PCI_INTERRUPT_PIN)说明PCI设备是否支持中断,如果不支持,则为0,如果支持,非0.
如果是非0的,PCI_INTERRUPT_PIN的值是中断引脚的编号()。
驱动通过下面代码读取中断号,以便使用:
result = pci_read_config_byte(dev,PCI_INTERRUPT_LINE, &myirq);
if (result) {
/* deal witherror */
}
linux中:
先module初始化,内含pci初始化(对于pci设备而言)