clinuxlinux-kernellinux-device-driverkernel-module

dma_alloc_coherent() memory remap to userspace via mmap


I'm working on a Linux kernel driver that allocates memory with dma_alloc_coherent() and maps it to userspace using mmap.

This works perfectly on kernel 4.18 (tested on 4.18.0-553.47.1.el8_10.x86_64), but on kernel 5.14 (5.14.0-503.34.1.el9_5.x86_64), the userspace sees only 0xFFFFFF when reading the memory region — even though the mmap() call itself succeeds without errors. When inspecting the memory content from the kernel side it's zero-filled as expected.

So, the issue seems to be that the userspace mapping is not pointing to the correct physical memory — possibly a remapping problem or cache issue.

Here's a simplified version of the code:

Allocation (in the kernel driver):

u8 *cpu_addr = dma_alloc_coherent(&pdev->dev, len, &dma_handle, GFP_KERNEL);

Then in the mmap implementation:

int my_mmap(struct file *file, struct vm_area_struct *vma){

    switch(vma->vm_pgoff) {

        case MMAP_COMEM:
            ret = mmap_kvirt_mem(vma, cpu_addr); //cpu vaddr returned from dma_alloc_coherent
            break;

and the memory remapping functions:

static int mmap_kvirt_mem(struct vm_area_struct *vma, unsigned long ptr) {
    unsigned long paddr = virt_to_phys((void *)ptr);

    unsigned long pfn = paddr >> PAGE_SHIFT;
    size_t size = vma->vm_end - vma->vm_start;

    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); //suggested by chatgpt

    if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) {
        pr_err("remap_pfn_range() failed\n");
        return -EAGAIN;
    }

    return 0;
}

From userspace, I call mmap() on the driver and then access the memory from the returned pointer. So I suspect:

This exact same code works without issues on kernel 4.18...

Is virt_to_phys() still a reliable way to get the physical address of a dma_alloc_coherent() buffer on recent kernels?

Is there a change in behavior in kernel 5.x related to remapping DMA memory into userspace that might explain this?

Could remap_pfn_range() silently mis-map the region, returning a valid-looking mapping but pointing somewhere else?


Solution

  • The dma_mmap_coherent() or dma_mmap_attrs() function needs to be used to mmap DMA coherent memory. Here is an untested, incomplete outline of the required code using dma_mmap_coherent():

    int my_mmap(struct file *file, struct vm_area_struct *vma){
    
        switch(vma->vm_pgoff) {
    
            case MMAP_COMEM:
                ret = mmap_dma_coherent_mem(vma);
                      
                break;
    
    static int mmap_dma_coherent_mem(struct vm_area_struct *vma) {
        struct my_device *mydev = vma->vm_private_data;
        struct device *hwdev = mydev->hwdev;  // pointer to hardware device
        void *cpu_addr = mydev->comem_addr;   // virtual address of coherent memory
        dma_addr_t dma_addr = mydev->comem_dma_handle; // DMA address of coherent memory
        size_t max_len = mydev->comem_len; // length of coherent memory
        unsigned long len;
        unsigned long pgoff;
        int ret;
    
        /*
         * Check the requested size of the region is within range
         */
        len = vma->vm_end - vma->vm_start;
        if (len > max_len)
            return -EINVAL;
    
        /*
         * We need to temporarily clear vm_pgoff for dma_mmap_coherent()
         */
        pgoff = vma->vm_pgoff;
        vma->vm_pgoff = 0;
        ret = dma_mmap_coherent(hwdev, vma, cpu_addr, dma_addr, len);
        vma->vm_pgoff = pgoff;
    
        return ret;
    }