2. napi 接收方式
还是得从接收中断处理函数开始,在static irqreturn_t ixgb_intr(int irq, void *data)函数中:
-
#ifdef CONFIG_IXGB_NAPI
-
if(netif_rx_schedule_prep(netdev)) { //判断设备是否启动,以及是否已经处于接收状态
-
-
/* Disable interrupts and register for poll. The flush
-
of the posted write is intentionally left out.
-
*/
-
-
atomic_inc(&adapter->irq_sem);
-
IXGB_WRITE_REG(&adapter->hw, IMC, ~0); //在这里关闭接收中断
-
__netif_rx_schedule(netdev); //在这个函数中,把设备挂到软中断接收调度队列中,并分配在初始化中设置的配额大小dev->quota = dev->weight; 然后
-
//触法接收软中断
-
}
现在这个中断处理函数就算处理完了,所做的工作就是对接收硬件中断掩码(即使来了硬件中断也不告知CPU),把设备挂接到接收软中断调度队列中,然后触法软中断,让软中断去处理数据包。接下来看接收
软中断处理函数就行了(还记得上一篇已经提到这个函数),再贴上这个函数吧:
-
/*
-
*给每个CPU的软中断调度队列分配一个预算,
-
* 每个网卡有自己的配额, 一次软中断处理完这个预算,
-
*然后再次触法软中断 。。。
-
*挂在这个软中断调度队列的所有的网卡以配额为单位去吃这个预算
-
*每个网卡以配额为单位吃掉后挂在软中断调度队列的尾部,让给
-
*其他网卡去吃预算
-
*/
-
static void net_rx_action(struct softirq_action *h)
-
{
-
struct softnet_data *queue = &__get_cpu_var(softnet_data);
-
unsigned long start_time = jiffies;
-
int budget = netdev_budget; //一次软中断的预算
-
void *have;
-
-
local_irq_disable();
-
-
while (!list_empty(&queue->poll_list)) {
-
struct net_device *dev;
-
-
if (budget <= 0 || jiffies - start_time > 1) //预算不够了,或者包处理时间超过1s,直接再次触法软中断
-
goto softnet_break;
-
-
local_irq_enable();
-
-
dev = list_entry(queue->poll_list.next,
-
struct net_device, poll_list); //遍历链表,找到接收设备
-
have = netpoll_poll_lock(dev);
-
-
if (dev->quota <= 0 || dev->poll(dev, &budget)) {
-
/*
-
*配额用完了或者还有很多数据包要接收
-
*/
-
netpoll_poll_unlock(have);
-
local_irq_disable();
-
list_move_tail(&dev->poll_list, &queue->poll_list); //把接收链表删除,并再次添加到softnet结构中
-
-
//再次设置配额,然后继续轮询接收剩下的数据包
-
if (dev->quota < 0)
-
dev->quota += dev->weight;
-
else
-
dev->quota = dev->weight;
-
} else {
-
//收到的数据包小于配额,则这一次轮询接收就完事了,要打开接收中断,等待数据包的到来
-
netpoll_poll_unlock(have);
-
dev_put(dev);
-
local_irq_disable();
-
}
-
}
-
out:
-
#ifdef CONFIG_NET_DMA
-
/*
-
* There may not be any more sk_buffs coming right now, so push
-
* any pending DMA copies to hardware
-
*/
-
if (net_dma_client) {
-
struct dma_chan *chan;
-
rcu_read_lock();
-
list_for_each_entry_rcu(chan, &net_dma_client->channels, client_node)
-
dma_async_memcpy_issue_pending(chan);
-
rcu_read_unlock();
-
}
-
#endif
-
local_irq_enable();
-
return;
-
-
softnet_break:
-
__get_cpu_var(netdev_rx_stat).time_squeeze++;
-
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
-
goto out;
-
}
注意在这里dev是设备自己的,在初始化时进行创建的。在中断函数中我们把它挂在了软中断接收调度队列中。自然而然的,dev
->quota ,dev
->poll 都是我们在初始化时注册的。下面我们分析初始化时注册的这个接收函数:
-
#ifdef CONFIG_IXGB_NAPI
-
netdev->poll = &ixgb_clean; //这就是初始化时我们注册的函数,这个是做驱动必须完成的函数
-
netdev->weight = 64; //这个配额可以根据自己项目的需要进行调节,一般如果流量较大的话,这个值就设置大一点。
-
#endif
-
-
-
-
static int
-
ixgb_clean(struct net_device *netdev, int *budget)
-
{
-
struct ixgb_adapter *adapter = netdev_priv(netdev);
-
int work_to_do = min(*budget, netdev->quota); //取预算和配额 较小的一个
-
int tx_cleaned;
-
int work_done = 0;
-
-
tx_cleaned = ixgb_clean_tx_irq(adapter);
-
ixgb_clean_rx_irq(adapter, &work_done, work_to_do); //在这个函数中进行接收,送到上层协议栈
-
-
*budget -= work_done; //减少预算
-
netdev->quota -= work_done; //减少配额
-
-
/* if no Tx and not enough Rx work done, exit the polling mode */
-
if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {
-
netif_rx_complete(netdev); //没有数据包了,就从软中断接收队列中把设备删除掉,并设置设备的状态,为下一次中断做准备
-
ixgb_irq_enable(adapter); //在这里把硬件接收中断掩码 打开
-
return 0;
-
}
-
-
return 1; //还有很多数据包要接收,就等着再次被调度
-
}
ixgb_clean_rx_irq
这个函数在上一篇non_napi中已经分析过了, 贴过来吧,保持完整性。
-
static boolean_t
-
#ifdef CONFIG_IXGB_NAPI
-
ixgb_clean_rx_irq(struct ixgb_adapter *adapter, int *work_done, int work_to_do)
-
#else
-
ixgb_clean_rx_irq(struct ixgb_adapter *adapter)
-
#endif
-
{
-
struct ixgb_desc_ring *rx_ring = &adapter->rx_ring;
-
struct net_device *netdev = adapter->netdev;
-
struct pci_dev *pdev = adapter->pdev;
-
struct ixgb_rx_desc *rx_desc, *next_rxd;
-
struct ixgb_buffer *buffer_info, *next_buffer, *next2_buffer;
-
uint32_t length;
-
unsigned int i, j;
-
boolean_t cleaned = FALSE;
-
-
i = rx_ring->next_to_clean;
-
rx_desc = IXGB_RX_DESC(*rx_ring, i);
-
buffer_info = &rx_ring->buffer_info[i];
-
-
while(rx_desc->status & IXGB_RX_DESC_STATUS_DD) { //如前面分析rx_desc是描述符,进行了一致性DMA映射,所以直接读取其值判断这个中断是否是数据包到来产生的中断
-
struct sk_buff *skb, *next_skb; //因为对skb进行了DMA映射,一次中断最多可以接收rxd->count个数据包,所以这里对rxd->count个描述符轮询
-
u8 status;
-
-
#ifdef CONFIG_IXGB_NAPI
-
if(*work_done >= work_to_do) //超过配额就不接收了,接收这么多,得让给其他网卡来接收了,不能这么自私
-
break;
-
-
(*work_done)++; //对接收的数据包进行计数
-
#endif
-
status = rx_desc->status;
-
skb = buffer_info->skb;
-
buffer_info->skb = NULL;
-
-
prefetch(skb->data);
-
-
if(++i == rx_ring->count) i = 0;
-
next_rxd = IXGB_RX_DESC(*rx_ring, i); //得到下一个描述符
-
prefetch(next_rxd);
-
-
if((j = i + 1) == rx_ring->count) j = 0;
-
next2_buffer = &rx_ring->buffer_info[j]; //预先取得第二个buffer
-
prefetch(next2_buffer);
-
-
next_buffer = &rx_ring->buffer_info[i]; //得到下一个buffer
-
next_skb = next_buffer->skb;
-
prefetch(next_skb);
-
-
cleaned = TRUE;
-
-
pci_unmap_single(pdev,
-
buffer_info->dma,
-
buffer_info->length,
-
PCI_DMA_FROMDEVICE); //把控制权还给CPU
-
-
length = le16_to_cpu(rx_desc->length);
-
-
if(unlikely(!(status & IXGB_RX_DESC_STATUS_EOP))) {
-
-
/* All receives must fit into a single buffer */
-
-
IXGB_DBG("Receive packet consumed multiple buffers "
-
"length<%x>\n", length);
-
-
dev_kfree_skb_irq(skb);
-
goto rxdesc_done;
-
}
-
-
if (unlikely(rx_desc->errors
-
& (IXGB_RX_DESC_ERRORS_CE | IXGB_RX_DESC_ERRORS_SE
-
| IXGB_RX_DESC_ERRORS_P |
-
IXGB_RX_DESC_ERRORS_RXE))) {
-
-
dev_kfree_skb_irq(skb);
-
goto rxdesc_done;
-
}
-
-
/* code added for copybreak, this should improve
-
* performance for small packets with large amounts
-
* of reassembly being done in the stack */
-
#define IXGB_CB_LENGTH 256
-
if (length < IXGB_CB_LENGTH) {
-
struct sk_buff *new_skb =
-
netdev_alloc_skb(netdev, length + NET_IP_ALIGN);
-
if (new_skb) {
-
skb_reserve(new_skb, NET_IP_ALIGN);
-
memcpy(new_skb->data - NET_IP_ALIGN,
-
skb->data - NET_IP_ALIGN,
-
length + NET_IP_ALIGN);
-
/* save the skb in buffer_info as good */
-
buffer_info->skb = skb;
-
skb = new_skb;
-
}
-
}
-
/* end copybreak code */
-
-
/* Good Receive */
-
skb_put(skb, length);
-
-
/* Receive Checksum Offload */
-
ixgb_rx_checksum(adapter, rx_desc, skb);
-
-
skb->protocol = eth_type_trans(skb, netdev);
-
#ifdef CONFIG_IXGB_NAPI
-
if(adapter->vlgrp && (status & IXGB_RX_DESC_STATUS_VP)) {
-
vlan_hwaccel_receive_skb(skb, adapter->vlgrp,
-
le16_to_cpu(rx_desc->special) &
-
IXGB_RX_DESC_SPECIAL_VLAN_MASK);
-
} else {
-
netif_receive_skb(skb);
-
}
-
#else /* CONFIG_IXGB_NAPI */
-
if(adapter->vlgrp && (status & IXGB_RX_DESC_STATUS_VP)) {
-
vlan_hwaccel_rx(skb, adapter->vlgrp,
-
le16_to_cpu(rx_desc->special) &
-
IXGB_RX_DESC_SPECIAL_VLAN_MASK);
-
} else {
-
netif_rx(skb); //non_napi 调用的接收函数
-
}
-
#endif /* CONFIG_IXGB_NAPI */
-
netdev->last_rx = jiffies;
-
-
rxdesc_done:
-
/* clean up descriptor, might be written over by hw */
-
rx_desc->status = 0;
-
-
/* use prefetched values */
-
rx_desc = next_rxd; //指向下一个描述符
-
buffer_info = next_buffer; //指向下一个buffer
-
}
-
-
rx_ring->next_to_clean = i; //移动位置
-
-
ixgb_alloc_rx_buffers(adapter); //再分配前面吃掉的skb,用多少再分配多少
-
-
return cleaned;
-
}
over...
其实从上面我们可以看到,网卡驱动,从原理上看还是比较简单的,但如果是一款新的网卡芯片,我们就必须读datasheet,来配置相关寄存器使网卡能正常工作,这个应该比较花时间,剩下的工作可以copy先有的网卡驱动代码,根据需要进行修改就可以了。
阅读(3477) | 评论(0) | 转发(0) |