setup_machine_fdt()

<kernel v5.0>

머신 설정

시스템을 설정하는 방법은 다음과 같이 두 가지로 나뉜다.

  • Legacy
    • 머신 디스크립터를 사용하여 아키텍처 또는 머신 specific한 코드를 사용한다.
    • 기존 ARM32를 사용한 대부분의 임베디드 SoC 업체들이 사용한 방법이다.
    • ARM64에서는 사용하지 않는다.
  • Device Tree
    • 발빠른 ARM32 SOC 업체들은 디바이스 트리를 지원하기 시작하였다.
    • ARM32에서는 DT_MACHINE_START() 매크로를 사용한 머신 디스크립터를 사용한다.
      • ARM64는 머신 디스크립터를 사용하지 않는다.
    • ARM64는 디바이스 트리만 지원한다. 즉 디바이스 트리 설정에 따라 시스템을 초기화하는 방식을 사용한다.
      • 드라이버 개발자들은 디바이스 트리의 코어 운용 기준을 정확히 알아야 하고 이에 따라 드라이버를 구성해야 한다.

 

인수로 받은 DTB 물리 주소를 사용하여 Machine 테이블에서 name으로 검색한 후 machine_desc 구조체 포인터로 리턴한다. – ARM32

setup_machine_fdt_1a

 

멀티플랫폼(ARCH_MULTIPLATFORM) – ARM32

  • 컴파일된 커널이 미리 지정된 여러 개의 플랫폼을 지원할 수 있게하였다. 부팅 중 플랫폼을 선택하여 동작한다.
  • 2012년 ARM에서 추가를 시도했으나 개별 플랫폼의 코드를 통합하는 것에 난이도가 높아 잘 사용되지 않을 전망이다.
  • 관련 설정
    • ARCH_MULTIPLATFORM
    • ARCH_MULTI_CPU_AUTO
    • MULTI_IRQ_HANDLER
    • ARCH_MULTI_V4
    • ARCH_MULTI_V4T
    • ARCH_MULTI_V4_V5
    • ARCH_MULTI_V5
    • ARCH_MULTI_V6
    • ARCH_MULTI_V6_V7
    • ARCH_MULTI_V7
  • 참고: ARM: initial multiplatform support | LWN.net

 

DTB용 Machine 정보

DT_MACHINE_START() 매크로 – ARM32

arch/arm/include/asm/mach/arch.h

#define DT_MACHINE_START(_name, _namestr)               \
static const struct machine_desc __mach_desc_##_name    \
 __used                                                 \
 __attribute__((__section__(".arch.info.init"))) = {    \
        .nr             = ~0,                           \
        .name           = _namestr,

#endif
  • .arch.info.init 섹션에 const 객체를 만들어 둔다.
  • DTB 기반 Machine 테이블을 선언한다.
    • 검색시 MACHINE_START() 매크로로 선언된 ATAG 기반 Machine 정보와 다르게 번호가 아닌 name으로 검색한다.
    • nr을 -1로 한다.
  • __used를 사용하여 이 객체가 참조되지 않아도 컴파일러가 제거하지 않도록 한다.

 

machine_desc 및 map_desc 구조체 – ARM32

  • 라즈베리파이2: bcm2709

setup_machine_fdt_2

 

setup_machine_fdt() – ARM32

arch/arm/kernel/devtree.c

/**
 * setup_machine_fdt - Machine setup when an dtb was passed to the kernel
 * @dt_phys: physical address of dt blob
 *
 * If a dtb was passed to the kernel in r2, then use it to choose the
 * correct machine_desc and to setup the system.
 */
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
        const struct machine_desc *mdesc, *mdesc_best = NULL;

#if defined(CONFIG_ARCH_MULTIPLATFORM) || defined(CONFIG_ARM_SINGLE_ARMV7M)
        DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
                .l2c_aux_val = 0x0,
                .l2c_aux_mask = ~0x0,
        MACHINE_END

        mdesc_best = &__mach_desc_GENERIC_DT;
#endif

        if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
                return NULL;

        mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

        if (!mdesc) {
                const char *prop;
                int size;
                unsigned long dt_root;

                early_print("\nError: unrecognized/unsupported "
                            "device tree compatible list:\n[ ");

                dt_root = of_get_flat_dt_root();
                prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
                while (size > 0) {
                        early_print("'%s' ", prop);
                        size -= strlen(prop) + 1;
                        prop += strlen(prop) + 1;
                }
                early_print("]\n\n");

                dump_machine_table(); /* does not return */
        }

        /* We really don't want to do this, but sometimes firmware provides buggy data */
        if (mdesc->dt_fixup)
                mdesc->dt_fixup();

        early_init_dt_scan_nodes();

        /* Change machine number to match the mdesc we're using */
        __machine_arch_type = mdesc->nr;

        return mdesc;
}

인수로 전달받은 디바이스 트리(fdt)가 물리 주소에 있는지 검색하여 해당 machine을 찾고 machine_desc 구조체 포인터로 알아온다. 또한 디바이스 트리로부터 초기 부트업 과정에 미리(early) 설정해야할 정보를 얻어온 다. 초기 정보에는 커멘드 라인 정보나 메모리 정보 등이 있다.

  • 코드 라인 5~12에서 멀티(2개 이상의 cpu가 다른 아키텍처) 플랫폼 또는 single ARMv7M을 사용한 시스템인 경우 빈 데이터 상태의 머신 구조체 정보를 사용한다.
  • 코드 라인 15~16에서 디바이스 트리(fdt)의 물리 주소를 검사하여 이상이 있는 경우 null을 가지고 그냥 빠져나간다.
  • 코드 라인 18~39에서 machine을 찾아 machine_desc 구조체 포인터를 알아온다. 못 찾은 경우 machine 테이블을 덤프하고 시스템을 정지한다.
  • 코드 라인 42~43에서 만일 fixup 함수가 준비되어 있으면 동작시킨다.
    • dt_fixup 멤버 변수는 보통 null이며 특별히 펌웨어에 문제가 있어 수정되어야 하는 경우 이 멤버 변수에 fixup 관련 함수를 등록하여 사용한다.
    • 사용 예:
      • arch/arm/mach-exynos/exynos.c – dt_fixup에 exynos_dt_fixup() 함수가 연결되어 있다.
  • early_init_dt_scan_nodes()
    • 노드를 스캔하여 early하게 초기화 해놓아야 할 항목들을 수행한다.
      • boot_commmand_line, dt_root_size_cells, dt_root_addr_cells, early하게 추가해야 할 가용메모리 정보등이 있다.

 

setup_machine_fdt() – ARM64

arch/arm64/kernel/setup.c

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
        void *dt_virt = fixmap_remap_fdt(dt_phys);
        const char *name;

        if (!dt_virt || !early_init_dt_scan(dt_virt)) {
                pr_crit("\n"
                        "Error: invalid device tree blob at physical address %pa (virtual address 0xx
%p)\n"
                        "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
                        "\nPlease check your bootloader.",
                        &dt_phys, dt_virt);

                while (true)
                        cpu_relax();
        }

        name = of_flat_dt_get_machine_name();
        if (!name)
                return;

        pr_info("Machine model: %s\n", name);
        dump_stack_set_arch_desc("%s (DT)", name);
}

디바이스 트리(fdt)를 읽어들여 fixmap에 매핑하고 초기 부트업 과정에 미리(early) 설정해야할 정보를 얻어온 다. 초기 정보에는 커멘드 라인 정보나 메모리 정보 등이 있다.

  • 코드 라인 3에서 디바이스 트리(fdt)를 읽어 fixmap에 매핑한다.
  • 코드 라인 6~16에서 노드를 스캔하여 다음과 같이 먼저(early) 초기화할 항목들을 수행한다. 디바이스 트리를 발견할 수 없거나  디바이스트리 물리 주소를 검사하여 이상이 있는 경우 에러 메시지를 출력하고 동작을 멈춘다.
    • boot_commmand_line 설정
    • initrd_start, initrd_end 설정
    • dt_root_size_cells, dt_root_addr_cells 설정
    • memory memblock1에 메모리 등록
  • 코드 라인 18~22에서 머신명을 알아와서 모델명을 출력한다. 루트 노드의 “model” 속성을 머신명으로 사용하며 속성이 발견되지 않는 경우 “compatible” 속성을 사용한다.
  • 코드 라인 23에서 dump_stack_print_info() 함수가 호출될 때 사용할 머신 모델 명을 미리 저장해둔다.

 

early_init_dt_scan_nodes()

drivers/of/fdt.c

void __init early_init_dt_scan_nodes(void)
{
        int rc = 0;

        /* Retrieve various information from the /chosen node */
        rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
        if (!rc)
                pr_warn("No chosen node found, continuing without\n");

        /* Initialize {size,address}-cells info */
        of_scan_flat_dt(early_init_dt_scan_root, NULL);

        /* Setup memory, calling early_init_dt_add_memory_arch */
        of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

DTB 노드를 스캔하여 먼저(early) 초기화해놓아야 할 항목들을 수행한다.

  • 코드 라인 6~8에서 “/chosen” 노드를 스캔하여 “linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 읽어와서 전역 initrd_start와 initrd_end에 저장한다. 또한 “bootargs” 속성 값을 읽어와서 전역 변수 boot_command_line에 저장한다.
    • initrd는 커널 부트 로드 과정에 필요한 초기 램디스크로, 이 램디스크는 보통 일시적으로 루트 파일 시스템으로 마운트되고 처음 유저 모드에서 초기화 코드(sysvinit, systemd, …)가 호출된다. 이 과정에서 다른 정규 파일 시스템이 마운트되고 루트 파일 시스템이 교체되는 과정이 진행된다. 유저 모드에서 실행하는 처음 초기화 코드를 통해 두 과정으로 진행하는데, 하나는 커널이 필요로 하는 최소의 드라이버들을 로드하고 그 후 나머지 추가적인 모듈들이 로드된다.
    • CONFIG_CMDLINE 커널 옵션을 사용하는 경우에는 “bootargs” 속성 값이 없다면 커널 빌드 시 주어진 디폴트 CONFIG_CMDLINE을 사용한다. 단, CONFIG_CMDLINE_FORCE 커널 옵션도 사용하는 경우 “bootargs” 속성 값의 유무와 상관없이 무조건 디폴트 CONFIG_CMDLINE을 사용한다.
  • 코드 라인 11에서 루트 노드를 스캔하여 “#size-cells” 속성 값과 “#address-cells” 속성 값을 읽어와서 전역 변수 dt_root_size_cells와 dt_root_addr_cells에 저장한다.
  • 코드 라인 14에서 “memory” 노드를 스캔하여 “reg” 속성 값을 읽어와서 파싱한 물리 메모리 시작 주소 및 크기 정보로 memory memblock에 추가한다.

 

#address-cells와 #size-cells

#address-cells와 #size-cells는 이 속성이 있는 노드의 자식(child)부터 그 이하 노드를 대상으로 주소 셀 및 크기 셀이 표현된 값을 읽어들일 때 그 값이 표현하는 데 필요한 워드 수를 의미한다. 적용 범위가 현재 노드가 아니라 항상 하위 노드 이하를 대상으로 함에 주의해야 한다. 디바이스 트리에서 숫자 표현은 항상 워드(4바이트) 단위를 초과할 수 없다. 따라서 이보다 더 큰 숫자를 표기하려고 할 때는 한 칸 띄어서 2개의 워드를 연달아 표현해야 한다.

  • 예) regs가 표현하고자 하는 시작 주소는 0x8_0000_0000이며, 크기는 0x2000_0000(512MB)이다. 주소가 4바이트를 초과하므로 2개의 워드를 사용하기 위해서는 속성이 있는 노드의 부모 노드에서 먼저 주소 셀의 크기와 크기 셀의 크기를 적절하게 결정해두어야 한다.
node1 {
        #address-cells = <2>;
        #size-cells = <1>;
        ...
        node2 {
                regs = <0x8 0x0 0x20000000>;
        }
}

 

다음 그림은 earyl_init_dt_scan_nodes() 함수가 처리하는 일을 보여준다.

 

of_scan_flat_dt()

drivers/of/fdt.c

/**
 * of_scan_flat_dt - scan flattened tree blob and call callback on each.
 * @it: callback function
 * @data: context data pointer
 *
 * This function is used to scan the flattened device-tree, it is
 * used to extract the memory information at boot before we can
 * unflatten the tree
 */
int __init of_scan_flat_dt(int (*it)(unsigned long node,
                                     const char *uname, int depth,
                                     void *data),
                           void *data)
{
        const void *blob = initial_boot_params;
        const char *pathp;
        int offset, rc = 0, depth = -1;

        if (!blob)
                return 0;

        for (offset = fdt_next_node(blob, -1, &depth);
             offset >= 0 && depth >= 0 && !rc;
             offset = fdt_next_node(blob, offset, &depth)) {

                pathp = fdt_get_name(blob, offset, NULL);
                if (*pathp == '/')
                        pathp = kbasename(pathp);
                rc = it(offset, pathp, depth, data);
        }
        return rc;
}

디바이스 트리의 모든 노드를 뒤져 ‘ / ’로 시작하는 모든 노드에 대해 인자로 받은 함수를 호출하여 그 함수가 요청하는 값을 읽어온 경우 처리를 종료한다.

  • 코드 라인 13~15에서 디바이스 트리에서 노드만 루프를 돌며 검색하고 원하는 결과(rc = 1) 값을 읽어온 경우 루프를 빠져나간다.
  • 코드 라인 17~19에서 노드의 이름을 가져와서 ‘ / ’로 시작하는 노드인 경우에는 처음 ‘ / ’를 제외한 이름을 구한다.
  • 코드 라인 20에서 모든 노드에 대해 인자로 받은 함수(it)를 호출한다. 호출할 때 주어지는 인자들을 알아보면 오프셋은 structure 블록 내부에서 현재 순회(iteration)하고 있는 노드의 오프셋 바이트를 말한다. 루트 노드의 오프셋이 0부터 시작함을 기억해둔다. 두 번째 인자 pathp는 노드명을 가리키는 포인터다. 세 번째 인자 depth는 현재 노드의 depth를 의미하며, 루트 노드가 0부터 시작한다. 마지막 인자 data는 it 함수 호출 시 전달할 데이터다.

 

early_init_dt_scan_chosen()

drivers/of/fdt.c

int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
                                     int depth, void *data)
{
        int l;
        const char *p;

        pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);

        if (depth != 1 || !data ||
            (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
                return 0;

        early_init_dt_check_for_initrd(node);

        /* Retrieve command line */
        p = of_get_flat_dt_prop(node, "bootargs", &l);
        if (p != NULL && l > 0)
                strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));

        /*
         * CONFIG_CMDLINE is meant to be a default in case nothing else
         * managed to set the command line, unless CONFIG_CMDLINE_FORCE
         * is set in which case we override whatever was found earlier.
         */
#ifdef CONFIG_CMDLINE
#if defined(CONFIG_CMDLINE_EXTEND)
        strlcat(data, " ", COMMAND_LINE_SIZE);
        strlcat(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
        strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#else
        /* No arguments from boot loader, use kernel's  cmdl*/
        if (!((char *)data)[0])
                strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif
#endif /* CONFIG_CMDLINE */

        pr_debug("Command line is: %s\n", (char*)data);

        /* break now */
        return 1;
}

루트 노드를 스캔하여 “linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 읽어와서 전역 initrd_start와 initrd_end에 저장한다. 또한 “bootargs” 속성 값을 읽어와 전역 변수 boot_command_line에 저장한다.

  • 코드 라인 9~11에서 루트 노드의 다음 단계 자식 노드명이 “chosen”이 아니면 이 함수에서 필요한 노드가 아니므로 대상 노드를 처리하지 않는다.
  • 코드 라인 13에서 “linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 찾아 전역 변수 initrd_start와 initrd_end에 저장한다.
  • 코드 라인 16~18에서 속성 bootargs를 찾아 전역 변수 boot_command_line에 저장한다.
  • 코드 라인 25~36에서 CONFIG_CMDLINE 커널 옵션을 사용하는 경우 “bootargs” 속성 값이 없다면 커널 빌드 시 주어진 디폴트 CONFIG_CMDLINE을 사용한다. 단, CONFIG_CMDLINE_FORCE 커널 옵션도 사용하는 경우 “bootargs” 속성 값의 유무와 상관없이 무조건 디폴트 CONFIG_CMDLINE 값을 사용한다.

 

다음 그림과 같이 커널 옵션에 따라 DTB 또는 커널 설정을 결정한다. default 설정을 사용하는 경우 부트 로더가 DTB를 로드한 후 bootargs 속성에 overwrite한 후 커널에 전달하는 과정을 알 수 있다.

 

early_init_dt_check_for_initrd()

drivers/of/fdt.c

/**
 * early_init_dt_check_for_initrd - Decode initrd location from flat tree
 * @node: reference to node containing initrd location ('chosen')
 */
static void __init early_init_dt_check_for_initrd(unsigned long node)
{
        u64 start, end;
        int len;
        const __be32 *prop;

        pr_debug("Looking for initrd properties... ");

        prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
        if (!prop)
                return;
        start = of_read_number(prop, len/4);

        prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
        if (!prop)
                return;
        end = of_read_number(prop, len/4);

        __early_init_dt_declare_initrd(start, end);
        phys_initrd_start = start;
        phys_initrd_size = end - start;

        pr_debug("initrd_start=0x%llx  initrd_end=0x%llx\n",
                 (unsigned long long)start, (unsigned long long)end);
}

“linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 찾아 전역 변수 initrd_start와 initrd_end에 저장한다.

  • 코드 라인 9~12에서 “linux,initrd-start” 속성 값을 읽어온다.
  • 코드 라인 14~17에서 “linux,initrd-end” 속성 값을 읽어온다.
  • 코드 라인 19~21에서 읽은 2개의 값을 읽어 가상 주소로 바꿔 전역 변수 initrd_start와 initrd_end에 저장한다. 그리고 물리 주소도 시작과 사이즈도 전역변수에 저장해둔다.

 

early_init_dt_scan_root()

drivers/of/fdt.c

/**
 * early_init_dt_scan_root - fetch the top level address and size cells
 */
int __init early_init_dt_scan_root(unsigned long node, const char *uname,
                                   int depth, void *data)
{
        const __be32 *prop;

        if (depth != 0)
                return 0;

        dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
        dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;

        prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
        if (prop)
                dt_root_size_cells = be32_to_cpup(prop);
        pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);

        prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
        if (prop)
                dt_root_addr_cells = be32_to_cpup(prop);
        pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);

        /* break now */
        return 1;
}

루트 노드를 스캔하여 “#size-cells” 속성 값과 “#address-cells” 속성 값을 읽어와서 전역 dt_root_size_cells 및 dt_root_addr_cells에 저장한다.

  • 코드 라인 6~7에서 노드의 depth가 0이 아닌 경우, 즉 루트 노드가 아닌 경우 빠져나간다.
  • 코드 라인 12~14에서 “#size-cells” 속성 값을 찾아 전역 변수 dt_root_size_cells에 저장하되, 찾지 못한 경우 기본 값 1로 한다. “#size-cells”는 size를 표현하는 cell의 수를 나타낸다.
  • 코드 라인 17~19에서 “#address-cells” 속성 값을 찾아 전역 변수 dt_root_addr_cells에 저장하되, 찾지 못한 경우 기본 값 1로 한다. “#address-cells”는 address를 표현하는 cell의 수를 나타낸다.

 

early_init_dt_scan_memory()

drivers/of/fdt.c

/**
 * early_init_dt_scan_memory - Look for and parse memory nodes
 */
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
                                     int depth, void *data)
{
        const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
        const __be32 *reg, *endp;
        int l;
        bool hotpluggable;

        /* We are scanning "memory" nodes only */
        if (type == NULL || strcmp(type, "memory") != 0)
                return 0;

        reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
        if (reg == NULL)
                reg = of_get_flat_dt_prop(node, "reg", &l);
        if (reg == NULL)
                return 0;

        endp = reg + (l / sizeof(__be32));
        hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);

        pr_debug("memory scan node %s, reg size %d,\n", uname, l);

        while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
                u64 base, size;

                base = dt_mem_next_cell(dt_root_addr_cells, &reg);
                size = dt_mem_next_cell(dt_root_size_cells, &reg);

                if (size == 0)
                        continue;
                pr_debug(" - %llx ,  %llx\n", (unsigned long long)base,
                    (unsigned long long)size);

                early_init_dt_add_memory_arch(base, size);

                if (!hotpluggable)
                        continue;

                if (early_init_dt_mark_hotplug_memory_arch(base, size))
                        pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
                                base, base + size);
        }

        return 0;
}

“memory” 노드를 스캔하여 “reg” 속성 값을 읽어와서 파싱한 물리 메모리 시작 주소 및 크기 정보로 memory memblock에 추가한다.

  • 코드 라인 4~11에서 노드에 대해 device_type 속성을 알아와서 memory 타입이 아닌 경우 그냥 리턴한다. powerpc 아키텍처에서는 device_type이 설정되지 않았어도 루트 다음에 “memory@0” 노드인 경우에는 계속 진행한다.
  • 코드 라인 13~17에서 “linux,usable-memory” 속성 또는 “reg” 속성을 찾아 사용 가능 메모리 크기를 정한다.
    • powerpc 아키텍처에서만 사용하는 속성이다.
  • 코드 라인 20에서 “hotpluggable” 속성이 있으면 그 여부를 저장한다.
  • 코드 라인 24~35에서 사용 가능 메모리 크기만큼 reg 값에 있는 메모리 base와 size를 사용하여 계산한 후 early_init_dt_add_memory_arch( )를 호출하여 메모리 블록을 추가한다. 배열인 경우에는 그 수만큼 루프를 돈다.
  • 코드 라인 37~43에서 hotplug 메모리의 경우 memblock에 hotplug 표식을 해둔다.

 

early_init_dt_add_memory_arch()

drivers/of/fdt.c

void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
{
        const u64 phys_offset = MIN_MEMBLOCK_ADDR;

        if (size < PAGE_SIZE - (base & ~PAGE_MASK)) {
                pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
                        base, base + size);
                return;
        }

        if (!PAGE_ALIGNED(base)) {
                size -= PAGE_SIZE - (base & ~PAGE_MASK);
                base = PAGE_ALIGN(base);
        }
        size &= PAGE_MASK;

        if (base > MAX_MEMBLOCK_ADDR) {
                pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
                                base, base + size);
                return;
        }

        if (base + size - 1 > MAX_MEMBLOCK_ADDR) {
                pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
                                ((u64)MAX_MEMBLOCK_ADDR) + 1, base + size);
                size = MAX_MEMBLOCK_ADDR - base + 1;
        }

        if (base + size < phys_offset) {
                pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
                           base, base + size);
                return;
        }
        if (base < phys_offset) {
                pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
                           base, phys_offset);
                size -= phys_offset - base;
                base = phys_offset;
        }
        memblock_add(base, size);
}

메모리 시작 주소와 크기로 memory memblock에 추가한다. 단, 물리 메모리 범위를 넘어가는 경우 size를 조정한다.

  • 코드 라인 3에서 물리 메모리의 하한 주소를 설정한다.
  • 코드 라인 5~9에서 사이즈가 너무 작으면 경고 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 11~15에서 시작 주소가 페이지 단위로 정렬되지 않았다면 시작 주소를 페이지 단위로 정렬하고 그 차이만큼 size를 줄인다. 그런 후 페이지 단위로 내림 정렬한다.
  • 코드 라인 17~21에서 시작 주소가 물리 메모리 상한 주소를 초과한다면 더 이상 처리하지 않고 함수를 빠져나간다.
  • 코드 라인 23~27에서 끝 주소가 시스템 최대 처리 상한 주소를 초과한다면 초과한 만큼의 크기를 조절한다.
  • 코드 라인 29~33에서 끝 주소가 물리 메모리 최소 주소 미만인 경우에는 더 이상 처리하지 않고 함수를 빠져나간다.
  • 코드 라인 34~39에서 시작 주소가 물리 메모리 최소 주소 미만인 경우에는 시작 주소를 물리 메모리 하한 주소로 조정하고, 크기도 그 차이만큼 감소시켜 조정한다.
  • 코드 라인 40에서 memblock_add( ) 함수를 사용하여 메모리 블록을 추가한다.

 

참고

 

6 thoughts to “setup_machine_fdt()”

  1. 안녕하세요 이상한점이 있어 문의드립니다.
    위에 early_init_dt_scan_memory()함수에서 devicetree에 있는 메모리 속성의 base, size 속성을 읽어 오는데 실제 제가 만든 dtb파일과 데이터가 다르더라고요
    메모리를 덤프해보니 정확히 달라지는 시점은 초기에 리눅스가 decompress및 위치 재조정 한후
    start_kernel진입 후 확인해 보니 이미지 데이터가 바뀌어 있었습니다.
    혹시 이부분에서 뭔가 dtb를 건드리는게 있는지 문의드립니다.
    감사합니다

  2. 안녕하세요?

    디바이스 트리의 메모리 노드에 아래와 같은 값이 있다고 가정할 때를 살펴보겠습니다.

    #size-cells = <0x2>;
    #address-cells = <0x2>;

    memory {
    reg = <0x0 0x40000000 0x0 0x40000000>;
    device_type = "memory";
    }

    디바이스 트리가 컴파일된 파일은 빅엔디안으로 저장된 *.dtb 확장자를 가지겠죠. (위의 예에서 주소와 사이즈는 64비트로 표현되었습니다)

    리눅스 커널은 위의 dtb(fdt라고도 불립니다)가 위치한 메모리에서 읽기만 수행하고 수정 작업은 하지 않습니다. dtb가 위치한 메모리를 모두 읽어 커널이 관리하는 slab 메모리 관리자에 할당하는데, 커널이 부팅하여 slab 메모리 관리자가 아직 동작하기 이전이므로 임시로 필요한 몇 개의 노드만을 이른(early) 시간에 직접 접근해서 값을 알아옵니다. 그 몇 개의 노드 중에 memory 노드가 포함되는데 커널에서 함수 호출 순서는 다음과 같습니다.

    start_kernel()
    -> setup_arch()
    -> setup_machine_fdt()
    -> early_init_dt_scan()
    -> early_init_dt_scan_nodes()
    -> of_scan_flat_dt()
    -> early_init_dt_scan_memory()

    커널이 부팅한 후에 디바이스 트리 값을 확인하고 싶으면 다음과 같은 위치에서 확인할 수 있습니다.

    $ cd /sys/firmware/devicetree/base/memory
    $ ls
    device_type name reg
    $ hexdump -C reg
    00000000 00 00 00 00 40 00 00 00 00 00 00 00 40 00 00 00

    실제 물리 메모리가 커널에 잘 적용되었는지 확인하려면 다음을 출력해봅니다.
    cat /proc/iomem
    ...
    40000000-7FFFFFFF : System RAM
    ...

    dtb의 위치는 부트로더가 커널을 처음 호출할 때 레지스터로 전달하는데 이의 주소도 한 번 확인해보시기 바랍니다.

  3. 상세한 답변 감사합니다.
    말씀하신데로 수행해 봤더니 아래처럼 나왔습니다.

    # ls
    device_type name reg
    # hexdump -C reg
    00000000 00 00 00 00 80 00 00 00 |……..|
    00000008
    # cat /proc/iomem
    00000000-2f7fffff : System RAM
    00008000-007ad0cb : Kernel code
    0081a000-008a90eb : Kernel data
    ff800000-ff801fff : /sopc@0/ethernet@0xff800000
    ff809000-ff8090ff : axi_slave0
    ffa00000-ffa000ff : axi_slave1
    ffc02000-ffc0201f : serial
    ffc02100-ffc0211f : serial
    ffc02200-ffc022ff : /sopc@0/i2c@0xffc02200
    ffc02900-ffc029ff : /sopc@0/gpio@0xffc02900
    ffc02a00-ffc02aff : /sopc@0/gpio@0xffc02a00
    ffc02b00-ffc02bff : /sopc@0/gpio@0xffc02b00

    그리고 이건 실제 제가 올린 devicetree 정보인데

    #address-cells = ;
    #size-cells = ;
    memory {
    device_type = “memory”;
    reg = ,
    ,
    ,
    ,
    ,
    ,
    ,
    ,
    ,
    ,
    ;
    }; //end memory

    보시면 reg 정보가 달라져서… 이부분이 왜 달라지는지 모르겠네요 ㅠㅠ

  4. 에고..이상하게 올라가네요

    #address-cells = 1;
    #size-cells = 1;

    memory {
    device_type = “memory”;
    reg = 0xc0100000 0x00000400,
    0xc0180000 0x000493e0,
    0xc0200000 0x000493e0,
    0xc0280000 0x000493e0,
    0xc0300000 0x000493e0,
    0xc0380000 0x000493e0,
    0xc0080000 0x00010000,
    0xc0090000 0x00010000,
    0xc00a0000 0x00010000,
    0xc00b0000 0x00010000,
    0xc00c0000 0x00010000
    }; //end memory

  5. 32비트 리눅스로 부팅되었군요.

    커널 부팅 후 디바이스 트리의 메모리 노드 값으로는 물리 메모리 0x0 부터 2G(0x80000000) 사이즈를 확보하라고 되어 있습니다.
    다만 System RAM에 보여주는 영역은 32bit인 경우는 lowmem 영역으로 제한됩니다.
    (arm에서 커널을 1G 크기로 사용하는 디폴트의 경우 최대 760MB)
    참고로 32bit에서만 lowmem 영역과 highmem 영역이 나뉘는 것을 기억하시면 됩니다.

    그러니깐 인식된 디바이스 트리 값과 /proc/iomem에 보여준 값은 정상입니다.
    문제는 성주님이 수정하신 디바이스트리 파일이 커널에서 적용된 디바이스 트리가 아닌 것 같습니다.

    메모리 노드에 abc = “aaa”; 와 같은 속성을 하나 추가하고 다시 커널로 부팅해서
    cd /sys/firmware/devicetree/base/memory 한 후 디렉토리에 abc 파일이 있는지 확인해보시면 됩니다.

  6. DTB를 잘못 작성한 것이 아니라 부트로더나 QEMU가 전달해준 메모리 정보가 반영될 수 있으므로 그것도 확인해보시기 바랍니다.
    early_init_dt_scan_chosen() 함수 밑에 그림을 참고하시기 바랍니다.

문영일에게 댓글 남기기 댓글 취소

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다