reserved_mem 분석 노트

2020 2 29 스터디 내용

1. Before you read

Have to read reserved-memory.txt carefully.

1.1 reserved-memory 간단 정

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

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;
}
  • 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으로 삽입된다.)

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

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);
    }
}
  • 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 35~42: cma 영역이라면, align을 조정할 필요가 있다, 조정되지 않는다면, cma 셋업에 실패하게 된다. 해당 패치를 참고하라.

    • 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 24~25: 오류가 없다면, __reserved_mem_init_node 함수를 호출한다. 해당 함수는 예약 메모리 드라이버 지원 추가를 위한 패치에 도입되었다.

    • 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;
}
  • 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함수를 호출한다.

Last updated