setup_machine_fdt()

머신 설정

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

 

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

 

setup_machine_fdt_1a

 

 

멀티플랫폼(ARCH_MULTIPLATFORM)

  • 2012년 ARM에서 추가를 시도했으나 개별 플랫폼의 코드를 통합하는 것에 난이도가 높아 32bit arm에서는 잘 사용되지 않을 전망
    • 라즈베리파이2는 멀티플랫폼 아님.
    • 참고로 64bit arm에서는 이 커널 옵션을 사용하지 않아도 항상 Device Tree를 사용하여 멀티 플랫폼으로 동작하게 구성한다.
  • 하나의 커널에서 DTB를 사용하여 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() 매크로

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

#define MACHINE_END };

 

machine_desc 및 map_desc 구조체

  • 라즈베리파이2: bcm2709

setup_machine_fdt_2

 

setup_machine_fdt()

인수로 전달받은 디바이스트리 물리 주소로 검색하여 해당 machine을 찾은 경우 machine_desc 구조체 포인터를 알아온다.

  • CONFIG_ARCH_MULTIPLATFORM이 설정된 경우 __mach_desc_GENERIC_DT 구조체를 선언하여 사용한다.
  • early_init_dt_verify()
    • 디바이스트리 물리 주소를 검사하여 이상이 있는 경우 null을 가지고 그냥 빠져나간다.
  • of_flat_dt_match_machine()
    • machine을 찾아 machine_desc 구조체 포인터를 알아온다.
    • 못 찾은 경우 machine 테이블을 덤프하고 정지한다.
  • 만일 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 - 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;

#ifdef CONFIG_ARCH_MULTIPLATFORM
        DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
        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;
}

 

early_init_dt_scan_nodes()

  • of_scan_flat_dt()
    • 디바이스 트리의 모든 노드를 뒤져 ‘/’로 시작하는 모든 노드에 대해 인수로 받은 함수를 호출한다.
  • early_init_dt_scan_chosen
    • /chosen 노드로 부터 몇 가지 정보를 알아온다.
  • early_init_dt_scan_root()
    • {size,address}-cells 정보를 초기화 한다.
  • early_init_dt_scan_memory()
    • early_init_dt_add_memory_arch를 호출하여 메모리 설정을 한다.
void __init early_init_dt_scan_nodes(void)
{
        /* Retrieve various information from the /chosen node */
        of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

        /* 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);
}

 

of_scan_flat_dt()

  • 디바이스트리에서 노드만 루프를 돌며 검색
  • fdt_get_name()을 사용하여 노드의 이름을 가져와서 ‘/’로 시작하는 노드인경우 처음 ‘/’을 제외한 이름을 알아온다.
  • 모든 노드에 대해 인수로 받은 함수를 호출한다.
/**
 * 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;

        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;
}

 

 

early_init_dt_scan_chosen()

  • 노드 정보중 depth가 1이 아니거나 data가 없는 경우 빠져나간다.
  • early_init_dt_check_for_initrd()
    • 속성 linux,initrd-start와 linux,initrd-end를 찾아 전역 변수 initrd_start와 initrd_end에 저장한다.
  • 속성 bootargs를 찾아 전역변수 boot_command_line에 저장한다.
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
#ifndef CONFIG_CMDLINE_FORCE
        if (!((char *)data)[0])
#endif
                strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif /* CONFIG_CMDLINE */

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

        /* break now */
        return 1;
}

 

early_init_dt_check_for_initrd()

  • 속성 linux,initrd-start와 linux,initrd-end를 찾아 전역 변수 initrd_start와 initrd_end에 저장한다.
#ifdef CONFIG_BLK_DEV_INITRD
/**
 * 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);

        initrd_start = (unsigned long)__va(start);
        initrd_end = (unsigned long)__va(end);
        initrd_below_start_ok = 1;

        pr_debug("initrd_start=0x%llx  initrd_end=0x%llx\n",
                 (unsigned long long)start, (unsigned long long)end);
}
#else
static inline void early_init_dt_check_for_initrd(unsigned long node)
{
}
#endif /* CONFIG_BLK_DEV_INITRD */

 

 

early_init_dt_scan_root()

  • 노드의 depth가 0이 아닌경우 빠져나간다.
  • #size-cells 노드를 찾아 전역 변수 dt_root_size_cells에 저장한다.
    • size-cells는 size를 표현하는 cell의 수
  • #address-cells 노드를 찾아 전역 변수 dt_root_addr_cells에 저장한다.
    • #address-cells는 address를 표현하는 cell의 수
/**
 * 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;
}

 

early_init_dt_scan_memory()

  • 노드에 대해 device_type 속성을 알아와서 memory  타입이 아닌 경우 그냥 리턴한다.
  • linux,usable-memory 속성 또는 reg 속성을 찾아 사용가능 메모리 크기를 정한다.
  • 사용가능 메모리크기만큼 reg 값에 있는 메모리 base와 size를 사용하여 계산한 후 early_init_dt_add_memory_arch()를 호출하여 메모리 블럭을 추가한다.
/**
 * early_init_dt_scan_memory - Look for an 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;

        /* We are scanning "memory" nodes only */
        if (type == NULL) {
                /*
                 * The longtrail doesn't have a device_type on the
                 * /memory node, so look for the node called /memory@0.
                 */
                if (!IS_ENABLED(CONFIG_PPC32) || depth != 1 || strcmp(uname, "memory@0") != 0)
                        return 0;
        } else if (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));

        pr_debug("memory scan node %s, reg size %d, data: %x %x %x %x,\n",
            uname, l, reg[0], reg[1], reg[2], reg[3]);

        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);
        }

        return 0;
}

 

early_init_dt_add_memory_arch()

  • memblock_add()을 사용하여 메모리 블럭을 추가
  • 물리 메모리 범위를 넘어가는 경우 추가되지 않도록 조정한다.
#ifdef CONFIG_HAVE_MEMBLOCK
#define MAX_PHYS_ADDR   ((phys_addr_t)~0)

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

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

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

        if (base + size - 1 > MAX_PHYS_ADDR) {
                pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
                                ((u64)MAX_PHYS_ADDR) + 1, base + size);
                size = MAX_PHYS_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);
}

 

5 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 파일이 있는지 확인해보시면 됩니다.

댓글 남기기

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