PCI设备驱动知识点

来源:互联网 发布:主流编程语言排行 编辑:程序博客网 时间:2024/05/01 20:14

     申明:此文章并非本人所写,只是加以修改和备注

原帖请看:http://hi.baidu.com/linux_kernel/blog/category/pci%C9%E8%B1%B8%C7%FD%B6%AF

那位大大可是个大牛人,上面有不少好东东!

 一、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是他所映射的内存地址空间,占据了内存地址空间的1024bytes的位置,而0000:00:1f.1则是个PCI外设的地址,以冒号和逗号分隔为4个部分:

第一个16位表示域;

第二个8位表示一个总线编号,2^8-256,故每个域最多能有256个总线;

第三个5位表示一个设备号,每个总线最多能挂载32个设备;

最后是3位,表示功能号,每个设备最多能有8种功能,也就是最多能够对应8个逻辑设备,每种功能都唯一的对应一个pci_dev结构体。
   
注:因为PCI规范允许单个系统拥有高达256个总线,但对于大型系统而言,这是不够的,所以,引入了域的概念。

 

    由此,我们能得出上述的PCI设备的地址是0号域0号总线上的31号设备上的1号功能。那上述的这个PCI设备到底是什么呢?下面是我的计算机上的lspci命令的输出:
    00:00.0 Host bridge: Intel Corporation 82845 845 (Brookdale) Chipset Host Bridge (rev 04)
    00:01.0 PCI bridge: Intel Corporation 82845 845 (Brookdale) 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系统的树型结构:
0号总线 (主桥)--00:01(PCI桥)

                     |--00:1d(USB号控制器,提供了0、1这两个逻辑设备,即功能)          
                     |--00:1e:0(PCI桥)

                     |--00:1f.多功能卡(提供了0 ISA bridge、1 IDE interface、3SMBus、5 Multimedia audio controller、6 Modem这5个逻辑设备,即功能)

1号总线(主桥)--01:00.0 VGA compatible controller 

2号总线(主桥)--02:00.0 IEEE1394       
                    |--02:01.0 8139网卡         
                    |--02:04 CardBus桥(提供了桥0、1两个逻辑设备,即功能)
     由上图能得出,我的计算机上共有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控制器中的寄存器,提供了对设备设置地址空间的访问。设置地址空间的前64字节是标准化的,他提供了厂商号,设备号,版本号等信息,唯一标识一个PCI设备。同时,他也提供了最多可多达6个的I/O地址区域,每个区域能是内存也能是I/O地址。这几个I/O地址区域是驱动程式找到设备映射到内存和I/O空间的具体位置的唯一途径。有了这两点,PCI驱动程式就完成了相当于探测的功能。关于这64个字节的设置空间的周详情况,可参阅《Linux设备驱动程式第三版》P306,图12-2,不再详述。
   
下面,我们来看一下8139too网卡设备的设置空间的周详情况。在2.6内核的系统中,能在目录/sys/bus/pci/devices/下看到非常
多以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中得到验证。为了能看到实际的运行效果,我们选择8139too网卡作为示例,从该网卡的linux驱动程式中裁剪相关代码。
    一个PCI设备的驱动程式必须要向内核中的PCI核心描述自己。同时,他也必须告诉PCI核心自己能够驱动哪些设备。下面,就介绍两个相关的重要数据结构。

  /* 用于定义该驱动程序支持的不同类型的PCI设备列表*/
    struct pci_device_id {
        __u32 vendor;

        __u32  device;       /* 指定设备的PCI厂商和设备ID,如驱动程序可以处理任何厂商或设备ID,可使用值PCI_ANY_ID*/
        __u32 subvendor;

        __u32 subdevice; /* 指定设备的PCI子系统厂商和设备ID,如驱动程序可处理任何子系统厂商或设备ID,可使用PCI_ANY_ID*/
        __u32 class;

        __u32 class_mask;    /* 可使驱动程序指定一种PCI类(class)设备,如果可以处理任何类型,则使用PCI_ANY_ID */
        kernel_ulong_t driver_data; /* 如果需要,则用来保存PCI驱动程序用于区分不同设备的信息*/
    };

  /* 用于向PCI核心描述PCI驱动程序*/     
    struct pci_driver {
        struct list_head node; 

        char *name;      /* 在内核的所有PCI驱动程序的名字必须唯一,通常设置为和驱动程序模块名相同的名字*/
        struct module *owner;
        const struct pci_device_id *id_table; //驱动所能操纵的设备id列表。
        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, pm_message_t state);   //指向挂起函数,挂起状态以state传递,该函数可选
        int (*resume)(struct pci_dev *dev);    //指向恢复函数,总是在被挂起之后调用,该函数也可选
        int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);   //使能唤事件
        void (*shutdown) (struct pci_dev *dev);
        struct device_driver    driver;
        struct pci_dynids dynids;
    };
   
pci_device_id唯一标识一个PCI设备。他的几个成员依次分别表示:厂商号、设备号、子厂商号、子设备号、类别、类别掩码(类可分为基类、子类)、私有数据。每一个PCI设备的驱动程式都有一个pci_device_id的数组,用于告诉PCI核心自己能够驱动哪些设备。8139too的驱动程式定义他的pci_device_id数组如下:
        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完成驱动程式的移除工作。关键的成员就这几
个。
    驱动程式通过pci_module_init向内核注册自己(我们有时会看到pci_register_driver函数,其实他们是同一个,在内核代码中会看到,只是个简单的#define):
            pci_module_init(&pci_driver);
    调用函数后,如果pci_device_id数组中标识的设备存在于系统中,并且该设备恰好还没有驱动程式,则该驱动程式会被安装。下面我们来看从8139too驱动代码中裁剪的pci设备初始化代码:
pci_driver.h:
/* pci_driver.h
* helinqiang@hotmail.com
* 2006-3-5
*/
#ifndef PCI_DRIVER_H
#define PCI_DRIVER_H
#include   //for struct pci_device_id
#include   //for MODULE_DEVICE_TABLE
#include    //for struct pci_driver
#define DRV_NAME    "8139too"
#define DRV_VERSION "0.9.27"
#define RTL8139_DRIVER_NAME   DRV_NAME " Fast Ethernet driver " DRV_VERSION
typedef enum{
    RTL8139 = 0,
    RTL8129,
}board_t;
static struct pci_device_id rtl8139_pci_tbl[] = {
    {0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x10ec, 0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x1113, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x1500, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x4033, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x1186, 0x1300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x1186, 0x1340, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x13d1, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x1259, 0xa117, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x1259, 0xa11e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x14ea, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x14ea, 0xab07, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x11db, 0x1234, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x1432, 0x9130, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x02ac, 0x1012, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x018a, 0x0106, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x126c, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x1743, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
    {0x021b, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
#ifdef CONFIG_SH_SECUREEDGE5410
    /* Bogus 8139 silicon reports 8129 without external PROM :-( */
    {0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
#endif
#ifdef CONFIG_8139TOO_8129
    {0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8129 },
#endif
/* some crazy cards report invalid vendor ids like
     * 0x0001 here.  The other ids are valid and constant,
     * so we simply don’t match on the main vendor id.
     */
    {PCI_ANY_ID, 0x8139, 0x10ec, 0x8139, 0, 0, RTL8139 },
    {PCI_ANY_ID, 0x8139, 0x1186, 0x1300, 0, 0, RTL8139 },
    {PCI_ANY_ID, 0x8139, 0x13d1, 0xab06, 0, 0, RTL8139 },
    {0,}
};
MODULE_DEVICE_TABLE(pci, rtl8139_pci_tbl);
static int __devinit rtl8139_init_one(struct pci_dev *pdev, const struct pci_device_id *id);
static void __devexit rtl8139_remove_one(struct pci_dev *pdev);
static struct pci_driver rtl8139_pci_driver = {
    .name       = DRV_NAME,
    .id_table   = rtl8139_pci_tbl,
    .probe      = rtl8139_init_one,
    .remove     = __devexit_p(rtl8139_remove_one),
};
#endif //PCI_DRIVER_H
pci_driver.c:
/* pci_driver.c
* helinqiang@hotmail.com
* 2006-3-5
*/
#include "pci_driver.h"
#include
MODULE_AUTHOR("Linqiang He, Hangzhou China");
MODULE_LICENSE("Dual BSD/GPL");
static int __init rtl8139_init_module(void)
{
    /* when we’re a module, we always print a version message,
     * even if no 8139 board is found.
     */
#ifdef MODULE
    printk (KERN_INFO RTL8139_DRIVER_NAME "\n");
#endif
    return pci_module_init(&rtl8139_pci_driver);
}
static void __exit rtl8139_cleanup_module (void)
{
    pci_unregister_driver(&rtl8139_pci_driver);
}
module_init(rtl8139_init_module);
module_exit(rtl8139_cleanup_module);
int __devinit rtl8139_init_one(struct pci_dev *pdev, const struct pci_device_id *id)
{
    //这里可插入各种调试代码,下文会有专门描述。
    return 0;
}
void __devexit rtl8139_remove_one (struct pci_dev *pdev)
{
}
    注册驱动程式成功后,rtl8139_init_one会被调用,在这个函数中,我们能通过插入一些打印输出语句看到PCI的设置地址空间和I/O地址区域的一些情况。
    首先,插入以下语句:
            u16 vendor, device;
            pci_read_config_word(pdev, 0, &vendor);
            pci_read_config_word(pdev, 2, &device);
            printk(KERN_INFO "%x, %x\n", vendor, device);
    这段代码读取了网卡设备的设置地址空间的前四位,他正好是设备的厂商号和设备号。下面是输出:
            10ec, 8139
    10ec和8139就是我的网卡的厂商号和设备号了。
    再插入下列代码:
            u32 addr1,addr2,addr3, addr4,addr5,addr6;
            pci_read_config_dword(pdev, 16, &addr1);
            pci_read_config_dword(pdev, 20, &addr2);
            pci_read_config_dword(pdev, 24, &addr3);
            pci_read_config_dword(pdev, 28, &addr4);
            pci_read_config_dword(pdev, 32, &addr5);
            pci_read_config_dword(pdev, 36, &addr6);
           
printk(KERN_INFO "%x,%x,%x,%x,%x,%x\n",addr1, addr2, addr3,addr4,addr5,addr6);
    这段代码读取网卡设备的6个I/O地址区域的址始位置。下面是输出:
   3401,e0000800,0,0,0,0
    可见,该设备只使用了前两个I/O地址区域,分别标识他的I/O端口区域和内存地址空间。
    另外,在这里,还可直接打印出网卡的MAC地址。不再详述。

    我们能在rtl8139_init_one中插入一些不同的调试代码,观察设备驱动模块在内核中的一些动作。8139too 网卡设备的设备内存的头6个字节存放的是该网卡的48位的MAC地址,我们能通过访问设备内存得到这个MAC地址。下面通过在rtl8139_init_one在插入代码,以四种不同方式访问设备内存。第一种是通过访问I/O内存实现,后三种则是通过访问I/O端口的形式实现。
第一种:
unsigned long mmio_start, addr1, addr2;
void __iomem *ioaddr;
mmio_start = pci_resource_start( pdev, 1);
ioaddr = pci_iomap(pdev, 1, 0);
addr1 = ioread32( ioaddr );
addr2 = ioread32( ioaddr + 4 );
printk(KERN_INFO "mmio start: %lX\n", mmio_start);
printk(KERN_INFO "ioaddr: %p\n", ioaddr);
printk(KERN_INFO "%02lX.%02lX.%02lX.%02lX.%02lX.%02lX\n",
(addr1) & 0xFF,
(addr1 >> 8) & 0xFF,
(addr1 >> 16 ) & 0xFF,
(addr1 >> 24 ) & 0xFF,
(addr2) & 0xFF,
(addr2 >> 8) & 0xFF );
运行结果:
  mmio start: E0000800
  ioaddr: f8aa6800
  00.02.3F.AC.41.9D
第二种:
unsigned long pio_start, pio_len, addr1, addr2;
void __iomem *ioaddr;
pio_start = pci_resource_start( pdev, 0);
pio_len = pci_resource_len (pdev, 0);
ioaddr = ioport_map(pio_start, pio_len);
addr1 = ioread32( ioaddr );
addr2 = ioread32( ioaddr + 4 );
printk(KERN_INFO "pio start: %lX\n", pio_start);
printk(KERN_INFO "ioaddr: %p\n", ioaddr);
printk(KERN_INFO "%02lX.%02lX.%02lX.%02lX.%02lX.%02lX\n",
(addr1) & 0xFF,
(addr1 >> 8) & 0xFF,
(addr1 >> 16 ) & 0xFF,
(addr1 >> 24 ) & 0xFF,
(addr2) & 0xFF,
(addr2 >> 8) & 0xFF );
运行结果:
   pio start: 3400
   ioaddr: 00013400
   00.02.3F.AC.41.9D
第三种:
unsigned long pio_start, addr1, addr2;
pio_start = pci_resource_start( pdev, 0 );
addr1 = inl( pio_start );
addr2 = inl( pio_start + 4 );
printk(KERN_INFO "port io start: %lX\n", pio_start);
printk(KERN_INFO "%02lX.%02lX.%02lX.%02lX.%02lX.%02lX\n",
(addr1) & 0xFF,
(addr1 >> 8) & 0xFF,
(addr1 >> 16) & 0xFF,
(addr1 >> 24) & 0xFF,
(addr2) & 0xFF,
(addr2 >> 8) & 0xFF );
运行结果:
   port io start: 3400
   00.02.3F.AC.41.9D
第四种:
unsigned long pio_start;
u8 addr1, addr2, addr3, addr4, addr5, addr6;
pio_start = pci_resource_start( pdev, 0 );
addr1 = inb( pio_start );
addr2 = inb( pio_start + 1 );
addr3 = inb( pio_start + 2 );
addr4 = inb( pio_start + 3 );
addr5 = inb( pio_start + 4 );
addr6 = inb( pio_start + 5 );
printk(KERN_INFO "port io start: %lX\n", pio_start);
printk(KERN_INFO "%02X.%02X.%02X.%02X.%02X.%02X\n",
addr1, addr2, addr3, addr4, addr5, addr6 );
运行结果:
   port io start: 3400
   00.02.3F.AC.41.9D

原创粉丝点击