Chinaunix首页 | 论坛 | 博客
  • 博客访问: 917276
  • 博文数量: 194
  • 博客积分: 7991
  • 博客等级: 少将
  • 技术积分: 2067
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-09 22:03
文章分类

全部博文(194)

文章存档

2010年(8)

2009年(71)

2008年(109)

2007年(6)

我的朋友

分类: LINUX

2008-02-26 09:23:56

 
NetXen是美国的一个10G网卡生产商,在linux内核源码树中包含了他们的网卡驱动程序,本文分析了该驱动程序的数据结构以及DMA过程,希望能为利用该网卡实现抓包零拷贝提供帮助。
 
1.数据结构

2.

 

发送环形缓冲区:adapter->cmd_buf_arr;

多个netxen_cmd_buffer结构组成了一个发送环形缓冲区,数据包由上层下来之后,会赋给一个netxen_cmd_buffer结构的skb成员,然后进行DMA映射,启动DMA传输至fw

 

传输环形缓冲区中缓冲区单元的个数

adapter->max_tx_desc_count = MAX_CMD_DESCRIPTORS_HOST;//256

 

 

共有三种环形接收缓冲区,NormalJumboLro

adapter->recv_ctx[1]. rcv_desc[3]. rx_buf_arr;

 

缓冲区单元的个数分别是:

adapter->max_rx_desc_count = MAX_RCV_DESCRIPTORS; //16384

adapter->max_jumbo_rx_desc_count = MAX_JUMBO_RCV_DESCRIPTORS; //1024

adapter->max_lro_rx_desc_count = MAX_LRO_RCV_DESCRIPTORS; //64

 

 

3.

 

#define TX_RINGSIZE (sizeof(struct netxen_cmd_buffer) * adapter->max_tx_desc_count)

adapter->cmd_buf_arr = (struct netxen_cmd_buffer *)vmalloc(TX_RINGSIZE);

针对每一个发送缓冲单元,都有一个netxen_cmd_buffer与之对应;多个缓冲单元组成了一个环形发送缓冲区:adapter->cmd_buf_arr

数据包由上层下来之后(传给netxen_nic_xmit_frame),会赋给netxen_cmd_buffer->skb,然后进行DMA映射,启动DMA传输至fw

 

netxen_nic_xmit_frame

       buffrag->dma = pci_map_single(adapter->pdev, skb->data, first_seg_len, PCI_DMA_TODEVICE);

       //传输过程比这个要复杂,如果当前skb有多个frags的话,会分别进行DMA映射,以便传给fw

 

发送完数据包之后,驱动是如何销毁该skb的?

原来是在NAPIpoll函数中进行的:

netxen_nic_poll

netxen_process_cmd_ring

       pci_unmap_single

       pci_unmap_page

       dev_kfree_skb_any(buffer->skb);

函数netxen_process_cmd_ring根据生产者消费者指针,确定某个发送缓冲单元的数据是否已经发送成功;

对已经发送过的数据包进行解映射操作,进而释放skbsk_buffslab池。

 

 

4.

 

一个recv_ctx

三个recv_desc(normaljumbolro)中包含三个环形接收缓冲区:adapter->recv_ctx[1]. rcv_desc[3]. rx_buf_arr;

 

Pci probe函数对三种recv_desc的分别进行了设置,不同的DMA大小、skb大小:

switch (RCV_DESC_TYPE(ring)) {

case RCV_DESC_NORMAL:

       rcv_desc->max_rx_desc_count = adapter->max_rx_desc_count; //16384

       rcv_desc->flags = RCV_DESC_NORMAL;

       rcv_desc->dma_size = RX_DMA_MAP_LEN; //1758

       rcv_desc->skb_size = MAX_RX_BUFFER_LENGTH; //1760

       break;

 

case RCV_DESC_JUMBO:

       rcv_desc->max_rx_desc_count = adapter->max_jumbo_rx_desc_count; //1024

       rcv_desc->flags = RCV_DESC_JUMBO;

       rcv_desc->dma_size = RX_JUMBO_DMA_MAP_LEN;//8060

       rcv_desc->skb_size = MAX_RX_JUMBO_BUFFER_LENGTH;//8062

       break;

 

case RCV_RING_LRO:

       rcv_desc->max_rx_desc_count = adapter->max_lro_rx_desc_count; //64

       rcv_desc->flags = RCV_DESC_LRO;

       rcv_desc->dma_size = RX_LRO_DMA_MAP_LEN;// (48*1024)-514

       rcv_desc->skb_size = MAX_RX_LRO_BUFFER_LENGTH;// (48*1024)-512

       break;

}

 

Pci probe函数然后分配了三个相应的接收数据包的环形缓冲区:

#define RCV_BUFFSIZE      (sizeof(struct netxen_rx_buffer) * rcv_desc->max_rx_desc_count)

rcv_desc->rx_buf_arr = (struct netxen_rx_buffer *)vmalloc(RCV_BUFFSIZE);

 

 

/*

 * Receive context. There is one such structure per instance of the

 * receive processing. Any state information that is relevant to

 * the receive, and is must be in this structure. The global data may be

 * present elsewhere.

 */

struct netxen_recv_context {

       struct netxen_rcv_desc_ctx rcv_desc[NUM_RCV_DESC_RINGS]; //3个接收ctx

       u32 status_rx_producer;

       u32 status_rx_consumer;

       dma_addr_t rcv_status_desc_phys_addr;

       struct pci_dev *rcv_status_desc_pdev;

       struct status_desc *rcv_status_desc_head;

};

 

/*

 * Rcv Descriptor Context. One such per Rcv Descriptor. There may

 * be one Rcv Descriptor for normal packets, one for jumbo and may be others.

 */

struct netxen_rcv_desc_ctx {//每一个接收ctx

       u32 flags;

       u32 producer;

       u32 rcv_pending;   /* Num of bufs posted in phantom */

       u32 rcv_free;         /* Num of bufs in free list */

       dma_addr_t phys_addr;

       struct pci_dev *phys_pdev;

       struct rcv_desc *desc_head;  /* address of rx ring in Phantom */

       u32 max_rx_desc_count;

       u32 dma_size;

       u32 skb_size;

       struct netxen_rx_buffer *rx_buf_arr;  /* rx buffers for receive*/

       int begin_alloc;

};

struct rcv_desc {

       __le16 reference_handle;

       __le16 reserved;

       __le32 buffer_length;    /*usually 2K,大小在netxen_post_rx_buffers中被指定为netxen_rcv_desc_ctx-> dma_size,共有三种不同大小(Normal, Jumbo, Lro),后续会看到*/

       __le64 addr_buffer;

};

/* In rx_buffer, we do not need multiple fragments as is a single buffer */

struct netxen_rx_buffer {

       struct sk_buff *skb; //netxen_post_rx_buffers分配并进行pci map,地址保存在desc_head

       u64 dma;

       u16 ref_handle;

       u16 state;

       u32 lro_expected_frags;

       u32 lro_current_frags;

       u32 lro_length;

};

接收环形缓冲区虽然是由多个netxen_rx_buffer缓冲区单元(结构)组成的,但是数据包内容并非在该缓冲区单元中,而是netxen_rx_buffer ->skb中,该skbnetxen_post_rx_buffers分配的。

netxen_post_rx_buffers为接收环形缓冲区中的每一个缓冲单元分配skb,然后对该skb进行DMA映射,并将映射结果保存在desc_head中。

有多少个rx_buf_arr就有多少个desc_head,二者一一对应,

desc_headnic open中由netxen_nic_hw_resources中分配(多个struct rcv_desc单元),构成另外一个连续缓冲区,与fw通过DMA进行共享,每个单元中都包含了上层接收数据包的skbDMA地址和大小信息:

总线地址: netxen_rcv_desc_ctx-> desc_head ->addr_buffer   ßskb映射后的地址

数据大小: netxen_rcv_desc_ctx-> desc_head ->buffer_length  ßskb大小

这样fw就知道往主机的什么地方DMA数据包了。

 

至此,接收缓冲区单元都已经分配完成,并且各个缓冲区单元都进行了DMA的映射,从而形成了接收数据包的环形缓冲区;

各个缓冲区单元的DMA映射信息(desc_head)也通过DMAfw进行了共享,这样,fw就可以将数据包放到主机中的各个接收缓冲区单元了,并适时的向主机发送中断信号;

主机采用NAPI接口,利用poll函数netxen_nic_poll进行数据包的接收:

netxen_nic_poll

netxen_process_rcv_ring(adapter, ctx, budget / MAX_RCV_CTX);

       netxen_process_rcv(adapter, ctxid, desc);

              pci_unmap_single(pdev, buffer->dma, rcv_desc->dma_size, PCI_DMA_FROMDEVICE); //解开DMA映射,此时数据包在buffer->skb中已经准备好了

              skb = (struct sk_buff *)buffer->skb;

              ret = netif_receive_skb(skb); //消费一个数据包

              //这里这里是与内核网络层的接口,零拷贝需要在此下功夫:不调用netif_receive_skb,而是将多个skb组成环形接收缓冲区,然后以一个连续的视图映射到用户空间

              //也就是说,数据包的生产者是fw,消费者是用户态程序,真正的零拷贝!
阅读(2003) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~