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 RegisterPCI local bus specification 规定, PCI 配置空间有 632 位寄存器保存基地址(即 bar0-bar5 )。 8139too 网卡驱动程序使用了其中的两个来分别表示 I/O 空间基地址映射和内存空间基地址映射。
4.          接下来调用 pci_request_regions 函数申请资源。该函数实际上调用了 request_regionrequest_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.          步骤 123 都应在自旋锁的保护下进行。
 
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.          更新 CAPRCurrent 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 签名档:改变能够改变的,接收不能改变的。