Linux下通过DMA API预留内存后数据丢失及虚拟内存不可读写问题
问题分析与解决方案
核心问题原因
- 数据被清空:
dma_alloc_coherent函数的默认行为是在分配内存时将整块区域清零,所以你用devmem预先写入的数据会被完全覆盖。 - 虚拟地址不可读写:设备树中设置了
no-map属性,意味着内核不会自动为这块预留内存创建页表映射。虽然dma_alloc_coherent返回了虚拟地址,但该映射的缓存属性或权限不符合读写需求,导致无法正常访问。 - 设备树内存大小错误:你需要预留2MB内存,但设备树中
reg属性的最后一个参数是0x00400000(对应4MB),与需求不符,需要修正为0x00200000。
修正步骤
1. 修正设备树的预留内存大小
将 reserved 节点的 reg 属性修改为2MB:
reserved: buffer@0x60000000 { compatible = "shared-dma-pool"; no-map; reg = <0x0 0x60000000 0x0 0x00200000>; /* 0x00200000 = 2MB */ };
2. 修改驱动代码,直接映射预留内存
不再使用 dma_alloc_coherent(避免清零),而是手动获取预留内存的物理地址并创建正确的虚拟地址映射:
static int my_driver_probe(struct platform_device *pdev){ struct device *dev = &pdev->dev; struct reserved_mem *rmem; phys_addr_t phys_addr; size_t mem_size; void __iomem *virt_addr; int rc; // 关联设备与预留内存 rc = of_reserved_mem_device_init(dev); if (rc) { dev_err(dev, "Failed to initialize reserved memory\n"); return rc; } // 获取预留内存的物理地址和大小 rmem = dev_get_dma_reserved_mem(dev); if (!rmem) { dev_err(dev, "No reserved memory assigned to device\n"); return -ENODEV; } phys_addr = rmem->base; mem_size = rmem->size; // 设置DMA掩码(如果设备需要DMA操作) rc = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); if (rc) { dev_err(dev, "Failed to set DMA mask\n"); return rc; } // 创建无缓存的虚拟地址映射(适合DMA或需要直写的场景) virt_addr = ioremap_nocache(phys_addr, mem_size); if (!virt_addr) { dev_err(dev, "Failed to map reserved memory to virtual address\n"); return -ENOMEM; } // 读取测试:应该能获取到devmem预先写入的值 u32 read_val = ioread32(virt_addr); dev_info(dev, "Read from virtual address 0x%pK: 0x%08X\n", virt_addr, read_val); dev_info(dev, "Reserved memory info: Physical address 0x%pa, Size %zu MB\n", &phys_addr, mem_size / 1024 / 1024); // 写入测试 iowrite32(0x87654321, virt_addr + 4); u32 write_check = ioread32(virt_addr + 4); dev_info(dev, "Write test: written 0x87654321, read back 0x%08X\n", write_check); // 驱动卸载时自动解除映射,避免内存泄漏 devm_add_action_or_reset(dev, (void (*)(void *))iounmap, virt_addr); return 0; }
3. 验证结果
重新编译设备树和驱动并加载后:
- 用
devmem预先写入物理地址0x60000000的数据会被保留,驱动读取虚拟地址能获取到该值。 - 虚拟地址通过
ioread32/iowrite32可以正常读写。 - 再次用
devmem读取0x60000000会得到驱动写入的值(或预先写入的值),不再是0。
关键注意事项
- 如果设备需要DMA传输,必须确保映射的缓存属性正确:
ioremap_nocache适合大多数DMA场景,避免缓存一致性问题。 no-map属性的作用是禁止内核为预留内存创建全局映射,避免该内存被其他内核组件意外占用,属于正确配置。
内容的提问来源于stack exchange,提问作者Pea
相关产品推荐
相关产品推荐

