PCI是外围设备互连(Peripheral Component Interconnect)的简称,作为⼀种通⽤的总线接⼝标准,它在⽬前的计算机系统中得到了⾮常⼴泛的应⽤。PCI提供了⼀组完整的总线接⼝规范,其⽬的是描述如何将计算机系统中的外围设备以⼀种结构化和可控化的⽅式连接在⼀起,同时它还刻画了外围设备在连接时的电⽓特性和⾏为规约,并且详细定义了计算机系统中的各个不同部件之间应该如何正确地进⾏交互。
⽆论是在基于Intel芯⽚的PC机中,或是在基于Alpha芯⽚的⼯作站上,PCI毫⽆疑问都是⽬前使⽤最⼴泛的⼀种总线接⼝标准。同旧式的ISA总线不同,PCI将计算机系统中的总线⼦系统与存储⼦系统完全地分开,CPU通过⼀块称为PCI桥(PCI-Bridge)的设备来完成同总线⼦系统的交互,如图1所⽰。
图1 PCI⼦系统的体系结构
图1 PCI⼦系统的体系结构
由于使⽤了更⾼的时钟频率,因此PCI总线能够获得⽐ISA总线更好的整体性能。PCI总线的时钟频率⼀般在25MHz到33MHz范围内,有些甚⾄能够达到66MHz或者133MHz,⽽在64位系统中则最⾼能达到26
6MHz。尽管⽬前PCI设备⼤多采⽤32位数据总线,但PCI规范中已经给出了64位的扩展实现,从⽽使PCI总线能够更好地实现平台⽆关性,现在PCI总线已经能够⽤于IA-32、Alpha、PowerPC、SPARC64和IA-64等体系结构中。
PCI总线具有三个⾮常显著的优点,使得它能够完成最终取代ISA总线这⼀历史使命:
在计算机和外设间传输数据时具有更好的性能;
能够尽量独⽴于具体的平台;
可以很⽅便地实现即插即⽤。
图2是⼀个典型的基于PCI总线的计算机系统逻辑⽰意图,系统的各个部分通过PCI总线和PCI-PCI桥连接在⼀起。从图中不难看出,CPU和RAM需要通过PCI桥连接到PCI总线0(即主PCI总线),⽽具有PCI接⼝的显卡则可以直接连接到主PCI总线上。PCI-PCI桥是⼀个特殊的PCI设备,它负责将PCI总线0和PCI总线1(即从PCI主线)连接在⼀起,通常PCI总线1称为PCI-PCI桥的下游(downstream),⽽PCI总线0则称为PCI-PCI桥的上游(upstream)。图中连接到从PCI总线上的是SCSI卡和以太⽹卡。为了兼容旧的ISA总线标准,PCI总线还可以通过PCI-ISA桥来连接ISA总线,从⽽能够⽀持以前的ISA设备。图中ISA总线上连接着⼀个多功能I/O控制器,⽤于控制键盘、⿏标和软驱。
图2 PCI系统⽰意图
图2 PCI系统⽰意图
⼆,和PCI驱动程序相关的⼏个数据结构
驱动程序总是离不开数据结构,在Linux中,⽤数据结构来表⽰各⾊各样的设备或者其他的东西。因此,我们掌握设备驱动程序的关键之⼀,就是对各种数据结构的理解和运⽤。
1.pci_device_id
在介绍该结构之前,让我们来看看PCI的地址空间:I/O空间,存储空间,配置空间。
CPU 可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使⽤,⽽配置空间则由Linux内核中的PCI初始化代码使⽤,内核在启动时负责对所有PCI设备进⾏初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在⽂件/proc/pci中列出所有到的PCI设备,以及这些我设备的参数和属性。
下图是PCI配置寄存器。
我们并不需要去了解配置寄存器的所有位代表了什么,有什么含义。我们只要⽤三个或者五个PCI寄存器去标识⼀个设备即可。通常,我们会选择下⾯三个寄存器:
vendorID:标识硬件制造商,是⼀个16位的寄存器。
deviceID:设备ID,由制造商选择,也是⼀个16位的寄存器。⼀般与⼚商ID配对⽣成⼀个唯⼀的32位硬件设备标识符。
class:每个外部设备属于某个类(class),也是⼀个16位的寄存器。当某个驱动程序可⽀持多个相似的设备,每个具有不同的签名,但都属于同⼀个类,这时,就可以⽤class类对它们的外设进⾏识别。
讲了这么多,那我们应该怎么去设置这些值呢?不⽤怕,内核已经为我们都想好了,它已经将这些都归纳到⼀个数据结构进去了,我们要做的就是对这个数据结构进⾏填充,是不是很⽅便啊!
这个数据结构就是--pci_device_id。
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 */
};
那现在问题⼜来了,我们前⾯说过,⼀个驱动程序可以匹配⼀个甚⾄多个设备。那么,此时我们⼜该如何呢?可以想到数组,对吧。是的,不过这⾥有点地⽅需要注意
staticstruct pci_device_id example_pci_tbl [] __initdata ={
{PCI_VENDOR_ID_EXAMPLE, PCI_DEVICE_ID_EXAMPLE, PCI_ANY_ID, PCI_ANY_ID,0,0, EXAMPLE},
{0,}
};
注意到了吧,是的,不管你这⾥匹配了多少设备,记得最后⼀个都是{0,}。
这⾥还有两个关于初始化该结构体的宏,可以⽤来简化相关的操作。
PCI_DEVICE(vendor, device)
创建⼀个仅和特定⼚商及设备ID相匹配的struct pci_device_id。它把结构体的subvendor和subdevice设为PCI_ANY_ID。
PCI_ANY_ID定义如下:
#define PCI_ANY_ID (~0)
PCI_DEVICE_CLASS(device_class, device_class_mask)
创建⼀个和特定PCI类相匹配的struct pci_device_id。
这⾥不再多说。
2.pci_driver
按照上⾯说的,你已经将你要匹配的设备说明了,但这仅仅只是说明,内核如何去识别它们呢?那就要⽤到下⾯的数据结构了--
pci_driver。
struct pci_driver {
struct list_head node;
char*name;
conststruct pci_device_id *id_table;/* must be non-NULL for probe to be called */
int(*probe)(struct pci_dev *dev,conststruct pci_device_id *id);/* New device inserted */
void(*remove)(struct pci_dev *dev);/* Device removed (NULL if not a hot-plug capable driver) */
int(*suspend)(struct pci_dev *dev,pm_message_t state);/* Device suspended */
int(*suspend_late)(struct pci_dev *dev,pm_message_t state);
int(*resume_early)(struct pci_dev *dev);
int(*resume)(struct pci_dev *dev);/* Device woken up */
void(*shutdown)(struct pci_dev *dev);
struct pci_error_handlers *err_handler;
struct device_driver driver;
struct pci_dynids dynids;
};
从上⾯的结构体定义可以看出,它的作⽤并不仅仅是识别设备的id_table结构,还包括了检测设备的函数probe( )和卸载设备的函数remove( ):这种结构体,我们之前就已经接触过很多了,不再多说。
3.pci_dev
让我们来最后最后⼀个相关的数据结构--pci_dev。
/*
* The pci_dev structure is used to describe PCI devices.
*/
struct pci_dev {
struct list_head bus_list;/* node in per-bus list */
struct pci_bus *bus;/* bus this device is on */
struct pci_bus *subordinate;/* bus this device bridges to */
void*sysdata;/* hook for sys-specific extension */
struct proc_dir_entry *procent;/* device entry in /proc/bus/pci */
struct pci_slot *slot;/* Physical slot this device is in */
unsignedint devfn;/* encoded device & function index */
unsignedshort vendor;
防鼠器unsignedshort device;
unsignedshort subsystem_vendor;
微安表
unsignedshort subsystem_device;
unsignedintclass;/* 3 bytes: (base,sub,prog-if) */
u8 revision;/* PCI revision, low byte of class word */
u8 hdr_type;/* PCI header type (`multi' flag masked out) */
u8 pcie_cap;/* PCI-E capability offset */
u8 pcie_type;/* PCI-E device/port type */绝缘阻抗测试
u8 rom_base_reg;/* which config register controls the ROM */
u8 pin;/* which interrupt pin this device uses */
struct pci_driver *driver;/* which driver has allocated this device */
u64 dma_mask;/* Mask of the bits of bus address this
device implements. Normally this is
0xffffffff. You only need to change
this if your device has broken DMA
or supports 64-bit transfers. */
struct device_dma_parameters dma_parms;
pci_power_t current_state;/* Current operating state. In ACPI-speak,
this is D0-D3, D0 being fully functional,
and D3 being off. */
int pm_cap;/* PM capability offset in the
configuration space */
unsignedint pme_support:5;/* Bitmask of states from which PME#
can be generated */
unsignedint pme_interrupt:1;
精油与肌肤百度影音
unsignedint d1_support:1;/* Low power state D1 is supported */
unsignedint d2_support:1;/* Low power state D2 is supported */
unsignedint no_d1d2:1;/* Only allow D0 and D3 */
unsignedint mmio_always_on:1;/* disallow turning off io/mem
decoding during bar sizing */
unsignedint wakeup_prepared:1;
unsignedint d3_delay;/* D3->D0 transition time in ms */
#ifdef CONFIG_PCIEASPM
struct pcie_link_state *link_state;/* ASPM link state. */
#endif
pci_channel_state_t error_state;/* current connectivity state */
struct device dev;/* Generic device interface */
int cfg_size;/* Size of configuration space */
int cfg_size;/* Size of configuration space */
/*
* Instead of touching interrupt line and base address registers
* directly, use the values stored here. They might be different!
*/
unsignedint irq;
struct resource resource[DEVICE_COUNT_RESOURCE];/* I/O and memory regions + expansion ROMs */
resource_size_t fw_addr[DEVICE_COUNT_RESOURCE];/* FW-assigned addr */
/* These fields are used by common fixups */
unsignedint transparent:1;/* Transparent PCI bridge */
unsignedint multifunction:1;/* Part of multi-function device */
/* keep track of device state */
unsignedint is_added:1;
unsignedint is_busmaster:1;/* device is busmaster */
unsignedint no_msi:1;/* device may not use msi */
unsignedint block_ucfg_access:1;/* userspace config space access is blocked */
unsignedint broken_parity_status:1;/* Device generates false positive parity */
unsignedint irq_reroute_variant:2;/* device needs IRQ rerouting variant */
unsignedint msi_enabled:1;
unsignedint msix_enabled:1;
unsignedint ari_enabled:1;/* ARI forwarding */
unsignedint is_managed:1;
unsignedint is_pcie:1;/* Obsolete. Will be removed.
Use pci_is_pcie() instead */
unsignedint needs_freset:1;/* Dev requires fundamental reset */
unsignedint state_saved:1;
unsignedint is_physfn:1;
unsignedint is_virtfn:1;
unsignedint reset_fn:1;
unsignedint is_hotplug_bridge:1;
unsignedint __aer_firmware_first_valid:1;
unsignedint __aer_firmware_first:1;
pci_dev_flags_t dev_flags;
atomic_t enable_cnt;/* pci_enable_device has been called */
u32 saved_config_space[16];/* config space saved at suspend time */
struct hlist_head saved_cap_space;
struct bin_attribute *rom_attr;/* attribute descriptor for sysfs ROM entry */
int rom_attr_enabled;/* has display of the rom attribute been enabled? */
struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE];/* sysfs file for resources */
struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE];/* sysfs file for WC mapping of resources */
#ifdef CONFIG_PCI_MSI
光纤环网struct list_head msi_list;
rs232和ttl
#endif
struct pci_vpd *vpd;
#ifdef CONFIG_PCI_IOV
union{
struct pci_sriov *sriov;/* SR-IOV capability related */
struct pci_dev *physfn;/* the PF this VF is associated with */
};
struct pci_ats *ats;/* Address Translation Service */
#endif
};
由上⾯的定义可以知道,它详细描述了⼀个PCI设备⼏乎所有的硬件信息,包括⼚商ID、设备ID、各种资源等:
三,基本框架
上⾯将我们要⽤到的⼀些基本信息都做了⼀些简单的介绍。下⾯,我们就来看看PCI驱动程序的⼀个基本的框架,如何将这些东西进⾏整理成⼀个程序。
1 staticstruct pci_device_id example_pci_tbl [] __initdata ={
2 {PCI_VENDOR_ID_EXAMPLE, PCI_DEVICE_ID_EXAMPLE, PCI_ANY_ID, PCI_ANY_ID,0,0, EXAMPLE},
3 {0,}
4 };
5/* 对特定PCI设备进⾏描述的数据结构 */
6struct example_pci {
7 unsignedint magic;
8/* 使⽤链表保存所有同类的PCI设备 */
9struct example_pci *next;
10
11/* ... */
12 }
13/* 中断处理模块 */
14 staticvoid example_interrupt(int irq,void*dev_id,struct pt_regs *regs)
15 {
16/* ... */
17 }
18/* 设备⽂件操作接⼝ */
19 staticstruct file_operations example_fops ={
20 owner: THIS_MODULE,/* demo_fops所属的设备模块 */
21 read: example_read,/* 读设备操作*/
22 write: example_write,/* 写设备操作*/
23 ioctl: example_ioctl,/* 控制设备操作*/
24 open: example_open,/* 打开设备操作*/
25 release: example_release /* 释放设备操作*/
26/* ... */
27 };
28/* 设备模块信息 */
29 staticstruct pci_driver example_pci_driver ={
30 name: example_MODULE_NAME,/* 设备模块名称 */
31 id_table: example_pci_tbl,/* 能够驱动的设备列表 */
32 probe: example_probe,/* 查并初始化设备 */
33 remove: example_remove /* 卸载设备模块 */
34/* ... */
35 };
36 staticint __init example_init_module (void)
37 {
38/* ... */
39 }
40 staticvoid __exit example_cleanup_module (void)
41 {
42 pci_unregister_driver(&demo_pci_driver);
43 }
44/* 加载驱动程序模块⼊⼝ */
45 module_init( example_init_module);
46/* 卸载驱动程序模块⼊⼝ */
47 module_exit( example_cleanup_module);
继续探究PCI驱动程序实现:
⼀,初始化设备模块
当Linux内核启动并完成对所有PCI设备进⾏扫描、登录和分配资源等初始化操作的同时,会建⽴起系统中所有PCI设备的拓扑结构,此后当PCI驱动程序需要对设备进⾏初始化时,⼀般都会调⽤如下的代码:
static int __init example_init_module (void)
{
/* 注册硬件驱动程序 */
if(!pci_register_driver(&example_pci_driver)){