为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

PCI总线、PCI设备、驱动知识点

2012-12-22 41页 doc 621KB 101阅读

用户头像

is_345321

暂无简介

举报
PCI总线、PCI设备、驱动知识点  PCI设备驱动知识点 分类: linux编程2010-12-31 16:55 132人阅读 评论(0) 收藏 举报      申明:此文章并非本人所写,只是加以修改和备注 原帖请看:http://hi.baidu.com/linux_kernel/blog/category/pci%C9%E8%B1%B8%C7%FD%B6%AF 那位大大可是个大牛人,上面有不少好东东!  一、PCI简介      PCI是一种外设总线规范。我们先来看一下什么是总线:总线是一种传输信号的路径或信道。典型情况是,总线是连接于一个或多个导...
PCI总线、PCI设备、驱动知识点
  PCI设备驱动 分类: linux编程2010-12-31 16:55 132人阅读 评论(0) 收藏 举报      申明:此文章并非本人所写,只是加以修改和备注 原帖请看: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   pci驱动的注册和初始化 分类: linux编程2011-01-13 10:30 222人阅读 评论(1) 收藏 举报     大家好,最近在看网络部分的代码,目前看到了网卡的初始化部分。书上讲到的内容主要是网卡驱动程序对网卡自身的初始化部分,即网卡驱动的probe函数是如何执行的,而很少讲到网卡是如何注册到系统中去的这一部分。     现在的网卡大部分都是连接到PCI总线上的。因此,网卡驱动是如何连接到PCI总线,又是如何与网卡设备联系起来,网卡在注册的最后又是如何调用到该网卡的probe函数的,这一个过程将在后面的文章中进行描述。整个文章分成两个部分,第一部分是讲解总线、设备以及驱动三者的联系,为第二部分具体讲解PCI总线、网卡设备和驱动做一点铺垫。     由于我在这方面也是初学,之所以想出来是想到在总结的过程中对自己的学习也是一个梳理的过程。所以有什么地方写得不好的,还请各位多多指正,非常感谢!也希望能在这里结识更多的朋友。     在总结的过程中参考了下面一些资料,在此表示感谢: [1] [2] [3] [4] 3rd Edition.  1. 总线、设备和驱动 1.1 简单介绍         Linux设备模型中三个很重要的概念就是总线、设备和驱动,即bus,device和driver。它们分别对应的数据结构分别为struct bus_type,struct device和struct device_driver。         总线是处理器与一个或多个设备之间的通道,在设备模型中,所有的设备都通过总线相连。在最底层,Linux系统中的每一个设备都用device结构的一个实例来表示。而驱动则是使总线上的设备能够完成它应该完成的功能。         在系统中有多种总线,如PCI总线、SCSI总线等。系统中的多个设备和驱动是通过总线让它们联系起来的。在bus_type中两个很重要的成员就是struct kset drivers和struct kset devices。它分别代表了连接在这个总线上的两个链,一个是设备链表,另一个则是设备驱动链表。也就是说,通过一个总线描述符,就可以找到挂载到这条总线上的设备,以及支持该总线的不同的设备驱动程序。 1.2 总线、设备与驱动的绑定         在系统启动时,它会对每种类型的总线创建一个描述符,并将使用该总线的设备链接到该总线描述符的devices链上来。也即是说在系统初始化时,它会扫描连接了哪些设备,并且为每个设备建立一个struce device变量,然后将该变量链接到这个设备所连接的总线的描述符上去。另一方面,每当加载了一个设备驱动,则系统也会准备一个struct device_driver结构的变量,然后再将这个变量也链接到它所在总线的描述符的drivers链上去。                 对于设备来说,在结构体struct device中有两个重要的成员,一个是struct bus_type *bus,另一个是struct device_driver *driver。bus成员就表示该设备是链接到哪一个总线上的,而driver成员就表示当前设备是由哪个驱动程序所驱动的。对于驱动程序来说,在结构体struct device_driver中也有两个成员,struct bus_type *bus和struct list_head devices,这里的bus成员也是指向这个驱动是链接到哪个总线上的,而devices这个链表则是表示当前这个驱动程序可以去进行驱动的那些设备。一个驱动程序可以支持一个或多个设备,而一个设备则只会绑定给一个驱动程序。         对于device与device_driver之间建立联系的方式,主要有两种方式。第一种,在计算机启动的时候,总线开始扫描连接在其上的设备,为每个设备建立一个struct device变量并链接到该总线的devices链上,然后开始初始化不同的驱动程序,驱动程序到它所在的总线的devices链上去遍历每一个还没有被绑定给某个驱动的设备,然后再查看是否能够支持这种设备,如果它能够支持这种设备,则将这个设备与这个驱动联系起来。即,将这个设备的device变量加到驱动的devices链上,同时让struct device中的device_driver指向当前这个驱动。第二种则是热插拔。也即是在系统运行时插入了设备,此时内核会去查找在该bus链上注册了的device_driver,然后再将设备与驱动联系起来。设备与驱动根据什么规则联系起来,它们是如何被联系起来的代码我们将在后面的章节进行详细的描述。 1.3 PCI总线         PCI是一种在CPU与I/O设备之间进行高速数据传输的一种总线。有很多设备都是使用PCI总线的,网卡就是其中之一。我们在前面讲了那些总线、设备与驱动方面的知识,原因就在于网卡是连接到PCI总线上,所以PCI总线、网卡设备以及网卡驱动就成了我们研究网卡的一个很重要的线索,尤其是在网络的链路层部分。下图显示了在一个系统中PCI设备的一个框图:         PCI子系统声明了一个bus_type结构,为pci_bus_type。它就是PCI总线的描述符。在这个变量上,链接了PCI设备以及支持PCI设备的驱动程序。 1.4 PCI设备与驱动         PCI设备通常由一组参数唯一地标识,它们被vendorID,deviceID和class nodes所标识,即设备厂商,型号等,这些参数保存在pci_device_id结构中。每个PCI设备都会被分配一个pci_dev变量,内核就用这个数据结构来表示一个PCI设备。         所有的PCI驱动程序都必须定义一个pci_driver结构变量,在该变量中包含了这个PCI驱动程序所提供的不同功能的函数,同时,在这个结构中也包含了一个device_driver结构,这个结构定义了PCI子系统与PCI设备之间的接口。在注册PCI驱动程序时,这个结构将被初始化,同时这个pci_driver变量会被链接到pci_bus_type中的驱动链上去。         在pci_driver中有一个成员struct pci_device_id *id_table,它列出了这个设备驱动程序所能够处理的所有PCI设备的ID值。 1.5 PCI设备与驱动的绑定过程         下面描述一下对于PCI设备与驱动绑定的过程。首先在系统启动的时候,PCI总线会去扫描连接到这个总线上的设备,同时为每一个设备建立一个pci_dev结构,在这个结构中有一个device成员,并将这些pci_dev结构链接到PCI总线描述符上的devices链。如下图所示:         第二步是当PCI驱动被加载时,pci_driver结构体将被初始化,这一过程在函数pci_register_driver中:         drv->driver.bus = &pci_bus_type;         drv->driver.probe = pci_device_probe;         最后会调用driver_register(&drv->driver)将这个PCI驱动挂载到总线描述符的驱动链上。同时在注册的过程中,会根据pci_driver中的id_table中的ID值去查看该驱动支持哪些设备,将这些设备挂载到pci_driver中的devices链中来。如下图所示:         对于不同的设备,可能驱动程序也不一样,因此,对于上图中的Dev3,可能就需要另外一个驱动程序来对其进行驱动。所以当加载了Dev3的驱动程序时,其示意图如下图所示:         上面这三个示意图就描述了总线、设备以及驱动在系统中是如何进行相互联系的。前面对于驱动注册这些函数的描述较为简单,因为网卡是一个PCI设备,因此在后面具体地讲到网卡注册时再来详细地讲解和PCI相关的注册等函数。 1.6 小结         本部分主要讲解了总线、设备以及驱动方面的一些知识,由于网卡是一个PCI设备,因此具体地讲到了一点PCI总线、PCI设备及相应的PCI驱动方面的知识,但是由于PCI本身就是很大的一个子系统,因此这里不可能对其进行详细地讲解,在后面对网卡的分析中,将对网卡中涉及到的和PCI相关的部分进行讲解。 2. 网卡在PCI层的注册 2.1 数据结构         前面第一章讲了总线、设备以及驱动方面的关系,也讲到了大多数网卡设备实际上是一个PCI设备。因此,本章就讲解网卡设备在注册时是如何注册到PCI总线上去的。在这里,以Intel的E100网卡驱动进行讲解。         前面讲到每个PCI设备都由一组参数唯一地标识,这些参数保存在结构体pci_device_id中,如下所示: 1. struct pci_device_id { 2.         __u32 vendor, device;                /* Vendor and device ID or PCI_ANY_ID*/ 3.         __u32 subvendor, subdevice;        /* Subsystem ID's or PCI_ANY_ID */ 4.         __u32 class, class_mask;        /* (class,subclass,prog-if) triplet */ 5.         kernel_ulong_t driver_data;        /* Data private to the driver */ 6. };         每个PCI设备驱动都有一个pci_driver变量,它描述了一个PCI驱动的信息,如下所示: 1. struct pci_driver { 2.         struct list_head node; 3.         char *name; 4.         const struct pci_device_id *id_table;        /* must be non-NULL for probe to be called */ 5.         int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);        /* New device inserted */ 6.         void (*remove) (struct pci_dev *dev);        /* Device removed (NULL if not a hot-plug capable driver) */ 7.         int  (*suspend) (struct pci_dev *dev, pm_message_t state);        /* Device suspended */ 8.         int  (*suspend_late) (struct pci_dev *dev, pm_message_t state); 9.         int  (*resume_early) (struct pci_dev *dev); 10.         int  (*resume) (struct pci_dev *dev);                        /* Device woken up */ 11.         int  (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);   /* Enable wake event */ 12.         void (*shutdown) (struct pci_dev *dev); 13.         struct pci_error_handlers *err_handler; 14.         struct device_driver        driver; 15.         struct pci_dynids dynids; 16.         int multithread_probe; 17. };         每个PCI驱动中都有一个id_table成员变量,记录了当前这个驱动所能够进行驱动的那些设备的ID值。         对于E100网卡驱动来说,它的pci_driver变量定义为: 1. static struct pci_driver e100_driver = { 2.         .name =         DRV_NAME, 3.         .id_table =     e100_id_table, 4.         .probe =        e100_probe, 5.         .remove =       __devexit_p(e100_remove), 6. #ifdef CONFIG_PM 7.         /* Power Management hooks */ 8.         .suspend =      e100_suspend, 9.         .resume =       e100_resume, 10. #endif 11.         .shutdown =     e100_shutdown, 12.         .err_handler = &e100_err_handler, 13. };         里面e100_id_table就表示该E100驱动所能够支持的PCI设备的ID号,其定义为: 1. #define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {/ 2.         PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, / 3.         PCI_CLASS_NETWORK_ETHERNET << 8, 0xFFFF00, ich } 4. static struct pci_device_id e100_id_table[] = { 5.         INTEL_8255X_ETHERNET_DEVICE(0x1029, 0), 6.         INTEL_8255X_ETHERNET_DEVICE(0x1030, 0), 7.         … 8.         { 0, } 9. };         当PCI层检测到一个PCI设备能够被某PCI驱动所支持时(这是通过函数pci_match_one_device来进行检测的),就会调用这个PCI驱动上的probe函数,在该函数中会对该特定的PCI设备进行一些具体的初始化等操作。比如对于E100设备驱动来说,其probe函数为e100_probe。在这个函数中,会对网卡设备进行初始化。         e100_probe主要就涉及到网卡设备net_device的初始化,我们现在先来关注一下从网卡注册一直到调用e100_probe这一个过程的整个流程。 2.2 E100初始化         E100驱动程序的初始化是在函数e100_init_module()中的,如下: 1. static int __init e100_init_module(void) 2. { 3.         if(((1 << debug) - 1) & NETIF_MSG_DRV) { 4.            
/
本文档为【PCI总线、PCI设备、驱动知识点】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索