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

+++最好的RTL8319网卡驱动分析

2011-05-24 32页 pdf 408KB 51阅读

用户头像

is_926114

暂无简介

举报
+++最好的RTL8319网卡驱动分析 RTL8139网卡驱动程序分析 独孤求真 www.osplay.org Email: Addylee2004@163.com OSPlay 原创文章 转载请注明出处。 c° 2007-7-23 根据sinister的建议,在接收部分加⼊了对NAPI和 非NAPI方式的分析。 在此对sinister⼤虾表示感谢。 1 CONTENTS 欢迎访问 OSPlay www.osplay.org Contents 1 预备知识 3 2 驱动的初始化 4 3 中断处理 18 4 软中断请求 20 4.1 NAP...
+++最好的RTL8319网卡驱动分析
RTL8139网卡驱动程序 独孤求真 www.osplay.org Email: Addylee2004@163.com OSPlay 原创文章 转载请注明出处。 c° 2007-7-23 根据sinister的建议,在接收部分加⼊了对NAPI和 非NAPI方式的分析。 在此对sinister⼤虾表示感谢。 1 CONTENTS 欢迎访问 OSPlay www.osplay.org Contents 1 预备知识 3 2 驱动的初始化 4 3 中断处理 18 4 软中断请求 20 4.1 NAPI方式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2 非NAPI方式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5 网卡接收操作 26 6 网卡发送操作 31 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结够如下: 1 // 该结构相当于 Windows 中的功能驱动程序对象 2 static struct pci driver rtl8139 pci driver = f 3 . name = DRV NAME , 4 . id table = rtl8139 pci tbl , 5 . probe = rtl8139 init one , 6 . remove = devexit p ( rtl8139 remove one ) , 7 #ifdef CONFIG PM 8 . suspend = rtl8139 suspend , 9 . resume = rtl8139 resume , 10 #endif /¤ CONFIG PM ¤/ 11 g ; 12 13 // 最重要的参数。 rtl8139 pci tbl 14 static struct pci device id rtl8139 pci tbl [ ] = f 15 f0 x10ec , 0x8139 , PCI ANY ID , PCI ANY ID , 0 , 0 , RTL8139 g , 16 f0 x10ec , 0x8138 , PCI ANY ID , PCI ANY ID , 0 , 0 , RTL8139 g , 17 f0 x1 1 13 , 0 x121 1 , PCI ANY ID , PCI ANY ID , 0 , 0 , RTL8139 g , 18 . . . . . . 19 g ; 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。 1 static int devinit rtl8139 init one ( struct pci dev ¤pdev , const struct pci device id ¤ent ) 2 f 3 struct net device ¤dev = NULL ; 4 struct rtl8139 private ¤tp ; 5 int i , addr len , option ; 6 void iomem ¤ ioaddr ; 7 static int board idx = �1; 8 u8 pci rev ; 9 10 . . . . . . 11 i = rtl8139 init board ( pdev , &dev ) ; 12 . . . . . . 13 g rtl8139_init_one的参数就是PCI总线驱动程序在设备枚举过程中创 建的。首先调用rtl8139_init_board。 1 static int devinit rtl8139 init board ( struct pci dev ¤pdev , struct net device ¤¤ dev out ) 5 欢迎访问 OSPlay www.osplay.org 2 f 3 void iomem ¤ ioaddr ; 4 struct net device ¤dev ; 5 struct rtl8139 private ¤tp ; 6 u8 tmp8 ; 7 int rc , disable dev on err = 0 ; 8 unsigned int i ; 9 unsigned long pio start , pio end , pio flags , pio len ; 10 unsigned long mmio start , mmio end , mmio flags , mmio len ; 11 u32 version ; 12 13 assert ( pdev ! = NULL ) ; 14 15 ¤ dev out = NULL ; 16 17 /¤ dev and priv zeroed in alloc etherdev ¤/ 18 /¤ 每⼀个网络设备驱动程序为了设备管理方便,统⼀接⼝等目的, 需要创建自⼰的⼀个设备对象来维护设备信息,在 Windows 系 统中,该对象称为功能设备对象。 ¤/ 19 /¤ 每⼀个设备对象是标准的结构,但是不同的驱动程序可能都要维 护不同的私有信息,所以在分配 net dev 结构的同时可以多分 配出 rtl8139 private 结构来。比如应用程序可能会经常查 询网卡地址,虽然驱动程序可以通过访问网卡上的存储空间来获 取网卡地址,但是驱动程序可不希望每次都通过慢速的 IO 访问 来获取这些信息,通常驱动程序会为这些信息维护内存中的数据 结构中,这些信息都可以放在 tp 中。¤/ 20 dev = alloc etherdev ( sizeof (¤ tp ) ) ; 21 if ( dev == NULL ) f 22 dev err (& pdev�>dev , "Unable to alloc new net device\n" ) ; 6 欢迎访问 OSPlay www.osplay.org 23 return �ENOMEM ; 24 g 25 SET MODULE OWNER ( dev ) ; 26 SET NETDEV DEV ( dev , &pdev�>dev ) ; 27 28 tp = netdev priv ( dev ) ; 29 tp�>pci dev = pdev ; 30 31 /¤ enable device ( incl . PCI PM wakeup and hotplug setup ) ¤/ 32 /¤ 启用设备的 memory/Io 译码,如果设备处于休眠状态,则唤醒 设备。在启用设备之前,虽然设备的基址寄存器中设置了值,但 是当总线有主设备对该地址进行寻址的时候,设备是不会响应 的。具体启用操作由总线驱动程序封装,这里通过调用 pci 总线 驱动程序提供的函数来完成该任务,在 windows 中某些写操作 由功能驱动向下层的总线驱动发送 IRP 完成同样的任务。 ¤/ 33 rc = pci enable device ( pdev ) ; 34 if ( rc ) 35 goto err out ; 36 37 /¤ ⼤多数设备上有自⼰的板载存储空间,其中包括 memory 空间 和 IO 空间,在 X86 这样的 IO 与 Memory 分立编址的系统 上,他们的区别就在于独立的 IO 访问指令,独立的地址译码, 在多数统⼀编址的系统上 memeor 空间和 IO 空间没有本质区 别。 CPU 给出的指令中的地址落在哪里,设备在电路级别会访问 到对应地址的板载存储空间。 PCI 总线驱动程序已经为设备分配 地址空间,并配置了基址寄存器,通常 BIOS 已经为这些设备配 置了互不冲突的地址,系统的 PCI 总线驱动程序可以直接扫 描 PCI 设备,并从基址寄存器中读出各个设备的基地址,也可以 推倒从新统⼀分配。这里功能驱动程序需要知道自⼰如何访问到 目标设备,所以从总线物理设备对象中读取基址信息。 ¤/ 38 pio start = pci resource start ( pdev , 0 ) ; 39 pio end = pci resource end ( pdev , 0 ) ; 40 pio flags = pci resource flags ( pdev , 0 ) ; 41 pio len = pci resource len ( pdev , 0 ) ; 7 欢迎访问 OSPlay www.osplay.org 42 43 mmio start = pci resource start ( pdev , 1 ) ; 44 mmio end = pci resource end ( pdev , 1 ) ; 45 mmio flags = pci resource flags ( pdev , 1 ) ; 46 mmio len = pci resource len ( pdev , 1 ) ; 47 48 /¤ 49 set this immediately , we need to know before 50 we talk to the chip directly 51 ¤/ 52 DPRINTK ( "PIO region size == 0x%02X\n" , pio len ) ; 53 DPRINTK ( "MMIO region size == 0x%02lX\n" , mmio len ) ; 54 55 /¤ 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 /¤ make sure PCI base addr 0 is PIO ¤/ 59 if ( ! ( pio flags & IORESOURCE IO ) ) f 60 dev err (& pdev�>dev , "region #0 not a PIO resource , aborting\n" ) ; 8 欢迎访问 OSPlay www.osplay.org 61 rc = �ENODEV ; 62 goto err out ; 63 g 64 /¤ check for weird/broken PCI region reporting ¤/ 65 if ( pio len < RTL MIN IO SIZE ) f 66 dev err (& pdev�>dev , "Invalid PCI I/O region size(s ), aborting\n" ) ; 67 rc = �ENODEV ; 68 goto err out ; 69 g 70 #else 71 /¤ make sure PCI base addr 1 is MMIO ¤/ 72 if ( ! ( mmio flags & IORESOURCE MEM ) ) f 73 dev err (& pdev�>dev , "region #1 not an MMIO resource , aborting\n" ) ; 74 rc = �ENODEV ; 75 goto err out ; 76 g 77 if ( mmio len < RTL MIN IO SIZE ) f 78 dev err (& pdev�>dev , "Invalid PCI mem region size(s ), aborting\n" ) ; 79 rc = �ENODEV ; 80 goto err out ; 81 g 82 #endif 83 84 /¤ PCI 设备的地址是由软件配置的,那么必然需要⼀种机制来防止 地址冲突,系统维护 了 io resource 和 memory resource ,分别登记了已经分 配出去的地址空间,当为新设备分配地址的时候,必须根据登记 9 欢迎访问 OSPlay www.osplay.org 的信息找到空闲的地址空间,这里就是在系统中登记,表示改设 备要使用这个地址空间了,具体的地址在总线设备对 象 pdev 结构中指定。注意把地址写⼊到设备的基址寄存器早 就已经由 bios 或者内核完成了。这里只是进行登记。 ¤/ 85 rc = pci request regions ( pdev , DRV NAME ) ; 86 if ( rc ) 87 goto err out ; 88 disable dev on err = 1 ; 89 90 /¤ enable PCI bus�mastering ¤/ 91 pci set master ( pdev ) ; 92 93 #ifdef USE IO OPS 94 ioaddr = ioport map ( pio start , pio len ) ; 95 if ( ! ioaddr ) f 96 dev err (& pdev�>dev , "cannot map PIO, aborting\n" ) ; 97 rc = �EIO ; 98 goto err out ; 99 g 100 dev�>base addr = pio start ; 101 tp�>mmio addr = ioaddr ; 102 tp�>regs len = pio len ; 103 #else 104 /¤ ioremap MMIO region ¤/ 105 /¤ 映射设备地址,这里需要搞清楚 3 种地址: 106 1 . 虚拟地址:经过页表页目录映射,访问的时候通 过 CPU 的 MMU 转换得到物理地址。 107 2 . 物理地址:在实模式下使用的或保护模式下经 过 CPU 的 MMU 转换后的地址。 108 3 . 总线地址:经过各种总线桥接器转换后在出现在总线的地址线上 的地址。 ¤/ 109 10 欢迎访问 OSPlay www.osplay.org 110 /¤ 通常程序指令中访问的是虚拟地址, CPU 在指令执行的时候通 过 MMU 把虚拟地址转换成物理地址,这个地址被送到北桥,北桥 根据自⼰地址空间判断出这个地址是在内存上还是在外部总线上 的设备上面,如果在外部设备上面,北桥可能要根据总线地址空 间的分配情况将这个物理地址变换成总线地址,再发送到总线上 面去。总线上的 Host � PCI 以及 PCI � PCI 都可以做这样 的变换,⼤多数情况下总线地址和物理地址是⼀样的,这取决于 系统设计。需要注意的是写⼊ PCI 设备基址寄存器中的值必须 总线地址,才能够匹配到出现在总线上的地址,从而响应寻址操 作。而 CPU 则要记住物理地址,并根据物理地址映射虚拟地 址。 ioaddr 是映射后的虚拟地址,以后程序中将通 过 ioaddr 访问设备。 ¤/ 111 112 ioaddr = pci iomap ( pdev , 1 , 0 ) ; 113 if ( ioaddr == NULL ) f 114 dev err (& pdev�>dev , "cannot remap MMIO , aborting\n " ) ; 115 rc = �EIO ; 116 goto err out ; 117 g 118 dev�>base addr = ( long ) ioaddr ; 119 tp�>mmio addr = ioaddr ; 120 tp�>regs len = mmio len ; 121 #endif /¤ USE IO OPS ¤/ 122 123 . . . . . . 124 125 // reset 就是向命令寄存器写个 reset 命令。 126 rtl8139 chip reset ( ioaddr ) ; 127 128 ¤ dev out = dev ; 129 return 0 ; 130 131 err out : 11 欢迎访问 OSPlay www.osplay.org 132 rtl8 139 cleanup dev ( dev ) ; 133 if ( disable dev on err ) 134 pci disable device ( pdev ) ; 135 return rc ; 136 g 回到rtl8139_init_one: 1 static int devinit rtl8139 init one ( struct pci dev ¤pdev , const struct pci device id ¤ent ) 2 f 3 . . . . . . 4 5 i = rtl8139 init board ( pdev , &dev ) ; 6 7 . . . . . . 8 9 tp = netdev priv ( dev ) ; 10 ioaddr = tp�>mmio addr ; 11 12 // 从 ioaddr 中读出 Mac 地址 13 addr len = read eeprom ( ioaddr , 0 , 8 ) == 0 x8129 ? 8 : 6 ; 14 for ( i = 0 ; i < 3 ; i ++) 15 ( ( u16 ¤ ) ( dev�>dev addr ) ) [ i ] = le 16 to cpu ( read eeprom ( ioaddr , i + 7 , addr len ) ) ; 16 17 memcpy ( dev�>perm addr , dev�>dev addr , dev�>addr len ) ; 18 12 欢迎访问 OSPlay www.osplay.org 19 /¤ The Rtl8139�specific entries in the device structure . ¤/ 20 /¤ 设置功能驱动程序对象的函数指针集 ¤/ 21 dev�>open = rtl8139 open ; 22 dev�>hard start xmit = rtl8139 start xmit ; 23 dev�>poll = rtl8139 poll ; 24 dev�>weight = 64 ; 25 dev�>stop = rtl8139 close ; 26 dev�>get stats = rtl8139 get stats ; 27 dev�>set multicast list = rtl8139 set rx mode ; 28 dev�>do ioctl = netdev ioctl ; 29 dev�>ethtool ops = &rtl8139 ethtool ops ; 30 dev�>tx timeout = rtl8139 tx timeout ; 31 dev�>watchdog timeo = TX TIMEOUT ; 32 #ifdef CONFIG NET POLL CONTROLLER 33 dev�>poll controller = rtl8139 poll controller ; 34 #endif 35 36 . . . . . . 37 38 /¤ Put the chip into low�power mode . ¤/ 39 /¤ ' R ' would leave the clock running . ¤/ 40 if ( rtl chip info [ tp�>chipset ] . flags & HasHltClk ) 41 RTL W8 ( HltClk , 'H' ) ; 42 43 return 0 ; 44 45 err out : 46 rtl8 139 cleanup dev ( dev ) ; 13 欢迎访问 OSPlay www.osplay.org 47 pci disable device ( pdev ) ; 48 return i ; 49 g 8139网卡的有⼀个接收缓冲寄存器,用于存放接收缓存的首地址,网 卡⼀边把网线上的发出的数据放到内部FIFO,⼀边从FIFO中把数据通过 DMA传送到由接收寄存器指定的内存地址中,接收到的数据依次排放,当 长度超过默认的缓冲区长度时,会回过头来放到开始的地方,所以接收 缓冲区被称为环形缓冲区。发送方面:8139有四个发送地址寄存器,CPU 将要发送的数据在内存中的地址写⼊这四个寄存器中的任何⼀个,网卡 就会通过DMA操作把数据发送出去。当发送或者接送完成后,网卡会发出 中断,中断处理程序通过读取网卡的中断状态寄存器来识别出是发送完 成发出的中断,接收到数据包的中断,还是错误中断。 当运行ifconfig ethx up的时候,rtl8139_open得到调用。该函数的 任务就是分配,初始化接收,发送缓冲区,分配中断号等。 1 static int rtl8139 open ( struct net device ¤dev ) 2 f 3 struct rtl8139 private ¤tp = netdev priv ( dev ) ; 4 int retval ; 5 void iomem ¤ ioaddr = tp�>mmio addr ; 6 7 // 为网卡申请中断,当中断到来时 rtl8139 interrupt 会被调 用。 8 retval = request irq ( dev�>irq , rtl8139 interrupt , IRQF SHARED , dev�>name , dev ) ; 9 if ( retval ) 10 return retval ; 11 // 分配接收,发送缓冲区, DMA 没有 CPU 的 MMU 单元,因此只 能使用物理地址上连续的内存空间。 14 欢迎访问 OSPlay www.osplay.org 12 tp�>tx bufs = pci alloc consistent ( tp�>pci dev , TX BUF TOT LEN , &tp�>tx bufs dma ) ; 13 tp�>rx ring = pci alloc consistent ( tp�>pci dev , RX BUF TOT LEN , &tp�>rx ring dma ) ; 14 if ( tp�>tx bufs == NULL j j tp�>rx ring == NULL ) 15 f 16 free irq ( dev�>irq , dev ) ; 17 18 if ( tp�>tx bufs ) 19 pci free consistent ( tp�>pci dev , TX BUF TOT LEN , tp�>tx bufs , tp�>tx bufs dma ) ; 20 if ( tp�>rx ring ) 21 pci free consistent ( tp�>pci dev , RX BUF TOT LEN , tp�>rx ring , tp�>rx ring dma ) ; 22 return �ENOMEM ; 23 g 24 25 tp�>mii . full duplex = tp�>mii . force media ; 26 tp�>tx flag = ( TX FIFO THRESH << 1 1 ) & 0x003f0000 ; 27 28 /¤ 初始化接送发送缓冲区,由于有四个发送地址寄存器,因此把发 送缓冲区分成 4 组,以后发送请求到来的时候,将待发送内容拷 贝到第⼀组,再将第⼀组的地址写⼊寄存器,之后依次轮流使用 第⼆,三,四,⼀组。 . . . ¤/ 29 rtl8139 init ring ( dev ) ; 30 /¤ 对网卡硬件进行相关的初始化。 ¤/ 31 rtl8139 hw start ( dev ) ; 32 33 return 0 ; 34 g 15 欢迎访问 OSPlay www.osplay.org rtl8139_hw_start主要是对网卡芯片进行初始化,主要就是根据网卡 的硬件手册,Programming guide向⼀些寄存器写⼊某些值。接下来我们 将看到⼤量类似的操作。 1 static void rtl8139 hw start ( struct net device ¤dev ) 2 f 3 struct rtl8139 private ¤tp = netdev priv ( dev ) ; 4 /¤ ioaddr 就是访问设备上的存储空间的基址。 ¤/ 5 void iomem ¤ ioaddr = tp�>mmio addr ; 6 7 . . . . . . 8 /¤ 向网卡命令寄存器写⼊⼀个 RESET 命令,这样网卡的各个寄存 器恢复到默认状态。 ¤/ 9 rtl8139 chip reset ( ioaddr ) ; 10 11 /¤ unlock Config [ 0 1 2 3 4 ] and BMCR register writes ¤/ 12 RTL W8 F ( Cfg9346 , Cfg9346 Unlock ) ; 13 /¤ MAC0 被定义成 0 ,在 8139 网卡 PCI 空间基址偏移为的个 自节是用于存放网卡06 MAC 地址的,现在把之前从 EEPROM 中 读出来的 MAC 地址写⼊这个地址,将来网卡在收包的时候,就会 根据这个寄存器中的值来确定自⼰的 MAC 地址。现在⼤家该明白 为什么我们平时能改 MAC 地址了吧。 ¤/ 14 /¤ Restore our idea of the MAC address . ¤/ 15 RTL W32 F ( MAC0 + 0 , cpu to le32 ( ¤ ( u32 ¤ ) ( dev�> dev addr + 0) ) ) ; 16 RTL W32 F ( MAC0 + 4 , cpu to le32 ( ¤ ( u32 ¤ ) ( dev�> dev addr + 4) ) ) ; 17 18 /¤ Must enable Tx/Rx before setting transfer thresholds ! ¤/ 19 /¤ 向命令写⼊命令,允许发送和接送。 ¤/ 20 RTL W8 ( ChipCmd , CmdRxEnb j CmdTxEnb ) ; 16 欢迎访问 OSPlay www.osplay.org 21 22 /¤ 向接收配置寄存器写⼊配置,以后该网卡只接收广播帧和目 的 MAC 地址是自⼰的帧。设为混杂模式的时候,则是向这个寄存 器写⼊。 AcceptAllPhys ¤/ 23 tp�>rx config = rtl8139 rx config j AcceptBroadcast j AcceptMyPhys ; 24 RTL W32 ( RxConfig , tp�>rx config ) ; 25 RTL W32 ( TxConfig , rtl8139 tx config ) ; 26 27 . . . . . . 28 /¤ 向中断屏蔽寄存器写⼊中断允许位。默认允许接送,发送,错误 等等中断。如果屏蔽了接送中断,那么当网卡接收到帧的时候就 不会发出中断了, NAPI 就是通过屏蔽这里的接收中断,而通 过轮询接收状态寄存器来查看是不是有帧收到了来减少中断次 数,提高效率的。由于网卡接收⼩包的速度快,如果按常规处理 流程,中断 �> CPU 保存⼀堆寄存器然后中断处 理 �> CPU 恢复⼀堆寄存器 �> 调度决策�> . . . . . . 在⼩包 高速发送的环境下,尤其是在测试的时候,很可能在刚⼀恢复线 程上下文,中断⼜来了。⼜得重新保存上下文进⼊中断处理,关 闭中断进行轮询就是要节省这⼏个NAPI CPU 时钟周期。对⼤包 来说,⼀个包收的要慢⼀点,很可能在执行 poll 轮询的时候, 第⼀次检测网卡状态寄存器发现有包了, CPU Copy 出来处理, 之后再检测那个寄存器的时候,下⼀个⼤包的接收还没完成,于 是开了中断,结束⼀次,然后恢线程上下文,然后过了很⼩的⼀ 段时间,中断⼜来了,所以⼤包省不了⼏个中断切换的时钟周 期,效果不明显。poll¤/ 29 RTL W16 ( IntrMask , rtl8139 intr mask ) ; 以上都是向某个地址写⼊⼀些值,基地址是PCI总线驱动程序配置 的,某个偏移位置的地址代表什么意思,该写⼊什么值是功能设备的 芯片逻辑规定的。以接送配置寄存器为例,RxConfig被被定义为0x44, 则该寄存器地址偏移是0x44,AcceptAllPhys被定义为0x01, AcceptMyPhys 被定义为0x02,就是说该寄存器的最低位为1时,网卡会进⼊混杂模式 接收所有的帧,第⼀位为0,第⼆位为1时,只接收目的MAC地址为自⼰的 帧。彻底的搞清楚每⼀个寄存器的每个BIT代表什么实在是没有必要, 不同的网卡芯片都是不⼀样的。所有这里我将略去细节的分析,⼒求从 主线上把握就可以了,如果确实需要,可以查阅相关芯片的Datasheet和 17 欢迎访问 OSPlay www.osplay.org Programming guide。 3 中断处理 当网卡收到数据,发送数据完成,或收发出错都可能发出中断,在中 断处理中根据网卡中断状态寄存器的值来判断是什么情况的中断,然后 调用相应的处理函数。 1 static irqreturn t rtl8139 interrupt ( int irq , void ¤ dev instance ) 2 f 3 struct net device ¤dev = ( struct net device ¤ ) dev instance ; 4 struct rtl8139 private ¤tp = netdev priv ( dev ) ; 5 void iomem ¤ ioaddr = tp�>mmio addr ; 6 u16 status , ackstat ; 7 int link changed = 0 ; /¤ avoid bogus " uninit " warning ¤/ 8 int handled = 0 ; 9 10 spin lock (&tp�>lock ) ; 11 /¤ 读取中断状态寄存器的值。 ¤/ 12 status = RTL R16 ( IntrStatus ) ; 13 14 . . . . . . 15 16 /¤ Receive packets are processed by poll routine . If not running start it now . ¤/ 17 /¤ 如果状态寄存器的接收位置 1 ,则进⼊接收处理函数。根 据 NAPI 机制。这里先向中断屏蔽寄存器中写 ⼊ rtl8139 norx intr mask , 关闭接收中 18 欢迎访问 OSPlay www.osplay.org 断, netif rx shedule prep 检查网卡是不是处于 up 状 态,然后在 dev�>state 上设 置 LINK STATE RX SCHED 标记,然后通过把接收 的 netif rx schedule poll 函数加⼊软中断队列。将来 软中断调度的时候,会调用 rtl8139 poll , 进行轮询。轮询完 成的时候,会清除 dev�>state 上 的 LINK STATE RX SCHED 标记。这主要是避免软中断队列 中出现多余的 poll 请求。我们都知道中断的优先级比较高, 如果直接在这里用 netif rx schedule 把 poll 请求加⼊ 软中断队列中,那么很可能在软中断还没被调度的时候,⼜来了 ⼀次接收中断,于是⼜有⼀个 poll 请求被加⼊队列中。等软中 断被调度的时候,很可能在第⼀次 poll 的时候就处理完成了所 有的接收,而后来的那些中断所收到的数据也被第⼀个处理 了。poll¤/ 18 if ( status & RxAckBits ) f 19 if ( netif rx schedule prep ( dev ) ) f 20 RTL W16 F ( IntrMask , rtl8139 norx intr mask ) ; 21 netif rx schedule ( dev ) ; 22 g 23 g 24 25 /¤ Check uncommon events with one test . ¤/ 26 /¤ 如果状态寄存器的相关错误位置 1 ,则进⼊错误处理函数。 ¤/ 27 if ( unlikely ( status & ( PCIErr j PCSTimeout j RxUnderrun j RxErr ) ) ) 28 rtl8139 weird interrupt ( dev , tp , ioaddr , status , link changed ) ; 29 30 /¤ 如果状态寄存器的发送位置 1 ,则进⼊发送中断的处理函 数。 ¤/ 31 if ( status & ( TxOK j TxErr ) ) f 32 rtl8139 tx interrupt ( dev , tp , ioaddr ) ; 33 if ( status & TxErr ) 34 RTL W16 ( IntrStatus , TxErr ) ; 35 g 19 欢迎访问 OSPlay www.osplay.org 36 37 out : 38 spin unlock (&tp�>lock ) ; 39 40 DPRINTK ( "%s: exiting interrupt , intr_status=%#4.4x .\n" , dev�>name , RTL R16 ( IntrStatus ) ) ; 41 return IRQ RETVAL ( handled ) ; 这里有必要说明⼏种关中断的方式以免读者混淆。 ² CPU指令执行要经过取指令,指令译码,执行,中断检查等过程, 通过CPU标志寄存器的IF位,可以让CPU在中断检查的时候忽略可屏 蔽中断信号。 ² 8259A 等中断控制器上面可以设置中断屏蔽位,当外设发出中断请 求时,8259A先检测该中断是否被屏蔽,如果没被屏蔽,8259A才会 让CPU的intr管脚有效,从而向CPU通知中断。 ² 每⼀个可中断的外设有⼀个中断屏蔽寄存器,来指明哪种情况下需 要向8259A中断控制器发出中断信号。 显然,这里的NAPI关闭接收中断是最后⼀种情况。在它关闭中断进行 轮询处理过程中,随时都可能被系统上的其它设备中断。 4 软中断请求 4.1 NAPI方式 __netif_rx_schedule(dev)是把poll函数加⼊软中断调度队列。 ; 1 void netif rx schedule ( struct net device ¤dev ) 2 f 3 unsigned long flags ; 20 4.1 NAPI方式 欢迎访问 OSPlay www.osplay.org 4 5 local irq save ( flags ) ; 6 dev hold ( dev ) ; 7 /¤ 每⼀个 CPU 有⼀个软中断调度队列,这里 poll list 只是⼀ 个双向链表结构,没别的意思,当软中断调度的时候,它会循环 处理队列中的调度请求,然后利用 list move tail ,直到队 列为空。 ¤/ 8 list add tail (& dev�>poll list , & get cpu var ( softnet data ) . poll list ) ; 9 if ( dev�>quota < 0) 10 dev�>quota += dev�>weight ; 11 else 12 dev�>quota = dev�>weight ; 13 /¤ 系统启动的时候,通过 open softirq ( NET RX SOFTIRQ , net rx action , NULL ) ; 把 NET RX SOFTIRQ 的处理函数 设置为 net rx action ,触发软中断 后, net rx action 在适当的时候会被调用。 14 raise softirq irqoff ( NET RX SOFTIRQ ) ; 15 local irq restore ( flags ) ; 16 g 再看看net_rx_action。 1 static void net rx action ( struct softirq action ¤h ) 2 f 3 struct softnet data ¤queue = & get cpu var ( softnet data ) ; 4 unsigned long start time = jiffies ; 5 int budget = netdev budget ; 6 void ¤have ; 7 8 local irq disable ( ) ; 21 4.1 NAPI方式 欢迎访问 OSPlay www.osplay.org 9 10 /¤ 循环处理软中断队列。 ¤/ 11 while ( ! list empty (& queue�>poll list ) ) f 12 struct net device ¤dev ; 13 14 /¤ ⼀次软中断轮询时间不能过长。 ¤/ 15 if ( budget <= 0 j j jiffies � start time > 1 ) 16 goto softnet break ; 17 18 local irq enable ( ) ; 19 20 dev = list entry ( queue�>poll list . next , struct net device , poll list ) ; 21 have = netpoll poll lock ( dev ) ; 22 23 /¤ 调用 poll 函数进⼊轮询处理。这里 是 rtl8139 poll 。¤/ 24 if ( dev�>quota <= 0 j j dev�>poll ( dev , &budget ) ) f 25 netpoll poll unlock ( have ) ; 26 local irq disable ( ) ; 27 list move tail (& dev�>poll list , &queue�> poll list ) ; 28 if ( dev�>quota < 0) 29 dev�>quota += dev�>weight ; 30 else 31 dev�>quota = dev�>weight ; 32 g else f 33 netpoll poll unlock ( have ) ; 34 dev put ( dev ) ; 22 4.1 NAPI方式 欢迎访问 OSPlay www.osplay.org 35 local irq disable ( ) ; 36 g 37 g 38 out : 39 #ifdef CONFIG NET DMA 40 /¤ 41 ¤ There may not be any more sk buffs coming right now , so push 42 ¤ any pending DMA copies to hardware 43 ¤/ 44 if ( net dma client ) f 45 struct dma chan ¤chan ; 46 rcu read lock ( ) ; 47 list for each entry rcu ( chan , &net dma client �>channels , client node ) 48 dma async memcpy issue pending ( chan ) ; 49 rcu read unlock ( ) ; 50 g 51 #endif 52 local irq enable ( ) ; 53 return ; 54 55 softnet break : 56 get cpu var ( netdev rx stat ) . time squeeze ++; 57 raise softirq irqoff ( NET RX SOFTIRQ ) ; 58 goto out ; 59 g 23 4.2 非NAPI方式 欢迎访问 OSPlay www.osplay.org 4.2 非NAPI方式 在2.6的内核中,许多驱动默认就直接使用NAPI方式了,在没使用 NAPI方式的情况下,网卡驱动在接收中断的时候,调用netif_rx而不 是netif_rx_action。 1 int netif rx ( struct sk buff ¤skb ) 2 f 3 struct softnet data ¤queue ; 4 unsigned long flags ; 5 6 /¤ if netpoll wants it , pretend we never saw it ¤/ 7 /¤ 注意这个 netpoll 和我们说的轮询那个 poll 无关,在内核调 试过程中,用 printk 输出东西的时候,有时还来不急显示, 就 panic 了,可以通过配置 netpoll 把 printk 的数据发 送到另外⼀台机器,从而可以在 panic 看的时候看到正确的输 出。 ¤/ 8 if ( netpoll rx ( skb ) ) 9 return NET RX DROP ; 10 11 if ( ! skb�>tstamp . off sec ) 12 net timest
/
本文档为【+++最好的RTL8319网卡驱动分析】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索