static int __devinit rtl8139_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)
函数功能描述:赋给
pci_driver
结构体中的
probe
函数指针,用于当
PCI
核心检测到一个需要控制的
pci_dev
时,对相应的设备进行始化工作。
附注:本函数的主要任务是创建并初始化
net_device
结构,该结构是网卡设备的抽象。
函数流程:
1.
调用
alloc_etherdev
函数,创建以太网类型的网卡设备。它是对
alloc_netdev
函数的封装,内核中给出了用于设置以太网通用属性的
setup
函数。
2.
使能当前
PCI
设备,包括唤醒、热插拔等功能的建立。
3.
读取
PCI
配置空间中的
Base Address Register
。
PCI local bus specification
规定,
PCI
配置空间有
6
个
32
位寄存器保存基地址(即
bar0-bar5
)。
8139too
网卡驱动程序使用了其中的两个来分别表示
I/O
空间基地址映射和内存空间基地址映射。
4.
接下来调用
pci_request_regions
函数申请资源。该函数实际上调用了
request_region
和
request_mem_region
,它们分别负责分配
I/O
端口和内存资源。(内核中资源一般分成四类,分别是
I/O
端口,内存,
DMA
通道和中断线,它们都由
resource
结构描述,不同的是每一类资源拥有共同的基类对象,这一点上体现出了内核设计上的
OO
思想。实际上,典型的面向对象思想如封装、继承、多态在内核中都有体现。每一类资源都以树状结构进行组织,源码显示,这种树状结构实际上是一种“双亲表示法”和“孩子、兄弟表示法”的融合)。
5.
设置
dev->base_addr
字段,它视驱动程序采用
I/O
空间抑或内存空间而定。如果采用
I/O
空间,还要将
I/O
端口地址映射成内存地址(这是一种比较好的做法);否则,把
PCI
配置空间中
bar1
寄存器中的内存地址进行重映射,只有经过重映射,驱动程序才能够访问该内存区域,这里涉及到页表项的添加。
6.
复位芯片。
7.
初始化
net_device
结构中的部分回掉函数,它们负责完成数据包的接收和传输等任务。
static int rtl8139_open (struct net_device *dev)
函数功能描述:申请资源,启动硬件和开启监控线程。
函数流程:
1.
调用
request_irq
函数,以可共享的方式(
IRQF_SHARED
)分配给网卡设备中断线,中断号存放在
dev->irq
中。
2.
申请用于
DMA
的设备内存。调用
pci_alloc_consistent
函数,它返回两个地址,一个是供设备驱动程序使用的内核虚地址;另一个是供
DMA
控制器使用的总线地址(它在某些体系结构下等同与物理地址)。用该函数申请到的设备内存物理上必须是连续的,因为这里
DMA
操作是将成块物理上连续的数据在主存与网卡的
FIFO
缓存间传输。
pci_alloc_consistent
函数实际上是调用
__get_free_pages
函数获取内存页,并使用
vrit_to_phys
函数将得到的内存页虚地址转换成物理地址。
3.
初始化
ring buffer.
。
4.
启动硬件。首先复位网卡设备,接着将从
EEPROM
设备中读取的硬件地址写入相应的寄存器,然后把第三步中获得的输入输出
DMA
缓冲区的总线地址赋予相应的寄存器,最后设置组播列表、使能所有已知的中断。
5.
启动传输队列,接受来自网络层的数据包。
6.
启动监控线程,该线程负责在传输超时的情况下做相应处理。该监控线程用工作队列实现。通过使用
schedule_delayed_work
函数,该线程函数可以在规定的时间点上被执行。
static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev)
函数功能描述:将网络层获得数据拷贝到
DMA
缓冲区
函数流程:
1.
将
sk_buf
结构中的数据拷贝到四路
DMA
输出缓冲区中的一个,保证包的大小至少为
ETH_ZLEN
。
2.
判断四路缓冲区是否已被用完,如果是,则调用
netif_stop_queue
函数来暂停网络层传输队列。这里要注意的是,完成
sk_buf
结构的拷贝后,要更新
tp->rx_curr
指针,它是一个
unsigned long
类型,同时,驱动程序还维护
tp->rx_dirty
指针。这两个指针负责四路
DMA
输出缓冲区的数据同步。它们都是不断递增的,当二者之差等于
NUM_TX_DESC
的时候,意味着四路
DMA
输出缓冲区都已被填满而且尚未被传输,这时候要做的自然是停止网络层继续向驱动程序发送
skb
。
static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs)
函数功能描述:中断处理
附注:通常情况下,中断的来临意味着
FIFO
中的数据已发送或
DMA
已将接受到的数据由
FIFO
拷贝到
DMA
缓冲区。更具体的来说,在禁止
early mode
的前提下,中断的发生表示一个完整的数据包被发送到了网络介质或者一个完整的数据包已通过
DMA
的传送到了内存缓冲区。为了不致使大量小数据包的接收给系统带来的频繁中断降低系统性能,
Linux
内核中提供了
NAPI
,即在关闭接收中断的状态下,以轮循的方式接收数据。
函数流程:
1.
读取中断状态寄存器,检查该中断是否属于当前网卡设备。由于以可共享的方式使用中断线,这一步是必须的。
2.
如果接收中断位被设置,首先调用
netif_rx_schedule_prep
函数,将设备对象的
dev->poll
方法加入到网络层的
poll
队列,如果函数执行成功,则清除中断状态寄存器的相应位(方法是在要清除的位写
1
),接着在设备对象上调用
__netif_rx_schedule
函数,它会在必要的时候出发一个软中断,从而通知网络层开始接受数据包(即调用
dev->poll
函数)。
3.
如果传输中断被位设置,则对已传送的数据包进行确认。具体的讲,就是通过读取相应的传输状态寄存器,获取传送的信息。芯片手册中规定,当驱动程序将数据包大小写入该寄存器的低
12
位的同时必须将其
13
位置
0
,而当传输
DMA
操作完成时,该位被自动置
1
,由此看来,这是与
DMA
控制器之间的握手操作。这里要明确指出的是,数据包由网络层传递给驱动程序并放置在
DMA
传输缓冲区的操作由上面谈到的
rtl8139_start_xmit
函数负责,中断处理函数中仅仅是对已发送的数据包的确认,同时记录一些信息,如发送的数据总量、发送的总数据包数、丢弃的总数据包数等等。
4.
步骤
1
、
2
、
3
都应在自旋锁的保护下进行。
static int rtl8139_poll(struct net_device *dev, int *budget)
函数功能描述:以轮循的方式将
DMA
输入缓冲区中的数据拷贝到
sk_buf
结构中并交付给网络层做进一步处理。
附注:本函数被赋值给
net_device
结构中的
poll
字段,它由软中断出发并不断被调用直到数据被处理完毕。整个过程要在输入自旋锁的保护下进行。
函数流程:
1.
清除中断状态寄存器的相应位。
2.
从
rx_ring
输入
DMA
缓冲区中读取数据包,申请并初始化
sk_buf
结构,同时用
rx_ring
中的数据包填充之,接着调用
netif_receive_skb
函数把
sk_buf
结构交付给网络层。这里应该注意的是,驱动程序通过在
rtl8139_private
结构中的
unsigned int
变量
cur_rx
来维护
rx_ring
中多个数据包的处理序列。独立于每个包的头
4
个字节放置的是接收数据包的状态和长度。
3.
更新
CAPR
(
Current Address of Packet Read
)寄存器,地址要双字对齐,应此,在计算
cur_rx
值时,使用表达式
cur_rx = (cur_rx + rx_size + 4 + 3) & ~3
。
4.
重复步骤
2
中的操作直到处理完
rx_ring
中的所有数据包。
5.
使用
__netif_rx_complete
函数将当前设备在
poll
队列中清除,接着使能接收数据中断。由于接收中断和
__netif_rx_complete
函数间存在竞争,即将设备从
poll
队列中清除的同时可能会有新的接收中断到来,应此这两个操作要在屏蔽本地
cpu
中断的状态下进行。
后记:
写这样的文章,对与我来说是比较有压力的。一方面,在
Linux
设备驱动开发领域,我算是一个新手;另一方面,光是看了几天代码写点总结未免有些纸上谈兵;更重要的是,我对芯片手册中的部分内容还不是很理解,例如
early mode
的用途等等,因此本文的错误是在所难免的,希望朋友们不吝赐教。此外,这几天惊闻
06
级硕士要改回
2
年半,心里颇有不快,那就借此拙文慰藉一下自己和实验室的同胞吧。借用方师兄的
qq
签名档:改变能够改变的,接收不能改变的。
转载于:https://blog.51cto.com/enchen/191920