🖍️
doodle
  • reserved_mem 분석 노트
Powered by GitBook
On this page
  • 1. Before you read
  • 2. Function schematic
  • 3. Dive into function

Was this helpful?

reserved_mem 분석 노트

2020 2 29 스터디 내용

Last updated 5 years ago

Was this helpful?

1. Before you read

Have to read carefully.

1.1 reserved-memory 간단 정

Reserved-memory 노드에 기술된 프로퍼티는 다음과 같다.

Reserved-memory 노드의 property

value

#address-cells, #size-cells (필수 속성)

루트 노드의 값과 동일해야 한다.

ranges (필수 속성)

값은 비어 있어야 한다.(empty)

Reserved-memory의 자식 노드들은 1개 이상의 예약 메모리 영역을 가진다. 예약 메모리 영역은

  1. reg 프로퍼티를 사용하여 예약된 메모리의 특정 범위(base, size)를 지정하거나,

  2. size 프로퍼티를 사용하여 동적으로 예약받을 수 있다. (이 옵션에 더불어 alignment, alloc-ranges 프로퍼티를 같이 사용할 수 있다.)

자식 노드는 반드시 둘 중 하나(reg, size)의 프로퍼티는 가져야 한다.

나머지 프로퍼티는 문서를 참고하라.

2. Function schematic

┗━start_kernel
  ┗━setup_arch
    ┗━arm64_memblock_init
      ┗━early_init_fdt_scan_reserved_mem  
        ┠━of_scan_flat_dt                 <----------------------------- START
        ┃ ┗━__fdt_scan_reserved_mem       
        ┃   ┠━____reserved_mem_reserve_reg
        ┠   ┗━fdt_reserved_mem_save_node
        ┗━fdt_init_reserved_mem
          ┠━__rmem_check_for_overlap
          ┠━__reserved_mem_alloc_size
          ┗━__reserved_mem_init_node

3. Dive into function

static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname,
                      int depth, void *data)
{
    static int found;
    int err;

    if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) {
        if (__reserved_mem_check_root(node) != 0) {
            pr_err("Reserved memory: unsupported node format, ignoring\n");
            /* break scan */
            return 1;
        }
        found = 1;
        /* scan next node */
        return 0;
    } else if (!found) {
        /* scan next node */
        return 0;
    } else if (found && depth < 2) {
        /* scanning of /reserved-memory has been finished */
        return 1;
    }

    // reserved-memory의 child-node들이 진입 가능
    if (!of_fdt_device_is_available(initial_boot_params, node))
        return 0;
        
    err = __reserved_mem_reserve_reg(node, uname);
    if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL))
        fdt_reserved_mem_save_node(node, uname, 0, 0);

    /* scan next node */
    return 0;
}
/**
 * res_mem_reserve_reg() - reserve all memory described in 'reg' property
 */
static int __init __reserved_mem_reserve_reg(unsigned long node,
                         const char *uname)
{
    int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
    phys_addr_t base, size;
    int len;
    const __be32 *prop;
    int nomap, first = 1;

    prop = of_get_flat_dt_prop(node, "reg", &len);
    if (!prop)
        return -ENOENT;

    if (len && len % t_len != 0) {
        pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n",
               uname);
        return -EINVAL;
    }

    // 함수의 반환된 값이 NULL이 "아닐" 때 nomap이 true
    nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

    while (len >= t_len) {
        base = dt_mem_next_cell(dt_root_addr_cells, &prop);
        size = dt_mem_next_cell(dt_root_size_cells, &prop);
        
        if (size &&
            early_init_dt_reserve_memory_arch(base, size, nomap) == 0)
            pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %ld MiB\n",
                uname, &base, (unsigned long)size / SZ_1M);
        else
            pr_info("Reserved memory: failed to reserve memory for node '%s': base %pa, size %ld MiB\n",
                uname, &base, (unsigned long)size / SZ_1M);

        len -= t_len;
        if (first) {
            fdt_reserved_mem_save_node(node, uname, base, size);
            first = 0;
        }
    }
    return 0;
}
/**
 * res_mem_save_node() - save fdt node for second pass initialization
 */
void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
                      phys_addr_t base, phys_addr_t size)
{
    struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];

    if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) {
        pr_err("not enough space all defined regions.\n");
        return;
    }

    rmem->fdt_node = node;
    rmem->name = uname;
    rmem->base = base;
    rmem->size = size;

    reserved_mem_count++;
    return;
}
int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base,
                    phys_addr_t size, bool nomap)
{
    if (nomap)
        return memblock_remove(base, size);
    return memblock_reserve(base, size);
}
  • Line 24: Reserved memory child node일 경우에 해당 루틴이 실행된다.

  • Line 28: 해당 노드로부터 reg 프로퍼티 값을 읽는다.

    • __reserved_mem_reserve_reg 진입

    • Line 7: 루트로부터 어드레스의 크기, 사이즈의 크기를 넘겨 받는다.

    • Line 13~14: reg 프로퍼티를 읽는다. 만약 프로퍼티가 존재하지 않으면, ENOENT를 리턴한다. reg 프로퍼티가 있다는 말은, 정적 예약 메모리라는 말이고, 프로퍼티가 없다면 동적 예약 메모리이란 말이다. 해당 함수는 정적 예약 메모리만 처리한다.

    • Line 17~21: 프로퍼티의 길이를 검증한다.

    • Line 24: 선택 프로퍼티 no-map이 있는지 확인한다.

    • Line 26~37: base와 size를 읽어오고, nomap 프로퍼티가 존재한다면, 해당 영역을 memblock_remove, 존재하지 않으면 memblock_reserve한다.

    • Line 38~41: 첫 번째의 경우(오류와 상관없이?), fdt_reserved_mem_save_node를 호출한다.

  • Line 29~30: 동적 예약 메모리를 사용하는 노드라면, fdt_reserved_mem_save_node를 호출한다. (단 base, size는 모두 0으로 삽입된다.)

void __init fdt_init_reserved_mem(void)
{
    int i;

    /* check for overlapping reserved regions */
    __rmem_check_for_overlap();

    for (i = 0; i < reserved_mem_count; i++) {
        struct reserved_mem *rmem = &reserved_mem[i];
        unsigned long node = rmem->fdt_node;
        int len;
        const __be32 *prop;
        int err = 0;

        prop = of_get_flat_dt_prop(node, "phandle", &len);
        if (!prop)
            prop = of_get_flat_dt_prop(node, "linux,phandle", &len);
        if (prop)
            rmem->phandle = of_read_number(prop, len/4);

        if (rmem->size == 0)
            err = __reserved_mem_alloc_size(node, rmem->name,
                         &rmem->base, &rmem->size);
        if (err == 0)
            __reserved_mem_init_node(rmem);
    }
}
static int __init __reserved_mem_alloc_size(unsigned long node,
    const char *uname, phys_addr_t *res_base, phys_addr_t *res_size)
{
    int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
    phys_addr_t start = 0, end = 0;
    phys_addr_t base = 0, align = 0, size;
    int len;
    const __be32 *prop;
    int nomap;
    int ret;

    prop = of_get_flat_dt_prop(node, "size", &len);
    if (!prop)
        return -EINVAL;

    if (len != dt_root_size_cells * sizeof(__be32)) {
        pr_err("invalid size property in '%s' node.\n", uname);
        return -EINVAL;
    }
    size = dt_mem_next_cell(dt_root_size_cells, &prop);

    nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

    prop = of_get_flat_dt_prop(node, "alignment", &len);
    if (prop) {
        if (len != dt_root_addr_cells * sizeof(__be32)) {
            pr_err("invalid alignment property in '%s' node.\n",
             uname);
            return -EINVAL;
        }
        align = dt_mem_next_cell(dt_root_addr_cells, &prop);
    }

    /* Need adjust the alignment to satisfy the CMA requirement */
    if (IS_ENABLED(CONFIG_CMA)
        && of_flat_dt_is_compatible(node, "shared-dma-pool")
        && of_get_flat_dt_prop(node, "reusable", NULL)
        && !of_get_flat_dt_prop(node, "no-map", NULL)) {
        unsigned long order =
            max_t(unsigned long, MAX_ORDER - 1, pageblock_order);

        align = max(align, (phys_addr_t)PAGE_SIZE << order);
    }

    prop = of_get_flat_dt_prop(node, "alloc-ranges", &len);
    if (prop) {

        if (len % t_len != 0) {
            pr_err("invalid alloc-ranges property in '%s', skipping node.\n",
                   uname);
            return -EINVAL;
        }

        base = 0;
        
        while (len > 0) {
            start = dt_mem_next_cell(dt_root_addr_cells, &prop);
            end = start + dt_mem_next_cell(dt_root_size_cells,
                               &prop);

            ret = early_init_dt_alloc_reserved_memory_arch(size,
                    align, start, end, nomap, &base);
            if (ret == 0) {
                pr_debug("allocated memory for '%s' node: base %pa, size %ld MiB\n",
                    uname, &base,
                    (unsigned long)size / SZ_1M);
                break;
            }
            len -= t_len;
        }

    } else {
        ret = early_init_dt_alloc_reserved_memory_arch(size, align,
                            0, 0, nomap, &base);
        if (ret == 0)
            pr_debug("allocated memory for '%s' node: base %pa, size %ld MiB\n",
                uname, &base, (unsigned long)size / SZ_1M);
    }
    
    if (base == 0) {
        pr_info("failed to allocate memory for node '%s'\n", uname);
        return -ENOMEM;
    }

    *res_base = base;
    *res_size = size;

    return 0;
}


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;

    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;
}
  • Line 6: 등록된 정적 예약 메모리 영역 중 겹치는 부분이 있다면, 경고를 띄운다.

    • 내부 구현은 간단하다. 정렬을 하고, 순회하면서 겹치는 영역을 찾는다.

  • Line 8~19: 예약 메모리 영역을 순회하면 노드에 핸들 프로퍼티가 있다면, 값을 저장한다.

  • Line 21~22: size가 0이라면, 동적 예약 메모리 영역이므로, 적절한 영역을 할당해주어야 한다. 따라서 __reserved_mem_alloc_size를 호출한다.

    • __reserved_mem_alloc_size로 진입.

    • 동적 예약 메모리 할당의 핵심인 __reserved_mem_alloc_size으로 진입.

    • Line 12~20: size 프로퍼티의 값을 읽는다. 해당 프로퍼티는 필수이므로, 프로퍼티를 못찾으면 바로 리턴한다.

    • Line 22~32: no-map 프로퍼티가 설정되어 있는지 확인, align 프로퍼티가 존재한다면 값을 읽는다.

    • Line 46~59: alloc-range 프로퍼티의 값이 존재하면, 그 값을 읽는다.

    • Line 61~69: 읽은 range를 대상으로 memblock_find_in_range을 호출하여 빈 영역을 찾는다. 만약 no-map이라면 해당 영역을 memblock_remove를 하고, 아니라면 memblock_reserve한다.

    • Line 73~77: alloc-range 프로퍼티가 존재하지 않으므로, 모든 영역에 대해서 빈 영역을 찾고, 해당 영역을 memblock_remove/reserve 한다.

    • Line 80~88: 메모리 할당에 실패시 에러 코드를 리턴, 성공시 인자로 들어온 base, size에 할당받은 주소와 사이즈를 대입한다.

    • Line 8: RESERVEDMEM_OF_DECLARE로 추가되는 테이블을 순회하며, 지원하는 노드에 대해 초기화 함수를 실행한다.

<200229_Memblock 발췌>

RESERVEDMEM_OF_DECLARE 정의
#define RESERVEDMEM_OF_DECLARE(name, compat, init)          \
    _OF_DECLARE(reservedmem, name, compat, init, reservedmem_of_init_fn)
RESERVEDMEM_OF_DECLARE 사용 사례 중, compat이 ufdt의 compatible과 일치하는 것
kernel/dma/coherent.c|397| RESERVEDMEM_OF_DECLARE(dma, "shared-dma-pool", rmem_dma_setup)
kernel/dma/contiguous.c|281| RESERVEDMEM_OF_DECLARE(cma, "shared-dma-pool", rmem_cma_setup);
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;

    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;
}
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;
}
static inline void dma_contiguous_set_default(struct cma *cma)
 {
     dma_contiguous_default_area = cma;
 }
  • Line 6~10: 각 예약 메모리 영역을 순회하면서, of_flat_dt_is_compatible 함수를 호출해서 compatible이 일치하는지 확인한다. 일치하지 않으면 스킵한다.

  • Line 13~17: initfn 함수를 실행하는데 이 함수는 아래의 열거된 함수와 대응된다.

    • rmem_dma_setup

    • rmem_cma_setup

rmem_cma_setup 함수를 훑어보면

  • Line 9~16: 노드가 reusable하고 no-map이 아닌지 확인한다. 또한 base와 size의 align을 검증한다. 앞서 봤던 복잡한 조건문이 해당 셋업 함수를 위한 것이다.

if (IS_ENABLED(CONFIG_CMA)
        && of_flat_dt_is_compatible(node, "shared-dma-pool")
        && of_get_flat_dt_prop(node, "reusable", NULL)
        && !of_get_flat_dt_prop(node, "no-map", NULL)) {
        unsigned long order =
            max_t(unsigned long, MAX_ORDER - 1, pageblock_order);

        align = max(align, (phys_addr_t)PAGE_SIZE << order);
    }
    dma_contiguous_default_area = cma;
  • Line 18: cma 테이블에서 정적인 cma struct를 받아 초기화 한다.(내용 추가 필요!)

  • Line 24: fixup 함수를 호출(내용은 비어있다)

  • Line 26~27: linux,cma-default 프로퍼티가 존재한다면(해당 예약 메모리 영역이 cma-default 영역이라면), dma_continguos_set_default함수를 호출한다.

fdt_reserved_mem_save_node는 정적/동적 예약 메모리 영역 모두 호출하게 된다. 이 함수는 동적 예약 메모리를 지원하기 위한 에 등장했다.

Line 35~42: cma 영역이라면, align을 조정할 필요가 있다, 조정되지 않는다면, cma 셋업에 실패하게 된다. 해당 를 참고하라.

Line 24~25: 오류가 없다면, __reserved_mem_init_node 함수를 호출한다. 해당 함수는 예약 메모리 드라이버 지원 추가를 위한 에 도입되었다.

reserved-memory.txt
패치
패치
패치