You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下通过DMA API预留内存后数据丢失及虚拟内存不可读写问题

问题分析与解决方案

核心问题原因

  1. 数据被清空:dma_alloc_coherent 函数的默认行为是在分配内存时将整块区域清零,所以你用 devmem 预先写入的数据会被完全覆盖。
  2. 虚拟地址不可读写:设备树中设置了 no-map 属性,意味着内核不会自动为这块预留内存创建页表映射。虽然 dma_alloc_coherent 返回了虚拟地址,但该映射的缓存属性或权限不符合读写需求,导致无法正常访问。
  3. 设备树内存大小错误:你需要预留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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 00:05:05