CMA(Contiguous Memory Allocator) for DMA

디바이스 드라이버가 사용하는 DMA 영역을 CMA(Contiguous Memory Allocator)에 등록하고 reserve memblock 추가한 후 해당 영역의 메모리 할당관리를 수행한다.

 

dma_contiguous_reserve()

drivers/base/dma-contiguous.c

/**
 * dma_contiguous_reserve() - reserve area(s) for contiguous memory handling
 * @limit: End address of the reserved memory (optional, 0 for any).
 *
 * This function reserves memory from early allocator. It should be
 * called by arch specific code once the early allocator (memblock or bootmem)
 * has been activated and all other subsystems have already allocated/reserved
 * memory.          
 */
void __init dma_contiguous_reserve(phys_addr_t limit)
{
        phys_addr_t selected_size = 0;
        phys_addr_t selected_base = 0;
        phys_addr_t selected_limit = limit;
        bool fixed = false;

        pr_debug("%s(limit %08lx)\n", __func__, (unsigned long)limit);

        if (size_cmdline != -1) {
                selected_size = size_cmdline;
                selected_base = base_cmdline;
                selected_limit = min_not_zero(limit_cmdline, limit);
                if (base_cmdline + size_cmdline == limit_cmdline)
                        fixed = true;
        } else {
#ifdef CONFIG_CMA_SIZE_SEL_MBYTES
                selected_size = size_bytes;
#elif defined(CONFIG_CMA_SIZE_SEL_PERCENTAGE)
                selected_size = cma_early_percent_memory();
#elif defined(CONFIG_CMA_SIZE_SEL_MIN)
                selected_size = min(size_bytes, cma_early_percent_memory());
#elif defined(CONFIG_CMA_SIZE_SEL_MAX)
                selected_size = max(size_bytes, cma_early_percent_memory());
#endif
        }

        if (selected_size && !dma_contiguous_default_area) {
                pr_debug("%s: reserving %ld MiB for global area\n", __func__,
                         (unsigned long)selected_size / SZ_1M);

                dma_contiguous_reserve_area(selected_size, selected_base,
                                            selected_limit,
                                            &dma_contiguous_default_area,
                                            fixed);
        }
}

커널 파라메터 또는 커널 옵션으로 요청한 메모리 사이즈만큼의 영역을 CMA(Contigugou Memory Allocator)에 관리 항목으로 추가하되 한 번만 요청을 받는다.

  • 커널 파라메터에 “cma=”가 설정되어 early_cma() 루틴이 호출되는 경우 size_cmdline, base_cmdline, limit_cmdline 등을 알아온다.
    • 요청 영역이 limit와 동일한 경우 fixed는 true로 설정하여 정확히 base(시작주소)에 할당 할 수 있도록 요청한다.
    • fixed가 false인 경우 base 부터 limit 사이즈 크기로 요청한다.
  • if (size_cmdline != -1) {
    • size_cmdline 정보를 알아온 경우 파싱된 전역변수 값들을 사용한다.
  • CONFIG_CMA_SIZE_SEL_MBYTES
    • size_cmdline 정보를 알아오지 못한 경우에 커널 옵션으로 설정한 값을 사용한다.
    • size_bytes = CMA_SIZE_MBYTES * SZ_1M
      • CMA_SIZE_MBYTES = CONFIG_CMA_SIZE_MBYTES
  • if (selected_size && !dma_contiguous_default_area) {
    • 사이즈가 0보다 크면서 cma 관리 영역을 처음 설정되는 경우(null)
  • dma_contiguous_reserve_area(selected_size, selected_base, selected_limit, &dma_contiguous_default_area, fixed);
    • cma를 구성할 메모리 시작 주소, 사이즈, limit 및 fixed 값으로 cma에 관리 항목을 추가하고 추후 리매핑을 하기 위해 리매핑 정보도 구성한다.
    • cma 관리 항목에 추가하고 reserve memblock에도 추가된다.

 

dma_contiguous_reserve_area()

drivers/base/dma-contiguous.c

/**
 * dma_contiguous_reserve_area() - reserve custom contiguous area
 * @size: Size of the reserved area (in bytes),
 * @base: Base address of the reserved area optional, use 0 for any
 * @limit: End address of the reserved memory (optional, 0 for any).
 * @res_cma: Pointer to store the created cma region.
 * @fixed: hint about where to place the reserved area
 *
 * This function reserves memory from early allocator. It should be
 * called by arch specific code once the early allocator (memblock or bootmem)
 * has been activated and all other subsystems have already allocated/reserved
 * memory. This function allows to create custom reserved areas for specific
 * devices.
 *
 * If @fixed is true, reserve contiguous area at exactly @base.  If false,
 * reserve in range from @base to @limit.
 */
int __init dma_contiguous_reserve_area(phys_addr_t size, phys_addr_t base,
                                       phys_addr_t limit, struct cma **res_cma,
                                       bool fixed)
{
        int ret;

        ret = cma_declare_contiguous(base, size, limit, 0, 0, fixed, res_cma);
        if (ret)
                return ret;

        /* Architecture specific contiguous memory fixup. */
        dma_contiguous_early_fixup(cma_get_base(*res_cma),
                                cma_get_size(*res_cma));

        return 0;
}
  • ret = cma_declare_contiguous(base, size, limit, 0, 0, fixed, res_cma);
    • cma_areas[]에 관리 항목을 추가하고 관리할 메모리 크기 정보인 base, size, limit, alignment 및 order_per_bit를 사용하여 base_pfn, count, bitmap 할당, alignment 및 order_per_bit 를 설정한다.
      • cma 에서 사용하는 비트맵은 각 비트가 1개 페이지를 표현하도록 하게 한다.
      • 영역의 alignment 단위는 1 페이지로 한다.
      • 해당 영역은 reserve memblock에도 등록된다.
    • 실패하는 경우 에러를 리턴한다.
    • 추가한 엔트리들은 CMA 드라이버가 로드될 때 호출되어 초기화한다.
  • dma_contiguous_early_fixup(cma_get_base(*res_cma), cma_get_size(*res_cma));
    • cma에서 관리할 메모리 영역을 dma_mmu_remap[] 배열에 추가한다.
    • 이렇게 배열에 정보만 단순히 추가한 후 나중에 setup_arch() -> paging_init() -> dma_contiguous_remap() 순서대로 함수를 호출하여 리매핑을 하게 한다.

 

dma_contiguous_early_fixup()

arch/arm/mm/dma-mapping.c

void __init dma_contiguous_early_fixup(phys_addr_t base, unsigned long size)
{
        dma_mmu_remap[dma_mmu_remap_num].base = base;
        dma_mmu_remap[dma_mmu_remap_num].size = size;
        dma_mmu_remap_num++;
}
  • dma_mmu_remap[] 배열에 추가한다.

 

CMA 메모리 early 설정 루틴

early_cma()

“cma= ” 커널 파라메터에 의해 호출되어 사용된다.

drivers/base/dma-contiguous.c

/*
 * Default global CMA area size can be defined in kernel's .config.
 * This is useful mainly for distro maintainers to create a kernel
 * that works correctly for most supported systems.
 * The size can be set in bytes or as a percentage of the total memory
 * in the system.
 *
 * Users, who want to set the size of global CMA area for their system
 * should use cma= kernel parameter.
 */
static const phys_addr_t size_bytes = CMA_SIZE_MBYTES * SZ_1M;
static phys_addr_t size_cmdline = -1;
static phys_addr_t base_cmdline;
static phys_addr_t limit_cmdline;

static int __init early_cma(char *p)
{
        pr_debug("%s(%s)\n", __func__, p);
        size_cmdline = memparse(p, &p);
        if (*p != '@')
                return 0;
        base_cmdline = memparse(p + 1, &p);
        if (*p != '-') {
                limit_cmdline = base_cmdline + size_cmdline;
                return 0;
        }
        limit_cmdline = memparse(p + 1, &p);

        return 0;
}
early_param("cma", early_cma);

다음과 같이 사용예를 알아본다.

  • cma=4M
    • 0x0000_0000 ~ arm_dma_limit(DMA 사이즈) 영역내에서 4M의 CMA용 공간을 할당한다.
  • cma=4M@0x00c00000
    • 0x00c0_0000 ~ arm_dma_limit(DMA 사이즈) 영역내에서 4M의 CMA용 공간을 할당한다.
  • cma=4M@0x10000000-0x40000000
    • 0x1000_0000 ~ 0x4000_0000 영역내에서 4M의 CMA용 공간을 할당한다.
  • cma=4M@0x10000000-0x10400000
    • 정확히 0x1000_0000 위치에서 4M의 CMA용 공간을 할당한다. (fix된 위치)

 

구조체 및 전역 변수

dma_contig_early_reserve 구조체

arch/arm/mm/dma-mapping.c

struct dma_contig_early_reserve {
        phys_addr_t base;
        unsigned long size;
};

 

전역변수

arch/arm/mm/dma-mapping.c

static struct dma_contig_early_reserve dma_mmu_remap[MAX_CMA_AREAS] __initdata;
static int dma_mmu_remap_num __initdata;
  • dma_mmu_remap[]
    • MAX_CMA_AREAS는 CONFIG_CMA_AREAS 커널 옵션 + 1 값이다.
  • dma_mmu_remap_num
    • 리매핑할 항목의 수

 

참고

댓글 남기기