Chinaunix首页 | 论坛 | 博客
  • 博客访问: 505463
  • 博文数量: 157
  • 博客积分: 3010
  • 博客等级: 中校
  • 技术积分: 1608
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-16 09:30
文章存档

2010年(155)

2008年(2)

我的朋友

分类:

2010-03-11 20:30:22

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
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
阅读《Linux内核情景分析》。
2 驱动的初始化
驱动程序的⼊⼝函数rtl8139_init_module调用了pci_register_driver
(&rtl8139_pci_driver); 其中参数rtl8139_pci_driver结够如下:
1 // 该结构相当于Windows 中的功能驱动程序对象
2 s t a t i c 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 = {
3 . name = DRV NAME ,
4 . i d t a b l e = r t l 8 1 3 9 p c i t b l ,
5 . probe = r t l 8 1 3 9 i n i t o n e ,
6 . remove = d e v e x i t p ( r t l 8 1 3 9 r e m o v e o n e ) ,
7 # ifdef CONFIG PM
8 . suspend = r t l 8 1 3 9 s u s p e n d ,
9 . resume = r t l 8 1 3 9 r e s u m e ,
10 # endif /∗ CONFIG PM ∗/
11 } ;
12
13 // 最重要的参数。r t l 8 1 3 9 p c i t b l
14 s t a t i c 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 [ ] = {
15 {0 x10ec , 0 x8139 , P C I A N Y I D , P C I A N Y I D , 0 , 0 ,
RTL8139 } ,
16 {0 x10ec , 0 x8138 , P C I A N Y I D , P C I A N Y I D , 0 , 0 ,
RTL8139 } ,
17 {0 x 1 1 1 3 , 0 x 1 2 1 1 , P C I A N Y I D , P C I A N Y I D , 0 , 0 ,
RTL8139 } ,
18 . . . . . .
19 } ;
4
欢迎访问OSPlay
rtl8139_pci_tbl中是由VerdonID,DeviceID等组成,表示该设备驱动
程序可以控制的设备,每⼀个PCI设备在配置空间中固化了自⼰的基本
信息,前面说过内核启动的时候PCI总线驱动程序会扫描PCI总线上的
设备,并且把这些信息收集起来,并且每⼀个设备的信息由⼀个专门
的结构保存起来,保存在⼀个pci_dev结构中。pci_register_driver会
根据rtl8139_pci_tbl中的信息和内核中扫描到的对比,如果有匹配的
话,就把功能驱动程序和目标设备对应起来了。在Windows系统中维护
设备信息的那个结构被称为物理设备对象,该对象由总线驱动程序创
建管理。之后pci_register_driver进行必要的初始化后,调用参数指定
的rtl8139_init_one。
1 s t a t i c 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
∗pdev , const struct p c i d e v i c e i d ∗ent )
2 {
3 struct n e t d e v i c e ∗dev = NULL ;
4 struct r t l 8 1 3 9 p r i v a t e ∗tp ;
5 int i , a d d r l e n , option ;
6 v o i d i o m e m ∗ io a d d r ;
7 s t a t i c int b o a r d i d x = −1;
8 u8 p c i r e v ;
9
10 . . . . . .
11 i = r t l 8 1 3 9 i n i t b o a r d ( pdev , & dev ) ;
12 . . . . . .
13 }
rtl8139_init_one的参数就是PCI总线驱动程序在设备枚举过程中创
建的。首先调用rtl8139_init_board。
1 s t a t i c 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 n e t d e v i c e ∗∗ d e v o u t )
5
欢迎访问OSPlay
2 {
3 v o i d i o m e m ∗ io a d d r ;
4 struct n e t d e v i c e ∗dev ;
5 struct r t l 8 1 3 9 p r i v a t e ∗tp ;
6 u8 tmp8 ;
7 int rc , d i s a b l e d e v o n e r r = 0 ;
8 u n s i g n e d int i ;
9 u n s i g n e d long p i o s t a r t , p i o e n d , p i o f l a g s , p i o l e n
;
10 u n s i g n e d long mmio start , mmio end , mmio flags ,
m m i o l e n ;
11 u32 v e r s i o n ;
12
13 assert ( pdev ! = NULL ) ;
14
15 ∗ d e v o u t = NULL ;
16
17 /∗ dev and priv z e r o e d in a l l o c e t h e r d e v ∗/
18 /∗ 每⼀个网络设备驱动程序为了设备管理方便,统⼀接⼝等目的,
需要创建自⼰的⼀个设备对象来维护设备信息,在Windows 系
统中,该对象称为功能设备对象。∗/
19 /∗ 每⼀个设备对象是标准的结构,但是不同的驱动程序可能都要维
护不同的私有信息,所以在分配n e t d e v 结构的同时可以多分
配出r t l 8 1 3 9 p r i v a t e 结构来。比如应用程序可能会经常查
询网卡地址,虽然驱动程序可以通过访问网卡上的存储空间来获
取网卡地址,但是驱动程序可不希望每次都通过慢速的IO 访问
来获取这些信息,通常驱动程序会为这些信息维护内存中的数据
结构中,这些信息都可以放在tp 中。∗/
20 dev = a l l o c e t h e r d e v ( s i z e o f (∗ tp ) ) ;
21 if ( dev == NULL ) {
22 d e v e r r (& pdev−>dev , " Unable to alloc new net
device \n" ) ;
6
欢迎访问OSPlay
23 return −ENOMEM ;
24 }
25 SET MODULE OWNER ( dev ) ;
26 SET NETDEV DEV ( dev , &pdev−>dev ) ;
27
28 tp = n e t d e v p r i v ( dev ) ;
29 tp−>p c i d e v = pdev ;
30
31 /∗ enable d e v i c e ( i n c l . PCI PM wakeup and hotplug
setup ) ∗/
32 /∗ 启用设备的memory/ I o 译码,如果设备处于休眠状态,则唤醒
设备。在启用设备之前,虽然设备的基址寄存器中设置了值,但
是当总线有主设备对该地址进行寻址的时候,设备是不会响应
的。具体启用操作由总线驱动程序封装,这里通过调用pci 总线
驱动程序提供的函数来完成该任务,在windows 中某些写操作
由功能驱动向下层的总线驱动发送IRP 完成同样的任务。∗/
33 rc = p c i e n a b l e d e v i c e ( pdev ) ;
34 if ( rc )
35 goto e r r o u t ;
36
37 /∗ ⼤多数设备上有自⼰的板载存储空间,其中包括memory 空间
和IO 空间,在X86 这样的IO 与Memory 分立编址的系统
上,他们的区别就在于独立的IO 访问指令,独立的地址译码,
在多数统⼀编址的系统上memeor 空间和IO 空间没有本质区
别。CPU 给出的指令中的地址落在哪里,设备在电路级别会访问
到对应地址的板载存储空间。PCI 总线驱动程序已经为设备分配
地址空间,并配置了基址寄存器,通常BIOS 已经为这些设备配
置了互不冲突的地址,系统的PCI 总线驱动程序可以直接扫
描PCI 设备,并从基址寄存器中读出各个设备的基地址,也可以
推倒从新统⼀分配。这里功能驱动程序需要知道自⼰如何访问到
目标设备,所以从总线物理设备对象中读取基址信息。∗/
38 p i o s t a r t = p c i r e s o u r c e s t a r t ( pdev , 0 ) ;
39 p i o e n d = p c i r e s o u r c e e n d ( pdev , 0 ) ;
40 p i o f l a g s = p c i r e s o u r c e f l a g s ( pdev , 0 ) ;
41 p i o l e n = p c i r e s o u r c e l e n ( pdev , 0 ) ;
7
欢迎访问OSPlay
42
43 m m i o s t a r t = p c i r e s o u r c e s t a r t ( pdev , 1 ) ;
44 mmio end = p c i r e s o u r c e e n d ( pdev , 1 ) ;
45 m m i o f l a g s = p c i r e s o u r c e f l a g s ( pdev , 1 ) ;
46 m m i o l e n = p c i r e s o u r c e l e n ( pdev , 1 ) ;
47
48 /∗
49 set this immediately , we need to know before
50 we talk to the chip d i r e c t l y
51 ∗/
52 DPRINTK ( "PIO region size == 0x%02X\n" , p i o l e n ) ;
53 DPRINTK ( "MMIO region size == 0x%02 lX\n" , m m i o l e n ) ;
54
55 /∗ PCI 设备板载存储空间可以通过IO 或者Memory 方式来访
问, PCI 设备上分别有IO 和Memory 基地址寄存器,
在X86 上的有独立的IO 指令,对于某些统⼀编址的体系结构
则只有通过Memory 方式来访问, CPU 执行IO /Memory 指
令时,其地址送到总线上, PCI 设备会根据IO /Memory 基址
寄存器判断出目标设备是不是自⼰。无论是IO 方式还
是Memory 方式,访问到通常是板载存储空间上的同⼀个地方。
由被寻址的从设备内部处理。例如:假设8 1 3 9 网
卡IO / Memory 基址寄存器的值分别是X / Y ,则使
用IO 指令( IN / OUT )访问X ,以及使用Memory 指令
( MOV )访问Y ,结果是⼀样的。这样保证了设备在统⼀编址
和独立编址的体系结构下的兼容性,为什么通过MOV 访问的地址
没有访问到内存上去呢?通常CPU 给出⼀条访存指令,地址被
发到北桥,北桥会根据地址空间的划分情况判别出该地址是落在
内存空间上还是其它总线上的设备空间上。如果是内存的话,则
向内存控制器发起访问操作,如果是PCI 空间的话,则同样的
向PCI 总线总裁提出申请。∗/
56
57 # ifdef U S E I O O P S
58 /∗ make sure PCI base addr 0 is PIO ∗/
59 if ( ! ( p i o f l a g s & IORESOURCE IO ) ) {
60 d e v e r r (& pdev−>dev , " region #0 not a PIO resource ,
aborting \n" ) ;
8
欢迎访问OSPlay
61 rc = −ENODEV ;
62 goto e r r o u t ;
63 }
64 /∗ check for weird /broken PCI region reporting ∗/
65 if ( p i o l e n < R T L M I N I O S I Z E ) {
66 d e v e r r (& pdev−>dev , " Invalid PCI I/O region size(s
), aborting \n" ) ;
67 rc = −ENODEV ;
68 goto e r r o u t ;
69 }
70 # e l s e
71 /∗ make sure PCI base addr 1 is MMIO ∗/
72 if ( ! ( m m i o f l a g s & IORESOURCE MEM ) ) {
73 d e v e r r (& pdev−>dev , " region #1 not an MMIO
resource , aborting \n" ) ;
74 rc = −ENODEV ;
75 goto e r r o u t ;
76 }
77 if ( m m i o l e n < R T L M I N I O S I Z E ) {
78 d e v e r r (& pdev−>dev , " Invalid PCI mem region size(s
), aborting \n" ) ;
79 rc = −ENODEV ;
80 goto e r r o u t ;
81 }
82 # endif
83
84 /∗ PCI 设备的地址是由软件配置的,那么必然需要⼀种机制来防止
地址冲突,系统维护
了io r e s o u r c e 和memory r e s o u r c e ,分别登记了已经分
配出去的地址空间,当为新设备分配地址的时候,必须根据登记
9
欢迎访问OSPlay
的信息找到空闲的地址空间,这里就是在系统中登记,表示改设
备要使用这个地址空间了,具体的地址在总线设备对
象pdev 结构中指定。注意把地址写⼊到设备的基址寄存器早
就已经由bios 或者内核完成了。这里只是进行登记。∗/
85 rc = p c i r e q u e s t r e g i o n s ( pdev , DRV NAME ) ;
86 if ( rc )
87 goto e r r o u t ;
88 d i s a b l e d e v o n e r r = 1 ;
89
90 /∗ enable PCI bus−mastering ∗/
91 p c i s e t m a s t e r ( pdev ) ;
92
93 # ifdef U S E I O O P S
94 i o a d d r = i o p o r t m a p ( p i o s t a r t , p i o l e n ) ;
95 if ( ! i o a d d r ) {
96 d e v e r r (& pdev−>dev , " cannot map PIO , aborting \n" ) ;
97 rc = −EIO ;
98 goto e r r o u t ;
99 }
100 dev−>b a s e a d d r = p i o s t a r t ;
101 tp−>m m i o a d d r = i o a d d r ;
102 tp−>r e g s l e n = p i o l e n ;
103 # e l s e
104 /∗ ioremap MMIO region ∗/
105 /∗ 映射设备地址,这里需要搞清楚3 种地址:
106 1 . 虚拟地址:经过页表页目录映射,访问的时候通
过CPU 的MMU 转换得到物理地址。
107 2 . 物理地址:在实模式下使用的或保护模式下经
过CPU 的MMU 转换后的地址。
108 3 . 总线地址:经过各种总线桥接器转换后在出现在总线的地址线上
的地址。∗/
109
10
欢迎访问OSPlay
110 /∗ 通常程序指令中访问的是虚拟地址, CPU 在指令执行的时候通
过MMU 把虚拟地址转换成物理地址,这个地址被送到北桥,北桥
根据自⼰地址空间判断出这个地址是在内存上还是在外部总线上
的设备上面,如果在外部设备上面,北桥可能要根据总线地址空
间的分配情况将这个物理地址变换成总线地址,再发送到总线上
面去。总线上的Host − PCI 以及PCI − PCI 都可以做这样
的变换,⼤多数情况下总线地址和物理地址是⼀样的,这取决于
系统设计。需要注意的是写⼊PCI 设备基址寄存器中的值必须
总线地址,才能够匹配到出现在总线上的地址,从而响应寻址操
作。而CPU 则要记住物理地址,并根据物理地址映射虚拟地
址。i o a d d r 是映射后的虚拟地址,以后程序中将通
过i o a d d r 访问设备。∗/
111
112 i o a d d r = p c i i o m a p ( pdev , 1 , 0 ) ;
113 if ( io a d d r == NULL ) {
114 d e v e r r (& pdev−>dev , " cannot remap MMIO , aborting \n
" ) ;
115 rc = −EIO ;
116 goto e r r o u t ;
117 }
118 dev−>b a s e a d d r = ( long ) i o a d d r ;
119 tp−>m m i o a d d r = i o a d d r ;
120 tp−>r e g s l e n = m m i o l e n ;
121 # endif /∗ U S E I O O P S ∗/
122
123 . . . . . .
124
125 // reset 就是向命令寄存器写个reset 命令。
126 r t l 8 1 3 9 c h i p r e s e t ( io a d d r ) ;
127
128 ∗ d e v o u t = dev ;
129 return 0 ;
130
131 e r r o u t :
11
欢迎访问OSPlay
132 r t l 8 1 3 9 c l e a n u p d e v ( dev ) ;
133 if ( d i s a b l e d e v o n e r r )
134 p c i d i s a b l e d e v i c e ( pdev ) ;
135 return rc ;
136 }
回到rtl8139_init_one:
1 s t a t i c 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
∗pdev , const struct p c i d e v i c e i d ∗ent )
2 {
3 . . . . . .
4
5 i = r t l 8 1 3 9 i n i t b o a r d ( pdev , & dev ) ;
6
7 . . . . . .
8
9 tp = n e t d e v p r i v ( dev ) ;
10 i o a d d r = tp−>m m i o a d d r ;
11
12 // 从i o a d d r 中读出Mac 地址
13 a d d r l e n = r e a d e e p r o m ( ioaddr , 0 , 8 ) == 0 x 8 1 2 9 ? 8
: 6 ;
14 for ( i = 0 ; i < 3 ; i ++)
15 ( ( u 1 6 ∗) ( dev−>d e v a d d r ) ) [ i ] = l e 1 6 t o c p u (
r e a d e e p r o m ( ioaddr , i + 7 , a d d r l e n ) ) ;
16
17 memcpy ( dev−>perm addr , dev−>d e v a d d r , dev−>a d d r l e n )
;
18
12
欢迎访问OSPlay
19 /∗ The Rtl8139−s p e c i f i c e n t r i e s in the d e v i c e
s t r u c t u r e . ∗/
20 /∗ 设置功能驱动程序对象的函数指针集∗/
21 dev−>open = r t l 8 1 3 9 o p e n ;
22 dev−>h a r d s t a r t x m i t = r t l 8 1 3 9 s t a r t x m i t ;
23 dev−>poll = r t l 8 1 3 9 p o l l ;
24 dev−>weight = 6 4 ;
25 dev−>stop = r t l 8 1 3 9 c l o s e ;
26 dev−>g e t s t a t s = r t l 8 1 3 9 g e t s t a t s ;
27 dev−>s e t m u l t i c a s t l i s t = r t l 8 1 3 9 s e t r x m o d e ;
28 dev−>d o i o c t l = n e t d e v i o c t l ;
29 dev−>e t h t o o l o p s = & r t l 8 1 3 9 e t h t o o l o p s ;
30 dev−>t x t i m e o u t = r t l 8 1 3 9 t x t i m e o u t ;
31 dev−>w a t c h d o g t i m e o = TX TIMEOUT ;
32 # ifdef CONFIG NET POLL CONTROLLER
33 dev−>p o l l c o n t r o l l e r = r t l 8 1 3 9 p o l l c o n t r o l l e r ;
34 # endif
35
36 . . . . . .
37
38 /∗ Put the chip into low−power mode . ∗/
39 /∗ ' R ' would leave the clock running . ∗/
40 if ( r t l c h i p i n f o [ tp−>chipset ] . flags & HasHltClk )
41 RTL W8 ( HltClk , 'H' ) ;
42
43 return 0 ;
44
45 e r r o u t :
46 r t l 8 1 3 9 c l e a n u p d e v ( dev ) ;
13
欢迎访问OSPlay
47 p c i d i s a b l e d e v i c e ( pdev ) ;
48 return i ;
49 }
8139网卡的有⼀个接收缓冲寄存器,用于存放接收缓存的首地址,网
卡⼀边把网线上的发出的数据放到内部FIFO,⼀边从FIFO中把数据通过
DMA传送到由接收寄存器指定的内存地址中,接收到的数据依次排放,当
长度超过默认的缓冲区长度时,会回过头来放到开始的地方,所以接收
缓冲区被称为环形缓冲区。发送方面:8139有四个发送地址寄存器,CPU
将要发送的数据在内存中的地址写⼊这四个寄存器中的任何⼀个,网卡
就会通过DMA操作把数据发送出去。当发送或者接送完成后,网卡会发出
中断,中断处理程序通过读取网卡的中断状态寄存器来识别出是发送完
成发出的中断,接收到数据包的中断,还是错误中断。
当运行ifconfig ethx up的时候,rtl8139_open得到调用。该函数的
任务就是分配,初始化接收,发送缓冲区,分配中断号等。
1 s t a t i c int r t l 8 1 3 9 o p e n ( struct n e t d e v i c e ∗dev )
2 {
3 struct r t l 8 1 3 9 p r i v a t e ∗tp = n e t d e v p r i v ( dev ) ;
4 int retval ;
5 v o i d i o m e m ∗ io a d d r = tp−>m m i o a d d r ;
6
7 // 为网卡申请中断,当中断到来时r t l 8 1 3 9 i n t e r r u p t 会被调
用。
8 retval = r e q u e s t i r q ( dev−>irq , r t l 8 1 3 9 i n t e r r u p t ,
IRQF SHARED , dev−>name , dev ) ;
9 if ( retval )
10 return retval ;
11 // 分配接收,发送缓冲区, DMA 没有CPU 的MMU 单元,因此只
能使用物理地址上连续的内存空间。
14
欢迎访问OSPlay
12 tp−>t x b u f s = p c i a l l o c c o n s i s t e n t ( tp−>p c i d e v ,
TX BUF TOT LEN , &tp−>t x b u f s d m a ) ;
13 tp−>r x r i n g = p c i a l l o c c o n s i s t e n t ( tp−>p c i d e v ,
RX BUF TOT LEN , &tp−>r x r i n g d m a ) ;
14 if ( tp−>t x b u f s == NULL | | tp−>r x r i n g == NULL )
15 {
16 f r e e i r q ( dev−>irq , dev ) ;
17
18 if ( tp−>t x b u f s )
19 p c i f r e e c o n s i s t e n t ( tp−>p c i d e v , TX BUF TOT LEN ,
tp−>tx bufs , tp−>t x b u f s d m a ) ;
20 if ( tp−>r x r i n g )
21 p c i f r e e c o n s i s t e n t ( tp−>p c i d e v , RX BUF TOT LEN ,
tp−>rx ring , tp−>r x r i n g d m a ) ;
22 return −ENOMEM ;
23 }
24
25 tp−>mii . f u l l d u p l e x = tp−>mii . f o r c e m e d i a ;
26 tp−>t x f l a g = ( TX FIFO THRESH << 1 1 ) & 0 x003f0000 ;
27
28 /∗ 初始化接送发送缓冲区,由于有四个发送地址寄存器,因此把发
送缓冲区分成4 组,以后发送请求到来的时候,将待发送内容拷
贝到第⼀组,再将第⼀组的地址写⼊寄存器,之后依次轮流使用
第⼆,三,四,⼀组。. . . ∗/
29 r t l 8 1 3 9 i n i t r i n g ( dev ) ;
30 /∗ 对网卡硬件进行相关的初始化。∗/
31 r t l 8 1 3 9 h w s t a r t ( dev ) ;
32
33 return 0 ;
34 }
15
欢迎访问OSPlay
rtl8139_hw_start主要是对网卡芯片进行初始化,主要就是根据网卡
的硬件手册,Programming guide向⼀些寄存器写⼊某些值。接下来我们
将看到⼤量类似的操作。
1 s t a t i c v o i d r t l 8 1 3 9 h w s t a r t ( struct n e t d e v i c e ∗dev )
2 {
3 struct r t l 8 1 3 9 p r i v a t e ∗tp = n e t d e v p r i v ( dev ) ;
4 /∗ i o a d d r 就是访问设备上的存储空间的基址。∗/
5 v o i d i o m e m ∗ io a d d r = tp−>m m i o a d d r ;
6
7 . . . . . .
8 /∗ 向网卡命令寄存器写⼊⼀个RESET 命令,这样网卡的各个寄存
器恢复到默认状态。∗/
9 r t l 8 1 3 9 c h i p r e s e t ( io a d d r ) ;
10
11 /∗ unlock Config [ 0 1 2 3 4 ] and BMCR r e g i s t e r writes ∗/
12 RTL W8 F ( Cfg9346 , C f g 9 3 4 6 U n l o c k ) ;
13 /∗ MAC0 被定义成0 ,在8 1 3 9 网卡PCI 空间基址偏移为的个
自节是用于存放网卡06 MAC 地址的,现在把之前从EEPROM 中
读出来的MAC 地址写⼊这个地址,将来网卡在收包的时候,就会
根据这个寄存器中的值来确定自⼰的MAC 地址。现在⼤家该明白
为什么我们平时能改MAC 地址了吧。∗/
14 /∗ Restore our idea of the MAC a d d r e s s . ∗/
15 RTL W32 F ( MAC0 + 0 , c p u t o l e 3 2 ( ∗ ( u32 ∗) ( dev−>
d e v a d d r + 0 ) ) ) ;
16 RTL W32 F ( MAC0 + 4 , c p u t o l e 3 2 ( ∗ ( u32 ∗) ( dev−>
d e v a d d r + 4 ) ) ) ;
17
18 /∗ Must enable Tx/Rx before s e t t i n g transfer
t h r e s h o l d s ! ∗/
19 /∗ 向命令写⼊命令,允许发送和接送。∗/
20 RTL W8 ( ChipCmd , CmdRxEnb | CmdTxEnb ) ;
16
欢迎访问OSPlay
21
22 /∗ 向接收配置寄存器写⼊配置,以后该网卡只接收广播帧和目
的MAC 地址是自⼰的帧。设为混杂模式的时候,则是向这个寄存
器写⼊。AcceptAllPhys ∗/
23 tp−>r x c o n f i g = r t l 8 1 3 9 r x c o n f i g | AcceptBroadcast
| AcceptMyPhys ;
24 RTL W32 ( RxConfig , tp−>r x c o n f i g ) ;
25 RTL W32 ( TxConfig , r t l 8 1 3 9 t x c o n f i g ) ;
26
27 . . . . . .
28 /∗ 向中断屏蔽寄存器写⼊中断允许位。默认允许接送,发送,错误
等等中断。如果屏蔽了接送中断,那么当网卡接收到帧的时候就
不会发出中断了, NAPI 就是通过屏蔽这里的接收中断,而通
过轮询接收状态寄存器来查看是不是有帧收到了来减少中断次
数,提高效率的。由于网卡接收⼩包的速度快,如果按常规处理
流程,中断−> CPU 保存⼀堆寄存器然后中断处
理−> CPU 恢复⼀堆寄存器−> 调度决策−> . . . . . . 在⼩包
高速发送的环境下,尤其是在测试的时候,很可能在刚⼀恢复线
程上下文,中断⼜来了。⼜得重新保存上下文进⼊中断处理,关
闭中断进行轮询就是要节省这⼏个NAPI CPU 时钟周期。对⼤包
来说,⼀个包收的要慢⼀点,很可能在执行poll 轮询的时候,
第⼀次检测网卡状态寄存器发现有包了, CPU Copy 出来处理,
之后再检测那个寄存器的时候,下⼀个⼤包的接收还没完成,于
是开了中断,结束⼀次,然后恢线程上下文,然后过了很⼩的⼀
段时间,中断⼜来了,所以⼤包省不了⼏个中断切换的时钟周
期,效果不明显。poll ∗/
29 RTL W16 ( IntrMask , r t l 8 1 3 9 i n t r m a s k ) ;
以上都是向某个地址写⼊⼀些值,基地址是PCI总线驱动程序配置
的,某个偏移位置的地址代表什么意思,该写⼊什么值是功能设备的
芯片逻辑规定的。以接送配置寄存器为例,RxConfig被被定义为0x44,
则该寄存器地址偏移是0x44,AcceptAllPhys被定义为0x01, AcceptMyPhys
被定义为0x02,就是说该寄存器的最低位为1时,网卡会进⼊混杂模式
接收所有的帧,第⼀位为0,第⼆位为1时,只接收目的MAC地址为自⼰的
帧。彻底的搞清楚每⼀个寄存器的每个BIT代表什么实在是没有必要,
不同的网卡芯片都是不⼀样的。所有这里我将略去细节的分析,⼒求从
主线上把握就可以了,如果确实需要,可以查阅相关芯片的Datasheet和
17
欢迎访问OSPlay
Programming guide。
3 中断处理
当网卡收到数据,发送数据完成,或收发出错都可能发出中断,在中
断处理中根据网卡中断状态寄存器的值来判断是什么情况的中断,然后
调用相应的处理函数。
1 s t a t i c i r q r e t u r n t r t l 8 1 3 9 i n t e r r u p t ( int irq , v o i d ∗
d e v i n s t a n c e )
2 {
3 struct n e t d e v i c e ∗dev = ( struct n e t d e v i c e ∗)
d e v i n s t a n c e ;
4 struct r t l 8 1 3 9 p r i v a t e ∗tp = n e t d e v p r i v ( dev ) ;
5 v o i d i o m e m ∗ io a d d r = tp−>m m i o a d d r ;
6 u 1 6 status , ackstat ;
7 int l i n k c h a n g e d = 0 ; /∗ avoid bogus " uninit "
warning ∗/
8 int handled = 0 ;
9
10 s p i n l o c k (& tp−>lock ) ;
11 /∗ 读取中断状态寄存器的值。∗/
12 status = RTL R16 ( I n t r S t a t u s ) ;
13
14 . . . . . .
15
16 /∗ R e c e i v e packets are p r o c e s s e d by poll routine . If
not running start it now . ∗/
17 /∗ 如果状态寄存器的接收位置1 ,则进⼊接收处理函数。根
据NAPI 机制。这里先向中断屏蔽寄存器中写
⼊r t l 8 1 3 9 n o r x i n t r m a s k , 关闭接收中
18
欢迎访问OSPlay
断, n e t i f r x s h e d u l e p r e p 检查网卡是不是处于up 状
态,然后在dev−>state 上设
置L I N K S T A T E R X S C H E D 标记,然后通过把接收
的n e t i f r x s c h e d u l e poll 函数加⼊软中断队列。将来
软中断调度的时候,会调用r t l 8 1 3 9 p o l l , 进行轮询。轮询完
成的时候,会清除dev−>state 上
的L I N K S T A T E R X S C H E D 标记。这主要是避免软中断队列
中出现多余的poll 请求。我们都知道中断的优先级比较高,
如果直接在这里用n e t i f r x s c h e d u l e 把poll 请求加⼊
软中断队列中,那么很可能在软中断还没被调度的时候,⼜来了
⼀次接收中断,于是⼜有⼀个poll 请求被加⼊队列中。等软中
断被调度的时候,很可能在第⼀次poll 的时候就处理完成了所
有的接收,而后来的那些中断所收到的数据也被第⼀个处理
了。poll ∗/
18 if ( status & RxAckBits ) {
19 if ( n e t i f r x s c h e d u l e p r e p ( dev ) ) {
20 R T L W 1 6 F ( IntrMask , r t l 8 1 3 9 n o r x i n t r m a s k ) ;
21 n e t i f r x s c h e d u l e ( dev ) ;
22 }
23 }
24
25 /∗ Check uncommon events with one test . ∗/
26 /∗ 如果状态寄存器的相关错误位置1 ,则进⼊错误处理函数。∗/
27 if ( u nlikely ( status & ( PCIErr | PCSTimeout |
RxUnderrun | RxErr ) ) )
28 r t l 8 1 3 9 w e i r d i n t e r r u p t ( dev , tp , ioaddr , status ,
l i n k c h a n g e d ) ;
29
30 /∗ 如果状态寄存器的发送位置1 ,则进⼊发送中断的处理函
数。∗/
31 if ( status & ( TxOK | TxErr ) ) {
32 r t l 8 1 3 9 t x i n t e r r u p t ( dev , tp , i o a d d r ) ;
33 if ( status & TxErr )
34 RTL W16 ( IntrStatus , TxErr ) ;
35 }
19
欢迎访问OSPlay
36
37 out :
38 s p i n u n l o c k (& tp−>lock ) ;
39
40 DPRINTK ( "%s: exiting interrupt , intr_status =%#4.4 x
.\n" , dev−>name , RTL R16 ( I n t r S t a t u s ) ) ;
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 v o i d n e t i f r x s c h e d u l e ( struct n e t d e v i c e ∗dev )
2 {
3 u n s i g n e d long flags ;
20
4.1 NAPI方式欢迎访问OSPlay
4
5 l o c a l i r q s a v e ( flags ) ;
6 d e v h o l d ( dev ) ;
7 /∗ 每⼀个CPU 有⼀个软中断调度队列,这里p o l l l i s t 只是⼀
个双向链表结构,没别的意思,当软中断调度的时候,它会循环
处理队列中的调度请求,然后利用l i s t m o v e t a i l ,直到队
列为空。∗/
8 l i s t a d d t a i l (& dev−>p o l l l i s t , & g e t c p u v a r (
s o f t n e t d a t a ) . p o l l l i s t ) ;
9 if ( dev−>quota < 0 )
10 dev−>quota += dev−>weight ;
11 e l s e
12 dev−>quota = dev−>weight ;
13 /∗ 系统启动的时候,通过o p e n s o f t i r q ( NET RX SOFTIRQ ,
n e t r x a c t i o n , NULL ) ; 把NET RX SOFTIRQ 的处理函数
设置为n e t r x a c t i o n ,触发软中断
后, n e t r x a c t i o n 在适当的时候会被调用。
14 r a i s e s o f t i r q i r q o f f ( NET RX SOFTIRQ ) ;
15 l o c a l i r q r e s t o r e ( flags ) ;
16 }
再看看net_rx_action。
1 s t a t i c v o i d n e t r x a c t i o n ( struct s o f t i r q a c t i o n ∗h )
2 {
3 struct s o f t n e t d a t a ∗queue = & g e t c p u v a r (
s o f t n e t d a t a ) ;
4 u n s i g n e d long s t a r t t i m e = j i f f i e s ;
5 int budget = n e t d e v b u d g e t ;
6 v o i d ∗have ;
7
8 l o c a l i r q d i s a b l e ( ) ;
21
4.1 NAPI方式欢迎访问OSPlay
9
10 /∗ 循环处理软中断队列。∗/
11 while ( ! l i s t e m p t y (& queue−>p o l l l i s t ) ) {
12 struct n e t d e v i c e ∗dev ;
13
14 /∗ ⼀次软中断轮询时间不能过长。∗/
15 if ( budget <= 0 | | j i f f i e s − s t a r t t i m e > 1 )
16 goto softnet break ;
17
18 l o c a l i r q e n a b l e ( ) ;
19
20 dev = l i s t e n t r y ( queue−>p o l l l i s t . next , struct
n e t d e v i c e , p o l l l i s t ) ;
21 have = n e t p o l l p o l l l o c k ( dev ) ;
22
23 /∗ 调用poll 函数进⼊轮询处理。这里
是r t l 8 1 3 9 p o l l 。∗/
24 if ( dev−>quota <= 0 | | dev−>poll ( dev , & budget ) ) {
25 n e t p o l l p o l l u n l o c k ( have ) ;
26 l o c a l i r q d i s a b l e ( ) ;
27 l i s t m o v e t a i l (& dev−>p o l l l i s t , &queue−>
p o l l l i s t ) ;
28 if ( dev−>quota < 0 )
29 dev−>quota += dev−>weight ;
30 e l s e
31 dev−>quota = dev−>weight ;
32 } e l s e {
33 n e t p o l l p o l l u n l o c k ( have ) ;
34 d e v p u t ( dev ) ;
22
4.1 NAPI方式欢迎访问OSPlay
35 l o c a l i r q d i s a b l e ( ) ;
36 }
37 }
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 c o p i e s to hardware
43 ∗/
44 if ( n e t d m a c l i e n t ) {
45 struct dma chan ∗chan ;
46 r c u r e a d l o c k ( ) ;
47 l i s t f o r e a c h e n t r y r c u ( chan , & n e t d m a c l i e n t
−>channels , c l i e n t n o d e )
48 d m a a s y n c m e m c p y i s s u e p e n d i n g ( chan ) ;
49 r c u r e a d u n l o c k ( ) ;
50 }
51 # endif
52 l o c a l i r q e n a b l e ( ) ;
53 return ;
54
55 s oftnet break :
56 g e t c p u v a r ( n e t d e v r x s t a t ) . t i m e s q u e e z e ++;
57 r a i s e s o f t i r q i r q o f f ( NET RX SOFTIRQ ) ;
58 goto out ;
59 }
23
4.2 非NAPI方式欢迎访问OSPlay
4.2 非NAPI方式
在2.6的内核中,许多驱动默认就直接使用NAPI方式了,在没使用
NAPI方式的情况下,网卡驱动在接收中断的时候,调用netif_rx而不
是netif_rx_action。
1 int n e t i f r x ( struct sk buff ∗skb )
2 {
3 struct s o f t n e t d a t a ∗queue ;
4 u n s i g n e d long flags ;
5
6 /∗ if netpoll wants it , pretend we never saw it ∗/
7 /∗ 注意这个netpoll 和我们说的轮询那个poll 无关,在内核调
试过程中,用printk 输出东西的时候,有时还来不急显示,
就panic 了,可以通过配置netpoll 把printk 的数据发
送到另外⼀台机器,从而可以在panic 看的时候看到正确的输
出。∗/
8 if ( n e t p o l l r x ( skb ) )
9 return NET RX DROP ;
10
11 if ( ! skb−>tstamp . o f f s e c )
12 net timestamp ( skb ) ;
13
14 /∗
15 ∗ The code is rearranged so that the path is the
most
16 ∗ short when CPU is congested , but is s t i l l
operating .
17 ∗/
18 l o c a l i r q s a v e ( flags ) ;
19 queue = & g e t c p u v a r ( s o f t n e t d a t a ) ;
20
24
4.2 非NAPI方式欢迎访问OSPlay
21 g e t c p u v a r ( n e t d e v r x s t a t ) . total ++;
22 if ( queue−>i n p u t p k t q u e u e . qlen <=
n e t d e v m a x b a c k l o g ) {
23 if ( queue−>i n p u t p k t q u e u e . qlen ) {
24 enqueue :
25 d e v h o l d ( skb−>dev ) ;
26 s k b q u e u e t a i l (& queue−>i n p u t p k t q u e u e , skb ) ;
27 l o c a l i r q r e s t o r e ( flags ) ;
28 return NET RX SUCCESS ;
29 }
30 /∗ 注意这个b a c k l o g d e v 是不是接收到数据包的那
个n e t d e v ,每⼀个NET RX SOFTIRQ 接收软中断
对象中有⼀个默认的n e t d e v 结构b a c k l o g d e v ,
这个是系统在n e t d e v i n i t 初始化网络系统的时候,
把b a c k l o g d e v 的poll 函数设置
为p r o c e s s b a c k l o g ,这里和NAPI 的区别就在于
在加⼊队列的时候,使用的是网卡的NAPI n e t d e v ,
而这里使用的是系统默认的b a c k l o g d e v 。所以
当n e t i f r x s h e d u l e 之后,软中断调度的
是p r o c e s s b a c k l o g 。∗/
31 n e t i f r x s c h e d u l e (& queue−>b a c k l o g d e v ) ;
32 goto enqueue ;
33 }
34
35 g e t c p u v a r ( n e t d e v r x s t a t ) . dropped ++;
36 l o c a l i r q r e s t o r e ( flags ) ;
37
38 kfree skb ( skb ) ;
39 return NET RX DROP ;
40 }
25
欢迎访问OSPlay
5 网卡接收操作
当RJ-45那个接⼝有数据从网线上“流⼊”的时候,网卡把它放到内
部FIFO中,同时进行DMA传输到接收环形缓冲区。之后网卡发出中断。
中断后进⼊接收处理函数。它先关闭接收中断后,将轮询函数挂⼊软中
断调度队列。之后在软中断处理过程中将调用rtl8139_poll。
1 s t a t i c int r t l 8 1 3 9 p o l l ( struct n e t d e v i c e ∗dev , int ∗
budget )
2 {
3 . . . . . .
4
5 s p i n l o c k (& tp−>r x l o c k ) ;
6 if ( l i k e l y ( RTL R16 ( I n t r S t a t u s ) & RxAckBits ) ) {
7 int work done ;
8 /∗ 在r t l 8 1 3 9 r x 中将接送到的数据拷贝出来并传递给上层协
议驱动。∗/
9 work done = r t l 8 1 3 9 r x ( dev , tp , o r i g b u d g e t ) ;
10 if ( l i k e l y ( work done > 0 ) ) {
11 ∗budget −= work done ;
12 dev−>quota −= work done ;
13 done = ( work done < o r i g b u d g e t ) ;
14 }
15 }
16
17 if ( done ) {
18 /∗ 轮询结束开启接收中断。∗/
19 l o c a l i r q s a v e ( flags ) ;
20 R T L W 1 6 F ( IntrMask , r t l 8 1 3 9 i n t r m a s k ) ;
21 n e t i f r x c o m p l e t e ( dev ) ;
22 l o c a l i r q r e s t o r e ( flags ) ;
26
欢迎访问OSPlay
23 }
24 s p i n u n l o c k (& tp−>r x l o c k ) ;
25 return ! done ;
26 }
rtl8139_rx把数据从网卡接收缓存中拷贝出来。数据在环形缓冲区的
存放格式如下:
| 长度| 状态位| 内容| 长度| 状态位| 内容| ...
1 s t a t i c int r t l 8 1 3 9 r x ( struct n e t d e v i c e ∗dev , struct
r t l 8 1 3 9 p r i v a t e ∗tp , int budget )
2 {
3 v o i d i o m e m ∗ io a d d r = tp−>m m i o a d d r ;
4 int r e c e i v e d = 0 ;
5 u n s i g n e d char ∗ r x r i n g = tp−>r x r i n g ;
6 /∗ 网卡不断的把数据放进环形接收缓冲区, CPU 读出来的时候,
读到哪里的顺序需要自⼰维护, tp−>c u r r x 记录上次读到哪
里,这里将接着从上⼀次的地方拷贝。∗/
7 u n s i g n e d int c u r r x = tp−>c u r r x ;
8 u n s i g n e d int r x s i z e = 0 ;
9
10 . . . . . .
11
12 /∗ 轮询寄存器,当ChipCmd RxBufEmpty 位没被网卡设置的时
候,则说明环形缓冲区中有接收到的数据等待处理。∗/
13 while ( n e t i f r u n n i n g ( dev ) && r e c e i v e d < budget && (
RTL R8 ( ChipCmd ) & RxBufEmpty ) == 0 ) {
14 u32 r i n g o f f s e t = c u r r x % RX BUF LEN ;
15 u32 r x s t a t u s ;
16 u n s i g n e d int p k t s i z e ;
17 struct sk buff ∗skb ;
18
27
欢迎访问OSPlay
19 rmb ( ) ;
20
21 /∗ read s i z e + status of next frame from DMA ring
buffer ∗/
22 r x s t a t u s = l e 3 2 t o c p u ( ∗ ( u32 ∗) ( r x r i n g +
r i n g o f f s e t ) ) ;
23 r x s i z e = r x s t a t u s >> 1 6 ;
24 p k t s i z e = r x s i z e − 4 ;
25
26 . . . . . .
27
28 /∗ Packet copy from FIFO s t i l l in p r o g r e s s .
29 ∗ Theoretically , this should never happen
30 ∗ s i n c e EarlyRx is d i s a b l e d .
31 ∗/
32 /∗ 当EarlyRX 允许的时候,可能会发生这种情况,⼀个完整的
数据包的⼀部分已经通过DMA 传送到了内存中,而另外⼀部
分还在网卡内部FIFO 中,网卡的DMA 操作还在进行
中。∗/
33 if ( unlikely ( r x s i z e == 0 xfff0 ) ) {
34 if ( ! tp−>f i f o c o p y t i m e o u t )
35 tp−>f i f o c o p y t i m e o u t = j i f f i e s + 2 ;
36 e l s e if ( t i m e a f t e r ( jiffies , tp−>
f i f o c o p y t i m e o u t ) ) {
37 DPRINTK ( "%s: hung FIFO. Reset ." , dev−>name ) ;
38 r x s i z e = 0 ;
39 goto n o e a r l y r x ;
40 }
41 if ( n e t i f m s g i n t r ( tp ) ) {
28
欢迎访问OSPlay
42 printk ( KERN DEBUG "%s: fifo copy in progress ."
, dev−>name ) ;
43 }
44 tp−>xstats . e a r l y r x ++;
45 break ;
46 }
47
48 n o e a r l y r x :
49 tp−>f i f o c o p y t i m e o u t = 0 ;
50
51 /∗ If Rx err or i n v a l i d r x s i z e / r x s t a t u s r e c e i v e d
52 ∗ ( which happens if we get l o s t in the ring ) ,
53 ∗ Rx p r o c e s s gets reset , so we abort any further
54 ∗ Rx p r o c e s s i n g .
55 ∗/
56 if ( unlikely ( ( r x s i z e > ( MAX ETH FRAME SIZE +4) ) | |
( r x s i z e < 8 ) | | ( ! ( r x s t a t u s & RxStatusOK ) ) ) )
{
57 r t l 8 1 3 9 r x e r r ( rx status , dev , tp , i o a d d r ) ;
58 r e c e i v e d = −1;
59 goto out ;
60 }
61
62 /∗ Malloc up new buffer , compatible with net−2e .
∗/
63 /∗ Omit the four octet CRC from the length . ∗/
64
65 /∗ 把数据拷贝到SKB 中来。∗/
66 skb = d e v a l l o c s k b ( p k t s i z e + 2 ) ;
29
欢迎访问OSPlay
67 if ( l i k e l y ( skb ) ) {
68 skb−>dev = dev ;
69 s k b r e s e r v e ( skb , 2 ) ;
70 #if R X B U F I D X == 3
71 wrap copy ( skb , rx ring , r i n g o f f s e t +4 , p k t s i z e )
;
72 # e l s e
73 e t h c o p y a n d s u m ( skb , & r x r i n g [ r i n g o f f s e t +
4 ] , p k t s i z e , 0 ) ;
74 # endif
75 skb put ( skb , p k t s i z e ) ;
76
77 /∗ 判断包的协议。∗/
78 skb−>p r o t o c o l = e t h t y p e t r a n s ( skb , dev ) ;
79
80 dev−>l a s t r x = j i f f i e s ;
81 /∗ 更新统计信息,这些信息就是我们用i f c o n f i g 命令看到
的。∗/
82 tp−>stats . r x b y t e s += p k t s i z e ;
83 tp−>stats . r x p a c k e t s ++;
84
85 /∗ 调用系统函数通知上层协议驱动数据包的到来。∗/
86 n e t i f r e c e i v e s k b ( skb ) ;
87 } e l s e {
88 if ( n e t r a t e l i m i t ( ) )
89 printk ( KERN WARNING "%s: Memory squeeze ,
dropping packet .\n" , dev−>name ) ;
90 tp−>stats . r x d r o p p e d ++;
91 }
30
欢迎访问OSPlay
92 r e c e i v e d ++;
93
94 c u r r x = ( c u r r x + r x s i z e + 4 + 3 ) & ˜ 3 ;
95 RTL W16 ( RxBufPtr , ( u 1 6 ) ( c u r r x − 1 6 ) ) ;
96
97 r t l 8 1 3 9 i s r a c k ( tp ) ;
98 }
99
100 . . . . . .
101
102 out :
103 return r e c e i v e d ;
104 }
6 网卡发送操作
当上层协议驱动要发送数据的时候,最终会调用到hard_start_xmit指
定的函数。发送过层很简单,只需要把待发数据拷贝到缓冲区里面,然
后在把缓冲区的地址写如发送地址寄存器就可以了。
1 s t a t i c int r t l 8 1 3 9 s t a r t x m i t ( struct sk buff ∗skb ,
struct n e t d e v i c e ∗dev )
2 {
3 struct r t l 8 1 3 9 p r i v a t e ∗tp = n e t d e v p r i v ( dev ) ;
4 v o i d i o m e m ∗ io a d d r = tp−>m m i o a d d r ;
5 u n s i g n e d int entry ;
6 u n s i g n e d int len = skb−>len ;
7 u n s i g n e d long flags ;
8
31
欢迎访问OSPlay
9 /∗ C a l c u l a t e the next Tx d e s c r i p t o r entry . ∗/
10 /∗ 从四个发送寄存器中选取⼀个。∗/
11 entry = tp−>c u r t x % NUM TX DESC ;
12
13 /∗ Note : the chip doesn ' t have auto−pad ! ∗/
14 if ( l i k e l y ( len < T X B U F S I Z E ) ) {
15 if ( len < ETH ZLEN )
16 memset ( tp−>tx buf [ entry ] , 0 , ETH ZLEN ) ;
17
18 /∗ 把待发送的数据拷贝到发送缓冲区中。∗/
19 s k b c o p y a n d c s u m d e v ( skb , tp−>tx buf [ entry ] ) ;
20 d e v k f r e e s k b ( skb ) ;
21 } e l s e {
22 d e v k f r e e s k b ( skb ) ;
23 tp−>stats . t x d r o p p e d ++;
24 return 0 ;
25 }
26
27 s p i n l o c k i r q s a v e (& tp−>lock , flags ) ;
28 /∗ 把发送缓冲区的地址写⼊发送缓冲区地址寄存器,之后网卡芯片
就会把数据发送出去,发送结束的时候会发出中断请求。∗/
29 RTL W32 F ( TxStatus0 + ( entry ∗ s i z e o f ( u32 ) ) , tp−>
t x f l a g | max ( len , ( u n s i g n e d int ) ETH ZLEN ) ) ;
30
31 dev−>t r a n s s t a r t = j i f f i e s ;
32 . . . . . .
33 return 0 ;
34 }
32 
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/icelord/archive/2007/11/02/1862989.aspx
阅读(876) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~