如何通过DMA API正确分配多个预留内存区域
如何为单个驱动正确分配多个预留内存区域
我需要为单个设备驱动预留3至4个内存区域,参考Xilinx相关指南实现了单个内存区域的预留,但当前配置的设备树包含两个预留内存区域,驱动的probe函数仅能成功分配第一个区域。以下是我的设备树配置及驱动probe函数代码,请问如何正确为每个区域分配内存?
设备树配置
reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; reserved_m1: buffer1@60000000 { compatible = "shared-dma-pool"; no-map; reg = <0x0 0x60000000 0x0 0x00400000>; }; reserved_m2: buffer2@80000000 { compatible = "shared-dma-pool"; no-map; reg = <0x0 0x80000000 0x0 0x00100000>; }; }; my_driver@0 { compatible = "dummy,my_driver"; status = "okay"; memory-region = <&reserved_m1>, <&reserved_m2>; };
原驱动probe函数代码
static int my_driver_probe(struct platform_device *pdev) { ... rc = of_reserved_mem_device_init(&pdev->dev); debug = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); v_addr_m1 = dma_alloc_coherent(&pdev->dev, MY_MEM1_SIZE, &paddr, GFP_KERNEL); printk("Allocated coherent memory Size: %d MB, vaddr: 0x%0llX paddr: 0x%0llX\n", MY_MEM_SIZE / 1024 / 1024, (u64)v_addr, (u32)paddr); rc = of_reserved_mem_device_init_by_idx(&pdev->dev, np, 1); v_addr_m2 = dma_alloc_coherent(&pdev->dev, MY_MEM2_SIZE, &paddr_m2, GFP_KERNEL); printk("Allocated coherent memory Size: %d MB, vaddr: 0x%0llX paddr: 0x%0llX\n", MY_MEM_SIZE / 1024 / 1024, (u64)v_addr, (u32)paddr); ... ... }
问题分析
原代码存在几个关键错误:
of_reserved_mem_device_init会将第一个内存区域绑定到设备,后续调用of_reserved_mem_device_init_by_idx不会新增绑定,反而可能导致逻辑冲突。dma_alloc_coherent默认只会从第一个绑定的预留内存池分配,无法直接获取第二个区域。- 代码中
np变量未定义,printk存在变量不匹配问题(如用全局MY_MEM_SIZE代替对应区域的MY_MEM1_SIZE/MY_MEM2_SIZE,v_addr未对应v_addr_m1/v_addr_m2)。
解决方案
方法一:直接获取物理地址并手动映射(推荐)
由于设备树配置了no-map,内核不会自动映射该内存区域,可直接读取每个区域的物理地址和大小,用devm_ioremap完成虚拟地址映射:
#include <linux/of_reserved_mem.h> #include <linux/io.h> static int my_driver_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct reserved_mem *rmem; void __iomem *v_addr_m1, *v_addr_m2; phys_addr_t paddr_m1, paddr_m2; size_t size_m1, size_m2; // 处理第一个预留内存区域 rmem = devm_of_reserved_mem_lookup_by_idx(dev, np, 0); if (!rmem) { dev_err(dev, "Failed to lookup reserved memory index 0\n"); return -ENODEV; } paddr_m1 = rmem->base; size_m1 = rmem->size; v_addr_m1 = devm_ioremap(dev, paddr_m1, size_m1); if (!v_addr_m1) { dev_err(dev, "Failed to remap reserved memory 0\n"); return -ENOMEM; } dev_info(dev, "Reserved memory 0: size %zu MB, vaddr 0x%pK, paddr 0x%pa\n", size_m1 / 1024 / 1024, v_addr_m1, &paddr_m1); // 处理第二个预留内存区域 rmem = devm_of_reserved_mem_lookup_by_idx(dev, np, 1); if (!rmem) { dev_err(dev, "Failed to lookup reserved memory index 1\n"); return -ENODEV; } paddr_m2 = rmem->base; size_m2 = rmem->size; v_addr_m2 = devm_ioremap(dev, paddr_m2, size_m2); if (!v_addr_m2) { dev_err(dev, "Failed to remap reserved memory 1\n"); return -ENOMEM; } dev_info(dev, "Reserved memory 1: size %zu MB, vaddr 0x%pK, paddr 0x%pa\n", size_m2 / 1024 / 1024, v_addr_m2, &paddr_m2); // 后续业务逻辑 return 0; }
方法二:逐个绑定预留区域并分配内存
如果需要使用dma_alloc_coherent分配,可逐个绑定预留区域,分配完成后释放绑定,再处理下一个区域:
static int my_driver_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; void *v_addr_m1, *v_addr_m2; dma_addr_t paddr_m1, paddr_m2; int rc; // 绑定第一个区域并分配内存 rc = of_reserved_mem_device_init_by_idx(dev, np, 0); if (rc) { dev_err(dev, "Failed to init reserved memory index 0\n"); return rc; } v_addr_m1 = dma_alloc_coherent(dev, MY_MEM1_SIZE, &paddr_m1, GFP_KERNEL); if (!v_addr_m1) { dev_err(dev, "Failed to allocate from region 0\n"); of_reserved_mem_device_release(dev); return -ENOMEM; } dev_info(dev, "Allocated from region 0: size %d MB, vaddr 0x%pK, paddr 0x%pad\n", MY_MEM1_SIZE / 1024 / 1024, v_addr_m1, &paddr_m1); of_reserved_mem_device_release(dev); // 绑定第二个区域并分配内存 rc = of_reserved_mem_device_init_by_idx(dev, np, 1); if (rc) { dev_err(dev, "Failed to init reserved memory index 1\n"); dma_free_coherent(dev, MY_MEM1_SIZE, v_addr_m1, paddr_m1); return rc; } v_addr_m2 = dma_alloc_coherent(dev, MY_MEM2_SIZE, &paddr_m2, GFP_KERNEL); if (!v_addr_m2) { dev_err(dev, "Failed to allocate from region 1\n"); of_reserved_mem_device_release(dev); dma_free_coherent(dev, MY_MEM1_SIZE, v_addr_m1, paddr_m1); return -ENOMEM; } dev_info(dev, "Allocated from region 1: size %d MB, vaddr 0x%pK, paddr 0x%pad\n", MY_MEM2_SIZE / 1024 / 1024, v_addr_m2, &paddr_m2); // 后续业务逻辑 return 0; }
注意事项
- 方法一更适合
no-map配置的预留内存,直接映射物理地址,避免内存池分配的限制。 - 方法二中每次分配后必须释放当前绑定的预留内存,否则无法绑定下一个区域。
- 确保
MY_MEM1_SIZE/MY_MEM2_SIZE与设备树中reg字段的大小完全匹配(如第一个区域为4MB,第二个为1MB)。
内容的提问来源于stack exchange,提问作者Pea
相关产品推荐
相关产品推荐

