PCI设备linux驱动(一)

时间:2022-04-05 10:29:28

一、PCI简介
    PCI是一种外设总线规范。我们先来看一下什么是总线:总线是一种传输信号的路径或信道。典型情况是,总线是连接于一个或多个导体的电气连线,总线上连接的所有设备可在同一时间收到所有的传输内容。总线由电气接口和编程接口组成。本文讨论Linux 下的设备驱动,所以,重点关注编程接口。
    PCI是Peripheral Component Interconnect(外围设备互联)的简称,是普遍使用在桌面及更大型的计算机上的外设总线。PCI架构被设计为ISA标准的替代品,它有三个主要目标:获得在计算机和外设之间传输数据时更好的性能;尽可能的平台无关;简化往系统中添加和删除外设的工作。

二、PCI寻址
        我们先来看一个例子,我的电脑装有1G的RAM,1G以后的物理内存地址空间都是外部设备IO在系统内存地址空间上的映射。/proc/iomem描述了系统中所有的设备I/O在内存地址空间上的映射。我们来看地址从1G开始的第一个设备在/proc/iomem中是如何描述的:
            40000000-400003ff : 0000:00:1f.1
    这是一个PCI设备,40000000-400003ff是它所映射的内存地址空间,占据了内存地址空间的1024 bytes的位置,而0000:00:1f.1则是一个PCI外设的地址,它以冒号和逗号分隔为4个部分,第一个16位表示域,第二个8位表示一个总线编号,第三个5位表示一个设备号,最后是3位,表示功能号。
    因为PCI规范允许单个系统拥有高达256个总线,所以总线编号是8位。但对于大型系统而言,这是不够的,所以,引入了域的概念,每个PCI域可以拥有最多256个总线,每个总线上可支持32个设备,所以设备号是5位,而每个设备上最多可有8种功能,所以功能号是3位。由此,我们可以得出上述的PCI设备的地址是0号域0号总线上的31号设备上的1号功能。

    那上述的这个PCI设备到底是什么呢?下面是我的电脑上的lspci命令的输出:
    00:00.0 Host bridge: Intel Corporation 82845 845 (*dale) Chipset Host Bridge (rev 04)
    00:01.0 PCI bridge: Intel Corporation 82845 845 (*dale) Chipset AGP Bridge(rev 04)
    00:1d.0 USB Controller: Intel Corporation 82801CA/CAM USB (Hub #1) (rev 02)
    00:1d.1 USB Controller: Intel Corporation 82801CA/CAM USB (Hub #2) (rev 02)
    00:1e.0 PCI bridge: Intel Corporation 82801 Mobile PCI Bridge (rev 42)
    00:1f.0 ISA bridge: Intel Corporation 82801CAM ISA Bridge (LPC) (rev 02)
    00:1f.1 IDE interface: Intel Corporation 82801CAM IDE U100 (rev 02)
    00:1f.3 SMBus: Intel Corporation 82801CA/CAM SMBus Controller (rev 02)
    00:1f.5 Multimedia audio controller:Intel Corporation 82801CA/CAM AC'97 Audio Controller (rev 02)
    00:1f.6 Modem: Intel Corporation 82801CA/CAM AC'97 Modem Controller (rev 02)
    01:00.0 VGA compatible controller: nVidia Corporation NV17 [GeForce4 420 Go](rev a3)
    02:00.0 FireWire (IEEE 1394): VIA Technologies, Inc. IEEE 1394 Host Controller(rev 46)
    02:01.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+(rev 10)
    02:04.0 CardBus bridge: O2 Micro, Inc. OZ6933 Cardbus Controller (rev 01)
    02:04.1 CardBus bridge: O2 Micro, Inc. OZ6933 Cardbus Controller (rev 01)
    lspci没有标明域,但对于一台PC而言,一般只有一个域,即0号域。通过这个输出我们可以看到它是一个IDE interface。由上述的输出可以看到,我的电脑上共有3个PCI总线(0号,1号,2号)。在单个系统上,插入多个总线是通过桥(bridge)来完成的,桥是一种用来连接总线的特殊PCI外设。所以,PCI系统的整体布局组织为树型,我们可以通过上面的lspci输出,来画出我的电脑上的PCI系统的树型结构:
00:00.0(主桥)--00:01.0(PCI桥)-----01:00:0(nVidia显卡)
                  |
                  |---00:1d(USB控制器)--00:1d:0(USB1号控制器)
                  |                   |
                  |                   |--00:1d:1(USB2号控制器)                   
                  |-00:1e:0(PCI桥)--02:00.0(IEEE1394)
                  |               |
                  |               |-02:01.0(8139网卡)
                  |               |
                  |               |-02:04(CardBus桥)-02:04.0(桥1)
                  |                                  |
                  |                                  |--02:04.1(桥2)
                  |
                  |-00:1f(多功能板卡)-00:1f:0(ISA桥)
                                       |
                                       |--00:1f:1(IDE接口)
                                       |
                                       |--00:1f:3(SMBus)
                                       |
                                       |--00:1f:5(多媒体声音控制器)
                                       |
                                       |--00:1f:6(调制解调器)
    由上图可以得出,我的电脑上共有8个PCI设备,其中0号总线上(主桥)上连有4个,1号总线上连有1个,2号总线上连有3个。00:1f是一个连有5个功能的多功能板卡。
    每一个PCI设备都有它映射的内存地址空间和它的I/O区域,这点是比较容易理解的。除此之外,PCI设备还有它的配置寄存器。有了配置寄存器,PCI的驱动程序就不需要探测就能访问设备。配置寄存器的布局是标准化的,配置空间的4个字节含有一个独一无二的功能ID,因此,驱动程序可通过查询外设的特定ID来识别其设备。所以,PCI接口标准在ISA之上的主要创新在于配置地址空间。

        前文已讲过,PCI驱动程序不需要探测就能访问设备,而这得益于配置地址空间。在系统引导阶段,PCI硬件设备保持未激活状态,但每个PCI主板均配备有能够处理PCI的固件,固件通过读写PCI控制器中的寄存器,提供了对设备配置地址空间的访问。
      配置地址空间的前64字节是标准化的,它提供了厂商号,设备号,版本号等信息,唯一标识一个PCI设备。同时,它也提供了最多可多达6个的I/O地址区域,每个区域可以是内存也可以是I/O地址。这几个I/O地址区域是驱动程序找到设备映射到内存和I/O空间的具体位置的唯一途径。有了这两点,PCI驱动程序就完成了相当于探测的功能。关于这64个字节的配置空间的详细情况,可参阅《Linux设备驱动程序第三版》P306,不再详述。
        下面,我们来看一下8139too网卡设备的配置空间的详细情况。在2.6内核的系统中,可以在目录/sys/bus/pci/drivers/下看到很多以PCI设备名命名的目录,但不是说这些设备都存在于你的系统中。我们进入8139too目录,其中有一个以它的设备地址0000:02:01.0命名的目录。在这个目录下可以找到该网卡设备相关的很多信息。其中resource记录了它的6个I/O地址区域。内容如下:
        0x0000000000003400 0x00000000000034ff    0x0000000000000101
        0x00000000e0000800 0x00000000e00008ff    0x0000000000000200
        0x0000000000000000 0x0000000000000000 0x0000000000000000
        0x0000000000000000 0x0000000000000000 0x0000000000000000
        0x0000000000000000 0x0000000000000000 0x0000000000000000
        0x0000000000000000 0x0000000000000000 0x0000000000000000
        0x0000000000000000 0x0000000000000000 0x0000000000000000
        由该文件可以看出,8139too设备使用了两个I/O地址区域,第一个是它映射的I/O端口范围,第二个是它映射的内存地址空间。关于这两个值可以在/proc/iomem和/proc/ioport中得到验证。

        一个PCI设备的驱动程序必须要向内核中的PCI核心描述自己。同时,它也必须告诉PCI核心自己能够驱动哪些设备。下面,就介绍两个相关的重要数据结构。 

PCI设备linux驱动(一)PCI设备linux驱动(一)struct  pci_device_id ... ... {
PCI设备linux驱动(一)PCI设备linux驱动(一)        __u32 vendor, device;            
/**//**//**//* Vendor and device ID or PCI_ANY_ID*/
PCI设备linux驱动(一)PCI设备linux驱动(一)        __u32 subvendor, subdevice; 
/**//**//**//* Subsystem ID's or PCI_ANY_ID */
PCI设备linux驱动(一)PCI设备linux驱动(一)        __u32 
class, class_mask;       /**//**//**//* (class,subclass,prog-if) triplet */
PCI设备linux驱动(一)PCI设备linux驱动(一)        kernel_ulong_t driver_data;     
/**//**//**//* Data private to the driver */
PCI设备linux驱动(一)}
;
PCI设备linux驱动(一)        
PCI设备linux驱动(一)PCI设备linux驱动(一)
struct  pci_driver ... ... {
PCI设备linux驱动(一)        
struct list_head node;
PCI设备linux驱动(一)        
char *name;
PCI设备linux驱动(一)        
struct module *owner;
PCI设备linux驱动(一)        
const struct pci_device_id *id_table;                          //驱动所能操纵的设备id列表。
PCI设备linux驱动(一)
        int (*probe)(struct pci_dev *dev, const struct pci_device_id *id); //插入新设备
PCI设备linux驱动(一)
        void (*remove)(struct pci_dev *dev);                         //移除设备。
PCI设备linux驱动(一)
        int (*suspend)(struct pci_dev *dev, pm_message_t state);
PCI设备linux驱动(一)        
int (*resume)(struct pci_dev *dev);
PCI设备linux驱动(一)        
int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);
PCI设备linux驱动(一)        
void (*shutdown) (struct pci_dev *dev);
PCI设备linux驱动(一)        
struct device_driver driver;
PCI设备linux驱动(一)        
struct pci_dynids dynids;
PCI设备linux驱动(一)}
;

pci_device_id唯一标识一个PCI设备。它的几个成员依次分别表示:厂商号,设备号,子厂商号,子设备号,类别,类别掩码(类可分为基类,子类),私有数据。

        每一个PCI设备的驱动程序都有一个pci_device_id的数组,用于告诉PCI核心自己能够驱动哪些设备。8139too的驱动程序定义它的pci_device_id数组如下:

PCI设备linux驱动(一)static   struct  pci_device_id rtl8139_pci_tbl[];

        该数组被初始化为8139系列的一组网卡,当PCI核心得到这个数组后,会拿数组中的每一项跟从PCI配置空间中读取到的数据进行比对,从而为该驱动程序找到正确的设备。而pci_driver代表一个pci驱动程序。成员id_talbe即是指向pci_device_id数组的指针。name是驱动程序的名字,probe完成探测工作,即拿pci_device_id数组与内核中的数据进行比对。remove完成驱动程序的移除工作。关键的成员就这几个。