setup_machine_fdt()

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

 

setup_machine_fdt_1a

 

 

멀티플랫폼(ARCH_MULTIPLATFORM)

  • 2012년 ARM에서 추가를 시도했으나 개별 플랫폼의 코드를 통합하는 것에 난이도가 높아 잘 사용되지 않을 전망
    • 라즈베리파이2는 멀티플랫폼 아님.
  • 하나의 커널에서 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);
}

 

답글 남기기

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