"removed-dma-pool" and "shared-dma-pool"

作者 by adtxl / 2022-11-10 / 暂无评论 / 47 个足迹

内核版本:Android common kernel 4.19.176

reserved-memory

关于memblock的介绍,可以参考之前转载的文章Linux内存管理(2)Linux物理内存初始化

关于内核reserved-memory这块,在实际使用中还是有些疑问。根据Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt文档,reserved-memory一些属性定义如下

参考https://android.googlesource.com/kernel/msm/+/android-7.1.0_r0.2/Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt

Additional properties:
compatible (optional) - standard definition
    - may contain the following strings:
        - shared-dma-pool: This indicates a region of memory meant to be
          used as a shared pool of DMA buffers for a set of devices. It can
          be used by an operating system to instanciate the necessary pool
          management subsystem if necessary.
    - removed-dma-pool: This indicates a region of memory which is meant to
      be carved out and not exposed to kernel.
        - vendor specific string in the form <vendor>,[<device>-]<usage>
no-map (optional) - empty property
    - Indicates the operating system must not create a virtual mapping
      of the region as part of its standard mapping of system memory,
      nor permit speculative access to it under any circumstances other
      than under the control of the device driver using the region.
no-map-fixup (optional) - empty property
    - Indicates the operating system must reserve the memory region and keep
      virtual mapping. Upon first allocation the actual allocated region is
      removed for any virtual mapping and behaves as "no-map" while the
      remaining memory is returned back to the system for normal use. One would
      like to use this property where he is not sure about how much region size
      must be reserved, so he gives it a max size which then is shrink once
      (first) allocation is done. This property is for some specific use cases,
      if unsure please don't use it. This property cannot be used together with
      "no-map" attribute.
reusable (optional) - empty property
    - The operating system can use the memory in this region with the
      limitation that the device driver(s) owning the region need to be
      able to reclaim it back. Typically that means that the operating
      system can use that region to store volatile or cached data that
      can be otherwise regenerated or migrated elsewhere.

根据文档,如果定义的是cma内存,一般可以像下面这样定义

    xxx_reserved: memory@xxx00000 {
            reg = <0 0xxx00000 0 0x400000>;
            compatible = "shared-dma-pool";
            reusable;
        };
// 或者类似下面这样,定义一块默认的cma内存
        /* global autoconfigured region for contiguous allocations */
        linux,cma {
            compatible = "shared-dma-pool";
            reusable;
            size = <0x00000000 0x08000000>;
            linux,cma-default;
        };

上面这样我都能理解,如果像下面这样定义,会怎么样呢

xxx_reserved: memory@e6c00000 {
            reg = <0 0xe6c00000 0 0x01000000>;
            compatible = "shared-dma-pool";
        };

问题:根据文档,这种"shared-dma-pool"兼容性属性是可以被系统使用的,那这段内存属不属于cma内存呢,和加reusable属性的cma内存有什么关系呢?

根据/proc/iomem文件节点内容,这段内存既属于system ram,也是reserved-memory

cat /proc/iomem
......
e6600000-e66fffff : System RAM
  e6600000-e66fffff : reserved
e6c00000-ffffffff : System RAM
  e6c00000-e7bfffff : reserved
......

2. 记录系统预留内存

针对compatible属性,在kernel4.19中,内核注册了三种不同的处理方法

image.png

在kernel初始化阶段,通过调用start_kernel()->setup_arch()->arm64_memblock_init()->early_init_fdt_scan_reserved_mem()->fdt_init_reserved_mem()->__reserved_mem_init_node()完成对reserved memory的创建和初始化:

/**
 * res_mem_init_node() - call region specific reserved memory init code
 */
static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
{
    extern const struct of_device_id __reservedmem_of_table[];
    const struct of_device_id *i;
    // __reservedmem_of_table是初始化中的一个section段,通过RESERVEDMEM_OF_DECLARE定义的都会被链接到这个段中
    for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {
        reservedmem_of_init_fn initfn = i->data;
        const char *compat = i->compatible;

        if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
            continue;
        // 这里实际运行上面注册的回调
        if (initfn(rmem) == 0) {
            pr_info("initialized node %s, compatible id %s\n",
                rmem->name, compat);
            return 0;
        }
    }
    return -ENOENT;
}

3. 区域的创建

具体是CMA也好,还是DMA内存,具体的创建都是使用的上面宏中定义的函数。

3.1 removed_dma_setup

具有"removed-dma-pool"属性的区域的创建,表示该内存位于DMA管理区,内核不可见,没有页表。

static int rmem_dma_device_init(struct reserved_mem *rmem, struct device *dev)
{
    struct removed_region *mem = rmem->priv;

    if (!mem && dma_init_removed_memory(rmem->base, rmem->size, &mem)) {
        pr_info("Reserved memory: failed to init DMA memory pool at %pa, size %ld MiB\n",
            &rmem->base, (unsigned long)rmem->size / SZ_1M);
        return -EINVAL;
    }
    set_dma_ops(dev, &removed_dma_ops);
    rmem->priv = mem;
    dma_assign_removed_region(dev, mem);
    return 0;
}

static void rmem_dma_device_release(struct reserved_mem *rmem,
                    struct device *dev)
{
    dev->dma_mem = NULL;
}

static const struct reserved_mem_ops removed_mem_ops = {
    .device_init    = rmem_dma_device_init,
    .device_release = rmem_dma_device_release,
};

static int __init removed_dma_setup(struct reserved_mem *rmem)
{
    rmem->ops = &removed_mem_ops;
    pr_info("Removed memory: created DMA memory pool at %pa, size %ld MiB\n",
        &rmem->base, (unsigned long)rmem->size / SZ_1M);
    return 0;
}

3.2 rmem_cma_setup

该函数即创建CMA区域。
请注意,如果compatible为"shared-dma-pool",如果未设置reusable或者设置了no-map,将直接返回错误。
所以,如果创建CMA区域,"share-dma-pool"和"reusable"一定是要搭配使用的。


static int rmem_cma_device_init(struct reserved_mem *rmem, struct device *dev)
{
    dev_set_cma_area(dev, rmem->priv);
    return 0;
}

static void rmem_cma_device_release(struct reserved_mem *rmem,
                    struct device *dev)
{
    dev_set_cma_area(dev, NULL);
}

static const struct reserved_mem_ops rmem_cma_ops = {
    .device_init    = rmem_cma_device_init,
    .device_release = rmem_cma_device_release,
};

static int __init rmem_cma_setup(struct reserved_mem *rmem)
{
    phys_addr_t align = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);
    phys_addr_t mask = align - 1;
    unsigned long node = rmem->fdt_node;
    struct cma *cma;
    int err;

    if (!of_get_flat_dt_prop(node, "reusable", NULL) ||
        of_get_flat_dt_prop(node, "no-map", NULL))
        return -EINVAL;

    if ((rmem->base & mask) || (rmem->size & mask)) {
        pr_err("Reserved memory: incorrect alignment of CMA region\n");
        return -EINVAL;
    }

    err = cma_init_reserved_mem(rmem->base, rmem->size, 0, rmem->name, &cma);
    if (err) {
        pr_err("Reserved memory: unable to setup CMA region\n");
        return err;
    }
    /* Architecture specific contiguous memory fixup. */
    dma_contiguous_early_fixup(rmem->base, rmem->size);

    if (of_get_flat_dt_prop(node, "linux,cma-default", NULL))
        dma_contiguous_set_default(cma);

    rmem->ops = &rmem_cma_ops;
    rmem->priv = cma;

    pr_info("Reserved memory: created CMA memory pool at %pa, size %ld MiB\n",
        &rmem->base, (unsigned long)rmem->size / SZ_1M);

    return 0;
}

3.3 rmem_dma_setup

这种情况处理的就是类似下面这种定义,

xxx_reserved: memory@e6c00000 {
            reg = <0 0xe6c00000 0 0x01000000>;
            compatible = "shared-dma-pool";
        };

如果有reusable,那么讲直接返回错误。因为shared-dma-pool+reusable的组合属于cma内存,会在上面处理。
如果CONFIG_ARM=y,即arm32,会有两个判断。
首先判断是否定义了no-map属性,如果未定义no-map,将返回错误。看样子在arm32上是不能有上面设备树这种定义的,不过如果在arm32上定义shared-dma-pool+no-map,感觉和removed-dma-pool有什么区别呢,都没有页表,system应该也都不会使用啊。
然后判断是否有"linux,dma-default"属性,有的话说明这段区域属于默认的dma区域。

static int __init rmem_dma_setup(struct reserved_mem *rmem)
{
    unsigned long node = rmem->fdt_node;

    if (of_get_flat_dt_prop(node, "reusable", NULL))
        return -EINVAL;

#ifdef CONFIG_ARM
    if (!of_get_flat_dt_prop(node, "no-map", NULL)) {
        pr_err("Reserved memory: regions without no-map are not yet supported\n");
        return -EINVAL;
    }

    if (of_get_flat_dt_prop(node, "linux,dma-default", NULL)) {
        WARN(dma_reserved_default_memory,
             "Reserved memory: region for default DMA coherent area is redefined\n");
        dma_reserved_default_memory = rmem;
    }
#endif

    rmem->ops = &rmem_dma_ops;
    pr_info("Reserved memory: created DMA memory pool at %pa, size %ld MiB\n",
        &rmem->base, (unsigned long)rmem->size / SZ_1M);
    return 0;
}

独特见解