Search code examples
clinuxdriverxilinxdma

DMA transfer form kernel to user space


I am trying to realize DMA in Xilinx, using DMA Engine. FPGA guys give me an AXI DMA IP-Core with DMA support, so I need to write a driver, which will transfer data from kernel to user space buffer. Here you can find an example of the driver and app, which works so:

1) process opens /dev/device_file that represents the driver
2) it maps kernel coherent memory, allocated via dma_alloc_coherent
3) fills buffer with some data
4) calls ioctl() to start dma transfer

It works fine, but i have such question - can I transfer user space buffer to kernel space (via read function from file_operations structure), prepare it (shift by PAGE_SIZE or something else) and execute dma operation ? I don't understand how to make user space memory available in DMA operation.


Solution

  • You probably want to implement mmap method of struct file_operations. Consider:

    static int
    sample_drv_mem_mmap(struct file *filep, struct vm_area_struct *vma)
    {
        /*
         * Set your "dev" pointer here (the one you used
         * for dma_alloc_coherent() invocation)
         */
        struct device   *dev;
    
        /*
         * Set DMA address here (the one you obtained with
         * dma_alloc_coherent() via its third argument)
         */
        dma_addr_t  dma_addr;
    
        /* Set your DMA buffer size here */
        size_t      dma_size;
    
        /* Physical page frame number to be derived from "dma_addr" */
        unsigned long   pfn;
    
        /* Check the buffer size requested by the user */
        if (vma->vm_end - vma->vm_start > dma_size)
            return -EINVAL;
    
        /*
         * For the sake of simplicity, do not let the user specify an offset;
         * you may want to take care of that in later versions of your code
         */
        if (vma->vm_pgoff != 0)
            return -EINVAL;
    
        pfn = PHYS_PFN(dma_to_phys(dev, dma_addr));
    
        return remap_pfn_range(vma, vma->vm_start, pfn,
                       vma->vm_end - vma->vm_start,
                       vma->vm_page_prot);
    }
    
    /* ... */
    
    static const struct file_operations sample_drv_fops = {
        /* ... */
    
        .mmap =     sample_drv_mem_mmap,
    
        /* ... */
    };
    

    Long story short, the idea is to convert the DMA (bus) address you have to a kernel physical address and then use remap_pfn_range() to do the actual mapping between the kernel and the userland.

    In the user application, one should invoke mmap() to request the mapping (instead of the read / write approach) For more information on that, please refer to man 2 mmap on your system.