学习应该是一个先把问题简单化,再把问题复杂化的过程。一开始就着手处理复杂的问题,难免让人有心惊胆颤,捉襟见肘的感觉。读Linux网卡驱动 也是一样。那长长的源码夹杂着那些我们陌生的变量和符号,望而生畏便是理所当然的了。不要担心,事情总有解决的办法,先把一些我们管不着的代码切割出去,留下必须的部分,把框架掌握了,那其他的事情自然就水到渠成了,这是笔者的心得。
一般在使用的Linux网卡驱动代码动辄3000行左右,这个代码量以及它所表达出来的知识量无疑是庞大的,我们有没有办法缩短一下这个代码量,使我们的学习变的简单些呢?经过笔者的不懈努力,在仍然能够使网络设备正常工作的前提下,把它缩减到了600多行,我们把暂时还用不上的功能先割出去。这样一来,事情就简单多了,真的就剩下一个框架了。
下面我们就来剖析这个可以执行的框架。
限于篇幅,以下分析用到的所有涉及到内核中的函数代码,我都不予列出,但给出在哪个具体文件中,请读者自行查阅。
首先,我们来看看设备的初始化。当我们正确编译完我们的程序后,我们就需要把生成的目标文件加载到内核中去,我们会先 ifconfig eth0 down和rmmod 8139too来卸载正在使用的网卡驱动,然后insmod 8139too.o把我们的驱动加载进去(其中8139too.o是我们编译生成的目标文件)。就像C程序有主函数main()一样,模块也有第一个执行的函数,即 module_init(rtl8139_init_module);在我们的程序中,rtl8139_init_module()在insmod之后首 先执行,它的代码如下:
static int __init rtl8139_init_module (void)
{
return pci_module_init (&rtl8139_pci_driver);
}
它直接调用了pci_module_init(),这个函数代码在Linux/drivers/net/eepro100.c中,并且把rtl8139_pci_driver(这个结构是在我们的驱动代码里定义的,它是驱动程序和PCI设备联系的纽带)的地址作为参数传给了它。 rtl8139_pci_driver定义如下:
static struct pci_driver rtl8139_pci_driver = {
name: MODNAME,
id_table: rtl8139_pci_tbl,
probe: rtl8139_init_one,
remove: rtl8139_remove_one,
};
pci_module_init()在驱动代码里没有定义,你一定想到了,它是Linux内核提供给模块是一个标准接口,那么这个接口都干了些什么?笔者跟踪了这个函数,里面调用了pci_register_driver(),这个函数代码在Linux/drivers/pci/pci.c 中,pci_register_driver做了三件事情。
①是把带过来的参数rtl8139_pci_driver在内核中进行了注册。内核中有一个PCI设备的大的链表,这里负责把这个PCI驱动挂到里面去。
②是查看总线上所有PCI设备(网卡设备属于PCI设备的一种)的配置空间,如果发现标识信息与rtl8139_pci_driver中的id_table相同,即rtl8139_pci_tbl,而它的定义如下:
static struct pci_device_id rtl8139_pci_tbl[] __devinitdata = {
{0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1},
{PCI_ANY_ID, 0x8139, 0x10ec, 0x8139, 0, 0,0 },
{0,}
};
那么就说明这个驱动程序就是用来驱动这个设备的,于是调用rtl8139_pci_driver中的probe函数即rtl8139_init_one,这个函数是在我们的驱动程序中定义了的,它是用来初始化整个设备和做一些准备工作。这里需要注意一下 pci_device_id是内核定义的用来辨别不同PCI设备的一个结构,例如在我们这里0x10ec代表的是Realtek公司,我们扫描PCI设备配置空间如果发现有Realtek公司制造的设备时,两者就对上了。当然对上了公司号后还得看其他的设备号什么的,都对上了才说明这个驱动是可以为这个设备服务的。
③是把这个rtl8139_pci_driver结构挂在这个设备的数据结构(pci_dev)上,表示这个设备从此就有了自己的驱动了。而驱动也找到了它服务的对象了。
PCI是一个总线标准,PCI总线上的设备就是PCI设备,这些设备有很多类型,当然也包括网卡设备,每一个PCI设备在内核中抽象为一个数据结构pci_dev,它描述了一个PCI设备的所有的特性,具体请查询相关文档,本文限于篇幅无法详细描述。但是有几个地方和驱动程序的关系特别大,必须予以说明。PCI设备都遵守PCI标准,这个部分所有的PCI设备都是一样的,每个PCI设备都有一段寄存器存储着配置空间,这一部分格式是一样的,比如第一个寄存器总是生产商号码,如Realtek就是10ec,而Intel则是另一个数字,这些都是商家像标准组织申请的,是肯定不同的。我就可以通过配置空间来辨别其生产商,设备号,不论你什么平台,x86也好,ppc也好,他们都是同一的标准格式。当然光有这些PCI配置空间的统一格式还是不够的,比如 说人类,都有鼻子和眼睛,但并不是所有人的鼻子和眼睛都长的一样的。网卡设备是PCI设备必须遵守规则,在设备里集成了PCI配置空间,但它是一个网卡就必须同时集成能控制网卡工作的寄存器。而寄存器的访问就成了一个问题。在Linux里面我们是把这些寄存器映射到主存虚拟空间上的,换句话说我们的CPU 访存指令就可以访问到这些处于外设中的控制寄存器。总结一下,PCI设备主要包括两类空间,一个是配置空间,它是操作系统或BIOS控制外设的统一格式的空 间,CPU指令不能访问,访问这个空间要借助BIOS功能,事实上Linux的访问配置空间的函数是通过CPU指令驱使BIOS来完成读写访问的。
而另一类是普通的控制寄存器空间,这一部分映射完后CPU可以访问来控制设备工作。
现在我们回到上面pci_register_driver的第二步,如果找到相关设备和我们的pci_device_id结构数组对上号了,说明我们找到服务对象了,则调用rtl8139_init_one,它主要做了七件事:
① 建立net_device结构,让它在内核中代表这个网络设备。但是读者可能会问,pci_dev也是代表着这个设备,那么两者有什么区别 呢,正如我们上面讨论的,网卡设备既要遵循PCI规范,也要担负起其作为网卡设备的职责,于是就分了两块,pci_dev用来负责网卡的PCI规范,而这里要说的net_device则是负责网卡的网络设备这个职责。
dev = init_etherdev (NULL, sizeof (*tp));
if (dev == NULL) {
printk ("unable to alloc new ethernet\n");
return -ENOMEM;
}
tp = dev->priv;
init_etherdev函数在Linux/drivers/net/net_init.c中,在这个函数中分配了net_device的内存并进行了初步的初始化。这里值得注意的是net_device中的一个成员priv,它代表着不同网卡的私有数据,比如Intel的网卡和Realtek 的网卡在内核中都是以net_device来代表。但是他们是有区别的,比如Intel和Realtek实现同一功能的方法不一样,这些都是靠着priv 来体现。所以这里把拿出来同net_device相提并论。分配内存时,net_device中除了priv以外的成员都是固定的,而priv的大小是可 以任意的,所以分配时要把priv的大小传过去。
②开启这个设备(其实是开启了设备的寄存器映射到内存的功能)
rc = pci_enable_device (pdev);
if (rc)
goto err_out;
pci_enable_device也是一个内核开发出来的接口,代码在drivers/pci/pci.c中,笔者跟踪发现这个函数主要就是把 PCI配置空间的Command域的0位和1位置成了1,从而达到了开启设备的目的,因为rtl8139的官方datasheet中,说明了这两位的作用 就是开启内存映射和I/O映射,如果不开的话,那我们以上讨论的把控制寄存器空间映射到内存空间的这一功能就被屏蔽了,这对我们是非常不利的,除此之外,pci_enable_device还做了些中断开启工作。
③获得各项资源
mmio_start = pci_resource_start (pdev, 1);
mmio_end = pci_resource_end (pdev, 1);
mmio_flags = pci_resource_flags (pdev, 1);
mmio_len = pci_resource_len (pdev, 1);
读者也许疑问我们的寄存器被映射到内存中的什么地方是什么时候有谁决定的呢。是这样的,在硬件加电初始化时,BIOS固件同一检查了所有的PCI 设备,并统一为他们分配了一个和其他互不冲突的地址,让他们的驱动程序可以向这些地址映射他们的寄存器,这些地址被BIOS写进了各个设备的配置空间,因为这个活动是一个PCI的标准的活动,所以自然写到各个设备的配置空间里而不是他们风格各异的控制寄存器空间里。当然只有BIOS可以访问配置空间。当操作系统初始化时,他为每个PCI设备分配了pci_dev结构,并且把BIOS获得的并写到了配置空间中的地址读出来写到了pci_dev中的 resource字段中。这样以后我们在读这些地址就不需要在访问配置空间了,直接跟pci_dev要就可以了,我们这里的四个函数就是直接从 pci_dev读出了相关数据,代码在include/linux/pci.h中。定义如下:
#define pci_resource_start(dev,bar) ((dev)->resource[(bar)].start)
#define pci_resource_end(dev,bar) ((dev)->resource[(bar)].end)
这里需要说明一下,每个PCI设备有0-5一共6个地址空间,我们通常只使用前两个,这里我们把参数1传给了bar就是使用内存映射的地址空间。
④把得到的地址进行映射
ioaddr = ioremap (mmio_start, mmio_len);
if (ioaddr == NULL) {
printk ("cannot remap MMIO, aborting\n");
rc = -EIO;
goto err_out_free_res;
}
ioremap是内核提供的用来映射外设寄存器到主存的函数,我们要映射的地址已经从pci_dev中读了出来(上一步),这样就水到渠成的成功 映射了而不会和其他地址有冲突。映射完了有什么效果呢,我举个例子,比如某个网卡有100个寄存器,他们都是连在一块的,位置是固定的,加入每个寄存器占 4个字节,那么一共400个字节的空间被映射到内存成功后,ioaddr就是这段地址的开头(注意ioaddr是虚拟地址,而mmio_start是物理地址,它是BIOS得到的,肯定是物理地址,而保护模式下CPU不认物理地址,只认虚拟地址),ioaddr+0就是第一个寄存器的地址,ioaddr+4就是第二个寄存器地址(每个寄存器占4个字节),以此类推,我们就能够在内存中访问到所有的寄存器进而操控他们了。
⑤重启网卡设备
重启网卡设备是初始化网卡设备的一个重要部分,它的原理就是向寄存器中写入命令就可以了(注意这里写寄存器,而不是配置空间,因为跟PCI没有什么关系),代码如下:
writeb ((readb(ioaddr+ChipCmd) & ChipCmdClear) | CmdReset,ioaddr+ChipCmd);
是我们看到第二参数ioaddr+ChipCmd,ChipCmd是一个位移,使地址刚好对应的就是ChipCmd哪个寄存器,读者可以查阅官方 datasheet得到这个位移量,我们在程序中定义的这个值为:ChipCmd = 0x37;与datasheet是吻合的。我们把这个命令寄存器中 相应位(RESET)置1就可以完成操作。
⑥获得MAC地址,并把它存储到net_device中。
for(i = 0; i < 6; i++) { /* Hardware Address */
dev->dev_addr[i] = readb(ioaddr+i);
dev->broadcast[i] = 0xff;
}
我们可以看到读的地址是ioaddr+0到ioaddr+5,读者查看官方datasheet会发现寄存器地址空间的开头6个字节正好存的是这个网卡设备的MAC地址,MAC地址是网络中标识网卡的物理地址,这个地址在今后的收发数据包时会用的上。
⑦向net_device中登记一些主要的函数
dev->open = rtl8139_open;
dev->hard_start_xmit = rtl8139_start_xmit;
dev->stop = rtl8139_close;
由于dev(net_device)代表着设备,把这些函数注册完后,rtl8139_open就是用于打开这个设备,rtl8139_start_xmit就是当应用程序要通过这个设备往外面发数据时被调用,具体的其实这个函数是在网络协议层中调用的,这就涉及到 Linux网络协议栈的内容,不再我们讨论之列,我们只是负责实现它。rtl8139_close用来关掉这个设备。
好了,到此我们把rtl8139_init_one函数介绍完了,初始化个设备完了之后呢,我们通过ifconfig eth0 up命令来把我们的设备激活。这个命令直接导致了我们刚刚注册的rtl8139_open的调用。这个函数激活了设备。这个函数主要做了三件事。
①注册这个设备的中断处理函数。当网卡发送数据完成或者接收到数据时,是用中断的形式来告知的,比如有数据从网线传来,中断也通知了我们,那么必须要有一个处理这个中断的函数来完成数据的接收。关于Linux的中断机制不是我们详细讲解的范畴,有兴趣的可以参考《Linux内核源代码情景分 析》,但是有个非常重要的资源我们必须注意,那就是中断号的分配,和内存地址映射一样,中断号也是BIOS在初始化阶段分配并写入设备的配置空间的,然后 Linux在建立pci_dev时从配置空间读出这个中断号然后写入pci_dev的irq成员中,所以我们注册中断程序需要中断号就是直接从 pci_dev里取就可以了。
retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev);
if (retval) {
return retval;
}
我们注册的中断处理函数是rtl8139_interrupt,也就是说当网卡发生中断(如数据到达)时,中断控制器8259A把中断号发给 CPU,CPU根据这个中断号找到处理程序,这里就是rtl8139_interrupt,然后执行。rtl8139_interrupt也是在我们的程 序中定义好了的,这是驱动程序的一个重要的义务,也是一个基本的功能。request_irq的代码在arch/i386/kernel/irq.c 中。
②分配发送和接收的缓存空间
根据官方文档,发送一个数据包的过程是这样的:先从应用程序中把数据包拷贝到一段连续的内存中(这段内存就是我们这里要分配的缓存),然后把这段内存的地址写进网卡的数据发送地址寄存器(TSAD)中,这个寄存器的偏移量是TxAddr0 = 0x20。在把这个数据包的长度写进另一个寄存器(TSD)中,它的偏移量是TxStatus0 = 0x10。然后就把这段内存的数据发送到网卡内部的发送缓冲中(FIFO),最后由这个发送缓冲区把 数据发送到网线上。
好了现在创建这么一个发送和接收缓冲内存的目的已经很显然了。
tp->tx_bufs = pci_alloc_consistent(tp->pci_dev, TX_BUF_TOT_LEN, &tp->tx_bufs_dma);
tp->rx_ring = pci_alloc_consistent(tp->pci_dev, RX_BUF_TOT_LEN, &tp->rx_ring_dma);
tp是net_device的priv的指针,tx_bufs是发送缓冲内存的首地址,rx_ring是接收缓存内存的首地址,他们都是虚拟地址,而最后一个参数tx_bufs_dma和rx_ring_dma均是这一段内存的物理地址。为什么同一个事物,既用虚拟地址来表示它还要用物理地址呢,是这样的,CPU执行程序用到这个地址时,用虚拟地址,而网卡设备向这些内存中存取数据时用的是物理地址(因为网卡相对CPU属于头脑比较简单型 的)。pci_alloc_consistent的代码在Linux/arch/i386/kernel/pci-dma.c中。
③发送和接收缓冲区初始化和网卡开始工作的操作
RTL8139有4个发送描述符(包括4个发送缓冲区的基地址寄存器(TSAD0-TSAD3)和4个发送状态寄存器(TSD0-TSD3)。也就是说我们分配的缓冲区要分成四个等分并把这四个空间的地址都写到相关寄存器里去,下面这段代码完成了这个操作。
for (i = 0; i < NUM_TX_DESC; i++)
((struct rtl8139_private*)dev->priv)->tx_buf[i] =
&((struct rtl8139_private*)dev->priv)->tx_bufs[i * TX_BUF_SIZE];
上面这段代码负责把发送缓冲区虚拟空间进行了分割。
for (i = 0; i < NUM_TX_DESC; i++)
{
writel(tp->tx_bufs_dma+(tp->tx_buf[i]tp->tx_bufs),ioaddr+TxAddr0+(i*4));
readl(ioaddr+TxAddr0+(i * 4));
}
上面这段代码负责把发送缓冲区物理空间进行了分割,并把它写到了相关寄存器中,这样在网卡开始工作后就能够迅速定位和找到这些内存并存取他们的数据。
writel(tp->rx_ring_dma,ioaddr+RxBuf);
上面这行代码是把接收缓冲区的物理地址写到了相关寄存器中,这样网卡接收到数据后就能准确的把数据从网卡中搬运到这些内存空间中,等待CPU来领走他们。
writeb((readb(ioaddr+ChipCmd) & ChipCmdClear) | CmdRxEnb | CmdTxEnb,ioaddr+ChipCmd);
重新RESET设备后,我们要激活设备的发送和接收的功能,上面这行代码就是向相关寄存器中写入相应值,激活了设备的这些功能。
writel ((TX_DMA_BURST << TxDMAShift),ioaddr+TxConfig);
上面这行代码是向网卡的TxConfig(位移是0x44)寄存器中写入TX_DMA_BURST << TxDMAShift这个值,翻译过来就是6<<8,就是把第8到第10这三位置成110,查阅管法文档发现6就是110代表着一次DMA的数据量为1024字节。
另外在这个阶段设置了接收数据的模式,和开启中断等等,限于篇幅由读者自行研究。
下面进入数据收发阶段:
当一个网络应用程序要向网络发送数据时,它要利用Linux的网络协议栈来解决一系列问题,找到网卡设备的代表net_device,由这个结构 来找到并控制这个网卡设备来完成数据包的发送,具体是调用net_device的hard_start_xmit成员函数,这是一个函数指针,在我们的驱 动程序里它指向的是rtl8139_start_xmit,正是由它来完成我们的发送工作的,下面我们就来剖析这个函数。它一共做了四件事。
①检查这个要发送的数据包的长度,如果它达不到以太网帧的长度,必须采取措施进行填充。
if( skb->len < ETH_ZLEN ){//if data_len < 60
if( (skb->data + ETH_ZLEN) <= skb->end ){
memset( skb->data + skb->len, 0x20, (ETH_ZLEN - skb->len) );
skb->len = (skb->len >= ETH_ZLEN) ? skb->len : ETH_ZLEN;}
else{
printk("%s:(skb->data+ETH_ZLEN) > skb->end\n",__FUNCTION__);
}
}
skb->data和skb->end就决定了这个包的内容,如果这个包本身总共的长度(skb->end- skb->data)都达不到要求,那么想填也没地方填,就出错返回了,否则的话就填上。
②把包的数据拷贝到我们已经建立好的发送缓存中。
memcpy (tp->tx_buf[entry], skb->data, skb->len);
其中skb->data就是数据包数据的地址,而tp->tx_buf[entry]就是我们的发送缓存地址,这样就完成了拷贝,忘记了这些内容的回头看看前面的介绍。
③光有了地址和数据还不行,我们要让网卡知道这个包的长度,才能保证数据不多不少精确的从缓存中截取出来搬运到网卡中去,这是靠写发送状态寄存器(TSD)来完成的。
writel(tp->tx_flag | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN),ioaddr+TxStatus0+(entry * 4));
我们把这个包的长度和一些控制信息一起写进了状态寄存器,使网卡的工作有了依据。
④判断发送缓存是否已经满了,如果满了在发就覆盖数据了,要停发。
if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
netif_stop_queue (dev);
谈完了发送,我们开始谈接收,当有数据从网线上过来时,网卡产生一个中断,调用的中断服务程序是rtl8139_interrupt,它主要做了三件事。
①从网卡的中断状态寄存器中读出状态值进行分析,status = readw(ioaddr+IntrStatus);
if ((status &(PCIErr | PCSTimeout | RxUnderrun | RxOverflow | RxFIFOOver | TxErr | TxOK | RxErr | RxOK)) == 0)
goto out;
上面代码说明如果上面这9种情况均没有的表示没什么好处理的了,退出。
② if (status & (RxOK | RxUnderrun | RxOverflow | RxFIFOOver))/* Rx interrupt */
rtl8139_rx_interrupt (dev, tp, ioaddr);
如果是以上4种情况,属于接收信号,调用rtl8139_rx_interrupt进行接收处理。
③ if (status & (TxOK | TxErr)) {
spin_lock (&tp->lock);
rtl8139_tx_interrupt (dev, tp, ioaddr);
spin_unlock (&tp->lock);
}
如果是传输完成的信号,就调用rtl8139_tx_interrupt进行发送善后处理。
下面我们先来看看接收中断处理函数rtl8139_rx_interrupt,在这个函数中主要做了下面四件事
①这个函数是一个大循环,循环条件是只要接收缓存不为空就还可以继续读取数据,循环不会停止,读空了之后就跳出。
int ring_offset = cur_rx % RX_BUF_LEN;
rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset));
rx_size = rx_status >> 16;
上面三行代码是计算出要接收的包的长度。
②根据这个长度来分配包的数据结构
skb = dev_alloc_skb (pkt_size + 2);
③如果分配成功就把数据从接收缓存中拷贝到这个包中
eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);
这个函数在include/linux/etherdevice.h中,实质还是调用了memcpy()。
static inline void eth_copy_and_sum(struct sk_buff*dest, unsigned char *src, int len, int base)
{
memcpy(dest->data, src, len);
}
现在我们已经熟知,&rx_ring[ring_offset + 4]就是接收缓存,也是源地址,而skb->data就是包的数据地址,也是目的地址,一目了然。
④把这个包送到Linux协议栈去进行下一步处理
skb->protocol = eth_type_trans (skb, dev);
netif_rx (skb);
在netif_rx()函数执行完后,这个包的数据就脱离了网卡驱动范畴,而进入了Linux网络协议栈里面,把这些数据包的以太网帧头,IP 头,TCP头都脱下来,最后把数据送给了应用程序,不过协议栈不再本文讨论范围内。netif_rx函数在net/core/dev.c,中。
而rtl8139_remove_one则基本是rtl8139_init_one的逆过程。
到此,本文已经将Linux驱动程序的框架勾勒了出来。
Linux网络设备结构
首先看一下Linux网络设备的结构,如下图:
-
网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接受数据。这一层的存在使得上层协议独立于具体的设备。
-
网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
-
设备驱动功能层各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,他通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接受操作。
-
网络设备与媒介层是完成数据包发送和接受的物理实体,包括网络适配器和具体的传输媒介,网络适配器被驱动功能层中的函数物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。
上面的结构还可以细分,如下图
网络协议接口层
这里主要进行数据包的收发,使用函数原型为:
dev_queue_xmit(struct sk_buff *skb); int netif_rx(struct sk_buff *skb);
这里使用了一个skb_buff结构体,定义于include/linux/skbuff.h中,它的含义为“套接字缓冲区”,用于在Linux网络子系统各层间传输数据。
它是一个双向链表,在老的内核中会有一个list域指向sk_buff_head也就是链表头,但是在linux2.6.30.4内核后已经不存在了,如下图:
操作套接字缓冲区的函数
1.分配
struct sk_buff *alloc_skb(unsigned int len, int priority); struct sk_buff *dev_alloc_skb(unsigned int len);
分配一个缓冲区。alloc_skb 函数分配一个缓冲区并初始化skb->data和skb->tail为skb->head。
参数len为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对ARM为32)对齐,参数priority为内存分配的优先级。
dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配。
2.释放
void kfree_skb(struct sk_buff *skb); void dev_kfree_skb(struct sk_buff *skb);
Linux内核内部使用kfree_skb()函数,而网络设备驱动程序中则最好使用dev_kfree_skb()。
sk_buff中比较重要的成员是指向数据包中数据的指针,如下图所示
用于寻址数据包中数据的指针,head指向已分配空间开头,data指向有效的octet开头,tail指向有效的octet结尾,而end指向tail可以到达的最大地址。
如果不这样做而分配一个大小固定的缓冲区,如果buffer不够用,则要申请一个更大的buffer,拷贝进去再增加,这样降低了性能。
3.变更
unsigned char *skb_put(struct sk_buff *skb, int len); unsigned char *skb_push(struct sk_buff *skb, int len); unsigned char *skb_pull(struct sk_buff *skb, int len); void skb_reserve(struct sk_buff ×skb, int len);
下图分别对应了这四个函数,看了这张图应该对这4个函数的作用了然于胸。
网络设备接口层
网络设备接口层的主要功能是为千变万化的网络设备定义了统一,抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。
打开和关闭网络设备
int (*open)(struct net_device *dev); int (*close)(struct net_device *dev);
要注意的是ifconfig是interface config的缩写,通常我们在用户空间输入:
ifconfig eth0 up
会调用这里的open函数。
在用户空间输入:
ifconfig eth0 down
会调用这里的stop函数。
在使用ifconfig向接口赋予地址时,要执行两个任务:
首先,它通过ioctl(SIOCSIFADDR)(Socket I/O Control Set Interface Address)赋予地址;
然后通过ioctl(SIOCSIFFLAGS)(Socket I/O Control Set Interface Flags)设置dev->flag中的IFF_UP标志以打开接口。
这个调用会使得设备的open方法得到调用。
类似的,在接口关闭时,ifconfig使用ioctl(SIOCSIFFLAGS)来清理IFF_UP标志,然后调用stop函数。
重要函数
int (*hard_header)(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len);
该方法根据先前检索到的源和目的硬件地址建立硬件头。
int (*rebuild_header)(struct sk_buff *skb);
以太网的mac地址是固定的,为了高效,第一个包去询问mac地址,得到对应的mac地址后就会作为cache把mac地址保存起来。以后每次发包不用询问了,直接把包的地址拷贝出来。
void (*tx_timeout)(struct net_device *dev);
如果数据包发送在超时时间内失败,这时该方法被调用,这个方法应该解决失败的问题,并重新开始发送数据。
int (*set_config)(struct net_device *dev, struct ifmap *map);
改变接口的配置,比如改变I/O端口和中断号等,现在的驱动程序通常无需该方法。
int (*do_ioctl)(struct net_device *dev, struct ifmap *map);
用来实现自定义的ioctl命令,如果不需要可以为NULL。
void (*set_multicast_list)(struct net_device *dev);
当设备的组播列表改变或设备标志改变时,该方法被调用。
int (*set_mac_address)(struct net_device *dev, void *addr);
如果接口支持mac地址改变,则可以实现该函数。
设备驱动接口层
net_device结构体的成员(属性和函数指针)需要被设备驱动功能层的具体数值和函数赋予。
对具体的设置xxx,工程师应该编写设备驱动功能层的函数,这些函数型如xxx_open(),xxx_stop(),xxx_tx(),xxx_hard_header(),xxx_get_stats(),xxx_tx_timeout()等。
网络设备与媒介层
网络设备与媒介层直接对应于实际的硬件设备。
下面从代码中分析dm9000的实现
网络设备的初始化
通过模块的加载函数看出DM9000A的驱动是以平台驱动的形式注册进内核的。
模块的加载函数
下边是模块的加载函数:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 static int __init 2 dm9000_init(void) 3 { 4 printk(KERN_INFO "%s Ethernet Driver, V%s/n", CARDNAME, DRV_VERSION); 5 6 return platform_driver_register(&dm9000_driver); 7 }
probe函数
下面来分析probe 函数,用来执行分配的内核函数是alloc_netdev,函数原型是:
struct net_device *alloc_netdev(int sizeof_priv, const char *name, void (*setup)(struct net_device*));
sizeof_priv是驱动程序私有数据区的大小;这个成员和net_device结构一同分配给网络设备。实际上,他们都处于一大块内存中,但是驱动程序不需要知道这些。
name是接口的名字,其在用户空间可见;这个名字可以使用类似printf中%d的格式,内核将用下一个可用的接口号替代%d。
setup是一个初始化函数,用来设置net_device结构剩余的部分。
网络子系统对alloc_netdev,为不同种类的接口封装了许多函数。
最常用的是alloc_etherdev,它定义在linux/etherdevice.h中:
struct net_device *alloc_etherdev(int sizeof_priv);
该函数使用eth%d的形式指定分配给网络设备的名字。
它提供了自己的初始化函数(ether_setup),用正确的值为以太网设备设置net_device中的许多成员。
那么在DM9000A中这个私有数据成员是什么呢,看下边的结构:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 /* Structure/enum declaration ------------------------------- */ 2 typedef struct board_info { 3 4 void __iomem *io_addr; /* Register I/O base address */ 5 void __iomem *io_data; /* Data I/O address */ 6 u16 irq; /* IRQ */ 7 8 u16 tx_pkt_cnt; 9 u16 queue_pkt_len; 10 u16 queue_start_addr; 11 u16 dbug_cnt; 12 u8 io_mode; /* 0:word, 2:byte */ 13 u8 phy_addr; 14 u8 imr_all; 15 16 unsigned int flags; 17 unsigned int in_suspend :1; 18 int debug_level; 19 20 enum dm9000_type type; 21 22 void (*inblk)(void __iomem *port, void *data, int length); 23 void (*outblk)(void __iomem *port, void *data, int length); 24 void (*dumpblk)(void __iomem *port, int length); 25 26 struct device *dev; /* parent device */ 27 28 struct resource *addr_res; /* resources found */ 29 struct resource *data_res; 30 struct resource *addr_req; /* resources requested */ 31 struct resource *data_req; 32 struct resource *irq_res; 33 34 struct mutex addr_lock; /* phy and eeprom access lock */ 35 36 struct delayed_work phy_poll; 37 struct net_device *ndev; 38 39 spinlock_t lock; 40 41 struct mii_if_info mii; 42 u32 msg_enable; 43 } board_info_t;
这个struct board_info就是那个私有数据,用来保存芯片相关的一些私有信息。
下面是probe函数的实现:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 /* 2 * Search DM9000 board, allocate space and register it 3 */ 4 static int __devinit 5 dm9000_probe(struct platform_device *pdev) 6 { 7 /*获得平台数据,这个应该在platform_device那边指定了*/ 8 struct dm9000_plat_data *pdata = pdev->dev.platform_data; 9 struct board_info *db; /* Point a board information structure */ 10 struct net_device *ndev; 11 const unsigned char *mac_src; 12 int ret = 0; 13 int iosize; 14 int i; 15 u32 id_val; 16 17 /*分配以太网的网络设备*/ 18 ndev = alloc_etherdev(sizeof(struct board_info)); 19 if (!ndev) { 20 dev_err(&pdev->dev, "could not allocate device./n"); 21 return -ENOMEM; 22 } 23 /*#define SET_NETDEV_DEV(net, pdev) ((net)->dev.parent = (pdev))*/ 24 SET_NETDEV_DEV(ndev, &pdev->dev); 25 26 dev_dbg(&pdev->dev, "dm9000_probe()/n"); 27 28 /*设置struct board_info为ndev的私有数据*/ 29 db = netdev_priv(ndev); 30 memset(db, 0, sizeof(*db)); 31 32 db->dev = &pdev->dev; 33 db->ndev = ndev; 34 35 spin_lock_init(&db->lock); 36 mutex_init(&db->addr_lock); 37 /*提交一个任务给一个工作队列,你需要填充一个work_struct结构db->phy_poll*/ 38 INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work); 39 /*获取IO内存和中断资源*/ 40 db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 41 db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 42 db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 43 44 if (db->addr_res == NULL || db->data_res == NULL || 45 db->irq_res == NULL) { 46 dev_err(db->dev, "insufficient resources/n"); 47 ret = -ENOENT; 48 goto out; 49 } 50 /*映射到内核,并获得IO内存的虚拟地址,ioremap完成页表的建立,不同于vmalloc,但是,它实际上不分配内存*/ 51 iosize = res_size(db->addr_res); 52 db->addr_req = request_mem_region(db->addr_res->start, iosize, 53 pdev->name); 54 55 if (db->addr_req == NULL) { 56 dev_err(db->dev, "cannot claim address reg area/n"); 57 ret = -EIO; 58 goto out; 59 } 60 61 db->io_addr = ioremap(db->addr_res->start, iosize); 62 63 if (db->io_addr == NULL) { 64 dev_err(db->dev, "failed to ioremap address reg/n"); 65 ret = -EINVAL; 66 goto out; 67 } 68 69 iosize = res_size(db->data_res); 70 db->data_req = request_mem_region(db->data_res->start, iosize, 71 pdev->name); 72 73 if (db->data_req == NULL) { 74 dev_err(db->dev, "cannot claim data reg area/n"); 75 ret = -EIO; 76 goto out; 77 } 78 79 db->io_data = ioremap(db->data_res->start, iosize); 80 81 if (db->io_data == NULL) { 82 dev_err(db->dev, "failed to ioremap data reg/n"); 83 ret = -EINVAL; 84 goto out; 85 } 86 87 /*获得网络设备的基地址*/ 88 ndev->base_addr = (unsigned long)db->io_addr; 89 /*获得网络设备的中断号*/ 90 ndev->irq = db->irq_res->start; 91 92 /*设置默认的IO函数*/ 93 dm9000_set_io(db, iosize); 94 95 /*如果平台数据不为空*/ 96 if (pdata != NULL) { 97 /* check to see if the driver wants to over-ride the 98 * default IO width */ 99 100 if (pdata->flags & DM9000_PLATF_8BITONLY) 101 dm9000_set_io(db, 1); 102 103 if (pdata->flags & DM9000_PLATF_16BITONLY) 104 dm9000_set_io(db, 2); 105 106 if (pdata->flags & DM9000_PLATF_32BITONLY) 107 dm9000_set_io(db, 4); 108 109 /* check to see if there are any IO routine 110 * over-rides */ 111 112 if (pdata->inblk != NULL) 113 db->inblk = pdata->inblk; 114 115 if (pdata->outblk != NULL) 116 db->outblk = pdata->outblk; 117 118 if (pdata->dumpblk != NULL) 119 db->dumpblk = pdata->dumpblk; 120 121 db->flags = pdata->flags; 122 } 123 124 #ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL 125 db->flags |= DM9000_PLATF_SIMPLE_PHY; 126 #endif 127 /*dm9000复位*/ 128 dm9000_reset(db); 129 /*读取Vendor ID Register,Product ID Register中的值,与0x90000A46比较,如果相等,则说明是DM9000*/ 130 /* try multiple times, DM9000 sometimes gets the read wrong */ 131 for (i = 0; i < 8; i++) { 132 id_val = ior(db, DM9000_VIDL); 133 id_val |= (u32)ior(db, DM9000_VIDH) << 8; 134 id_val |= (u32)ior(db, DM9000_PIDL) << 16; 135 id_val |= (u32)ior(db, DM9000_PIDH) << 24; 136 137 if (id_val == DM9000_ID) 138 break; 139 dev_err(db->dev, "read wrong id 0x%08x/n", id_val); 140 } 141 142 if (id_val != DM9000_ID) { 143 dev_err(db->dev, "wrong id: 0x%08x/n", id_val); 144 ret = -ENODEV; 145 goto out; 146 } 147 148 /* Identify what type of DM9000 we are working on */ 149 /*读取Chip Revision Register中的值*/ 150 id_val = ior(db, DM9000_CHIPR); 151 dev_dbg(db->dev, "dm9000 revision 0x%02x/n", id_val); 152 153 switch (id_val) { 154 case CHIPR_DM9000A: 155 db->type = TYPE_DM9000A; 156 break; 157 case CHIPR_DM9000B: 158 db->type = TYPE_DM9000B; 159 break; 160 default: 161 dev_dbg(db->dev, "ID %02x => defaulting to DM9000E/n", id_val); 162 db->type = TYPE_DM9000E; 163 } 164 165 /* from this point we assume that we have found a DM9000 */ 166 167 /* driver system function */ 168 /*设置部分net_device字段*/ 169 ether_setup(ndev); 170 171 ndev->open = &dm9000_open; 172 ndev->hard_start_xmit = &dm9000_start_xmit; 173 ndev->tx_timeout = &dm9000_timeout; 174 ndev->watchdog_timeo = msecs_to_jiffies(watchdog); 175 ndev->stop = &dm9000_stop; 176 ndev->set_multicast_list = &dm9000_hash_table; 177 /*对ethtool支持的相关声明可在<linux/ethtool.h>中找到。它的核心是一个ethtool_ops类型的结构,里边包含一个全部的24个不同的方法来支持ethtool*/ 178 ndev->ethtool_ops = &dm9000_ethtool_ops; 179 ndev->do_ioctl = &dm9000_ioctl; 180 181 #ifdef CONFIG_NET_POLL_CONTROLLER 182 ndev->poll_controller = &dm9000_poll_controller; 183 #endif 184 185 db->msg_enable = NETIF_MSG_LINK; 186 db->mii.phy_id_mask = 0x1f; 187 db->mii.reg_num_mask = 0x1f; 188 db->mii.force_media = 0; 189 db->mii.full_duplex = 0; 190 db->mii.dev = ndev; 191 db->mii.mdio_read = dm9000_phy_read; 192 db->mii.mdio_write = dm9000_phy_write; 193 /*MAC地址的源是eeprom*/ 194 mac_src = "eeprom"; 195 196 /* try reading the node address from the attached EEPROM */ 197 for (i = 0; i < 6; i += 2) 198 dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i); 199 /*如果从eeprom中读取的地址无效,并且私有数据不为空,从platform_device的私有数据中获取dev_addr*/ 200 if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) { 201 mac_src = "platform data"; 202 memcpy(ndev->dev_addr, pdata->dev_addr, 6); 203 } 204 /*如果地址依然无效,从PAR:物理地址(MAC)寄存器(Physical Address Register)中读取*/ 205 if (!is_valid_ether_addr(ndev->dev_addr)) { 206 /* try reading from mac */ 207 208 mac_src = "chip"; 209 for (i = 0; i < 6; i++) 210 ndev->dev_addr[i] = ior(db, i+DM9000_PAR); 211 } 212 /*查看以太网网卡设备地址是否有效*/ 213 if (!is_valid_ether_addr(ndev->dev_addr)) 214 dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please " 215 "set using ifconfig/n", ndev->name); 216 /*将ndev保存到pdev->dev->driver_data中*/ 217 platform_set_drvdata(pdev, ndev); 218 /*一切都初始化好后,注册网络设备*/ 219 ret = register_netdev(ndev); 220 221 if (ret == 0) 222 printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)/n", 223 ndev->name, dm9000_type_to_char(db->type), 224 db->io_addr, db->io_data, ndev->irq, 225 ndev->dev_addr, mac_src); 226 return 0; 227 228 out: 229 dev_err(db->dev, "not found (%d)./n", ret); 230 231 dm9000_release_board(pdev, db); 232 free_netdev(ndev); 233 234 return ret; 235 }
挂起和唤醒函数
挂起函数完成了设置挂起标志,并没有真正把设备移除而只是设置了移除标志,复位PHY,停止PHY,禁止所有中断,禁止接收引脚。
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 static int 2 dm9000_drv_suspend(struct platform_device *dev, pm_message_t state) 3 { 4 struct net_device *ndev = platform_get_drvdata(dev); 5 board_info_t *db; 6 7 if (ndev) { 8 db = netdev_priv(ndev); 9 db->in_suspend = 1; 10 11 if (netif_running(ndev)) { 12 netif_device_detach(ndev); 13 dm9000_shutdown(ndev); 14 } 15 } 16 return 0; 17 }
唤醒函数完成了复位dm9000,初始化dm9000,标记设备为attached,清除挂起标志。
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 static int 2 dm9000_drv_resume(struct platform_device *dev) 3 { 4 struct net_device *ndev = platform_get_drvdata(dev); 5 board_info_t *db = netdev_priv(ndev); 6 7 if (ndev) { 8 9 if (netif_running(ndev)) { 10 dm9000_reset(db); 11 dm9000_init_dm9000(ndev); 12 netif_device_attach(ndev); 13 } 14 db->in_suspend = 0; 15 } 16 return 0; 17 }
网络设备的打开与释放
首先来看这个open函数
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 static int 2 dm9000_open(struct net_device *dev) 3 { 4 board_info_t *db = netdev_priv(dev); 5 unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK; 6 7 if (netif_msg_ifup(db)) 8 dev_dbg(db->dev, "enabling %s/n", dev->name); 9 10 /* If there is no IRQ type specified, default to something that 11 * may work, and tell the user that this is a problem */ 12 13 if (irqflags == IRQF_TRIGGER_NONE) 14 dev_warn(db->dev, "WARNING: no IRQ resource flags set./n"); 15 16 irqflags |= IRQF_SHARED; 17 /*注册中断处理函数*/ 18 if (request_irq(dev->irq, &dm9000_interrupt, irqflags, dev->name, dev)) 19 return -EAGAIN; 20 21 /* Initialize DM9000 board */ 22 /*复位DM9000*/ 23 dm9000_reset(db); 24 /*初始化DM9000的寄存器*/ 25 dm9000_init_dm9000(dev); 26 27 /* Init driver variable */ 28 db->dbug_cnt = 0; 29 /*检查链路载波状况*/ 30 mii_check_media(&db->mii, netif_msg_link(db), 1); 31 /*启动发送队列*/ 32 netif_start_queue(dev); 33 /*之前在probe函数中调用 INIT_DELAYED_WORK初始化了工作队列,并关联了一个操作函数dm9000_poll_work(),此时运行dm9000_schedule_poll来调用这个函数*/ 34 dm9000_schedule_poll(db); 35 36 return 0; 37 }
然后是stop函数
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 static int 2 dm9000_stop(struct net_device *ndev) 3 { 4 board_info_t *db = netdev_priv(ndev); 5 6 if (netif_msg_ifdown(db)) 7 dev_dbg(db->dev, "shutting down %s/n", ndev->name); 8 /*杀死延时工作队列phy_poll*/ 9 cancel_delayed_work_sync(&db->phy_poll); 10 /*停止发送队列*/ 11 netif_stop_queue(ndev); 12 /*通知内核链路失去连接*/ 13 netif_carrier_off(ndev); 14 /* free interrupt */ 15 free_irq(ndev->irq, ndev); 16 /*关闭DM9000*/ 17 dm9000_shutdown(ndev); 18 return 0; 19 }
复位PHY,停止PHY,禁止所有中断,禁止接收引脚。
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 static void 2 dm9000_shutdown(struct net_device *dev) 3 { 4 board_info_t *db = netdev_priv(dev); 5 6 /* RESET device */ 7 dm9000_phy_write(dev, 0, MII_BMCR, BMCR_RESET); /* PHY RESET */ 8 iow(db, DM9000_GPR, 0x01); /* Power-Down PHY */ 9 iow(db, DM9000_IMR, IMR_PAR); /* Disable all interrupt */ 10 iow(db, DM9000_RCR, 0x00); /* Disable RX */ 11 }
中断处理函数
发生中断的情况有3种:
- 接收到数据
- 发送完数据
- 链路状态改变
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
下面说一下DM9000A中的存储部分,DM9000A内部有一个4K Dword SRAM,其中3KB是作为发送,16KB作为接收,如下图所示。
其中0x0000~0x0BFF是传说中的TX buffer(TX buffer中只能存放两个包),0x0C00~0x3FFF是RX buffer
因此在写内存操作时,当IMR的第7位被设置,如果到达了地址的结尾比如到了3KB,则回卷到0。
相似的方式,在读操作中,当IMR的第7位被设置如果到达了地址的结尾比如16K,则回卷到0x0C00。
那么传说中的发送函数又是哪个呢,在probe函数里进行了初始化函数指针操作。
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 ndev->open = &dm9000_open; 2 ndev->hard_start_xmit = &dm9000_start_xmit; 3 ndev->tx_timeout = &dm9000_timeout; 4 ndev->watchdog_timeo = msecs_to_jiffies(watchdog); 5 ndev->stop = &dm9000_stop; 6 ndev->set_multicast_list = &dm9000_hash_table; 7 ndev->ethtool_ops = &dm9000_ethtool_ops; 8 ndev->do_ioctl = &dm9000_ioctl;
可以看到当上层调用hard_start_xmit时,在我们的驱动程序中实际调用的是dm9000_start_xmit,下面来分析一dm9000_start_xmit的源码。
发送函数dm9000_start_xmit
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 /* 2 * Hardware start transmission. 3 * Send a packet to media from the upper layer. 4 */ 5 static int 6 dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev) 7 { 8 unsigned long flags; 9 /*获得ndev的私有数据,也就是芯片相关的信息*/ 10 board_info_t *db = netdev_priv(dev); 11 12 dm9000_dbg(db, 3, "%s:/n", __func__); 13 /*只能存放两个,如果已经有两个就退出*/ 14 if (db->tx_pkt_cnt > 1) 15 return 1; 16 17 spin_lock_irqsave(&db->lock, flags); 18 /* 19 *MWCMD是Memory data write command with address increment Register(F8H) 20 *写数据到TX SRAM 21 *写这个命令后,根据IO操作模式(8-bit or 16-bit)来增加写指针1或2 22 */ 23 writeb(DM9000_MWCMD, db->io_addr); 24 /*把数据从sk_buff中拷贝到TX SRAM中*/ 25 (db->outblk)(db->io_data, skb->data, skb->len); 26 /*为了统计发送了多少个字节,这个在使用ifconfig中显示出的那个发送了多少个字节就是这里计算的*/ 27 dev->stats.tx_bytes += skb->len; 28 /*待发送的计数加一*/ 29 db->tx_pkt_cnt++; 30 /*如果只有一个要发送就立即发送,如果这是第二个就应该进行排队了*/ 31 if (db->tx_pkt_cnt == 1) { 32 /*把数据的长度填到TXPLL(发送包长度低字节)和TXPLH(发送包长度高字节)中*/ 33 iow(db, DM9000_TXPLL, skb->len); 34 iow(db, DM9000_TXPLH, skb->len >> 8); 35 /*置发送控制寄存器(TX Control Register)的发送请求位TXREQ(Auto clears after sending completely),这样就可以发送出去了*/ 36 iow(db, DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */ 37 /* 38 *记下此时的时间,这里起一个时间戳的作用,之后的超时会用到。如果当前的系统时间超过设备的trans_start时间 39 *至少一个超时周期,网络层将最终调用驱动程序的tx_timeout。那个这个"一个超时周期"又是什么呢?这个是我们在 40 *probe函数中设置的,ndev->watchdog_timeo = msecs_to_jiffies(watchdog); 41 */ 42 dev->trans_start = jiffies; /* save the time stamp */ 43 } else { 44 /*如果是第二个包则把skb->len复制给队列的queue_pkt_len,然后告诉上层停止发送队列,因为发送队列已经满了*/ 45 db->queue_pkt_len = skb->len; 46 netif_stop_queue(dev); 47 } 48 spin_unlock_irqrestore(&db->lock, flags); 49 /*每个数据包写入网卡SRAM后都要释放skb*/ 50 dev_kfree_skb(skb); 51 52 return 0; 53 }
下面来看一下刚才提到的那个超时函数,发送超时函数:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 /* Our watchdog timed out. Called by the networking layer */ 2 static void dm9000_timeout(struct net_device *dev) 3 { 4 board_info_t *db = netdev_priv(dev); 5 u8 reg_save; 6 unsigned long flags; 7 8 /* Save previous register address */ 9 reg_save = readb(db->io_addr); 10 spin_lock_irqsave(&db->lock, flags); 11 12 netif_stop_queue(dev); 13 dm9000_reset(db); 14 dm9000_init_dm9000(dev); 15 /* We can accept TX packets again */ 16 dev->trans_start = jiffies; 17 netif_wake_queue(dev); 18 19 /* Restore previous register address */ 20 writeb(reg_save, db->io_addr); 21 spin_unlock_irqrestore(&db->lock, flags); 22 }
这个函数首先停止了发送队列,然后复位dm9000,初始化dm9000,重新设置了时间戳,然后唤醒发送队列,通知网络子系统可再次传输数据包。
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 /* 2 * DM9000 interrupt handler 3 * receive the packet to upper layer, free the transmitted packet 4 */ 5 6 static void dm9000_tx_done(struct net_device *dev, board_info_t *db) 7 { 8 /*从网络状态寄存器(Network Status Register)中获得发送状态*/ 9 int tx_status = ior(db, DM9000_NSR); 10 /*如果发送状态为NSR_TX2END(第二个包发送完毕)或NSR_TX1END(第一个包发送完毕)*/ 11 if (tx_status & (NSR_TX2END | NSR_TX1END)) { 12 /*如果一个数据包发送完,待发送数据包个数减1*/ 13 db->tx_pkt_cnt--; 14 /*如果一个数据包发送完,已发送数据包个数加1*/ 15 dev->stats.tx_packets++; 16 17 if (netif_msg_tx_done(db)) 18 dev_dbg(db->dev, "tx done, NSR %02x/n", tx_status); 19 20 /*如果还有数据包要发送*/ 21 if (db->tx_pkt_cnt > 0) { 22 /*将发送的长度放到TXPLL,TXPLH寄存器中*/ 23 iow(db, DM9000_TXPLL, db->queue_pkt_len); 24 iow(db, DM9000_TXPLH, db->queue_pkt_len >> 8); 25 /*置发送请求位*/ 26 iow(db, DM9000_TCR, TCR_TXREQ); 27 /*保存时间戳*/ 28 dev->trans_start = jiffies; 29 } 30 /*通知内核将待发送数据包放入发送队列*/ 31 netif_wake_queue(dev); 32 } 33 }
接收函数dm9000_rx
每接受到一个包,DM9000都会在包的前面加上4个字节,"01H",status与RSR(RX Status Register)的内容相同,"LENL"(数据包长度低8位),"LENH"(数据包长度高8位)。
所以首先要读取这4个字节来确定数据包的状态,第一个字节"01H"表示接下来的是有效的数据包,"00H"表示没有数据包,若为其他值则表示网卡没有正确初始化,需要从新初始化。
这4个字节封装在一起:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 struct dm9000_rxhdr { 2 u8 RxPktReady; 3 u8 RxStatus; 4 __le16 RxLen; 5 } __attribute__((__packed__));
清晰一些如下图
接收函数如下
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 /* 2 * Received a packet and pass to upper layer 3 */ 4 static void 5 dm9000_rx(struct net_device *dev) 6 { 7 board_info_t *db = netdev_priv(dev); 8 struct dm9000_rxhdr rxhdr; 9 struct sk_buff *skb; 10 u8 rxbyte, *rdptr; 11 bool GoodPacket; 12 int RxLen; 13 14 /* Check packet ready or not */ 15 do { 16 /*MRCMDX为内存数据预取读命令,并且没有地址增加,读数据的第一个字节,直到读到0x01(数据有效)为止。*/ 17 ior(db, DM9000_MRCMDX); /* Dummy read */ 18 /*获得数据*/ 19 rxbyte = readb(db->io_data); 20 /*因为只能为0x00或0x01,所以如果大于0x01,则返回*/ 21 if (rxbyte > DM9000_PKT_RDY) { 22 dev_warn(db->dev, "status check fail: %d/n", rxbyte); 23 /*停止设备*/ 24 iow(db, DM9000_RCR, 0x00); 25 /*停止中断请求*/ 26 iow(db, DM9000_ISR, IMR_PAR); 27 return; 28 } 29 /*如果为0x00,则返回*/ 30 if (rxbyte != DM9000_PKT_RDY) 31 return; 32 33 /*如果有有效数据包,设置标志标量*/ 34 GoodPacket = true; 35 /*MRCMD是地址增加的内存数据读命令*/ 36 writeb(DM9000_MRCMD, db->io_addr); 37 /*读取RX SRAM中的数据放入struct dm9000_rxhdr中*/ 38 (db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr)); 39 /*将一个无符号的26位小头数值转换成CPU使用的值*/ 40 RxLen = le16_to_cpu(rxhdr.RxLen); 41 42 if (netif_msg_rx_status(db)) 43 dev_dbg(db->dev, "RX: status %02x, length %04x/n", 44 rxhdr.RxStatus, RxLen); 45 46 /*一个数据包的长度应大于64字节*/ 47 if (RxLen < 0x40) { 48 GoodPacket = false; 49 if (netif_msg_rx_err(db)) 50 dev_dbg(db->dev, "RX: Bad Packet (runt)/n"); 51 } 52 /*一个数据包的长度应小于1536字节*/ 53 if (RxLen > DM9000_PKT_MAX) { 54 dev_dbg(db->dev, "RST: RX Len:%x/n", RxLen); 55 } 56 57 /* rxhdr.RxStatus is identical to RSR register. */ 58 if (rxhdr.RxStatus & (RSR_FOE | RSR_CE | RSR_AE | 59 RSR_PLE | RSR_RWTO | 60 RSR_LCS | RSR_RF)) { 61 GoodPacket = false; 62 if (rxhdr.RxStatus & RSR_FOE) { 63 if (netif_msg_rx_err(db)) 64 dev_dbg(db->dev, "fifo error/n"); 65 dev->stats.rx_fifo_errors++; 66 } 67 if (rxhdr.RxStatus & RSR_CE) { 68 if (netif_msg_rx_err(db)) 69 dev_dbg(db->dev, "crc error/n"); 70 dev->stats.rx_crc_errors++; 71 } 72 if (rxhdr.RxStatus & RSR_RF) { 73 if (netif_msg_rx_err(db)) 74 dev_dbg(db->dev, "length error/n"); 75 dev->stats.rx_length_errors++; 76 } 77 } 78 79 /* Move data from DM9000 */ 80 if (GoodPacket 81 /*分配一个套接字缓冲区*/ 82 && ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) { 83 skb_reserve(skb, 2); 84 rdptr = (u8 *) skb_put(skb, RxLen - 4); 85 86 /**/ 87 (db->inblk)(db->io_data, rdptr, RxLen); 88 dev->stats.rx_bytes += RxLen; 89 90 /*以太网支持代码导出了辅助函数eth_type_trans,用来查找填入protocol中的正确值*/ 91 skb->protocol = eth_type_trans(skb, dev); 92 /*将套接字缓冲区发向上层*/ 93 netif_rx(skb); 94 /*增加发送包的引用计数*/ 95 dev->stats.rx_packets++; 96 97 } else { 98 /*如果该数据包是坏的,则清理该数据包*/ 99 (db->dumpblk)(db->io_data, RxLen); 100 } 101 } while (rxbyte == DM9000_PKT_RDY); 102 }
如下图链路层包的传输过程
在netif_rx中会调用napi_schedule,然后该函数又会去调用__napi_schedule()。
在函数__napi_schedule()中会去调用设备的poll函数,它是设备自己注册的。
在设备的poll函数中,会去调用netif_receive_skb函数,在该函数中有下面一条语句pt_prev->func,此处的func为一个函数指针,在之前的注册中设置为ip_rcv。
因此,就完成了从链路层上传到网络层的这一个过程了。