Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2132074
  • 博文数量: 288
  • 博客积分: 10594
  • 博客等级: 上将
  • 技术积分: 3469
  • 用 户 组: 普通用户
  • 注册时间: 2006-10-27 19:27
文章分类

全部博文(288)

文章存档

2012年(4)

2011年(30)

2010年(40)

2009年(32)

2008年(71)

2007年(79)

2006年(32)

分类: LINUX

2007-09-12 15:19:26

PCI设备驱动 三
2006年08月11日 星期五 下午 01:55
    为了能看到实际的运行效果,我们选择8139too网卡作为示例,从该网卡的linux驱动程序中裁剪相关代码。
    一个PCI设备的驱动程序必须要向内核中的PCI核心描述自己。同时,它也必须告诉PCI核心自己能够驱动哪些设备。下面,就介绍两个相关的重要数据结构。
    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 */
    };
       
    struct pci_driver {
        struct list_head node;
        char *name;
        struct module *owner;
        const struct pci_device_id *id_table; //驱动所能操纵的设备id列表。
        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_t 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);
    这段代码读取了网卡设备的配置地址空间的前四位,它正好是设备的厂商号和设备号。下面是输出:
            Mar  9 21:44:39 localhost kernel: 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地址区域的址始位置。下面是输出:
    Mar  9 21:55:06 localhost kernel: 3401,e0000800,0,0,0,0
    可见,该设备只使用了前两个I/O地址区域,分别标识它的I/O端口区域和内存地址空间。
    另外,在这里,还可直接打印出网卡的MAC地址。不再详述。
 
阅读(2081) | 评论(0) | 转发(0) |
0

上一篇:PCI设备驱动二

下一篇:PCI设备驱动四

给主人留下些什么吧!~~