将多个内核缓冲区映射到连续的用户空间缓冲区?

时间:2020-01-22 00:29:01

标签: linux linux-kernel linux-device-driver

我已经使用dma_alloc_coherent分配了多个内核可访问的缓冲区,每个缓冲区的大小为4MiB。目标是将这些缓冲区映射到连续的用户空间虚拟内存中。问题在于remap_pfn_range似乎不起作用,因为用户空间内存有时可以工作,有时不能,或者有时会复制缓冲区的页面映射。

 // in probe() function
 dma_alloc_coherent(&pcie->dev, BUF_SIZE, &bus_addr0, GFP_KERNEL);
 dma_alloc_coherent(&pcie->dev, BUF_SIZE, &bus_addr1, GFP_KERNEL);

 // ...

 // in mmap() function
 vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

 pfn = dma_to_phys(&pcie->dev, &bus_addr0) >> PAGE_SHIFT;
 remap_pfn_range(pfn, vma->vm_start + 0, pfn, BUF_SIZE, vma->vm_page_prot);

 pfn = dma_to_phys(&pcie->dev, &bus_addr1) >> PAGE_SHIFT;
 remap_pfn_range(pfn, vma->vm_start + BUF_SIZE, pfn, BUF_SIZE, vma->vm_page_prot);

我不确定将多个内核缓冲区映射到连续的用户空间内存的最佳方法,但是我感觉自己做错了。预先感谢。

2 个答案:

答案 0 :(得分:1)

我不知道为什么没有更好的接口来将多个缓冲区连续映射到用户空间。理论上,您可以对 remap_pfn_range() 进行多次调用,但在某些平台(例如 ARM)上基本上不可能为 dma_alloc_coherent() 分配的内存获得正确的 pfn。

我想出了一个解决这个问题的方法,它可能不被认为是“好”的,但在我在多个平台(x86_64 和各种 ARM)上的使用中似乎工作得很好。解决方法是在多次调用struct vm_area_struct时临时修改dma_mmap_coherent()中的起始地址和结束地址,每个缓冲区调用一次。只要您将 VMA 开始和结束地址重置为其原始值,一切似乎都可以正常工作(请参阅我之前的免责声明)。

这是一个例子:

static int mmap(struct file *file, struct vm_area_struct *vma)
{

    . . . 

    int rc;
    unsigned long vm_start_orig = vma->vm_start;
    unsigned long vm_end_orig = vma->vm_end;

    for (int idx = 0; idx < buffer_list_size; idx++) {

        buffer_entry = &buffer_list[idx];
        
        /* Temporarily modify VMA start and end addresses */
        if (idx > 0) {
            vma->vm_start = vma->vm_end;
        }
        vma->vm_end = vma->vm_start + buffer_entry->size;
        
        rc = dma_mmap_coherent(dev, vma,
                               buffer_entry->virt_address, 
                               buffer_entry->phys_addr, 
                               buffer_entry->size);
                               
        if (rc != 0) {
            pr_err("dma_mmap_coherent: %d (IDX = %d)\n", rc, idx);
            return -EAGAIN;
        }
    }
    
    /* Restore VMA addresses */
    vma->vm_start = vm_start_orig;
    vma->vm_end = vm_end_orig;
    
    return rc;
}

答案 1 :(得分:0)

不幸的是,当前唯一支持mmap()DMA相干内存的方法是宏dma_mmap_coherent()或函数dma_mmap_attrs()(由dma_mmap_coherent()调用)。不幸的是,这不支持将单个VMA跨多个单独分配的DMA相干内存块进行拆分。

(我希望有一种受支持的方法可以将VMA的mmap()拆分为DMA相干内存的多个分配,因为它会影响我帮助维护的内核子系统中的缓冲区分配。我必须更改它才能分配缓冲区作为DMA相干内存的单个块,而不是许多页面大小的块。)