DMA 事务每次都需要复制到缓冲区?
DMA transaction requires copying into buffer every time?
这可能是个愚蠢的问题,但到目前为止我还没有弄明白 DMA。
内存到内存DMAing时,需要分配DMA缓冲区
(例如 dma_alloc_coherent()
),然后对于每次传输,我们需要将缓冲区复制到分配的内存(源缓冲区),然后触发 DMA 事务。
那么,如果每笔交易都需要额外的 memcpy()
,那么
使用 DMA 的好处?
将源复制到目标的步骤 - 不使用 DMA:
- 将缓冲区 (
memcpy()
) 从源复制到目标
将源复制到目标的步骤 - 使用 DMA:
- 将缓冲区 (
memcpy()
) 从源复制到 DMA 缓冲区
- 触发 DMA 事务(最终应将缓冲区复制到
目标缓冲区)
这个问题的一个例子是以太网驱动程序,它需要从收到的 sk_buf
复制到 FPGA 的物理地址。在这种情况下,它需要首先将 sk_buf
复制到 DMA 源缓冲区(从 dma_alloc_coherent()
)。
如果可以将 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。
我想我的回答与 post 所有者不再相关,但也许它会在将来帮助其他一些程序员。
正如此处评论之一所述,如果您的 FPGA 具有 DMA 控制器(允许 FPGA 读取已映射到 DMA 的内存),那么您应该能够在没有 memcpy()
操作。
我将尝试在这里给出一个简短的(尽可能...)示例,说明如何在 Rx 流中的以太网驱动程序中实现它(但是实现它的方法不止一种,这个只是理解基本步骤和概念的一般示例)。请注意,我已尝试对其进行简化,所以不要尝试对其进行编译(这不是完整的代码 - 要查看完整的以太网驱动程序,您可以尝试开始查看 this 驱动程序)。
现在,让我们关注这些基本步骤:
- 初始化 Rx 缓冲区
- Rx 帧接收
- 为下一个 DMA 事务准备 Rx 缓冲区
- 释放 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;
}
这可能是个愚蠢的问题,但到目前为止我还没有弄明白 DMA。
内存到内存DMAing时,需要分配DMA缓冲区
(例如 dma_alloc_coherent()
),然后对于每次传输,我们需要将缓冲区复制到分配的内存(源缓冲区),然后触发 DMA 事务。
那么,如果每笔交易都需要额外的 memcpy()
,那么
使用 DMA 的好处?
将源复制到目标的步骤 - 不使用 DMA:
- 将缓冲区 (
memcpy()
) 从源复制到目标
将源复制到目标的步骤 - 使用 DMA:
- 将缓冲区 (
memcpy()
) 从源复制到 DMA 缓冲区 - 触发 DMA 事务(最终应将缓冲区复制到 目标缓冲区)
这个问题的一个例子是以太网驱动程序,它需要从收到的 sk_buf
复制到 FPGA 的物理地址。在这种情况下,它需要首先将 sk_buf
复制到 DMA 源缓冲区(从 dma_alloc_coherent()
)。
如果可以将 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。
我想我的回答与 post 所有者不再相关,但也许它会在将来帮助其他一些程序员。
正如此处评论之一所述,如果您的 FPGA 具有 DMA 控制器(允许 FPGA 读取已映射到 DMA 的内存),那么您应该能够在没有 memcpy()
操作。
我将尝试在这里给出一个简短的(尽可能...)示例,说明如何在 Rx 流中的以太网驱动程序中实现它(但是实现它的方法不止一种,这个只是理解基本步骤和概念的一般示例)。请注意,我已尝试对其进行简化,所以不要尝试对其进行编译(这不是完整的代码 - 要查看完整的以太网驱动程序,您可以尝试开始查看 this 驱动程序)。
现在,让我们关注这些基本步骤:
- 初始化 Rx 缓冲区
- Rx 帧接收
- 为下一个 DMA 事务准备 Rx 缓冲区
- 释放 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;
}