收发流程图.docx

上传人:b****1 文档编号:2998288 上传时间:2023-05-05 格式:DOCX 页数:22 大小:24.10KB
下载 相关 举报
收发流程图.docx_第1页
第1页 / 共22页
收发流程图.docx_第2页
第2页 / 共22页
收发流程图.docx_第3页
第3页 / 共22页
收发流程图.docx_第4页
第4页 / 共22页
收发流程图.docx_第5页
第5页 / 共22页
收发流程图.docx_第6页
第6页 / 共22页
收发流程图.docx_第7页
第7页 / 共22页
收发流程图.docx_第8页
第8页 / 共22页
收发流程图.docx_第9页
第9页 / 共22页
收发流程图.docx_第10页
第10页 / 共22页
收发流程图.docx_第11页
第11页 / 共22页
收发流程图.docx_第12页
第12页 / 共22页
收发流程图.docx_第13页
第13页 / 共22页
收发流程图.docx_第14页
第14页 / 共22页
收发流程图.docx_第15页
第15页 / 共22页
收发流程图.docx_第16页
第16页 / 共22页
收发流程图.docx_第17页
第17页 / 共22页
收发流程图.docx_第18页
第18页 / 共22页
收发流程图.docx_第19页
第19页 / 共22页
收发流程图.docx_第20页
第20页 / 共22页
亲,该文档总共22页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

收发流程图.docx

《收发流程图.docx》由会员分享,可在线阅读,更多相关《收发流程图.docx(22页珍藏版)》请在冰点文库上搜索。

收发流程图.docx

收发流程图

数据包

一、PCI设备

大多数网卡都是一个PCI设备,PCI设备都包含了一个标准的配置寄存器,寄存器中,包含了PCI设备的厂商I

D、设备ID等等信息,驱动程序使用来描述这些寄存器的标识符。

这样,在驱动程序中,常常就可以看到定义一个structpci_device_id类型的数组,告诉内核支持不同类型的PCI设备的列表。

在内核中,一个PCI设备,使用structpci_driver结构来描述。

设备的探测函数:

因为在系统引导的时候,PCI设备已经被识别,当内核发现一个已经检测到的设备同驱动注册的id_table中的信息相匹配时,它就会触发驱动的probe函数,以e100为例:

staticint__devinite100_probe(structpci_dev*pdev,conststructpci_device_id*ent){

1.分配网络设备

2.设置各成员指针函数

3.设置网络设备名称

4.设置网络设备名称

5.网络设备指针,指向自己

6.将PCI设备的私有数据区指向网络设备

7.激活PCI设备

8.判断I/O区域是否是I/O内存,如果不是,则报错退出

9.分配I/O内存区域

10分配完成后,映射I/O内存

11设置设备私有数据结构的大部份默认参数

12初始化自旋锁,锅的初始化必须在调用hw_reset之前执行

13硬件复位,通过向指定I/O端口设置复位指令实现.

14添加两个内核定时器,watchdog和blink_timer

15注册网络设备

16return}二、打开设备

interr=0;

if((err=e100_up(nic)))//函数启动网卡

DPRINTK(IFUP,ERR,"Cannotopeninterface,aborting.\n");

returnerr;}信号载波(carrier)的存在,载波的存在意味着设备可以工作

关闭载波信号;

打开载波信号;

检测载波信号;

对于探测网卡网线是否连接,这一组函数被使用得较多;

接着,调用e100_up函数启动网卡,这个"启动"的过程,最重要的步骤有:

1、调用request_irq向内核注册中断;

这样,中断函数e100_intr将被调用;

三、网卡xx

在内核中断处理中,会检测中断与我们刚才注册的中断号匹配,于是,注册的中断处理函数就被调用了。

当需要发/收数据,出现错误,连接状态变化等,网卡的中断信号会被触发。

当接收到中断后,中断函数读取中断状态位,进行合法性判断,如判断中断信号是否是自己的等,然后,应答设备中断

CODE:

u8stat_ack=readb(&nic->csr->scb.stat_ack);

DPRINTK(INTR,DEBUG,"stat_ack=0x%02X\n",stat_ack);

if(stat_ack==stat_ack_not_ours||/*Notourinterrupt*/

stat_ack==stat_ack_not_present)/*Hardwareisejected*/

returnIRQ_NONE;

/*Ackinterrupt(s)*/

writeb(stat_ack,&nic->csr->scb.stat_ack);

/*WehitReceiveNoResource(RNR);restartRUaftercleaning*/

if(stat_ack&stat_ack_rnr)

nic->ru_running=RU_SUSPENDED;

e100_disable_irq(nic);

returnIRQ_HANDLED;}对于数据包的接收而言,我们关注的是poll函数中,调用e100_rx_clean进行数据的接收:

CODE:

/*

*初始化阶段分配给接口的weight值,轮询函数必须接受二者之间的最小值。

表示

*轮询函数本次要处理的数据包个数。

*/

unsignedintwork_done=0;

inttx_cleaned;

/*进行数据包的接收和传输*/

e100_rx_clean(nic,&work_done,work_to_do);

tx_cleaned=e100_tx_clean(nic);

/*接收和传输完成后,就退出poll模块,重启中断*/

/*IfnoRxandTxcleanupworkwasdone,exitpollingmode.*/

e100_enable_irq(nic);

return0;}*budget-=work_done;

return1;}staticinlinevoide100_rx_clean(structnic*nic,unsignedint*work_done,

unsignedintwork_to_do){structrx*rx;

intrestart_required=0;

structrx*rx_to_start=NULL;

/*arewealreadyrnr?

thenpayattention!

!

!

thisensuresthat

*thestatemachineprogressionneverallowsastartwitha

*partiallycleanedlist,avoidingaracebetweenhardware

*andrx_to_cleanwheninNAPImode*/

if(RU_SUSPENDED==nic->ru_running)

restart_required=1;

/*Indicatenewlyarrivedpackets*/

for(rx=nic->rx_to_clean;rx->skb;rx=nic->rx_to_clean=rx->next){

interr=e100_rx_indicate(nic,rx,work_done,work_to_do);

if(-EAGAIN==err){

/*hitquotasohavemoreworktodo,restartonce

restart_required=0;

break;

}elseif(-ENODATA==err)

break;/*Nomoretoclean*/}/*saveourstartingpointastheplacewe'llrestartthereceiver*/

if(restart_required)

rx_to_start=nic->rx_to_clean;

/*Allocnewskbstorefilllist*/

for(rx=nic->rx_to_use;!

rx->skb;rx=nic->rx_to_use=rx->next){

if(unlikely(e100_rx_alloc_skb(nic,rx)))

break;/*Betterlucknexttime(seewatchdog)*/}if(restart_required){

//ackthernr?

writeb(stat_ack_rnr,&nic->csr->scb.stat_ack);

e100_start_receiver(nic,rx_to_start);

if(work_done)

(*work_done)++;}}

四、网卡的数据接收

内核如何从网卡接受数据,传统的经典过程:

1、数据到达网卡;

2、网卡产生一个xx给内核;

3、内核使用I/O指令,从网卡I/O区域中去读取数据;

我们在许多网卡驱动中,都可以在网卡的中断函数中见到这一过程。

轮询技术,所谓NAPI技术,说来也不神秘,就是说,内核屏蔽中断,然后隔一会儿就去问网卡,"你有没有数据啊?

如果数据量少,轮询同样占用大量的不必要的CPU资源另一个问题,就是从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,DMA技术--让网卡直接从主内存之间读写它们的I/O数据,CPU,这儿不干你事,自己找乐子去:

1、首先,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓冲区)。

2、内核将这个缓冲区通过DMA映射,把这个队列交给网卡;

3、网卡收到数据,就直接放进这个环形缓冲区了--也就是直接放进主内存了;然后,向系统产生一个中断;

4、内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;--呵呵,这一个过程比传统的过程少了不少工作,因为设备直接把数据放进了主内存,不需要CPU的干预,效率是不是提高不少?

对应以上4步,来看它的具体实现:

1、分配环形DMA缓冲区

Linux内核中,用skb来描述一个缓存,所谓分配,就是建立一定数量的skb,然后把它们组织成一个双向链表;

2、建立DMA映射

内核通过调用

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函数调用完成的:

CODE:

staticinte100_rx_alloc_list(structnic*nic){structrx*rx;

unsignedinti,count=nic->params.rfds.count;

nic->rx_to_use=nic->rx_to_clean=NULL;

nic->ru_running=RU_UNITIALIZED;

/*结构structrx用来描述一个缓冲区节点,这里分配了count个*/

if(!

(nic->rxs=kmalloc(sizeof(structrx)*count,GFP_ATOMIC)))

return-ENOMEM;

memset(nic->rxs,0,sizeof(structrx)*count);

/*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间

skb用来描述内核中的一个数据包,呵呵,说到重点了*/

for(rx=nic->rxs,i=0;i

rx->next=(i+1

rx+1:

nic->rxs;

rx->prev=(i==0)?

nic->rxs+count-1:

rx-1;

if(e100_rx_alloc_skb(nic,rx)){/*分配缓存*/

e100_rx_clean_list(nic);

return-ENOMEM;}}

nic->rx_to_use=nic->rx_to_clean=nic->rxs;

nic->ru_running=RU_SUSPENDED;

return0;}CODE:

#defineRFD_BUF_LEN(sizeof(structrfd)+VLAN_ETH_FRAME_LEN)

staticinlineinte100_rx_alloc_skb(structnic*nic,structrx*rx){/*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于,

它是原子的,所以,通常在中断上下文中使用*/

if(!

(rx->skb=dev_alloc_skb(RFD_BUF_LEN+NET_IP_ALIGN)))

return-ENOMEM;

/*初始化必要的成员*/

skb_reserve(rx->skb,NET_IP_ALIGN);

/*这里在数据区之前,留了一块sizeof(structrfd)这么大的空间,该结构的一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过它,来判断是否真有数据到达等,诸如此类*/

memcpy(rx->skb->data,&nic->blank_rfd,sizeof(structrfd));

/*这是最关键的一步,建立DMA映射,把每一个缓冲区rx->skb->data都映射给了设备,缓存区节点

rx利用dma_addr保存了每一次映射的地址,这个地址后面会被用到*/rx->dma_addr=pci_map_single(nic->pdev,rx->skb->data,

RFD_BUF_LEN,PCI_DMA_BIDIRECTIONAL);

if(pci_dma_mapping_error(rx->dma_addr)){

dev_kfree_skb_any(rx->skb);

rx->skb=0;

rx->dma_addr=0;

return-ENOMEM;}/*LinktheRFDtoendofRFAbylinkingpreviousRFDto

*thisone,andclearingELbitofprevious.*/

if(rx->prev->skb){

structrfd*prev_rfd=(structrfd*)rx->prev->skb->data;

/*put_unaligned(val,ptr);用到把var放到ptr指针的地方,它能处理内存对齐的问题

prev_rfd是在缓冲区开始处保存的一点空间,它的link成员,也保存了映射后的地址*/

put_unaligned(cpu_to_le32(rx->dma_addr),

(u32*)&prev_rfd->link);

wmb();

pci_dma_sync_single_for_device(nic->pdev,rx->prev->dma_addr,

sizeof(structrfd),PCI_DMA_TODEVICE);}return0;}e100_rx_alloc_list函数在一个循环中,建立了环形缓冲区,并调用e100_rx_alloc_skb为每个缓冲区分配了空间,并做了DMA映射。

这样,我们就可以来看接收数据的过程了。

CODE:

unsignedintwork_done=0;

inttx_cleaned;

e100_rx_clean(nic,&work_done,work_to_do);

tx_cleaned=e100_tx_clean(nic);

/*IfnoRxandTxcleanupworkwasdone,exitpollingmode.*/

e100_enable_irq(nic);

return0;}*budget-=work_done;

return1;}目前,我们只关心rx,所以,e100_rx_clean函数就成了我们关注的对像,它用来从缓冲队列中接收全部数据(这或许是取名为clean的原因吧!

):

CODE:

staticinlinevoide100_rx_clean(structnic*nic,unsignedint*work_done,

unsignedintwork_to_do){structrx*rx;

intrestart_required=0;

structrx*rx_to_start=NULL;

/*arewealreadyrnr?

thenpayattention!

!

!

thisensuresthat

*thestatemachineprogressionneverallowsastartwitha

*partiallycleanedlist,avoidingaracebetweenhardware

*andrx_to_cleanwheninNAPImode*/

if(RU_SUSPENDED==nic->ru_running)

restart_required=1;

/*函数最重要的工作,就是遍历环形缓冲区,接收数据*/

for(rx=nic->rx_to_clean;rx->skb;rx=nic->rx_to_clean=rx->next){interr=e100_rx_indicate(nic,rx,work_done,work_to_do);

if(-EAGAIN==err){

/*hitquotasohavemoreworktodo,restartonce

restart_required=0;

break;

}elseif(-ENODATA==err)

break;/*Nomoretoclean*/}/*saveourstartingpointastheplacewe'llrestartthereceiver*/

if(restart_required)

rx_to_start=nic->rx_to_clean;

/*Allocnewskbstorefilllist*/

for(rx=nic->rx_to_use;!

rx->skb;rx=nic->rx_to_use=rx->next){

if(unlikely(e100_rx_alloc_skb(nic,rx)))

break;/*Betterlucknexttime(seewatchdog)*/}if(restart_required){

//ackthernr?

writeb(stat_ack_rnr,&nic->csr->scb.stat_ack);

e100_start_receiver(nic,rx_to_start);

if(work_done)

(*work_done)++;}}

CODE:

staticinlineinte100_rx_indicate(structnic*nic,structrx*rx,

unsignedint*work_done,unsignedintwork_to_do){structsk_buff*skb=rx->skb;

structrfd*rfd=(structrfd*)skb->data;

u16rfd_status,actual_size;

if(unlikely(work_done&&*work_done>=work_to_do))

return-EAGAIN;

pci_dma_sync_single_for_cpu函数前面已经介绍过,它让CPU在取消DMA映射之前,具备

访问DMA缓存的能力*/

pci_dma_sync_single_for_cpu(nic->pdev,rx->dma_addr,

sizeof(structrfd),PCI_DMA_FROMDEVICE);

rfd_status=le16_to_cpu(rfd->status);

DPRINTK(RX_STATUS,DEBUG,"status=0x%04X\n",rfd_status);

/*Ifdataisn'tready,nothingtoindicate*/

return-ENODATA;

/*Getactualdatasize*/

actual_size=le16_to_cpu(rfd->actual_size)&0x3FFF;

if(unlikely(actual_size>RFD_BUF_LEN-sizeof(structrfd)))

actual_size=RFD_BUF_LEN-sizeof(structrfd);

/*取消映射,因为通过DMA,网卡已经把数据放在了主内存中,这里一取消,也就意味着,

CPU可以处理主内存中的数据了*/

pci_unmap_single(nic->pdev,rx->dma_addr,

RFD_BUF_LEN,PCI_DMA_FROMDEVICE);

/*thisallowsforafastrestartwithoutre-enablinginterrupts*/

nic->ru_running=RU_SUSPENDED;

/*正确地设置data指针,因为最前面有一个sizeof(structrfd)大小区域,跳过它*/skb_reserve(skb,sizeof(structrfd));

/*更新skb的tail和len指针,也是就更新接收到这么多数据的长度*/

skb_put(skb,actual_size);

/*设置协议位*/

if(unlikely(!

(rfd_status&cb_ok))){

/*Don'tindicateifhardwareindicateserrors*/

dev_kfree_skb_any(skb);

/*Don'tindicateoversized

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2