【问题标题】:DMA transaction requires copying into buffer every time?DMA事务每次都需要复制到缓冲区?
【发布时间】:2017-01-14 04:37:35
【问题描述】:

这可能是一个愚蠢的问题,但到目前为止我还没有了解 DMA。
在做内存到内存DMAing时,需要分配DMA缓冲区 (例如dma_alloc_coherent()),那么对于每次传输,我们需要将缓冲区复制到分配的内存(源缓冲区),然后触发 DMA 事务。

那么,如果每笔交易都需要额外的memcpy(),那么 使用 DMA 的好处?

将源复制到目标的步骤 - 没有 DMA

  1. 将缓冲区 (memcpy()) 从源复制到目标

将源复制到目标的步骤 - 使用 DMA

  1. 将缓冲区 (memcpy()) 从源复制到 DMA 缓冲区
  2. 触发 DMA 事务(最终将缓冲区复制到 目标缓冲区)

这个问题的一个例子是以太网驱动程序,它需要从收到的sk_buf 复制到 FPGA 的物理地址。在这种情况下,它需要首先将sk_buf 复制到DMA 源缓冲区(来自dma_alloc_coherent())。

【问题讨论】:

  • 您指的是哪个驱动程序?您能否向我们展示一些在主线内核中您发现问题所在的实际代码?
  • 在内核中找到简单的例子有点困难。我正在编写自己的以太网驱动程序,但我不确定使用 dma 是否有效——就像我写的那样。如果我的问题或假设有任何错误,我想知道。

标签: linux-kernel linux-device-driver dma


【解决方案1】:

如果您可以将dma_map_single()sk_buf 指针一起使用,那么您不必将其复制到使用dma_alloc_coherent() 分配的缓冲区中。网络设备驱动程序中有很多这样的例子。

int dma_len = skb->len;
dma_addr_t dma_addr = dma_map_single(dev, skb->data, skb->len, DMA_TO_DEVICE);

// error checking code here
// then send the dma_addr to the drvice
// when it is done, unmap it
dma_unmap_single(dev, dma_addr, dma_len, DMA_TO_DEVICE);

有关详细信息,请参阅DMA Mapping API documentation

【讨论】:

  • 但我正在使用内存到内存(fpga 空间已映射),所以我不确定我应该使用 DMA_TO_DEVICE 吗?我确实阅读了 DMA Mapping API 文档,但没有明确何时应该是内存到内存以及内存到/从设备
  • memory-to-memory 仅在您在两侧使用内存时发生(具有 incremental 地址和相应的总线宽度。在 固定您必须使用 memory-to-devicedevice-to-memory 传输的 FIFO 地址。
  • 这是一个增量地址。如何指定它是内存到内存的?
  • 如果您的处理器芯片组不支持从内存到内存的 DMA,我建议在您的 FPGA 中添加一个 DMA 控制器,以便它可以从主机内存读取。我认为这是您在问题中描述的情况,因为您询问了使用 DMA 从主机内存读取的网络接口。
【解决方案2】:

我想我的答案与帖子所有者不再相关,但也许它会在未来对其他一些程序员有所帮助。

正如此处的其中一个 cmets 所述,如果您的 FPGA 有一个 DMA 控制器(它允许 FPGA 读取已映射到 DMA 的内存),那么您应该能够在没有 memcpy() 操作的情况下进行 DMA .

我将尝试在这里给出一个简短的(尽可能...)示例,说明如何在 Rx 流中的以太网驱动程序中实现它(但实现它的方法不止一种,这只是了解基本步骤和概念的一般示例)。请注意,我已经尝试简化它,所以不要尝试编译它(这不是完整的代码 - 要查看完整的以太网驱动程序,您可以尝试开始查看 this 驱动程序)。

现在,让我们关注这些基本步骤:

  • 初始化 Rx 缓冲区
  • Rx 帧接收
  • 为下一个 DMA 事务准备 Rx 缓冲区
  • 释放 Rx 缓冲区

初始化接收缓冲区

static int init_dma_rx_desc_rings(struct net_device *dev, gfp_t flags)
{
    struct my_private *tp = (struct my_private *)dev->priv;

    int ret = -ENOMEM;
    int i;

    tp->dma_buf_sz = BUF_SIZE_16KiB;

    for (i = 0; i < DMA_RX_SIZE; i++) {
        ret = init_rx_buffers(dev, i, flags);
        if (ret)
            goto err_init_rx_buffers;
    }

    return 0;

err_init_rx_buffers:
    for (i = 0; i < DMA_RX_SIZE; i++) {
        ret = free_rx_buffer(dev, i);
        if (ret)
            goto err_init_rx_buffers;
    }

    return ret;
}

static int init_rx_buffers(struct net_device *dev, int i, gfp_t flags)
{
    struct my_private *tp = (struct my_private *)dev->priv;
    struct sk_buff *skb = __netdev_alloc_skb_ip_align(dev, tp->dma_buf_sz, flags);

    if (!skb) {
        printk("Rx init fails; skb is NULL\n");
        return -ENOMEM;
    }

    tp->rx_skbuff[i] = skb;
    tp->rx_skbuff_dma[i] = dma_map_single(tp->device, skb->data,
                                          tp->dma_buf_sz, DMA_FROM_DEVICE);

    if (dma_mapping_error(tp->device, tp->rx_skbuff_dma[i])) {
        printk("DMA mapping error\n");
        dev_kfree_skb_any(skb);
        return -EINVAL;
    }

    return 0;
}

Rx 帧接收

/* should be called by the interrupt handler or NAPI poll method*/
static void receive_packets(struct net_device *dev)
{
    struct my_private *tp = (struct my_private *)dev->priv;
    unsigned int count = 0;
    int rx_work_limit = tp->dirty_rx + RX_RING_SIZE - tp->cur_rx;
    unsigned int next_entry = tp->cur_rx;

    while (count < rx_work_limit) {
        int entry = next_entry;

        /* read the status of the incoming frame */
        int status = get_rx_status(tp);

        /* check if managed by the DMA otherwise go ahead */
        if (unlikely(status & dma_own))
            break;

        count++;
        tp->cur_rx = get_rx_entry(tp->cur_rx, DMA_RX_SIZE);
        next_entry = tp->cur_rx;

        /*  If frame length is greater than skb buffer size
            (preallocated during init) then the packet is ignored */
        int frame_len = get_rx_frame_len(tp);   
        if (frame_len > tp->dma_buf_sz) {
            printk("len %d larger than size (%d)\n", frame_len, tp->dma_buf_sz);    
            continue;
        }

        struct sk_buff *skb = tp->rx_skbuff[entry];
        if (unlikely(!skb)) {
            printk("Inconsistent Rx chain\n");
            continue;
        }

        prefetch(skb->data - NET_IP_ALIGN);
        tp->rx_skbuff[entry] = NULL;

        skb_put(skb, frame_len);
        dma_unmap_single(tp->device, tp->rx_skbuff_dma[entry],
                         tp->dma_buf_sz, DMA_FROM_DEVICE);

       /* from this point it is safe to access the data of the rx skb. 
          the DMA transaction is already complete
          and the rx buffer is unmapped from the DMA */

        netif_receive_skb(skb);
    }

    rx_refill(dev);

    return count;
}

为下一个 DMA 事务准备 Rx 缓冲区

static inline void rx_refill(struct net_device *dev)
{
    struct my_private *tp = (struct my_private *)dev->priv;
    int dirty = get_num_of_rx_dirty(tp);
    unsigned int entry = tp->dirty_rx;

    while (dirty-- > 0) {
        if (likely(!tp->rx_skbuff[entry])) {
            struct sk_buff *skb;

            skb = netdev_alloc_skb_ip_align(dev, tp->dma_buf_sz);
            if (unlikely(!skb)) {
                printk("fail to alloc skb entry %d\n", entry);
                break;
            }

            rx_q->rx_skbuff[entry] = skb;
            rx_q->rx_skbuff_dma[entry] = dma_map_single(tp->device, skb->data,
                                                        tp->dma_buf_sz, DMA_FROM_DEVICE);       
            if (dma_mapping_error(tp->device, tp->rx_skbuff_dma[entry])) {
                printk("Rx DMA map failed\n");
                dev_kfree_skb(skb);
                break;
            }
        }

        entry = get_rx_entry(entry, DMA_RX_SIZE);
    }

    tp->dirty_rx = entry;
}

释放 Rx 缓冲区

static void free_rx_buffer(struct net_device *dev, int i)
{
    struct my_private *tp = (struct my_private *)dev->priv;

    if (tp->rx_skbuff[i]) {
        dma_unmap_single(tp->device, tp->rx_skbuff_dma[i],
                         tp->dma_buf_sz, DMA_FROM_DEVICE);
        dev_kfree_skb_any(tp->rx_skbuff[i]);
    }
    tp->rx_skbuff[i] = NULL;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多