分类: LINUX
2010-03-12 19:49:03
发送环形缓冲区:adapter->cmd_buf_arr;
多个netxen_cmd_buffer结构组成了一个发送环形缓冲区,数据包由上层下来之后,会赋给一个netxen_cmd_buffer结构的skb成员,然后进行DMA映射,启动DMA传输至fw。
传输环形缓冲区中缓冲区单元的个数
adapter->max_tx_desc_count = MAX_CMD_DESCRIPTORS_HOST;//256
共有三种环形接收缓冲区,Normal、Jumbo、Lro:
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
#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的?
原来是在NAPI的poll函数中进行的:
netxen_nic_poll
netxen_process_cmd_ring
pci_unmap_single
pci_unmap_page
dev_kfree_skb_any(buffer->skb);
函数netxen_process_cmd_ring根据生产者消费者指针,确定某个发送缓冲单元的数据是否已经发送成功;
对已经发送过的数据包进行解映射操作,进而释放skb回sk_buff的slab池。
一个recv_ctx,
三个recv_desc(normal,jumbo,lro)中包含三个环形接收缓冲区: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中,该skb是由netxen_post_rx_buffers分配的。
netxen_post_rx_buffers为接收环形缓冲区中的每一个缓冲单元分配skb,然后对该skb进行DMA映射,并将映射结果保存在desc_head中。
有多少个rx_buf_arr就有多少个desc_head,二者一一对应,
desc_head在nic open中由netxen_nic_hw_resources中分配(多个struct rcv_desc单元),构成另外一个连续缓冲区,与fw通过DMA进行共享,每个单元中都包含了上层接收数据包的skb的DMA地址和大小信息:
总线地址: netxen_rcv_desc_ctx-> desc_head ->addr_buffer ??skb映射后的地址
数据大小: netxen_rcv_desc_ctx-> desc_head ->buffer_length ??skb大小
这样fw就知道往主机的什么地方DMA数据包了。
至此,接收缓冲区单元都已经分配完成,并且各个缓冲区单元都进行了DMA的映射,从而形成了接收数据包的环形缓冲区;
各个缓冲区单元的DMA映射信息(desc_head)也通过DMA与fw进行了共享,这样,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,消费者是用户态程序,真正的零拷贝!