转自http://blog.csdn.net/ustc_dylan/article/details/6329375
网络驱动是一种典型的PCI设备驱动,无论在嵌入式平台还是在PC领域,网络相关的项目开发有着比较广阔的前景,因此,分析当前Linux内核中网络设备的驱动,不但能了解网络相关的基本原理,而且可以借鉴Linux内核的先进的技术,将其应用到嵌入式或其他领域。本文以Linux内核中的rtl8139网络驱动为例,对网络驱动的源码进行了简单分析,并对其中涉及的相关概念和技术进行了简单的介绍。
一、PCI设备驱动模型
rtl8139是典型的PCI设备,Linux内核的PCI核心驱动为PCI驱动开发者提供了方便的系统接口,极大地方便了PCI设备驱动的开发。
1 pci设备驱动相关的数据结构
pci设备描述结构体
驱动开发者要想为某个PCI设备开发驱动就必须定义一个与当前PCI设备相对应的pci_driver数据结构,用来描述将要开发的pci驱动的相关信息,比如驱动的名称,当前驱动可以支持哪些设备,以及当前驱动支持的一些操作等,类似地,还需要有个结构体来表示PCI设备,描述PCI设备的硬件信息,如厂商ID,设备ID,以及各种资源等,详见注释。
二、PCI核心驱动API
Linux内核的PCI驱动为PCI设备驱动的开发提供了方便的结构,下面列举几个常用的接口:
pci_register_driver(struct pci_driver *drv)
功能:注册PCI驱动,参数为要注册的pci驱动的结构体。
下面来详细的分析以下这个函数,如此,才能更清楚的了解驱动和设备的匹配过程。
- pci_register_driver->driver_register(&drv->driver);->bus_add_driver->driver_attach->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
在这个过程中有涉及到一个更为抽象的结构体struct device_driver,它是pci_driver的更高级的抽象,即下层是pci_driver,其上是device_driver,这符合通常的程序设计逻辑,越往上层抽象级别越高,因为在操作系统看来,它并不需要知道具体是什么设备,所有的设备对操作系统来说都是相同的,即都用struct device_driver来表示。
在driver_register中先调用driver_find(drv->name, drv->bus),首先在相应的总线上查找drv->name的驱动是否已经被注册过,如果被注册过则返回,否则进行注册过程,即调用bus_add_driver(drv)。
int bus_add_driver(struct device_driver *drv)函数首先判断当前总线是否支持自动探测,如果执行则执行探测函数driver_attach(drv)。
- if (drv->bus->p->drivers_autoprobe) {
- error = driver_attach(drv);
- if (error)
- goto out_unregister;
- }int driver_attach(struct device_driver *drv)
- {
- return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
- }
这个函数对PCI总线的上所有已经连接的PCI设备与当前的PCI驱动进程一次匹配的过程,即对每一个PCI设备都调用匹配函数__driver_attach。
- static int __driver_attach(struct device *dev, void *data)
- {
- struct device_driver *drv = data;
- /*
- * Lock device and try to bind to it. We drop the error
- * here and always return 0, because we need to keep trying
- * to bind to devices and some drivers will return an error
- * simply if it didn't support the device.
- *
- * driver_probe_device() will spit a warning if there
- * is an error.
- */
- if (!driver_match_device(drv, dev))
- return 0;
- if (dev->parent) /* Needed for USB */
- device_lock(dev->parent);
- device_lock(dev);
- if (!dev->driver)
- driver_probe_device(drv, dev);
- device_unlock(dev);
- if (dev->parent)
- device_unlock(dev->parent);
- return 0;
- }
该函数首先判断总线提供的match函数是否为空,如果非空则执行总线提供的match函数,在rtl8139网络驱动中,match非空,参见代码:
- drv->driver.bus = &pci_bus_type;struct bus_type pci_bus_type = {
- .name = "pci",
- .match = pci_bus_match,
- .uevent = pci_uevent,
- .probe = pci_device_probe,
- .remove = pci_device_remove,
- .shutdown = pci_device_shutdown,
- .dev_attrs = pci_dev_attrs,
- .bus_attrs = pci_bus_attrs,
- .pm = PCI_PM_OPS_PTR,
- };
这里将match函数即pci_bus_match,最后该函数调用到
- static inline const struct pci_device_id *
- pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
- {
- if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
- (id->device == PCI_ANY_ID || id->device == dev->device) &&
- (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
- (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
- !((id->class ^ dev->class) & id->class_mask))
- return id;
- return NULL;
- }
在这里进行了pci_driver和pci_dev的匹配,如果匹配成功,则返回pci_device_id。如果匹配不成功,而且当前设备还没有驱动,则调用driver_probe_device(drv,dev)。
- int driver_probe_device(struct device_driver *drv, struct device *dev)
- {
- int ret = 0;
- if (!device_is_registered(dev))
- return -ENODEV;
- pr_debug("bus: '%s': %s: matched device %s with driver %s/n",
- drv->bus->name, __func__, dev_name(dev), drv->name);
- pm_runtime_get_noresume(dev);
- pm_runtime_barrier(dev);
- ret = really_probe(dev, drv);
- pm_runtime_put_sync(dev);
- return ret;
- }
执行到这里说明,说明PCI总线没有提供match函数或者总线提供的match函数返回非空。还需要进行更深层次的探测,至少在总线提供的match函数中仅仅是进行了匹配,并没有将驱动和设备关联起来,这些操作就是在下面的函数中实现的。
- int driver_probe_device(struct device_driver *drv, struct device *dev)
- {
- int ret = 0;
- if (!device_is_registered(dev))
- return -ENODEV;
- pr_debug("bus: '%s': %s: matched device %s with driver %s/n",
- drv->bus->name, __func__, dev_name(dev), drv->name);
- pm_runtime_get_noresume(dev);
- pm_runtime_barrier(dev);
- ret = really_probe(dev, drv);
- pm_runtime_put_sync(dev);
- return ret;
- }
重点看really_probe函数:
- static int really_probe(struct device *dev, struct device_driver *drv)
- {
- int ret = 0;
- atomic_inc(&probe_count);
- pr_debug("bus: '%s': %s: probing driver %s with device %s/n",
- drv->bus->name, __func__, drv->name, dev_name(dev));
- WARN_ON(!list_empty(&dev->devres_head));
- dev->driver = drv;
- if (driver_sysfs_add(dev)) {
- printk(KERN_ERR "%s: driver_sysfs_add(%s) failed/n",
- __func__, dev_name(dev));
- goto probe_failed;
- }
- if (dev->bus->probe) {
- ret = dev->bus->probe(dev);
- if (ret)
- goto probe_failed;
- } else if (drv->probe) {
- ret = drv->probe(dev);
- if (ret)
- goto probe_failed;
- }
- driver_bound(dev);
- ret = 1;
- pr_debug("bus: '%s': %s: bound device %s to driver %s/n",
- drv->bus->name, __func__, dev_name(dev), drv->name);
- goto done;
- probe_failed:
- devres_release_all(dev);
- driver_sysfs_remove(dev);
- dev->driver = NULL;
- if (ret != -ENODEV && ret != -ENXIO) {
- /* driver matched but the probe failed */
- printk(KERN_WARNING
- "%s: probe of %s failed with error %d/n",
- drv->name, dev_name(dev), ret);
- }
- /*
- * Ignore errors returned by ->probe so that the next driver can try
- * its luck.
- */
- ret = 0;
- done:
- atomic_dec(&probe_count);
- wake_up(&probe_waitqueue);
- return ret;
- }
在此函数中,首先将驱动和设备关联起来,即红色代码dev->driver = drv; 指明了当前设备的驱动。按照常规的程序设计思想,驱动和设备关联后是否还需要做一些其他工作才能是设备在相应的驱动下正常工作呢,这就是probe函数实现的功能了,很明显设备和驱动获取都需要做一些工作,因此这里分别留出设备和驱动的probe函数。其中设备的probe即设备所在总线的probe,这里暂且不去分析,因为与网络驱动关系不大,都是PCI总线相关的东西,重点来看驱动的probe,在前面提到的pci_driver结构体中,对于rtl8139驱动来说,其pci_driver结构体被初始化为:
- static struct pci_driver rtl8139_pci_driver = {
- .name = DRV_NAME,
- .id_table = rtl8139_pci_tbl,
- .probe = rtl8139_init_one,
- .remove = __devexit_p(rtl8139_remove_one),
- #ifdef CONFIG_PM
- .suspend = rtl8139_suspend,
- .resume = rtl8139_resume,
- #endif /* CONFIG_PM */
- };
这里即调用rtl8139_init_one,经过上面的逐层分析,我们从pci核心驱动一步一步的走到了rtl8139网络设备的驱动,豁然开朗了,以后看网络驱动的时候就不会感到开始的地方有点迷糊了。代码分析重在代码之间的过渡,如果衔接不好,很多地方都会产生疑问。
上次讲到如何从pci核心驱动一步一步的进入了rtl8139网络驱动,并且调用的第一个函数是驱动的probe函数,即rtl8139_init_one,本文就从这里入手,简单的介绍rtl8139网络驱动的相关原理和源码分析。
1 rtl8139_init_one
上文讲到当实现了驱动和设备的匹配后,需要设备和驱动做一些相应的工作,如正常使用前的初始化操作等,rtl8139_init_one就实现了一些初始化操作,原则上probe函数应该尽可能的短,尽量避免执行耗时的操作。rtl8139_init_one仅仅实现了两个结构体struct net_device和struct rtl8139_private的初始化。前一篇文章中也提到了数据结构的抽象层次的问题,在网络子系统中,所有的网络设备都用net_device来表示,但是并不是所有的网络设备都有相同的属性,因此,对应不同的网络设备增加一个private数据结构来描述,这里就是struct rtl8139_private。
rtl8139_init_one主要函数和功能分析
(1)dev = rtl8139_init_board (pdev);
- /* dev and priv zeroed in alloc_etherdev */
- dev = alloc_etherdev (sizeof (*tp));
- if (dev == NULL) {
- dev_err(&pdev->dev, "Unable to alloc new net device/n");
- return ERR_PTR(-ENOMEM);
- }
- SET_NETDEV_DEV(dev, &pdev->dev);
- tp = netdev_priv(dev);
- tp->pci_dev = pdev;
- /* enable device (incl. PCI PM wakeup and hotplug setup) */
- rc = pci_enable_device (pdev);
- if (rc)
- goto err_out;
- pio_start = pci_resource_start (pdev, 0);
- pio_end = pci_resource_end (pdev, 0);
- pio_flags = pci_resource_flags (pdev, 0);
- pio_len = pci_resource_len (pdev, 0);
- 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);
- ... ...
- rc = pci_request_regions (pdev, DRV_NAME);
a). dev = alloc_etherdev (sizeof (*tp)); --> 分配struct rtl8139_private数据结构,并进行预初始化,之所以称之为预初始化是因为只进行了某些固定数据成员的初始化。
b). 调用pci核心驱动的接口函数:pci_enable_device (),pci_enable_device 也是一个内核开发出来的接口,代码在drivers/pci/pci.c中,笔者跟踪发现这个函数主要就是把PCI配置空间的Command域的0位和1 位置成了1,从而达到了开启设备的目的,因为rtl8139的官方datasheet中,说明了这两位的作用就是开启内存映射和I/O映射,如果不开的话,那我们以上讨论的把控制寄存器空间映射到内存空间的这一功能就被屏蔽了。
pci_resource_[start|end|flags|len]:在硬件加电初始化时,BIOS固件统一检查了所有的PCI设备,并统一为他们分配了一个和其他互不冲突的地址,让他们的驱动程序可以向这些地址映射他们的寄存器,这些地址被BIOS写进了各个设备的配置空间,因为这个活动是一个PCI的标准的活动,所以自然写到各个设备的配置空间里而不是他们风格各异的控制寄存器空间里。当然只有BIOS可以访问配置空间。当操作系统初始化时,他为每个PCI设备分配了pci_dev结构,并且把BIOS获得的并写到了配置空间中的地址读出来写到了pci_dev中的resource字段中。这样以后我们在读这些地址就不需要在访问配置空间了,直接跟pci_dev要就可以了,我们这里的四个函数就是直接从pci_dev读出了相关数据,代码在include/linux/pci.h中。具体参见PCI配置空间相关的介绍。
c). rc = pci_request_regions (pdev, DRV_NAME);通知内核该设备对应的IO端口和内存资源已经使用,其他的PCI设备不要再使用这个区域
d). 获得当前pci设备对应的IO端口和IO内存的基址。
2. rtl8139_open
此函数在网络设备端口被打开时调用,例如执行命令ifconfig eth0 up,就会触发这个函数,此函数是真正的rtl8139网络设备的初始化函数。这个函数主要做了三件事。
① 注册这个设备的中断处理函数。
- retval = request_irq (dev->irq, rtl8139_interrupt, IRQF_SHARED, dev->name, dev);
当网卡发送数据完成或者接收到数据时,是用中断的形式来告知的,比如有数据从网线传来,中断也通知了我们,那么必须要有一个处理这个中断的函数来完成数据的接收。关于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 = dma_alloc_coherent(&tp->pci_dev->dev, TX_BUF_TOT_LEN,
- &tp->tx_bufs_dma, GFP_KERNEL);
- tp->rx_ring = dma_alloc_coherent(&tp->pci_dev->dev, RX_BUF_TOT_LEN,
- &tp->rx_ring_dma, GFP_KERNEL);
tp 是net_device的priv的指针,tx_bufs是发送缓冲内存的首地址,rx_ring是接收缓存内存的首地址,他们都是虚拟地址,而最后一个参数tx_bufs_dma和rx_ring_dma均是这一段内存的物理地址。为什么同一个事物,既用虚拟地址来表示它还要用物理地址呢,是这样的, CPU执行程序用到这个地址时,用虚拟地址,而网卡设备通过DMA操作向这些内存中存取数据时用的是物理地址(因为网卡相对CPU属于头脑比较简单型的)。 pci_alloc_consistent的代码在Linux/arch/i386/kernel/pci-dma.c中。
③发送和接收缓冲区初始化和网卡开始工作的操作
RTL8139有4个发送描述符(包括4个发送缓冲区的基地址寄存器(TSAD0-TSAD3)和4个发送状态寄存器(TSD0-TSD3)。也就是说我们分配的缓冲区要分成四个等分并把这四个空间的地址都写到相关寄存器里去,下面这段代码完成了这个操作。
- /* Initialize the Rx and Tx rings, along with various 'dev' bits. */
- static void rtl8139_init_ring (struct net_device *dev)
- {
- struct rtl8139_private *tp = netdev_priv(dev);
- int i;
- tp->cur_rx = 0;
- tp->cur_tx = 0;
- tp->dirty_tx = 0;
- for (i = 0; i < NUM_TX_DESC; i++)
- tp->tx_buf[i] = &tp->tx_bufs[i * TX_BUF_SIZE];
- }
上面这段代码负责把发送缓冲区虚拟空间进行了分割。
- /* init Tx buffer DMA addresses */
- for (i = 0; i < NUM_TX_DESC; i++)
- RTL_W32_F (TxAddr0 + (i * 4), tp->tx_bufs_dma + (tp->tx_buf[i] - tp->tx_bufs));
上面这段代码负责把发送缓冲区物理空间进行了分割,并把它写到了相关寄存器中,这样在网卡开始工作后就能够迅速定位和找到这些内存并存取他们的数据。
- /* init Rx ring buffer DMA address */
- RTL_W32_F (RxBuf, tp->rx_ring_dma);
上面这行代码是把接收缓冲区的物理地址写到了相关寄存器中,这样网卡接收到数据后就能准确的把数据从网卡中搬运到这些内存空间中,等待CPU来领走他们。
/* make sure RxTx has started */
tmp = RTL_R8 (ChipCmd);
if ((!(tmp & CmdRxEnb)) || (!(tmp & CmdTxEnb)))
RTL_W8 (ChipCmd, CmdRxEnb | CmdTxEnb);
重新RESET设备后,我们要激活设备的发送和接收的功能,上面这行代码就是向相关寄存器中写入相应值,激活了设备的这些功能。
static const unsigned int rtl8139_tx_config =
TxIFG96 | (TX_DMA_BURST << TxDMAShift) | (TX_RETRY << TxRetryShift);RTL_W32 (TxConfig, rtl8139_tx_config);
上面这行代码是向网卡的TxConfig(位移是0x44)寄存器中写入TX_DMA_BURST << TxDMAShift这个值,翻译过来就是6<<8,就是把第8到第10这三位置成110,查阅管法文档发现6就是110代表着一次DMA的数据量为1024字节。
3. 网络数据包的收发过程
当一个网络应用程序要向网络发送数据时,它要利用Linux的网络协议栈来解决一系列问题,找到网卡设备的代表net_device,由这个结构来找到并控制这个网卡设备来完成数据包的发送,具体是调用net_device的hard_start_xmit成员函数,这是一个函数指针,在我们的驱动程序里它指向的是rtl8139_start_xmit,正是由它来完成我们的发送工作的,下面我们就来剖析这个函数。它一共做了四件事。
①检查这个要发送的数据包的长度,如果它达不到以太网帧的长度,必须采取措施进行填充。
- /* Calculate the next Tx descriptor entry. */
- entry = tp->cur_tx % NUM_TX_DESC;
- /* Note: the chip doesn't have auto-pad! */
- if (likely(len < TX_BUF_SIZE)) { //TX_BUF_SIZE = 1536
- if (len < ETH_ZLEN) //ETH_ZLEN = 60
- memset(tp->tx_buf[entry], 0, ETH_ZLEN);
- skb_copy_and_csum_dev(skb, tp->tx_buf[entry]);
- dev_kfree_skb(skb);
- } else {
- dev_kfree_skb(skb);
- dev->stats.tx_dropped++;
- return NETDEV_TX_OK;
- }
②把包的数据拷贝到我们已经建立好的发送缓存中。主要实现了把skb结构中的数据拷贝到tp->tx_buf[entry]指向的发送缓冲区中。
- void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
- {
- __wsum csum;
- long csstart;
- /*首先计算skb->data的长度*/
- if (skb->ip_summed == CHECKSUM_PARTIAL)
- csstart = skb->csum_start - skb_headroom(skb);
- else
- csstart = skb_headlen(skb);
- BUG_ON(csstart > skb_headlen(skb));
- skb_copy_from_linear_data(skb, to, csstart);
- csum = 0;
- if (csstart != skb->len)
- csum = skb_copy_and_csum_bits(skb, csstart, to + csstart,
- skb->len - csstart, 0);
- if (skb->ip_summed == CHECKSUM_PARTIAL) {
- long csstuff = csstart + skb->csum_offset;
- *((__sum16 *)(to + csstuff)) = csum_fold(csum);
- }
- }
在拷贝函数中需要注意几个问题:
a. 如何计算要拷贝的skb的数据的长度,即这里的csstart的计算,这里参考下面的公式:
If skb is linear (i.e., skb->data_len == 0), the length of skb->data is skb->len.
If skb is not linear (i.e., skb->data_len != 0), the length of skb->data is (skb->len) - (skb->data_len) for the head ONLY. The rest must see struct skb_shared_info->frags[i].size and struct skb_shared_info->frag_list, which contains a linked-list of struct sk_buff because, deducing from [2],
skb->data_len = struct skb_shared_info->frags[0...struct skb_shared_info->nr_frags].size + size of data in struct skb_shared_info->frag_listThe rest of the data is not stored as a separate skb if the length of the data permits, but as an array of struct skb_frag_struct in struct skb_shared_info ([4]: To allow 64K frame to be packed as single skb without frag_list). struct skb_frag_struct contains struct page * to point to the true data. If the length of the data is longer than that that can be contained in the array, struct skb_shared_info->frag_list will be used to contain a linked-list of struct sk_buff (i.e., the data undergo fragmentation because, according to [1], the frag_list is used to maintain a chain of SKBs organized for fragmentation purposes, it is not used for maintaining paged data.)
As an additional information, skb->truesize = skb->len + sizeof(struct sk_buff). Don't forget that skb->len contains the length of the total data space that the skb refers to taking into account SKB_DATA_ALIGN() and non-linear condition.
skb->len is modified when doing skb_pull(), skb_push() or skb_put().
③光有了地址和数据还不行,我们要让网卡知道这个包的长度,才能保证数据不多不少精确的从缓存中截取出来搬运到网卡中去,这是靠写发送状态寄存器(TSD)来完成的。
- RTL_W32_F (TxStatus0 + (entry * sizeof (u32)),
- tp->tx_flag | max(len, (unsigned int)ETH_ZLEN));
我们把这个包的长度和一些控制信息一起写进了状态寄存器,使网卡的工作有了依据。
④判断发送缓存是否已经满了,如果满了在发就覆盖数据了,要停发。
- if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
- netif_stop_queue (dev);
谈完了发送,我们开始谈接收,当有数据从网线上过来时,网卡产生一个中断,调用的中断服务程序是rtl8139_interrupt,它主要做了三件事。
①从网卡的中断状态寄存器中读出状态值进行分析,status = RTL_R16 (IntrStatus);if ((status &(PCIErr | PCSTimeout | RxUnderrun | RxOverflow |
RxFIFOOver | TxErr | TxOK | RxErr | RxOK)) == 0)
goto out;
上面代码说明如果上面这9种情况均没有的表示没什么好处理的了,退出。
② NAPI接收机制
- /* Receive packets are processed by poll routine.
- If not running start it now. */
- if (status & RxAckBits){
- if (napi_schedule_prep(&tp->napi)) {
- RTL_W16_F (IntrMask, rtl8139_norx_intr_mask);
- __napi_schedule(&tp->napi);
- }
- }
napi_schedule_prep(&tp->napi)判断以下当前驱动是否支持NAPI或者NAPI需要的前提条件是否满足,如果满足,设置中断屏蔽字,屏蔽之后产生的中断,然后激活一个软中断,具体代码如下:(至于list_add_tail将会稍后分析)
- static inline void ____napi_schedule(struct softnet_data *sd,
- struct napi_struct *napi)
- {
- list_add_tail(&napi->poll_list, &sd->poll_list);
- __raise_softirq_irqoff(NET_RX_SOFTIRQ);
- }
在软中断注册的轮询函数中完成网络数据包的接收操作。
③发送中断处理
- if (status & (TxOK | TxErr)) {
- rtl8139_tx_interrupt (dev, tp, ioaddr);
- if (status & TxErr)
- RTL_W16 (IntrStatus, TxErr);
- }
如果是传输完成的信号,就调用rtl8139_tx_interrupt进行发送善后处理。
下面我们先来看看接收中断处理函数rtl8139_rx,在这个函数中主要做了下面四件事
①这个函数是一个大循环,循环条件是只要接收缓存不为空就还可以继续读取数据,循环不会停止,读空了之后就跳出。
int ring_offset = cur_rx % RX_BUF_LEN;
rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset));
rx_size = rx_status >> 16;
上面三行代码是计算出要接收的包的长度。
②根据这个长度来分配包的数据结构pkt_size = rx_size - 4;
skb = netdev_alloc_skb_ip_align(dev, pkt_size);
③如果分配成功就把数据从接收缓存中拷贝到这个包中
- if (likely(skb)) {
- #if RX_BUF_IDX == 3
- wrap_copy(skb, rx_ring, ring_offset+4, pkt_size);
- #else
- skb_copy_to_linear_data (skb, &rx_ring[ring_offset + 4], pkt_size);
- #endif
- skb_put (skb, pkt_size);
- skb->protocol = eth_type_trans (skb, dev);
- dev->stats.rx_bytes += pkt_size;
- dev->stats.rx_packets++;
- netif_receive_skb (skb);
这里采用了wrap_copy和skb_copy_to_linear_data两个拷贝函数,实质还是调用了memcpy()。
- static inline void wrap_copy(struct sk_buff *skb, const unsigned char *ring,
- u32 offset, unsigned int size)
- {
- u32 left = RX_BUF_LEN - offset;
- if (size > left) {
- skb_copy_to_linear_data(skb, ring + offset, left);
- skb_copy_to_linear_data_offset(skb, left, ring, size - left);
- } else
- skb_copy_to_linear_data(skb, ring + offset, size);
- }
- static inline void skb_copy_to_linear_data(struct sk_buff *skb, const void *from,
const unsigned int len)
- {
- memcpy(skb->data, from, len);
- }
现在我们已经熟知,&rx_ring[ring_offset + 4]就是接收缓存,也是源地址,而skb->data就是包的数据地址,也是目的地址,一目了然。
④把这个包送到Linux协议栈去进行下一步处理
- skb->protocol = eth_type_trans (skb, dev);
- netif_receive_skb (skb);
在netif_receive_skb (skb)函数执行完后,这个包的数据就脱离了网卡驱动范畴,而进入了Linux网络协议栈里面,把这些数据包的以太网帧头,IP头,TCP头都脱下来,最后把数据送给了应用程序,不过协议栈不再本文讨论范围内。