logo资料库

Linux下的RTL 8139网卡驱动分析.pdf

第1页 / 共32页
第2页 / 共32页
第3页 / 共32页
第4页 / 共32页
第5页 / 共32页
第6页 / 共32页
第7页 / 共32页
第8页 / 共32页
资料共32页,剩余部分请下载后查看
预备知识
驱动的初始化
中断处理
软中断请求
NAPI方式
非NAPI方式
网卡接收操作
网卡发送操作
RTL8139网卡驱动程序分析 独孤求真 www.osplay.org Email: Addylee2004@163.com OSPlay 原创文章 转载请注明出处。 c⃝ 2007-7-23 根据sinister的建议,在接收部分加⼊了对NAPI和 非NAPI方式的分析。 在此对sinister⼤虾表示感谢。 1
欢迎访问 OSPlay www.osplay.org 3 4 18 20 . 20 . 24 26 31 CONTENTS Contents 1 预备知识 2 驱动的初始化 3 中断处理 4 软中断请求 NAPI方式 . 4.1 . 4.2 非NAPI方式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 网卡接收操作 6 网卡发送操作 Abstract 对多数驱动程序开发的学习者来说,总是感觉很难⼊门,不能从 整体上把握驱动程序是如何驱动硬件设备⼯作的。本文以Linux内 核中8139网卡驱动为例,对驱动程序的⼯作过程进行详细的分析, 为初学者拨开迷雾,走出雾里看花的迷茫。本文虽然以Linux驱动 为例,但是技术总是相通的,为了给Windows驱动初学者同样的启 发,我有意的借用了许多Windows驱动中的名词,同时顺便略述了 Windows驱动中的⼀些容易让初学者感到迷惑的概念。 本⼈水平有限,纰漏之处在所难免,希望读者海涵,并不吝赐 教。 2
欢迎访问 OSPlay www.osplay.org 1 预备知识 Rtl8139是⼀个PCI网卡,老式的设备地址是固定的,对设备的扩充通 常通过跳线等方式来更改地址以避免地址冲突,例如通过跳线来设置IED 主盘,从盘。PCI总线设备可以通过软件编程灵活的设置各个设备的地 址。在操作系统启动的时候,系统根据PCI总线协议规范对主板上的PCI 进行扫描,同时为发现的设备配置相关资源,包括中断请求号,地址空 间等。每⼀个PCI设备上有⼀个配置空间,配置空间中包含了设备的基本 信息,例如设备类别ID,⼚商ID,设备板载存储空间等信息,操作系统 在扫描所有的PCI设备后,可以根据这些信息统⼀分配地址资源以避免 地址冲突。系统通过在PCI设备中的基地址寄存器中写⼊⼀个分配到的 基地址,之后CPU在指令执行的时候给出⼀个地址,这个地址首先送到 Host-PCI桥,就是我们通常所说的北桥,北桥判断出这个地址是落在内 存或是PCI等设备的地址空间上1,如果落在PCI空间地址中,则北桥通过 PCI总线仲裁申请,把地址送到PCI总线上,总线上的每⼀个PCI设备会根 据自⼰的存储空间⼤⼩以及基地址寄存器中的值来比较,如果被寻址的 地址落在自⼰的地址空间范围内,则该设备会作为PCI从设备响应完成数 据传输。这就是说,系统上的设备都有自⼰的地址空间,以总线带宽为 32位的系统为例,可以容纳的地址空间为4G,CPU在这4G的地址空间⼀部 分划分到内存空间,还有以部分很可能划分给PCI等外部设备,这通常取 决于硬件系统设计者。PCI设备灵活的配置方式也不可避免的带来了复杂 性。PCI协议对设备的枚举,检测,配置的过程是复杂的,通常操作系统 提供了PCI总线协议驱动程序,并在启动的时候完成了这⼀复杂的过程, 这样⼤⼤减⼩了PCI设备驱动程序开发者的⼯作量。这就好像平时⼤家 做网络程序开发的时候都没必要自⼰实现TCP/IP协议⼀样。用Windows的 术语来说,对PCI设备的枚举由总线驱动程序完成,而具体的对PCI设备 的控制是功能驱动程序的⼯作。本文要描述的是Rtl8139网卡功能驱动程 序。对PCI协议有⼀定了解是必要的。关于PCI总线驱动程序的知识可以 1新的集成内存控制器的CPU自⼰能判断对内存的寻址。 3
欢迎访问 OSPlay www.osplay.org 阅读《Linux内核情景分析》。 2 驱动的初始化 驱动程序的⼊⼝函数rtl8139_init_module调用了pci_register_driver (&rtl8139_pci_driver); 其中参数rtl8139_pci_driver结够如下: d e v e x i t p ( r t l 8 1 3 9 r e m o v e o n e ) , . name . id table . probe . remove 1 // 该结构相当于 Windows 中的功能驱动程序对象 2 static struct p c i d r i v e r r t l 8 1 3 9 p c i d r i v e r = { = DRV NAME , 3 = r t l 8 1 3 9 p c i t b l , 4 = r t l 8 1 3 9 i n i t o n e , 5 6 = 7 #ifdef CONFIG PM 8 9 10 #endif /∗ CONFIG PM ∗/ 11 } ; 12 13 14 static struct p c i d e v i c e i d r t l 8 1 3 9 p c i t b l [ ] = { {0 x10ec , 0 x8139 , PCI ANY ID , PCI ANY ID , 0 , 0 , 15 // 最重要的参数。 r t l 8 1 3 9 p c i t b l . suspend . resume = rtl8139 suspend , = rtl8139 resume , {0 x10ec , 0 x8138 , PCI ANY ID , PCI ANY ID , 0 , 0 , {0 x 1 1 1 3 , 0 x 1 2 1 1 , PCI ANY ID , PCI ANY ID , 0 , 0 , 16 17 RTL8139 } , RTL8139 } , RTL8139 } , . . . . . . 18 19 } ; 4
欢迎访问 OSPlay www.osplay.org rtl8139_pci_tbl中是由VerdonID,DeviceID等组成,表示该设备驱动 程序可以控制的设备,每⼀个PCI设备在配置空间中固化了自⼰的基本 信息,前面说过内核启动的时候PCI总线驱动程序会扫描PCI总线上的 设备,并且把这些信息收集起来,并且每⼀个设备的信息由⼀个专门 的结构保存起来,保存在⼀个pci_dev结构中。pci_register_driver会 根据rtl8139_pci_tbl中的信息和内核中扫描到的对比,如果有匹配的 话,就把功能驱动程序和目标设备对应起来了。在Windows系统中维护 设备信息的那个结构被称为物理设备对象,该对象由总线驱动程序创 建管理。之后pci_register_driver进行必要的初始化后,调用参数指定 的rtl8139_init_one。 ∗ pdev , const struct p c i d e v i c e i d ∗ ent ) 1 static int d e v i n i t r t l 8 1 3 9 i n i t o n e ( struct p c i d e v 2 { 3 4 5 6 7 8 9 10 11 12 13 } struct net device ∗ dev = NULL ; struct r t l 8 1 3 9 p r i v a t e ∗tp ; int i , addr len , option ; void static int board idx = −1; u8 p c i r e v ; iomem ∗ ioaddr ; . . . . . . i = r t l 8 1 3 9 i n i t b o a r d ( pdev , &dev ) ; . . . . . . rtl8139_init_one的参数就是PCI总线驱动程序在设备枚举过程中创 建的。首先调用rtl8139_init_board。 1 static int d e v i n i t r t l 8 1 3 9 i n i t b o a r d ( struct p c i d e v ∗ pdev , struct net device ∗∗ dev out ) 5
2 { 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 欢迎访问 OSPlay www.osplay.org iomem ∗ ioaddr ; void struct net device ∗ dev ; struct r t l 8 1 3 9 p r i v a t e ∗tp ; u8 tmp8 ; int rc , d i s a b l e d e v o n e r r = 0 ; unsigned int i ; unsigned long pio start , pio end , pio flags , pio len ; unsigned long mmio start , mmio end , mmio flags , mmio len ; u32 version ; assert ( pdev ! = NULL ) ; ∗ dev out = NULL ; /∗ dev and priv zeroed in alloc etherdev ∗/ /∗ 每⼀个网络设备驱动程序为了设备管理方便,统⼀接⼝等目的, 需要创建自⼰的⼀个设备对象来维护设备信息,在 Windows 系 统中,该对象称为功能设备对象。 ∗/ /∗ 每⼀个设备对象是标准的结构,但是不同的驱动程序可能都要维 护不同的私有信息,所以在分配 net dev 结构的同时可以多分 配出 r t l 8 1 3 9 p r i v a t e 结构来。比如应用程序可能会经常查 询网卡地址,虽然驱动程序可以通过访问网卡上的存储空间来获 取网卡地址,但是驱动程序可不希望每次都通过慢速的 IO 访问 来获取这些信息,通常驱动程序会为这些信息维护内存中的数据 结构中,这些信息都可以放在 tp 中。∗/ dev = alloc etherdev ( sizeof (∗ tp ) ) ; if ( dev == NULL ) { dev err (& pdev−>dev , "Unable to alloc new net device\n" ) ; 6
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 欢迎访问 OSPlay www.osplay.org return −ENOMEM ; } SET MODULE OWNER ( dev ) ; SET NETDEV DEV ( dev , &pdev−>dev ) ; tp = netdev priv ( dev ) ; tp−>p c i d e v = pdev ; /∗ enable device ( incl . PCI PM wakeup and hotplug setup ) ∗/ /∗ 启用设备的 memory/ Io 译码,如果设备处于休眠状态,则唤醒 设备。在启用设备之前,虽然设备的基址寄存器中设置了值,但 是当总线有主设备对该地址进行寻址的时候,设备是不会响应 的。具体启用操作由总线驱动程序封装,这里通过调用 pci 总线 驱动程序提供的函数来完成该任务,在 windows 中某些写操作 由功能驱动向下层的总线驱动发送 IRP 完成同样的任务。 ∗/ rc = p c i e n a b l e d e v i c e ( pdev ) ; if ( rc ) goto err out ; /∗ ⼤多数设备上有自⼰的板载存储空间,其中包括 memory 空间 和 IO 空间,在 X86 这样的 IO 与 Memory 分立编址的系统 上,他们的区别就在于独立的 IO 访问指令,独立的地址译码, 在多数统⼀编址的系统上 memeor 空间和 IO 空间没有本质区 别。 CPU 给出的指令中的地址落在哪里,设备在电路级别会访问 到对应地址的板载存储空间。 PCI 总线驱动程序已经为设备分配 地址空间,并配置了基址寄存器,通常 BIOS 已经为这些设备配 置了互不冲突的地址,系统的 PCI 总线驱动程序可以直接扫 描 PCI 设备,并从基址寄存器中读出各个设备的基地址,也可以 推倒从新统⼀分配。这里功能驱动程序需要知道自⼰如何访问到 目标设备,所以从总线物理设备对象中读取基址信息。 ∗/ pio start = p c i r e s o u r c e s t a r t ( pdev , 0 ) ; pio end = p c i r e s o u r c e e n d ( pdev , 0 ) ; pio flags = p c i r e s o u r c e f l a g s ( pdev , 0 ) ; pio len = p c i r e s o u r c e l e n ( pdev , 0 ) ; 7
42 43 44 45 46 47 48 49 50 51 52 53 54 55 欢迎访问 OSPlay www.osplay.org mmio start = p c i r e s o u r c e s t a r t ( pdev , mmio end = p c i r e s o u r c e e n d ( pdev , 1 ) ; mmio flags = p c i r e s o u r c e f l a g s ( pdev , mmio len = p c i r e s o u r c e l e n ( pdev , 1 ) ; 1 ) ; 1 ) ; /∗ set this immediately , we need to know before we talk to the chip directly ∗/ DPRINTK ( "PIO region size == 0x%02X\n" , pio len ) ; DPRINTK ( "MMIO region size == 0x%02lX\n" , mmio len ) ; /∗ PCI 设备板载存储空间可以通过 IO 或者 Memory 方式来访 问, PCI 设备上分别有 IO 和 Memory 基地址寄存器, 在 X86 上的有独立的 IO 指令,对于某些统⼀编址的体系结构 则只有通过 Memory 方式来访问, CPU 执行 IO/Memory 指 令时,其地址送到总线上, PCI 设备会根据 IO/Memory 基址 寄存器判断出目标设备是不是自⼰。无论是 IO 方式还 是 Memory 方式,访问到通常是板载存储空间上的同⼀个地方。 由被寻址的从设备内部处理。例如:假设 8139 网 卡 IO / Memory 基址寄存器的值分别是 X / Y ,则使 用 IO 指令( IN / OUT )访问 X ,以及使用 Memory 指令 ( MOV )访问 Y ,结果是⼀样的。这样保证了设备在统⼀编址 和独立编址的体系结构下的兼容性,为什么通过 MOV 访问的地址 没有访问到内存上去呢?通常 CPU 给出⼀条访存指令,地址被 发到北桥,北桥会根据地址空间的划分情况判别出该地址是落在 内存空间上还是其它总线上的设备空间上。如果是内存的话,则 向内存控制器发起访问操作,如果是 PCI 空间的话,则同样的 向 PCI 总线总裁提出申请。 ∗/ 56 57 #ifdef USE IO OPS 58 59 60 /∗ make sure PCI base addr 0 is PIO ∗/ if ( ! ( pio flags & IORESOURCE IO ) ) { dev err (& pdev−>dev , "region #0 not a PIO resource , aborting\n" ) ; 8
分享到:
收藏