Gbo*_*Gbo 6 linux mmap linux-device-driver linux-kernel
我正在将DMA相干内存从内核映射到用户空间。在用户级别,我使用mmap(),在内核驱动程序中,使用dma_alloc_coherent()和之后remap_pfn_range(),重新映射页面。这基本上可以正常工作,因为我可以将数据写入应用程序中的映射区域并在内核驱动程序中进行验证。
但是,尽管使用了dma_alloc_coherent(应该分配未缓存的内存),pgprot_noncached()并且内核仍通过以下dmesg输出通知我:
映射[内存0xABC-0xCBA]的pfn内存范围未缓存负值,得到了回写
以我的理解,写回是缓存的内存。但是我需要未缓存的内存来进行DMA操作。
代码(仅显示重要部分):
用户应用
fd = open(dev_fn, O_RDWR | O_SYNC);
if (fd > 0)
{
mem = mmap ( NULL
, mmap_len
, PROT_READ | PROT_WRITE
, MAP_SHARED
, fd
, 0
);
}
Run Code Online (Sandbox Code Playgroud)
为了测试目的,我使用了mmap_len = getpagesize(); 是4096。
内核驱动
typedef struct
{
size_t mem_size;
dma_addr_t dma_addr;
void *cpu_addr;
} Dma_Priv;
fops_mmap()
{
dma_priv->mem_size = vma->vm_end - vma->vm_start;
dma_priv->cpu_addr = dma_alloc_coherent ( &gen_dev
, dma_priv->mem_size
, &dma_priv->dma_addr
, GFP_KERNEL
);
if (dma_priv->cpu_addr != NULL)
{
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
remap_pfn_range ( vma
, vma->vm_start
, virt_to_phys(dma_priv->cpu_addr)>>PAGE_SHIFT
, dma_priv->mem_size
, vma->vm_page_prot
)
}
}
Run Code Online (Sandbox Code Playgroud)
我发现的有用信息
1)修补Linux:https : //www.kernel.org/doc/ols/2008/ols2008v2-pages-135-144.pdf
第7页->带O_SYNC的 mmap (未缓存):
应用程序可以使用O_SYNC标志打开/ dev / mem,然后对其进行mmap。这样,应用程序将使用未缓存的内存类型访问该地址。仅当没有其他冲突的映射到同一区域时,mmap才会成功。
我使用了标志,没有帮助。
第7页->不带O_SYNC的 mmap (uncached-minus):
没有O_SYNC的mmap,没有现有的映射以及没有回写区域:对于属于此类别的mmap,我们使用未缓存的减号类型映射。在该区域没有任何MTRR的情况下,有效类型将不被缓存。但是在存在MTRR的情况下,使该区域成为可写合并的,则有效类型将为可写合并。
2)pgprot_noncached()
在/arch/x86/include/asm/pgtable.h中,我发现了这一点:
#define pgprot_noncached(prot) \
((boot_cpu_data.x86 > 3) \
? (__pgprot(pgprot_val(prot) | \
cachemode2protval(_PAGE_CACHE_MODE_UC_MINUS))) \
: (prot))
Run Code Online (Sandbox Code Playgroud)
x86是否有可能总是将未缓存的请求设置为UC_MINUS,从而导致在缓存的回写中与MTRR结合使用?
我正在使用Ubuntu 16.04.1,内核:4.10.0-40-generic。
编辑:已解决
https://www.kernel.org/doc/Documentation/x86/pat.txt
希望将某些页面导出到用户空间的驱动程序通过使用mmap接口和以下各项的组合来做到这一点:1)pgprot_noncached()2)io_remap_pfn_range()或remap_pfn_range()或vmf_insert_pfn()
有了PAT支持,将添加新的API pgprot_writecombine。因此,驱动程序可以继续使用上述顺序,在步骤1中使用pgprot_noncached()或pgprot_writecombine(),然后执行步骤2。
此外,第2步在内存类型列表中内部跟踪该区域为UC或WC,以确保没有冲突的映射。
请注意,这组API仅适用于IO(非RAM)区域。如果驱动程序要导出RAM区域,则必须执行上述步骤0的set_memory_uc()或set_memory_wc()并跟踪这些页面的使用情况,并在将页面释放到空闲池之前使用set_memory_wb()。
我set_memory_uc()在pgprot_noncached()之前添加了它并完成了任务。
if (dma_priv->cpu_addr != NULL)
{
set_memory_uc(dma_priv->cpu_addr, (dma_priv->mem_size/PAGE_SIZE));
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
remap_pfn_range ( vma
, vma->vm_start
, virt_to_phys(dma_priv->cpu_addr)>>PAGE_SHIFT
, dma_priv->mem_size
, vma->vm_page_prot
)
}
Run Code Online (Sandbox Code Playgroud)
https://www.kernel.org/doc/Documentation/x86/pat.txt
想要将某些页面导出到用户空间的驱动程序通过使用 mmap 接口和 1) pgprot_noncached() 2) io_remap_pfn_range() 或 remap_pfn_range() 或 vmf_insert_pfn() 的组合来实现
通过 PAT 支持,添加了新的 API pgprot_writecombine。因此,驱动程序可以继续使用上述顺序,在步骤 1 中使用 pgprot_noncached() 或 pgprot_writecombine(),然后进行步骤 2。
此外,步骤 2 在内存类型列表中内部跟踪该区域为 UC 或 WC,以确保没有冲突的映射。
请注意,这组 API 仅适用于 IO(非 RAM)区域。如果驱动程序想要导出 RAM 区域,则必须按照上面的步骤 0 执行 set_memory_uc() 或 set_memory_wc(),并跟踪这些页面的使用情况,并在页面被释放到空闲池之前使用 set_memory_wb()。
我set_memory_uc()之前添加过pgprot_noncached(),它就完成了。
if (dma_priv->cpu_addr != NULL)
{
set_memory_uc(dma_priv->cpu_addr, (dma_priv->mem_size/PAGE_SIZE));
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
remap_pfn_range ( vma
, vma->vm_start
, virt_to_phys(dma_priv->cpu_addr)>>PAGE_SHIFT
, dma_priv->mem_size
, vma->vm_page_prot
)
}
Run Code Online (Sandbox Code Playgroud)
此答案是作为对问题Mmap DMA 内存未缓存:“map pfn ram range req uncached-minus got write-back”的编辑发布的,由 OP Gbo在 CC BY-SA 4.0 下发布。