网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记Word下载.docx
《网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记Word下载.docx》由会员分享,可在线阅读,更多相关《网卡驱动和队列层中的数据包接收Linux TCPIP协议栈笔记Word下载.docx(16页珍藏版)》请在冰豆网上搜索。
内核通过调用
dma_map_single(structdevice*dev,void*buffer,size_tsize,enumdma_data_directiondirection)
建立映射关系。
structdevice*dev,描述一个设备;
buffer:
把哪个地址映射给设备;
也就是某一个skb——要映射全部,当然是做一个双向链表的循环即可;
size:
缓存大小;
direction:
映射方向——谁传给谁:
一般来说,是“双向”映射,数据在设备和内存之间双向流动;
对于PCI设备而言(网卡一般是PCI的),通过另一个包裹函数pci_map_single,这样,就把buffer交给设备了!
设备可以直接从里边读/取数据。
3、这一步由硬件完成;
4、取消映射
dma_unmap_single,对PCI而言,大多调用它的包裹函数pci_unmap_single,不取消的话,缓存控制权还在设备手里,要调用它,把主动权掌握在CPU手里——因为我们已经接收到数据了,应该由CPU把数据交给上层网络栈;
当然,不取消之前,通常要读一些状态位信息,诸如此类,一般是调用
dma_sync_single_for_cpu()
让CPU在取消映射前,就可以访问DMA缓冲区中的内容。
关于DMA映射的更多内容,可以参考《Linux设备驱动程序》“内存映射和DMA”章节相关内容!
OK,有了这些知识,我们就可以来看e100的代码了,它跟上面讲的步骤基本上一样的——绕了这么多圈子,就是想绕到e100上面了,呵呵!
在e100_open函数中,调用e100_up,我们前面分析它时,略过了一个重要的东东,就是环形缓冲区的建立,这一步,是通过
e100_rx_alloc_list函数调用完成的:
static
inte100_rx_alloc_list(structnic*nic)
1.{
2.structrx*rx;
3.unsignedinti,count=nic->
params.rfds.count;
4.
5.nic->
rx_to_use=nic->
rx_to_clean=NULL;
6.nic->
ru_running=RU_UNINITIALIZED;
7.
8./*结构structrx用来描述一个缓冲区节点,这里分配了count个*/
9.if(!
(nic->
rxs=kmalloc(sizeof(structrx)*count,GFP_ATOMIC)))
10.return-ENOMEM;
11.memset(nic->
rxs,0,sizeof(structrx)*count);
12.
13./*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间
14.skb用来描述内核中的一个数据包,呵呵,说到重点了*/
15.for(rx=nic->
rxs,i=0;
i<
count;
rx++,i++){
16.rx->
next=(i+1<
count)?
rx+1:
nic->
rxs;
17.rx->
prev=(i==0)?
rxs+count-1:
rx-1;
18.if(e100_rx_alloc_skb(nic,rx)){/*分配缓存*/
19.e100_rx_clean_list(nic);
20.return-ENOMEM;
21.}
22.}
23.
24.nic->
rx_to_clean=nic->
25.nic->
ru_running=RU_SUSPENDED;
26.
27.return0;
28.}
staticinte100_rx_alloc_list(structnic*nic){structrx*rx;
unsignedinti,count=nic->
nic->
/*结构structrx用来描述一个缓冲区节点,这里分配了count个*/if(!
rxs=kmalloc(sizeof(structrx)*count,GFP_ATOMIC)))return-ENOMEM;
memset(nic->
/*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间skb用来描述内核中的一个数据包,呵呵,说到重点了*/for(rx=nic->
rx++,i++){rx->
rx->
if(e100_rx_alloc_skb(nic,rx)){/*分配缓存*/e100_rx_clean_list(nic);
return-ENOMEM;
}}nic->
return0;
}
#defineRFD_BUF_LEN(sizeof(structrfd)+VLAN_ETH_FRAME_LEN)
1.static
inline
inte100_rx_alloc_skb(structnic*nic,structrx*rx)
2.{
3./*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于,
4.它是原子的,所以,通常在中断上下文中使用*/
5.if(!
(rx->
skb=dev_alloc_skb(RFD_BUF_LEN+NET_IP_ALIGN)))
6.return-ENOMEM;
8./*初始化必要的成员*/
9.rx->
skb->
dev=nic->
netdev;
10.skb_reserve(rx->
skb,NET_IP_ALIGN);
11./*这里在数据区之前,留了一块sizeof(structrfd)这么大的空间,该结构的
12.一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过
13.它,来判断是否真有数据到达等,诸如此类*/
14.memcpy(rx->
data,&
blank_rfd,sizeof(structrfd));
15./*这是最关键的一步,建立DMA映射,把每一个缓冲区rx->
data都映射给了设备,缓存区节点
16.rx利用dma_addr保存了每一次映射的地址,这个地址后面会被用到*/
17.rx->
dma_addr=pci_map_single(nic->
pdev,rx->
data,
18.RFD_BUF_LEN,PCI_DMA_BIDIRECTIONAL);
19.
20.if(pci_dma_mapping_error(rx->
dma_addr)){
21.dev_kfree_skb_any(rx->
skb);
22.rx->
skb=0;
23.rx->
dma_addr=0;
24.return-ENOMEM;
25.}
27./*LinktheRFDtoendofRFAbylinkingpreviousRFDto
28.*thisone,andclearingELbitofprevious.*/
29.if(rx->
prev->
skb){
30.structrfd*prev_rfd=(structrfd*)rx->
data;
31./*put_unaligned(val,ptr);
用到把var放到ptr指针的地方,它能处理处理内存对齐的问题
32.prev_rfd是在缓冲区开始处保存的一点空间,它的link成员,也保存了映射后的地址*/
33.put_unaligned(cpu_to_le32(rx->
dma_addr),
34.(u32*)&
prev_rfd->
link);
35.wmb();
36.prev_rfd->
command&
=~cpu_to_le16(cb_el);
37.pci_dma_sync_single_for_device(nic->
dma_addr,
38.sizeof(structrfd),PCI_DMA_TODEVICE);
39.}
40.
41.return0;
42.}
#defineRFD_BUF_LEN(sizeof(structrfd)+VLAN_ETH_FRAME_LEN)staticinlineinte100_rx_alloc_skb(structnic*nic,structrx*rx){/*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于,它是原子的,所以,通常在中断上下文中使用*/if(!
skb=dev_alloc_skb(RFD_BUF_LEN+NET_IP_ALIGN)))return-ENOMEM;
/*初始化必要的成员*/rx->
skb_reserve(rx->
/*这里在数据区之前,留了一块sizeof(structrfd)这么大的空间,该结构的一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过它,来判断是否真有数据到达等,诸如此类*/memcpy(rx->
/*这是最关键的一步,建立DMA映射,把每一个缓冲区rx->
data都映射给了设备,缓存区节点rx利用dma_addr保存了每一次映射的地址,这个地址后面会被用到*/rx->
data,RFD_BUF_LEN,PCI_DMA_BIDIRECTIONAL);
if(pci_dma_mapping_error(rx->
dma_addr)){dev_kfree_skb_any(rx->
return-ENOMEM;
}/*LinktheRFDtoendofRFAbylinkingpreviousRFDto*thisone,andclearingELbitofprevious.*/if(rx->
skb){structrfd*prev_rfd=(structrfd*)rx->
/*put_unaligned(val,ptr);
用到把var放到ptr指针的地方,它能处理处理内存对齐的问题prev_rfd是在缓冲区开始处保存的一点空间,它的link成员,也保存了映射后的地址*/put_unaligned(cpu_to_le32(rx->
dma_addr),(u32*)&
wmb();
prev_rfd->
pci_dma_sync_single_for_device(nic->
dma_addr,sizeof(structrfd),PCI_DMA_TODEVICE);
}return0;
e100_rx_alloc_list函数在一个循环中,建立了环形缓冲区,并调用e100_rx_alloc_skb为每个缓冲区分配了空间,并做了
DMA映射。
这样,我们就可以来看接收数据的过程了。
前面我们讲过,中断函数中,调用netif_rx_schedule,表明使用轮询技术,系统会在未来某一时刻,调用设备的poll函数:
inte100_poll(structnet_device*netdev,int*budget)
2.structnic*nic=netdev_priv(netdev);
3.unsignedintwork_to_do=min(netdev->
quota,*budget);
4.unsignedintwork_done=0;
5.inttx_cleaned;
6.
7.e100_rx_clean(nic,&
work_done,work_to_do);
8.tx_cleaned=e100_tx_clean(nic);
9.
10./*IfnoRxandTxcleanupworkwasdone,exitpollingmode.*/
11.if((!
tx_cleaned&
&
(work_done==0))||!
netif_running(netdev)){
12.netif_rx_complete(netdev);
13.e100_enable_irq(nic);
14.return0;
15.}
16.
17.*budget-=work_done;
18.netdev->
quota-=work_done;
20.return1;
21.}
staticinte100_poll(structnet_device*netdev,int*budget){structnic*nic=netdev_priv(netdev);
unsignedintwork_to_do=min(netdev->
unsignedintwork_done=0;
inttx_cleaned;
e100_rx_clean(nic,&
tx_cleaned=e100_tx_clean(nic);
/*IfnoRxandTxcleanupworkwasdone,exitpollingmode.*/if((!
netif_running(netdev)){netif_rx_complete(netdev);
e100_enable_irq(nic);
return0;
}*budget-=work_done;
netdev->
return1;
目前,我们只关心rx,所以,e100_rx_clean函数就成了我们关注的对像,它用来从缓冲队列中接收全部数据(这或许是取名为clean的原因吧!
):
voide100_rx_clean(structnic*nic,unsignedint*work_done,
1.unsignedintwork_to_do)
3.structrx*rx;
4.intrestart_required=0;
5.structrx*rx_to_start=NULL;
7./*arewealreadyrnr?
thenpayattention!
!
thisensuresthat
8.*thestatemachineprogressionneverallowsastartwitha
9.*partiallycleanedlist,avoidingaracebetweenhardware
10.*andrx_to_cleanwheninNAPImode*/
11.if(RU_SUSPENDED==nic->
ru_running)
12.restart_required=1;
13.
14./*函数最重要的工作,就是遍历环形缓冲区,接收数据*/
rx_to_clean;
skb;
rx=nic->
rx_to_clean=rx->
next){
16.interr=e100_rx_indicate(nic,rx,work_done,work_to_do);
17.if(-EAGAIN==err){
18./*hitquotasohavemoreworktodo,restartonce
19.*cleanupiscomplete*/
20.restart_required=0;
21.break;
22.}else
if(-ENODATA==err)
23.break;
/*Nomoretoclean*/
24.}
25.
26./*saveourstartingpointastheplacewe'
llrestartthereceiver*/
27.if(restart_required)
28.rx_to_start=nic->
29.
30./*Allocnewskbstorefilllist*/
31.for(rx=nic->
rx_to_use;
!
rx->
rx_to_use=rx->
32.if(unlikely(e100_rx_alloc_skb(nic,rx)))
33.break;
/*Betterlucknexttime(seewatchdog)*/
34.}
35.
36.if(restart_required){
37.//ackthernr?
38.writeb(stat_ack_rnr,&
csr->
scb.stat_ack);
39.e100_start_receiver(nic,rx_to_start);
40.if(work_done)
41.(*work_done)++;
43.}
staticinlinevoide100_rx_clean(structnic*nic,unsignedint*work_done,unsignedintwork_to_do)
{
structrx*rx;
intrestart_required=0;
structrx*rx_to_start=NULL;
/*arewealreadyrnr?
thisensuresthat*thestatemachineprogressionneverallowsastartwitha*partiallycleanedlist,avoidingaracebetweenhardware*andrx_to_cleanwheninNAPImode*/if(RU_SUSPENDED==nic->
ru_running)
restart_required=1;
/*函数最重要的工作,就是遍历环形缓冲区,接收数据*/
for(rx=nic->
next)