网卡驱动:stmmac DMA接收流程(待完善)

dma buffer及zero-copy

在打开网卡时,stmmac_init_rx_buffers()函数负责分配dma buffer。

static int stmmac_init_rx_buffers(struct stmmac_priv *priv, struct dma_desc *p,
                  int i, gfp_t flags, u32 queue)
{
    struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];
    struct sk_buff *skb;
    skb = __netdev_alloc_skb_ip_align(priv->dev, priv->dma_buf_sz, flags);
    if (!skb) {
        netdev_err(priv->dev,
               "%s: Rx init fails; skb is NULL\n", __func__);
        return -ENOMEM;
    }
    rx_q->rx_skbuff[i] = skb;
    rx_q->rx_skbuff_dma[i] = dma_map_single(priv->device, skb->data,
                        priv->dma_buf_sz,
                        DMA_FROM_DEVICE);
    if (dma_mapping_error(priv->device, rx_q->rx_skbuff_dma[i])) {
        netdev_err(priv->dev, "%s: DMA mapping error\n", __func__);
        dev_kfree_skb_any(skb);
        return -EINVAL;
    }
    if (priv->synopsys_id >= DWMAC_CORE_4_00)
        p->des0 = cpu_to_le32(rx_q->rx_skbuff_dma[i]);
    else
        p->des2 = cpu_to_le32(rx_q->rx_skbuff_dma[i]);
    if ((priv->hw->mode->init_desc3) &&
        (priv->dma_buf_sz == BUF_SIZE_16KiB))
        priv->hw->mode->init_desc3(p);
    return 0;
}

1)skb = __netdev_alloc_skb_ip_align(priv->dev, priv->dma_buf_sz, flags); 分配skb

2)rx_q->rx_skbuff[i] = skb; 用于zero-copy,以后再接收软中断中直接使用rx_q->rx_skbuff[i],并交给上层协议处理

3)rx_q->rx_skbuff_dma[i] = dma_map_single(priv->device, skb->data, priv->dma_buf_sz, DMA_FROM_DEVICE); 进行dma映射,得到rx_q->rx_skbuff_dma[i] 源地址(即物理地址)

4)p->des0 = cpu_to_le32(rx_q->rx_skbuff_dma[i]); 在des0设置源地址(即填充buffer地址)

接受数据流程

  1. DMA产生中断,调用 stmmac_interrupt服务程序

  2. stmmac_interrupt 通过调用 stmmac_dma_interrupt 处理DMA相关中断(包括发送和接收)。在stmmac_dma_interrupt中,通过NAPI机制触发软中断,调用stmmac_poll接收数据包。

  3. 在stmmac_poll中调用如下代码收数据包

   work_done = stmmac_rx(priv, budget, rx_q->queue_index);
    if (work_done < budget) {
        napi_complete_done(napi, work_done);
        stmmac_enable_dma_irq(priv, chan);
    }
    return work_done;

1)调用stmmac_rx接收,budget为循环读取dma descriptor(通过处理descriptor来获取数据包,每个descriptor对应一个数据包)的最大次数,work_done为实际循环的次数。

2)if (work_done < budget) 代表实际循环读取dma descriptor的次数小于最大次数budget,代表已经处理完所有需要被处理的descriptor,取完所有的数据包。这时,调用stmmac_enable_dma_irq()开启中断,再次接收数据。如果work_done = budget,代表可能还有数据包需要处理,那么这些数据包留到net_rx_action再次调用stmmac_poll时处理。

3)return work_done; 该返回值会被net_rx_action使用。

  1. 在stmmac_rx中,循环收包,循环次数while (count < limit)。每次循环以DMA descriptor为处理单位,即每次循环时从一个descriptor指定的buffer读取一个数据包,再通过 napi_gro_receive 送给协议层,处理完后count++。这里limit = budget。

  2. 调用 stmmac_rx_refill重新填充descriptor。

在触发DMA中断前,DMA已经将网卡收到的数据包搬到descriptor指定的buffer,而这个buffer又采用了zero-copy机制,所以直接将该buffer的地址copy给一个skb,再将此skb送到协议层处理,最后再调用 stmmac_rx_refill重新填充descriptor并设置buffer。之前descriptor指定的buffer地址已经被赋值给skb,由协议层负责该skb(buffer)的管理(释放内存等)。

  1. 总结:

这里有三种“一次获取多个数据包”的情况

1)一次软中断可能会多次调用net_rx_action

2)net_rx_action可能会多次调用stmmac_poll

3)stmmac_poll处理多个descriptor

你可能感兴趣的:(网络,服务器,linux)