原文>>>http://blog.chinaunix.net/u2/67414/showart_1657718.html
为了能看到实际的运行效果,我们选择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
* [email protected]
* 2006-3-5
*/
#ifndef PCI_DRIVER_H
#define PCI_DRIVER_H
#include <linux/mod_devicetable.h> //for struct pci_device_id
#include <linux/module.h> //for MODULE_DEVICE_TABLE
#include <linux/pci.h> //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
* [email protected]
* 2006-3-5
*/
#include "pci_driver.h"
#include <linux/init.h>
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地址。不再详述。
接着上文给出的源代码,我们可以在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 );
运行结果:
Mar 10 22:34:56 localhost kernel: mmio start: E0000800
Mar 10 22:34:56 localhost kernel: ioaddr: f8aa6800
Mar 10 22:34:56 localhost kernel: 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 );
运行结果:
Mar 10 22:30:52 localhost kernel: pio start: 3400
Mar 10 22:30:52 localhost kernel: ioaddr: 00013400
Mar 10 22:30:52 localhost kernel: 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 );
运行结果:
Mar 10 22:36:18 localhost kernel: port io start: 3400
Mar 10 22:36:18 localhost kernel: 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 );
运行结果:
Mar 10 22:37:19 localhost kernel: port io start: 3400
Mar 10 22:37:19 localhost kernel: 00.02.3F.AC.41.9D
补充:>>>>>>>>>>>>>>>>>>>>>>>>
I/O端口是驱动程序与许多设备 之间的通信方式,LINUX 的内核为我们提供了I/O端口分配的操作接口,但对PCI设备来讲,它的配置地址空间已经为其指定了I/O端口范围,不需要额外的分配操作。Linux内核
提供了如下一些访问I/O端口的内联函数:
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
下面我们重点来看一下2.6内核引入的ioport_map函数:
void *ioport_map( unsigned long port, unsigned int count );
通过这个函数,可以把port开始的count个连续端口重映射为一段“内存空间”。然后就可以在其返回的地址上象访问I/O内存一样访问这几个I/O端口。当不需要这种映射时,需要调用下面的函数来撤消:
void iport_unmap (void *addr);
除了I/O端口之外,和设备通信的另一种主要机制是通过使用映射到内存的寄存器或设备内存。这两种都称为I/O内存,因为寄存器和内存的差别对软件 是透明的。
对于分配好的I/O内存,一般不鼓励直接使用指向I/O内存的指针进行访问,最好通过页表,用包装函数访问。要通过页表访问,那么需要对分配好的I/O内存进行映射,确保该I/O内存对内核而言是可访问的。完成I/O内存映射的函数是ioremap.
#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
pci_iomap — create a virtual mapping cookie for a PCI BAR
void __iomem * pci_iomap (
struct pci_dev * dev , int bar , unsigned long maxlen )
;
dev
PCI device that owns the BAR
bar
BAR number
maxlen
length of the memory to map
Using this function you will get a __iomem address to your device BAR. You can access it using ioread*() and iowrite*(). These functions hide the details if this is a MMIO or PIO address space and will just do what you expect from them in the correct way.
maxlen
specifies the maximum length to map. If you want to get access to the complete BAR without checking for its length first, pass 0
here.