6

I'm porting a large code base to a Linux kernel device driver. The ASIC uses a huge number of DMA channels.

I kmalloc memory with GFP_KERNEL|GFP_DMA. Before starting the DMA, I use dma_map_single to get the hardware (physical) memory address to feed to the hardware. (Also does a flush/invalidate the memory from dcache?) I sometimes need CPU access to the data once DMA is complete, but not often. Before I access the data via code, I do a dma_unmap_single to avoid cache coherency issues.

In the cases where I do not ever need CPU access, do I still need to call dma_unmap_single? Should I dma_unmap_single every pointer I dma_map_single? Does dma_map_single consume a resource (e.g., table entry) that dma_unmap_single will release?

The DMA-API.txt isn't clear on good DMA memory hygiene.

Thanks!

mgalgs
  • 15,671
  • 11
  • 61
  • 74
David Poole
  • 3,432
  • 5
  • 34
  • 34

3 Answers3

5

With dma_map_single you map the memory for DMA transfer. You get the physical pointer to the memory, so the device can DMA to that address.

With dma_unmap_single you unmap the memory mapped above. You should do this when your transfers are over.

You can map a memory region, and use it for multiple DMA transfers and then you unmap when the job is done. Each time you want to access the DMA memory you must synchronize it. If the device is going to access the memory, you should do dma_sync_single_for_device; it the host is going to access the memory, you should do dma_sync_single_for_cpu

Federico
  • 3,782
  • 32
  • 46
  • Is it that "dma_map_single" simply converts virtual to physical address ? – ransh Sep 07 '16 at 05:00
  • *simply* is a big word, quick and dirty anser is 'yes'. Follow the [implementation](http://lxr.free-electrons.com/source/include/linux/dma-mapping.h#L123) for details. Check also the [DMA API documentation](https://www.kernel.org/doc/Documentation/DMA-API.txt) because is the best reference than any word on stackoverflow :) – Federico Sep 14 '16 at 21:56
  • 1
    dma_map_single is converting virtual address to bus address, to be precise. Bus address can be different from physical address. I think https://www.kernel.org/doc/Documentation/DMA-API-HOWTO.txt is more helpful. – Chan Kim May 12 '20 at 14:50
1

Short Answer:

Yes. You should dma_unmap_single every buffer you have been mapped with dma_map_single.

Long Answer:

Also yes.
You should call dma_unmap_single at the end of every DMA transaction.

But since dma_map_single / dma_unmap_single is an expensive operation, sometimes we may prefer (when the packet is not too big) to reuse the DMA-mapped buffer, so instead of calling dma_unmap_single at the end of the DMA transaction, we are calling dma_sync_single_for_cpu, then copying the data from the DMA-mapped buffer into some other buffer, and then calling dma_sync_single_for_device, so now we can reuse the already DMA-mapped buffer without unmapping and remapping it again before our next DMA transaction.

I guess the threshold of how big the packet varies between architectures and should be measured.

measure_time(dma_sync_single_for_cpu + memcpy(packet_size) + dma_sync_single_for_device) 
>?<
measure_time(dma_unmap_single + dma_map_single)

Short Example:

    if (frame_len < FRAME_LEN_THRESHOLD) {            
        skb = netdev_alloc_skb_ip_align(priv->dev, frame_len);
        if (unlikely(!skb)) {
            printk("packet dropped\n");
            continue;
        }
        
        dma_sync_single_for_cpu(priv->device, rx_skbuff_dma[entry],                                   
                                frame_len, DMA_FROM_DEVICE);

        // same as memcpy
        skb_copy_to_linear_data(skb, rx_skbuff[entry]->data, frame_len);

        dma_sync_single_for_device(priv->device, rx_skbuff_dma[entry],
                                   frame_len, DMA_FROM_DEVICE);

        /* now we can reuse rx_skbuff_dma[entry]. 
           no need to call dma_unmap_single */                           
    } else {
        skb = rx_skbuff[entry];
        if (unlikely(!skb)) {
            printk("packet dropped\n");
            continue;
        }

        rx_skbuff[entry] = NULL;

        dma_unmap_single(priv->device, rx_skbuff_dma[entry],
                         priv->dma_buffer_size, DMA_FROM_DEVICE);

        /* if we want to use rx_skbuff_dma[entry] for another DMA transaction, 
           we will need to realocate a buffer and call dma_map_single */                 
    }
J.M.
  • 472
  • 1
  • 6
  • 15
0

the dma_map api may allocate bounce buffers, in this case, you will need to call the dma_unmap function.