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:
virt_to_phys(cpu_addr)
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?
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;
}