sanity_check_meminfo()

이 함수에서는 등록된 memory memblock에 대해 미리 사전 체크를 하여 early memory allocator로 동작할 수 있도록 다음과 같이 준비한다.

  • lowmem 영역만 사용해야 하는 case에 대해 memblock 영역 삭제
    • HIGHMEM을 사용하지 않을 경우 또는 캐시가 VIPT aliasing을 사용하는 경우 memory 영역에 등록된 memblock 들이 lowmem 영역을 초과하는 경우 해당 초과 영역들을 제거한다.
  • arm_lowmem_limithigh_memory 설정
    • memory block의 끝 주소를 arm_lowmem_limit으로 하되  vmalloc_limit(vmalloc_min의 물리주소)을 초과하지 않도록 한다.
    • high_memory는 arm_lowmem_limit의 가상 주소 값이다.
  • memblock.current_limit 설정
    • memory memblock들이 2M 단위로 align되어 있어야 커널 설정 초기에 사용되는 early memory allocator에서 2M 영역을 할당하여 사용하는 reserve memblock을 운영하여야 하므로 각 memory memblock 들이 2M align되어 있지 않은 memblock이 있는 경우 그 지점의 2M round down 주소까지로 사용을 제한하도록 memblock_limit를 설정한다.

sanity_check_meminfo_1a

 

sanity_check_meminfo()

arch/arm/mm/mmu.c

void __init sanity_check_meminfo(void)
{
        phys_addr_t memblock_limit = 0; 
        int highmem = 0; 
        phys_addr_t vmalloc_limit = __pa(vmalloc_min - 1) + 1; 
        struct memblock_region *reg;

        for_each_memblock(memory, reg) {
                phys_addr_t block_start = reg->base;
                phys_addr_t block_end = reg->base + reg->size;
                phys_addr_t size_limit = reg->size;

                if (reg->base >= vmalloc_limit)
                        highmem = 1; 
                else
                        size_limit = vmalloc_limit - reg->base;


                if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) {

                        if (highmem) {
                                pr_notice("Ignoring RAM at %pa-%pa (!CONFIG_HIGHMEM)\n",
                                          &block_start, &block_end);
                                memblock_remove(reg->base, reg->size);
                                continue;
                        }

                        if (reg->size > size_limit) {
                                phys_addr_t overlap_size = reg->size - size_limit;

                                pr_notice("Truncating RAM at %pa-%pa to -%pa",
                                          &block_start, &block_end, &vmalloc_limit);
                                memblock_remove(vmalloc_limit, overlap_size);
                                block_end = vmalloc_limit;
                        }
                }
  • phys_addr_t vmalloc_limit = __pa(vmalloc_min – 1) + 1;
    • vmalloc_min
      • VMALLOC_END(0xff00_0000) – (240 << 20) – VMALLOC_OFFSET(8M)
      • 0xff00_000 – 240M – 8M = 0xef80_0000
      • VMALLOC 영역이 최소 보장되어야 하는 하한 주소로 아키텍처 마다 다르다.
        • 32 bit ARM 에서는 0xef80_0000으로 고정되어 사용된다.
    • vmalloc_limit
      • vmalloc_min의 물리 주소가 담긴다.
      • rpi2: 0x6f80_0000
  • for_each_memblock(memory, reg) {
    • 등록된 전체 memory memblock 영역을 루프로 돈다.
  • if (reg->base >= vmalloc_limit)
    • 영역의 시작 물리 주소가 vmalloc_limit을 초과한 경우 highmem 영역이라 판단한다.
  • if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) {
    • HIGHMEM 설정이 안되어 있거나 d-cache가 vipt aliasing을 사용하는 경우
      • rpi2: d-cache는 CACHEID_VIPT_NONALIASING
  • if (highmem) {
    • highmem 영역을 사용하는 블럭은 삭제한다.
  • if (reg->size > size_limit) {
    • size가 lowmem 영역을 넘어가는 경우 넘어가는 부분 만큼을 제거한다.

HIGHMEM을 사용하지 않는 경우 memblock들이 vmalloc_limit을 초과하는 경우 해당 영역을 제거한다.

sanity_check_meminfo_3b

 

                if (!highmem) {
                        if (block_end > arm_lowmem_limit) {
                                if (reg->size > size_limit)
                                        arm_lowmem_limit = vmalloc_limit;
                                else
                                        arm_lowmem_limit = block_end;
                        }
  • if (block_end > arm_lowmem_limit) {
    • 블럭이 arm_lowmem_limit를 초과한 경우
  • if (reg->size > size_limit)
    • 블럭 사이즈가 lowmem 영역까지 남은 공간을 초과하는 경우 arm_lowmem_limit에 vmalloc_limit을 대입하고 그렇지 않은 경우 블럭의 끝을 지정한다.

sanity_check_meminfo_4a

 

                        /*
                         * Find the first non-pmd-aligned page, and point
                         * memblock_limit at it. This relies on rounding the
                         * limit down to be pmd-aligned, which happens at the
                         * end of this function.
                         *
                         * With this algorithm, the start or end of almost any
                         * bank can be non-pmd-aligned. The only exception is
                         * that the start of the bank 0 must be section-
                         * aligned, since otherwise memory would need to be
                         * allocated when mapping the start of bank 0, which
                         * occurs before any free memory is mapped.
                         */
                        if (!memblock_limit) {
                                if (!IS_ALIGNED(block_start, PMD_SIZE))
                                        memblock_limit = block_start;
                                else if (!IS_ALIGNED(block_end, PMD_SIZE))
                                        memblock_limit = arm_lowmem_limit;
                        }

                }
        }

        high_memory = __va(arm_lowmem_limit - 1) + 1;

        /*
         * Round the memblock limit down to a pmd size.  This
         * helps to ensure that we will allocate memory from the
         * last full pmd, which should be mapped.
         */
        if (memblock_limit)
                memblock_limit = round_down(memblock_limit, PMD_SIZE);
        if (!memblock_limit)
                memblock_limit = arm_lowmem_limit;

        memblock_set_current_limit(memblock_limit);
}
  • if (!memblock_limit) {
    • memblock_limit값이 설정되지 않았으면
  • if (!IS_ALIGNED(block_start, PMD_SIZE))
    • 블럭의 시작 주소가 2M align되어 있지 않은 경우 memblock_limit에 블럭 시작 주소를 대입한다.
  • else if (!IS_ALIGNED(block_end, PMD_SIZE))
    • 블럭의 끝 주소가 2M align되어 있지 않은 경우 memblock_limit에 블럭 끝 주소를 대입한다.
  • memblock_limit = round_down(memblock_limit, PMD_SIZE);
    • memblock_limit 주소를 2M round down 한다.
  • memblock_set_current_limit(memblock_limit);
    • 전역 변수 memblock.current_limit를 설정한다.
  • 등록된 memblock은 커널 설정 초기에 2M 단위의 메모리를 할당 받아 사용한다. 따라서 align되지 않은 메모리가 배열에 등록된 경우 align 된 영역까지만 사용하고 나머지 메모리는 사용하지 않도록 memblock_limit를 설정한다.

sanity_check_meminfo_5a

 

lowmem 영역

  • lowmem 영역은 물리 메모리가 1:1로 커널 영역에 매핑되어 사용할 수 있는 영역이다.
    • vmalloc_limit
      • 현재 커널에서 lowmem 영역을 최대 키울 수 있는 한도내의 물리 메모리 끝 주소
      • 메모리 크기와 관계 없이 커널 영역의 크기에 따라 계산되는 물리 주소
      • 예)
        • VM_SPLIT_3G: 0x2f80_0000 (max lowmem=760M)
        • VM_SPLIT_2G: 0x6f80_0000 (max lowmem=1G+760M)
    • arm_lowmem_limit
      • 물리 메모리 크기가 max lowmem을 초과하는 경우 arm_lowmem_limit는 vmalloc_limit 값과 동일하다.
      • 물리 메모리 크기가 max lowmem보다 작은 경우 arm_lowmem_limit는 물리 메모리의 끝 주소가 대입된다.
    • high_memory
      • arm_lowmem_limit의 가상 주소와 동일하다.

sanity_check_meminfo_2b

 

참고

early_paging_init()

해당 머신의 바뀐 메모리 정보를 위해 초기화를 수행한다. LPAE의 경우 phisical to virtual transalation이 필요하여 추가로 몇 개의 루틴들이 수행되어야 한다.

  • mdesc→init_meminfo() 수행
  • LPAE의 경우 추가로 다음 항목들 수행
    • fixup_pv_table() 수행
    • page table  수정
    • 캐시 플러쉬
    • 해당 CPU 아키텍처의 MMU에 페이지 테이블 설정 변경
    • TTBR1 레지스터 재 설정
    • BP 및 TLB 캐시 플러쉬

early_paging_init

 

early_paging_init()

  • CONFIG_ARM_LPAE가 설정된 경우 두 개의 구현된 함수 중 윗 부분 함수를 수행하고 그렇지 않은 경우 아랫 부분 함수를 수행한다.
  • 머신 구조체의 init_meminfo 콜백 함수가 등록되어 있지 않은 경우 early하게 메모리 정보를 초기화(주로 패치 목적) 할 필요가 없는 것으로 간주하고 빠져나간다.
  • init_mm은 커널이 사용하는 mm_struct 구조체 포인터 변수이다.
  • 커널 코드의 시작과 끝을 map_start, map_end에 저장
  • pgd_offset_k(0)은 커널이 사용하는 pgd(페이지 글로벌 디렉토리)에 있는 첫 번째 엔트리 주소를 알아온다.
  • mdesc->init_meminfo() 콜백 함수를 사용하여 메모리 정보를 초기화한다. 이를 통해 메모리 기초 정보가 바뀌었으므로 관련 정보를 모두 수정하여야 한다. 커널 4.2에서 이 멤버 변수는 pv_fixup으로 변경된다.
    • fixup_pv_table()을 호출하여 각 pv_table 엔트리들을 모두 패치한다.
    • pv_table의 내용이 바뀌었으므로 flush_cache_louis() 함수를 사용하여 명령 캐시(i-cache) 를 flush 한다.
    • 레벨1과 레벨2의 페이지 테이블을 다시 매핑한다.
    • flush_cache_all()을 사용하여 모든 캐시를 비운다.
    • cpu_switch_mm()
      • ARMv7:
        • TTBR0에 pgd0를 설정한다.
        • CONTEXTIDR에 Context ID를 설정한다.
    • cpu_set_ttbr()을 사용하여 TTBR 1레지스터를 설정한다.
    • 마지막으로 branch predict 캐시와 TLB 캐시를 모두 비운다.
#ifdef CONFIG_ARM_LPAE
/*
 * early_paging_init() recreates boot time page table setup, allowing machines
 * to switch over to a high (>4G) address space on LPAE systems
 */
void __init early_paging_init(const struct machine_desc *mdesc,
                              struct proc_info_list *procinfo)
{
        pmdval_t pmdprot = procinfo->__cpu_mm_mmu_flags;
        unsigned long map_start, map_end;
        pgd_t *pgd0, *pgdk;
        pud_t *pud0, *pudk, *pud_start;
        pmd_t *pmd0, *pmdk;
        phys_addr_t phys;
        int i;

        if (!(mdesc->init_meminfo))
                return;

        /* remap kernel code and data */
        map_start = init_mm.start_code & PMD_MASK;
        map_end   = ALIGN(init_mm.brk, PMD_SIZE);

        /* get a handle on things... */
        pgd0 = pgd_offset_k(0);
        pud_start = pud0 = pud_offset(pgd0, 0);
        pmd0 = pmd_offset(pud0, 0);

        pgdk = pgd_offset_k(map_start);
        pudk = pud_offset(pgdk, map_start);
        pmdk = pmd_offset(pudk, map_start);

        mdesc->init_meminfo();

        /* Run the patch stub to update the constants */
        fixup_pv_table(&__pv_table_begin,
                (&__pv_table_end - &__pv_table_begin) << 2);

        /*
         * Cache cleaning operations for self-modifying code
         * We should clean the entries by MVA but running a
         * for loop over every pv_table entry pointer would
         * just complicate the code.
         */
        flush_cache_louis();
        dsb(ishst);
        isb();

        /*
         * FIXME: This code is not architecturally compliant: we modify
         * the mappings in-place, indeed while they are in use by this
         * very same code.  This may lead to unpredictable behaviour of
         * the CPU.
         *
         * Even modifying the mappings in a separate page table does
         * not resolve this.
         *
         * The architecture strongly recommends that when a mapping is
         * changed, that it is changed by first going via an invalid
         * mapping and back to the new mapping.  This is to ensure that
         * no TLB conflicts (caused by the TLB having more than one TLB
         * entry match a translation) can occur.  However, doing that
         * here will result in unmapping the code we are running.
         */
        pr_warn("WARNING: unsafe modification of in-place page tables - tainting kernel\n");
        add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);

        /*
         * Remap level 1 table.  This changes the physical addresses
         * used to refer to the level 2 page tables to the high
         * physical address alias, leaving everything else the same.
         */
        for (i = 0; i < PTRS_PER_PGD; pud0++, i++) {
                set_pud(pud0,
                        __pud(__pa(pmd0) | PMD_TYPE_TABLE | L_PGD_SWAPPER));
                pmd0 += PTRS_PER_PMD;
        }

        /*
         * Remap the level 2 table, pointing the mappings at the high
         * physical address alias of these pages.
         */
        phys = __pa(map_start);
        do {
                *pmdk++ = __pmd(phys | pmdprot);
                phys += PMD_SIZE;
        } while (phys < map_end);

        /*
         * Ensure that the above updates are flushed out of the cache.
         * This is not strictly correct; on a system where the caches
         * are coherent with each other, but the MMU page table walks
         * may not be coherent, flush_cache_all() may be a no-op, and
         * this will fail.
         */
        flush_cache_all();

        /*
         * Re-write the TTBR values to point them at the high physical
         * alias of the page tables.  We expect __va() will work on
         * cpu_get_pgd(), which returns the value of TTBR0.
         */
        cpu_switch_mm(pgd0, &init_mm);
        cpu_set_ttbr(1, __pa(pgd0) + TTBR1_OFFSET);

        /* Finally flush any stale TLB values. */
        local_flush_bp_all();
        local_flush_tlb_all();
}
#else

void __init early_paging_init(const struct machine_desc *mdesc,
 struct proc_info_list *procinfo)
{
 if (mdesc->init_meminfo)
 mdesc->init_meminfo();
}

#endif

 

cpu_switch_mm() 매크로

  • cpu_do_switch_mm 매크로를 호출하여 MMU에 페이지 디렉토리 설정 변경을 요청한다.
  • cpu_do_switch_mm 매크로는 각 아키텍처에 따라 수행 방법이 다르다.
    • MULTI_CPU를 사용하는 경우
      • processor->switch_mm() 콜백 함수를 호출한다.
        • switch_mm() 콜백 함수는 페이지 테이블을 설정한다.
      • ARMv7:
        • CONFIG_CPU_V7을 사용하므로 MULTI_CPU 이다.
        • switch_mm은 cpu_v7_switch_mm() 함수가 연결되어 있다.
    • MULTI_CPU를 사용하지 않는 경우
      • 각 아키텍처 이름에 맞게 호출 함수가 존재한다.

arch/arm/include/asm/proc-fns.h

#define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm)
  • 아래와 같이 두 개의 루틴 중 빌드 구성에 따라 선택하여 호출한다.

arch/arm/include/asm/glue-proc.h

#define cpu_do_switch_mm                __glue(CPU_NAME,_switch_mm)
  • __glue() 매크로는 두 개의 인수를 합친다.

arch/arm/include/asm/proc-fns.h

#define cpu_do_switch_mm                processor.switch_mm
  • MULTI_CPU로 설정된 경우 해당 프로세서 구조체를 통해 호출

 

cpu_v7_switch_mm()

  • CONTEXTIDR 레지스터에 context ID(tsk) 설정
  • TTBR0에 pgd0 설정

arch/arm/mm/proc-v7-2level.S

/*
 *      cpu_v7_switch_mm(pgd_phys, tsk)
 *
 *      Set the translation table base pointer to be pgd_phys
 *
 *      - pgd_phys - physical address of new TTB
 *
 *      It is assumed that:
 *      - we are not using split page tables
 */
ENTRY(cpu_v7_switch_mm)
#ifdef CONFIG_MMU
        mov     r2, #0
        mmid    r1, r1                          @ get mm->context.id
        ALT_SMP(orr     r0, r0, #TTB_FLAGS_SMP)
        ALT_UP(orr      r0, r0, #TTB_FLAGS_UP)
#ifdef CONFIG_ARM_ERRATA_430973
        mcr     p15, 0, r2, c7, c5, 6           @ flush BTAC/BTB
#endif
#ifdef CONFIG_PID_IN_CONTEXTIDR
        mrc     p15, 0, r2, c13, c0, 1          @ read current context ID
        lsr     r2, r2, #8                      @ extract the PID
        bfi     r1, r2, #8, #24                 @ insert into new context ID
#endif
#ifdef CONFIG_ARM_ERRATA_754322
        dsb
#endif
        mcr     p15, 0, r1, c13, c0, 1          @ set context ID
        isb
        mcr     p15, 0, r0, c2, c0, 0           @ set TTB 0
        isb
#endif
        bx      lr
ENDPROC(cpu_v7_switch_mm)

 

 

__glue() 매크로

  • 2 개의 인수를 합쳐 하나의 이름으로 만든다.

arch/arm/include/asm/glue.h

#define ____glue(name,fn)       name##fn
#define __glue(name,fn)         ____glue(name,fn)

 

 

 

flush_cache_louis()

  • setup_arch() → early_paging_init() – flush_cache_louis()

flush_cache_louis

parse_early_param()

<kernel v5.10>

커멘드 라인 파라미터 파싱

부트 커멘드라인 문자열을 파싱하여 해당 파라미터가 early 파라미터인 경우 해당 설정 함수를 호출한다. 호환을 위해 “console” 파라미터는 earlycon으로 동작한다.

boot_command_line(이하 cmdline 또는 커멘드 라인) 파라미터로 요청받은 문자열을 토큰으로 parsing 하여 이에 대응하는 설정 함수를 setup_param 테이블에서 찾고 해당 항목이 early로 설정되어 있는 경우 이를 호출하여 실행한다. 또한 요청 토큰이 “console”인 경우 setup_param 테이블에서 “earlycon”을 찾아 해당 설정 함수를 호출한다. DTB 내부에서도 chosen 노드의  stdout-path 속성값에 해당하는 console  디바이스를 earlycon_of_table 에서 찾은 경우 해당 설정 함수를 동작시킨다.

  • setup_param 테이블에서 early_console 함수를 찾아 등록된 초기화 함수 수행
    • __setup_start 부터 __setup_end 영역
    • obs_kernel_param 구조체 엔트리들
  • 다음 조건에 해당되는 커널 파라미터를 발견하면 해당 커널 파라미터에 등록된 함수를 호출한다.
    • early 파라미터 호출
      • 요청한 파라미터가 등록된 커널 파라미터 문자열과 같으면서 early 설정이 된 경우
    • earlycon 호출
      • 요청한 파라미터가 “console”이고 등록된 커널 파라미터는 “earlycon”인 경우
  • cmdline 문자열 중 “console” 을 발견 시 콘솔 디바이스명으로 모든 earlycon으로 등록된 모든 초기화 함수를 수행
  • 예) rpi2: earlycon=xxx를 설정한 경우:
    • pl011_setup_earlycon(“xxx”) → setup_earlycon() → pl011_early_console_setup(“xxx”)
    • uart_setup_earlycon(“xxx”) → setup_earlycon() → early_serial8250_setup(“xxx”)
    • uart8250_setup_earlycon(“xxx”) → 상동
    • setup_of_earlycon(“xxx”) → early_init_dt_scan_chosen_serial()
      • 디바이스 트리에서 /chosen의 stdout-path를 찾아냄
  • 예) rpi2는 두 개의 드라이버를 console로 지정한다.
    • dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p6 rootfstype=ext4 elevator=deadline rootwait
      • ttyAMA는 ARM에서 제공하는 pl011 UART 드라이버이다.
      • tty는 리눅스 기본 tty 드라이버이다.
    • earlycon이 필요 시
      • “earlycon=pl011,0x3f201000,115200n8”
    • earlyprintk가 필요 시
      • “earlyprintk”
    • earlycon과 earlyprintk와 동시 사용 시
      • “earlycon=pl011,0x3f201000,115200n8 earlyprintk”

 

다음 그림은 매치되는 early 파라미터들의 셋업 함수를 호출한다.

  • 예) rpi2에 등록된 earlycon 관련 항목들
    • EARLYCON_DECLARE(pl011, pl011_early_console_setup);
      • 연결함수: pl011_setup_earlycon() -> setup_earlycon() -> pl011_early_console_setup()
    • EARLYCON_DECLARE(uart, early_serial8250_setup);
      • 연결함수: uart_setup_earlycon() -> setup_earlycon() -> early_serial8250_setup()
    • EARLYCON_DECLARE(uart8250, early_serial8250_setup);
      • 연결함수: uart8250_setup_earlycon() -> setup_earlycon() -> early_serial8250_setup()
    • early_param(“earlycon”, setup_of_earlycon);
      • 연결함수: setup_of_earlycon() -> early_init_dt_scan_chosen_serial() -> of_setup_earlycon() -> 예) pl011_early_console_setup()
  • 예) rpi2에 등록된 earlyprintk 관련 항목
    • early_param(“earlyprintk”, setup_early_printk);

 

parse_early_param()

init/main.c

/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
        static int done __initdata;
        static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;

        if (done)
                return;

        /* All fall through to do_early_param. */
        strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
        parse_early_options(tmp_cmdline);
        done = 1; 
}

커멘트 라인 파라미터들 중 early 파라미터에 해당하는 설정 함수를 호출한다.

  • 코드 라인 10에서 전역 변수 boot_command_line의 내용을 tmp_cmdline에 복사한다.
    • boot_command_line
      • A) 부트로더가 다음 중 하나를 전달해 준다.
        • ATAG _CMDLINE 문자열 – ARM32 only
        • DTB의 “/chosen” 노드의 “bootargs” 속성 값
      • B) 커널에서도 준비한 문자열
        • 커널 옵션으로 입력한 커맨드라인 문자열 CONFIG_CMDLINE이 준비된다.
      • 위의 A) 및 B)를 아래 옵션에 따라 조합하여 사용한다.
        • 1) CONFIG_CMDLINE_EXTEND
          • A) DTB(or ATAG)와 B) 커널 cmdline을 합쳐서 사용한다.
        • 2) CONFIG_CLDLINE_FORCE
          • B) 커널 cmdline을 사용한다.
        • 3) no option (default)
          • A) DTB(or ATAG)를 사용한다.
  • 코드 라인 12에서 early 파라미터에 해당하는 설정 함수를 호출한다.

 

parse_early_options()

init/main.c

void __init parse_early_options(char *cmdline)
{
        parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,
                   do_early_param);
}

do_early_param() 함수 주소를 인수로 parse_args() 함수를 호출한다.

  • 파라미터 블록, 개수, 범위가 지정되는 경우 그 파라미터 범위에 해당하는 토큰과 매치되는 경우 해당 파라미터에 값을 대입한다.
  • 그러나 파라미터 블록, 개수 및 범위가 0으로 전달되는 경우 각 토큰을 파싱하게 되면 param과 val 값을 가지고 항상 unknown handler인 do_early_param() 함수가 호출된다.

 

parse_args()

kernel/params.c

/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
char *parse_args(const char *doing,
                 char *args,
                 const struct kernel_param *params,
                 unsigned num,
                 s16 min_level,
                 s16 max_level,
                 void *arg,
                 int (*unknown)(char *param, char *val,
                                const char *doing, void *arg))
{
        char *param, *val, *err = NULL;

        /* Chew leading spaces */
        args = skip_spaces(args);

        if (*args)
                pr_debug("doing %s, parsing ARGS: '%s'\n", doing, args);

        while (*args) {
                int ret;
                int irq_was_disabled;

                args = next_arg(args, &param, &val);
                /* Stop at -- */
                if (!val && strcmp(param, "--") == 0)
                        return err ?: args;
                irq_was_disabled = irqs_disabled();
                ret = parse_one(param, val, doing, params, num,
                                min_level, max_level, arg, unknown);
                if (irq_was_disabled && !irqs_disabled())
                        pr_warn("%s: option '%s' enabled irq's!\n",
                                doing, param);

                switch (ret) {
                case 0:
                        continue;
                case -ENOENT:
                        pr_err("%s: Unknown parameter `%s'\n", doing, param);
                        break;
                case -ENOSPC:
                        pr_err("%s: `%s' too large for parameter `%s'\n",
                               doing, val ?: "", param);
                        break;
                default:
                        pr_err("%s: `%s' invalid for parameter `%s'\n",
                               doing, val ?: "", param);
                        break;
                }

                err = ERR_PTR(ret);
        }

        return err;
}

커멘드 라인 파라미터를 파싱한 토큰과 값으로 파라미터 블럭 @params에서 @min_level ~ @max_level 범위의 파라미터 테이블을 검색하여 일치하는 토큰이 있는 경우 해당 파라미터에 값을 대입한다. 만일 매치되는 조건이 없으면 @unknown 함수를 호출한다.

  • 코드 라인 14~17에서 args 인수에는 cmdline의 주소를 담고 있는데 space(0x20, 탭, 라인피드 등) 인 경우 skip 한다.
    • 예) ”  console=xxx” -> “console=xxx”
  • 코드 라인 19~23에서 parsing할 문자열이 남아 있는 동안 루프를 돌며 토큰을 “=” 문자로 분리하여 param과 val로 담는다.
    • (abc=1)
    • space 문자로 토큰과 토큰을 분리한다.
      • (aaa=1 bbb=2 ccc=3)
    • 쌍 따옴표(“)가 사용된 경우 중간에 space가 있어도 토큰을 분리하지 않는다.
      • (aaa=”1″ bbb=”2 2″ ccc=”3″)
        • param=(aaa), val=(1)
        • param=(bbb), val=(2 2)
        • param=(ccc), val=(3)
  • 코드 라인 25~26에서 val 값이 없으면서 param 값이 “–” 인 경우 더 이상 파싱을 하지 않고 함수를 종료한다.
  • 코드 라인 27에서 SCTLR의 인터럽트 마스크(“I”, 1번 비트)가 disable된 상태인지 알아온다.
    • true = irq disabled status, false = irq enabled status
  • 코드 라인 28~29에서 파싱된 토큰의 param과 val 그리고 doing(“early options”메시지 출력용) 및 do_early_param() 함수 포인터를 인자로 parse_one()을 호출한다.
    • 파라미터 블록, 개수, 범위가 지정되는 경우 그 파라미터 범위에 해당하는 토큰과 매치되는 경우 해당 파라미터에 값을 대입한다
    • 그러나 파라미터 블록, 개수 및 범위가 0으로 전달되는 경우 각 토큰을 파싱하게 되면 param과 val 값을 가지고 항상 unknown handler인 do_early_param() 함수가 호출된다.
  • 코드 라인 30~32에서 irq 설정 상태가 바뀌었으면 어떤 파라미터 옵션에서 바뀌었는지 warning을 출력한다.
    • 파라미터로 인해 특정 코드(디바이스 드라이버 등)에서 irq를 enable하고 나오는지를 확인하기 위해 경고 출력을 위한 디버그 코드이다.
    • 커널이 초기 설정 중에는 인터럽트가 마스크되어 동작하지 않고 있는데 갑자기 커널 파라미터로 인해 인터럽트가 발생되면 안되기 때문에 이를 확인하기 위함이다.
  • 코드 라인 34~48에서 파싱한 결과가 에러인 경우 에러 메시지를 출력하고 리턴한다. 성공인 경우  다음 토큰을 위해 루프를 계속 진행한다.

 

do_early_param()

init/main.c

/* Check for early params. */
static int __init do_early_param(char *param, char *val, 
                                 const char *unused, void *arg)
{                
        const struct obs_kernel_param *p;

        for (p = __setup_start; p < __setup_end; p++) {
                if ((p->early && parameq(param, p->str)) ||
                    (strcmp(param, "console") == 0 &&
                     strcmp(p->str, "earlycon") == 0)
                ) {
                        if (p->setup_func(val) != 0)
                                pr_warn("Malformed early option '%s'\n", param);
                }
        }
        /* We accept everything at this stage. */
        return 0;
}

다음 조건에 해당되는 early 커널 파라미터를 발견하면 @val 값 인수를 가지고 해당 커널 파라미터에 등록된 함수를 호출한다.

  • 요청한 커멘드 라인 파라미터가 early 커널 셋업 파라미터와 매치된 경우
    • 예) “earlyprintk”
  • 요청한 커멘드 라인 파라미터가 “console”로 시작한 경우
    • 예) “console=pl011”
  • early_param(“earlycon”)으로 등록한 셋업 함수
    • 현재 param_setup_earlycon() 함수 하나만 사용되고 있다.

 

  • 코드 라인 6에서 __setup_start 주소 영역 부터 __setup_end 주소 영역까지에 여러 개의 커널 파라미터인 obs_kernel_param 구조체가 있는데 순서대로 검색하여 p를 대입한다.
  • 코드 라인 7~10에서 p->early 항목이 설정되어 있으면서 p->str이 인수로 받은 param 문자열과 같은 경우이거나 p->str이 “earlycon”이면서 인수로 받은 param 문자열이 “console”인 경우
  • 코드 라인 11~12에서 p->setup_func()을 호출하여 에러가 있는 경우 경고 메시지를 출력한다.

 


디바이스 트리를 통한 파라미터 지정

디바이스 트리를 통해 파라미터 및 earlycon을 지정할 수 있다.

 

다음과 같이 DTB의 chosen 노드에 stdout-path를 지정하여 earlycon에 사용할 수 있고, bootargs를 통해 커멘드 라인 파라미터를 지정할 수 있다.

        chosen {
                bootargs = "console=ttyS0,115200n8 earlyprintk";
                stdout-path = "serial0:115200n8";
        };

 

디바이스 트리를 통한 earlycon 지정

커멘드 라인 파라메터에서 “earlycon” 또는 “console”이 지정될 때 디바이스 트리의 chosen 노드에서 “stdout-path”를 early 콘솔 디바이스로 지정한다. “console” 뒤에 디바이스가 지정되면 안된다.

early_init_dt_scan_chosen_stdout()

drivers/of/fdt.c

int __init early_init_dt_scan_chosen_stdout(void)
{
        int offset;
        const char *p, *q, *options = NULL;
        int l;
        const struct earlycon_id **p_match;
        const void *fdt = initial_boot_params;

        offset = fdt_path_offset(fdt, "/chosen");
        if (offset < 0)
                offset = fdt_path_offset(fdt, "/chosen@0");
        if (offset < 0)
                return -ENOENT;

        p = fdt_getprop(fdt, offset, "stdout-path", &l);
        if (!p)
                p = fdt_getprop(fdt, offset, "linux,stdout-path", &l);
        if (!p || !l)
                return -ENOENT;

        q = strchrnul(p, ':');
        if (*q != '\0')
                options = q + 1;
        l = q - p;

        /* Get the node specified by stdout-path */
        offset = fdt_path_offset_namelen(fdt, p, l);
        if (offset < 0) {
                pr_warn("earlycon: stdout-path %.*s not found\n", l, p);
                return 0;
        }

        for (p_match = __earlycon_table; p_match < __earlycon_table_end;
             p_match++) {
                const struct earlycon_id *match = *p_match;

                if (!match->compatible[0])
                        continue;

                if (fdt_node_check_compatible(fdt, offset, match->compatible))
                        continue;

                if (of_setup_earlycon(match, offset, options) == 0)
                        return 0;
        }
        return -ENODEV;
}

이 함수는 CONFIG_SERIAL_EARLYCON이 설정되어 있는 경우 DTB에서 /chosen 노드의 stdout-path 속성에 지정된 compatible(디바이스명)을 알아와서 earlycon 테이블에 등록한 모든 earlycon 디바이스의 이름과 같은 디바이스의 설정 함수를 호출한다.

  • 코드 라인 9~13에서 “/chosen” 또는 “/chosen@0” 노드를 검색한다.
  • 코드 라인 15~19에서 발견한 노드에서 “stdout-path” 또는 “linux,stdout-path” 속성을 검색한다.
  • 코드 라인 21~31에서 발견한 속성에서 디바이스명과 옵션을 분리한다.
    • 예) “serial0:115200n8” 에서 offset은 “serial0″를 의미하고, options는 “115200n8″을 의미한다.
  • 코드 라인33~38에서 __earlycon_of_table 주소 부터 compatible[0]이 있는 동안 검색한다.
  • 코드 라인40~41에서 compatible 속성 값에서 디바이스명(match->compatible)이 매치되지 않으면 skip 한다.
    • fdt_node_check_compatible()
      • compatible 속성 값에서 문자열 비교: 0=match, 1=non match, 길이=”compatible” 속성이 발견되지 않는 경우
  • 코드 라인 43~44에서 early console 디바이스를 셋업한다.
    • match->data에는 디바이스의 setup 함수 주소가 담겨있다.
      • rpi2 예) match->data = pl011_early_console_setup()

 

예) DTB용 콘솔 디바이스 드라이버 소스 – earlycon_of_table 섹션에 저장된다.

  • OF_EARLYCON_DECLARE(pl011, “arm,pl011”, pl011_early_console_setup);

 

of_setup_earlycon()

drivers/tty/serial/earlycon.c

int __init of_setup_earlycon(const struct earlycon_id *match,
                             unsigned long node,
                             const char *options)
{
        int err;
        struct uart_port *port = &early_console_dev.port;
        const __be32 *val;
        bool big_endian;
        u64 addr;

        spin_lock_init(&port->lock);
        port->iotype = UPIO_MEM;
        addr = of_flat_dt_translate_address(node);
        if (addr == OF_BAD_ADDR) {
                pr_warn("[%s] bad address\n", match->name);
                return -ENXIO;
        }
        port->mapbase = addr;

        val = of_get_flat_dt_prop(node, "reg-offset", NULL);
        if (val)
                port->mapbase += be32_to_cpu(*val);
        port->membase = earlycon_map(port->mapbase, SZ_4K);

        val = of_get_flat_dt_prop(node, "reg-shift", NULL);
        if (val)
                port->regshift = be32_to_cpu(*val);
        big_endian = of_get_flat_dt_prop(node, "big-endian", NULL) != NULL ||
                (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) &&
                 of_get_flat_dt_prop(node, "native-endian", NULL) != NULL);
        val = of_get_flat_dt_prop(node, "reg-io-width", NULL);
        if (val) {
                switch (be32_to_cpu(*val)) {
                case 1:
                        port->iotype = UPIO_MEM;
                        break;
                case 2:
                        port->iotype = UPIO_MEM16;
                        break;
                case 4:
                        port->iotype = (big_endian) ? UPIO_MEM32BE : UPIO_MEM32;
                        break;
                default:
                        pr_warn("[%s] unsupported reg-io-width\n", match->name);
                        return -EINVAL;
                }
        }

        val = of_get_flat_dt_prop(node, "current-speed", NULL);
        if (val)
                early_console_dev.baud = be32_to_cpu(*val);

        val = of_get_flat_dt_prop(node, "clock-frequency", NULL);
        if (val)
                port->uartclk = be32_to_cpu(*val);

        if (options) {
                early_console_dev.baud = simple_strtoul(options, NULL, 0);
                strlcpy(early_console_dev.options, options,
                        sizeof(early_console_dev.options));
        }
        earlycon_init(&early_console_dev, match->name);
        err = match->setup(&early_console_dev, options);
        if (err < 0)
                return err;
        if (!early_console_dev.con->write)
                return -ENODEV;

        register_console(early_console_dev.con);
        return 0;
}

디바이스 트리를 통해 early 콘솔 디바이스 속성 값들을 읽어온 후 디바이스의 설정 함수를 호출한 후 콘솔 디바이스로 등록한다.

  • 코드 라인 12~18에서 레지스터 주소를 알아온다.
    • UPIO_MEM
      • 유저 스페이스 노출가능한 mmio(주소 지정된 io)로 처리 bit 및 endian이 지정되지 않은 driver-specific mmio이다.
      • 참고로 8250은 8bit 리틀 엔디안 이고 pl011은 16bit 리틀 엔디안이다.
  • 코드 라인 20~22에서 “reg-offset” 속성이 있는 경우 해당 값 만큼 offset을 주소에 더한다.
  • 코드 라인 23에서 early 콘솔 디바이스를 4K 페이지만큼 매핑한다.
  • 코드 라인 25~27에서 “reg-shift” 속성이 있는 경우 regshift 값에 대입해둔다.
  • 코드 라인 28~30에서 빅 엔디안 요청이 있는지 여부를 확인한다. 다음 2가지 경우로 판단한다.
    • “big-endian” 속성이 있는 경우
    • cpu가 big-endian으로 동작하면서 “native-endian” 속성이 있는 경우
  • 코드 라인 31~47에서 콘솔 버스 크기를 알아와서 크기에 따라 다음과 같이 포트 타입을 지정한다.
    • 1 바이트 크기인 경우 포트 타입을 UPIO_MEM으로 한다.
    • 2 바이트 크기인 경우 포트 타입을 UPIO_MEM16으로 한다.
    • 4 바이트 크기인 경우 포트 타입을 UPIO_MEM32BE 또는 UPIO_MEM32로 한다.
  • 코드 라인 49~51에서 “current-speed” 값을 읽어 시리얼 속도를 지정한다.
  • 코드 라인 53~55에서 “clock-frequency” 값을 읽어 클럭 값을 지정한다.
  • 코드 라인 57~61에서 콘솔 디바이스에 옵션(콘솔명 + ‘:’ + 옵션)이 지정된 경우 시리얼 속도를 지정하고, 옵션도 지정해둔다.
  • 코드 라인 62에서 early 콘솔 디바이스의 이름과 데이터를 지정한 후, 설정 정보를 출력한다.
    • 예) earlycon: pl11 at MMIO 0x0000000009000000 (options ”)
  • 코드 라인 63~65에서 매치된 디바이스의 설정 함수를 호출한다.
  • 코드 라인 66~67에서 early 콘솔 드라이버에 출력 후크 함수가 구현되지 않은 경우 -ENODEV 에러를 반환한다.
  • 코드 라인 69에서 콘솔 디바이스로 등록한다.

 

참고

 

setup_machine_tags()

arch 번호로 태그테이블에서 machine을 검색하여 machine_desc 구조체 포인터를 찾고 ATAG를 디바이스 트리 구조로 변경한다.

setup_machine_tags

 

setup_machine_tags()

  • for_each_machine_desc()
    • __arch_info_begin ~ __arch_info_end 영역에 위치한 machine_desc 구조체 배열에서 머신 번호가 같은 경우를 찾는다.
    • machine_desc 구조체 배열은 .arch.info.init 섹션에 위치한다.
  • 만일 machine을 검색하여 찾지 못한 경우 machine table을 덤프하고 정지한다.
  • CONFIG_DEPRECATED_PARAM_STRUCT
    • ATAG 사용 하기 전에는 PARAM_STRUCT를 사용했다.
    •  convert_to_tag_list()
      • 태그의 처음이 ATAG_CORE가 아니면 PARAM_STRUCT 방식이라고 판단하여 ATAG 구조로 변환한다.
  • 처음 태그가 ATAG_CORE가 아닌 경우 “Warning: Neither atags nor dtb found” 경고 메시지를  출력하고 default 태그 구조체를 사용한다.
  • fixup 콜백함수가 null이 아닌 경우 fixup 콜백 함수를 수행한다.
    • 펌웨어에 문제가 있는 경우를 패치하기 위한 함수가 존재하는 경우 호출
    • 예) mach-msm/board-msm7x30.c – msm7x30_fixup() 참고
  • 태그가 ATAG_CORE 인 경우
    • 물리 메모리 사이즈가 이미 존재하는 경우 태그 정보를 무시하기 위해 squash_mem_tags()를 호출하여 ATAG_MEM을 ATAG_NONE으로 변경한다.
      • memblock_phys_mem_size = memblock.memory.total_size
    • save_atags()
      • 전역 변수 atags_copy 문자열 배열에 태그를 저장
    • parse_tags()
      • __tagtable_begin 부터 __tagtable_end 위치에 존재하는 태그 테이블에서 하나 씩 비교하여 동일한 태그인 경우 해당 태그의 parse 루틴을 호출한 후 리턴한다.
        • 태그 테이블은 .taglist.init 섹션에 위치한다.
      • parsing이 실패하면 “Ignoring unrecognised tag”라고 경고 출력한다.
      • 각 태그에 대한 파싱 함수 목록
        • ATAG_CORE: parse_tag_core()
        • ATAG_MEM: parse_tag_mem32()
        • ATAG_CMDLINE: parse_tag_cmdline
        • ATAG_INITRD: parse_tag_initrd()
        • ATAG_INITRD2, parse_tag_initrd2()
        • ATAG_VIDEOTEXT: parse_tag_videotext()
        • ATAG_RAMDIST: parse_tag_ramdisk()
        • ATAG_SERAIL: parse_tag_serialnr()
        • ATAG_REVISION: parse_tag_revision()
  • 마지막으로 전역 변수 boot_command_line에 default_cmd_line 값을 대입한다.
    • default_cmd_line은 컴파일 시 초기 설정된 값이 있고 커널 파라메터 설정에 따라 parse_tag_cmdline()을 수행하고 난 후 변경될 수 있다.
const struct machine_desc * __init
setup_machine_tags(phys_addr_t __atags_pointer, unsigned int machine_nr)
{
        struct tag *tags = (struct tag *)&default_tags;
        const struct machine_desc *mdesc = NULL, *p;
        char *from = default_command_line;

        default_tags.mem.start = PHYS_OFFSET;

        /*
         * locate machine in the list of supported machines.
         */
        for_each_machine_desc(p)
                if (machine_nr == p->nr) {
                        pr_info("Machine: %s\n", p->name);
                        mdesc = p;
                        break;
                }

        if (!mdesc) {
                early_print("\nError: unrecognized/unsupported machine ID"
                            " (r1 = 0x%08x).\n\n", machine_nr);
                dump_machine_table(); /* does not return */
        }

        if (__atags_pointer)
                tags = phys_to_virt(__atags_pointer);
        else if (mdesc->atag_offset)
                tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);

#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
        /*
         * If we have the old style parameters, convert them to
         * a tag list.
         */
        if (tags->hdr.tag != ATAG_CORE)
                convert_to_tag_list(tags);
#endif
        if (tags->hdr.tag != ATAG_CORE) {
                early_print("Warning: Neither atags nor dtb found\n");
                tags = (struct tag *)&default_tags;
        }

        if (mdesc->fixup)
                mdesc->fixup(tags, &from);

        if (tags->hdr.tag == ATAG_CORE) {
                if (memblock_phys_mem_size())
                        squash_mem_tags(tags);
                save_atags(tags);
                parse_tags(tags);
        }

        /* parse_early_param needs a boot_command_line */
        strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

        return mdesc;
}

 

ATAG용 Machine 정보

MACHINE_START()

  • ATAG용 machine_desc 구조체 선언 매크로
    • ATAG용에서는 nr로 검색하므로 nr 값이 중요하다.
      • arm용 머신 번호는 arch/arm/tools/mach-types 화일을 참고한다.
    • DTB용은 DT_MACHINE_START() 매크로를 사용하고 name으로 검색한다.
  • __used를 사용하여 이 객체가 참조되지 않아도 컴파일러가 제거하지 않도록 한다.
  • MACHINE_END()와 쌍으로 사용한다.

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

/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#define MACHINE_START(_type,_name)                      \
static const struct machine_desc __mach_desc_##_type    \
 __used                                                 \
 __attribute__((__section__(".arch.info.init"))) = {    \
        .nr             = MACH_TYPE_##_type,            \
        .name           = _name,

#define MACHINE_END                             \
};

 

machine_desc 구조체

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

struct machine_desc {
        unsigned int            nr;             /* architecture number  */
        const char              *name;          /* architecture name    */
        unsigned long           atag_offset;    /* tagged list (relative) */
        const char *const       *dt_compat;     /* array of device tree
                                                 * 'compatible' strings */

        unsigned int            nr_irqs;        /* number of IRQs */

#ifdef CONFIG_ZONE_DMA
        phys_addr_t             dma_zone_size;  /* size of DMA-able area */
#endif

        unsigned int            video_start;    /* start of video RAM   */
        unsigned int            video_end;      /* end of video RAM     */

        unsigned char           reserve_lp0 :1; /* never has lp0        */
        unsigned char           reserve_lp1 :1; /* never has lp1        */
        unsigned char           reserve_lp2 :1; /* never has lp2        */
        enum reboot_mode        reboot_mode;    /* default restart mode */
        unsigned                l2c_aux_val;    /* L2 cache aux value   */
        unsigned                l2c_aux_mask;   /* L2 cache aux mask    */
        void                    (*l2c_write_sec)(unsigned long, unsigned);
        struct smp_operations   *smp;           /* SMP operations       */
        bool                    (*smp_init)(void);
        void                    (*fixup)(struct tag *, char **);
        void                    (*dt_fixup)(void);
        void                    (*init_meminfo)(void);
        void                    (*reserve)(void);/* reserve mem blocks  */
        void                    (*map_io)(void);/* IO mapping function  */
        void                    (*init_early)(void);
        void                    (*init_irq)(void);
        void                    (*init_time)(void);
        void                    (*init_machine)(void);
        void                    (*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
        void                    (*handle_irq)(struct pt_regs *);
#endif
        void                    (*restart)(enum reboot_mode, const char *);
};

 

라즈베리파이 1 & 2 MACHINE 구조체 선언

arch/arm/mach-bcm2709/bcm2709.c

static const char * const bcm2709_compat[] = {
        "brcm,bcm2709",
        "brcm,bcm2708", /* Could use bcm2708 in a pinch */
        NULL
};

MACHINE_START(BCM2709, "BCM2709")
    /* Maintainer: Broadcom Europe Ltd. */
#ifdef CONFIG_SMP
        .smp            = smp_ops(bcm2709_smp_ops),
#endif
        .map_io = bcm2709_map_io,
        .init_irq = bcm2709_init_irq,
        .init_time = bcm2709_timer_init,
        .init_machine = bcm2709_init,
        .init_early = bcm2709_init_early,
        .reserve = board_reserve,
        .restart        = bcm2709_restart,
        .dt_compat = bcm2709_compat,
MACHINE_END

MACHINE_START(BCM2708, "BCM2709")
    /* Maintainer: Broadcom Europe Ltd. */
#ifdef CONFIG_SMP
        .smp            = smp_ops(bcm2709_smp_ops),
#endif
        .map_io = bcm2709_map_io,
        .init_irq = bcm2709_init_irq,
        .init_time = bcm2709_timer_init,
        .init_machine = bcm2709_init,
        .init_early = bcm2709_init_early,
        .reserve = board_reserve,
        .restart        = bcm2709_restart,
        .dt_compat = bcm2709_compat,
MACHINE_END

 

ATAG Parsing

tag 및 tagtable 구조체

arch/arm/include/uapi/asm/setup.h

struct tag {
        struct tag_header hdr;
        union {
                struct tag_core         core;
                struct tag_mem32        mem;
                struct tag_videotext    videotext;
                struct tag_ramdisk      ramdisk;
                struct tag_initrd       initrd;
                struct tag_serialnr     serialnr;
                struct tag_revision     revision;
                struct tag_videolfb     videolfb;
                struct tag_cmdline      cmdline;

                /*
                 * Acorn specific
                 */
                struct tag_acorn        acorn;

                /*
                 * DC21285 specific
                 */
                struct tag_memclk       memclk;
        } u;
};

struct tagtable {
        __u32 tag;
        int (*parse)(const struct tag *);
};

 

 

parse_tag()

  •  태그 영역에 저장된 태그들 중 아키텍처 번호가 같은 태그들에 연결된 parse 콜백 함수를  호출한다.
/*
 * Scan the tag table for this tag, and call its parse function.
 * The tag table is built by the linker from all the __tagtable
 * declarations.
 */
static int __init parse_tag(const struct tag *tag)
{
        extern struct tagtable __tagtable_begin, __tagtable_end;
        struct tagtable *t;

        for (t = &__tagtable_begin; t < &__tagtable_end; t++)
                if (tag->hdr.tag == t->tag) {
                        t->parse(tag);
                        break;
                }

        return t < &__tagtable_end;
}

 

 

parse_tag_core()

  • 전역 변수 root_mountflags에 루트 마운트 플래그 속성에서 MS_RDONLY를 제거하고 저장
  • 전역 변수 ROOT_DEV에 디바이스 번호를 저장한다.

arch/arm/kernel/atags_parse.c

static int __init parse_tag_core(const struct tag *tag)
{
        if (tag->hdr.size > 2) {
                if ((tag->u.core.flags & 1) == 0)
                        root_mountflags &= ~MS_RDONLY;
                ROOT_DEV = old_decode_dev(tag->u.core.rootdev);
        }
        return 0;
}

__tagtable(ATAG_CORE, parse_tag_core);

 

parse_tag_mem32()

  • arm_add_memory() 함수를 사용하여 메모리 영역을 추가한다.
static int __init parse_tag_mem32(const struct tag *tag)
{
        return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
}

__tagtable(ATAG_MEM, parse_tag_mem32);

 

parse_tag_videotext()

  • screen_info 구조체에 파라메터 값들을 저장한다.
#if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE)
static int __init parse_tag_videotext(const struct tag *tag)
{
        screen_info.orig_x            = tag->u.videotext.x;
        screen_info.orig_y            = tag->u.videotext.y;
        screen_info.orig_video_page   = tag->u.videotext.video_page;
        screen_info.orig_video_mode   = tag->u.videotext.video_mode;
        screen_info.orig_video_cols   = tag->u.videotext.video_cols;
        screen_info.orig_video_ega_bx = tag->u.videotext.video_ega_bx;
        screen_info.orig_video_lines  = tag->u.videotext.video_lines;
        screen_info.orig_video_isVGA  = tag->u.videotext.video_isvga;
        screen_info.orig_video_points = tag->u.videotext.video_points;
        return 0;
}

__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
#endif

 

parse_tag_ramdisk()

  • 전역 변수 rd_image_start에 램디스크 시작 주소를 저장한다.
  • 전역 변수 rd_doload와 rd_prompt에 플래그 상태를 저장한다.
  • 전역 변수 rd_size에 램디스크 사이즈를 저장한다.
#ifdef CONFIG_BLK_DEV_RAM
static int __init parse_tag_ramdisk(const struct tag *tag)
{
        extern int rd_size, rd_image_start, rd_prompt, rd_doload;

        rd_image_start = tag->u.ramdisk.start;
        rd_doload = (tag->u.ramdisk.flags & 1) == 0;
        rd_prompt = (tag->u.ramdisk.flags & 2) == 0;

        if (tag->u.ramdisk.size)
                rd_size = tag->u.ramdisk.size;

        return 0;
}

__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
#endif

 

parse_tag_serialnr()

  • 전역 변수 system_serial_low와 system_serial_high에 시리얼 low 값과 high 값을 저장한다.
static int __init parse_tag_serialnr(const struct tag *tag)
{
        system_serial_low = tag->u.serialnr.low;
        system_serial_high = tag->u.serialnr.high;
        return 0;
}

__tagtable(ATAG_SERIAL, parse_tag_serialnr);

 

parse_tag_revision()

  •  전역 변수 system_rev에 리비전 정보를 저장한다.
static int __init parse_tag_revision(const struct tag *tag)
{
        system_rev = tag->u.revision.rev;
        return 0;
}

__tagtable(ATAG_REVISION, parse_tag_revision);

 

parse_tag_cmdline()

  • 다음 3가지 case에 대해 수행한다.
    • CONFIG_CMDLINE_EXTEND
      • default_command_line에 ATAG가 전달한 cmdline을 추가한다.
    • CONFIG_CMDLINE_FORCE
      • ATAG가 전달한 cmdline을 무시하고 default_command_line을 사용한다.
    • cmdline 관련 옵션이 없는 경우
      • default_command_line에 ATAG가 전달한 cmdline을 겹쳐 쓴다.
static int __init parse_tag_cmdline(const struct tag *tag)
{
#if defined(CONFIG_CMDLINE_EXTEND)
        strlcat(default_command_line, " ", COMMAND_LINE_SIZE);
        strlcat(default_command_line, tag->u.cmdline.cmdline,
                COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
        pr_warn("Ignoring tag cmdline (using the default kernel command line)\n");
#else
        strlcpy(default_command_line, tag->u.cmdline.cmdline,
                COMMAND_LINE_SIZE);
#endif
        return 0;
}

__tagtable(ATAG_CMDLINE, parse_tag_cmdline);

 

기타 함수

arm_add_memory()

arch/arm/kernel/setup.c

int __init arm_add_memory(u64 start, u64 size)
{
        u64 aligned_start;

        /*   
         * Ensure that start/size are aligned to a page boundary.
         * Size is rounded down, start is rounded up.
         */
        aligned_start = PAGE_ALIGN(start);
        if (aligned_start > start + size)
                size = 0; 
        else 
                size -= aligned_start - start;

#ifndef CONFIG_ARCH_PHYS_ADDR_T_64BIT
        if (aligned_start > ULONG_MAX) {
                pr_crit("Ignoring memory at 0x%08llx outside 32-bit physical address space\n",
                        (long long)start);
                return -EINVAL;
        }

        if (aligned_start + size > ULONG_MAX) {
                pr_crit("Truncating memory at 0x%08llx to fit in 32-bit physical address space\n",
                        (long long)start);
                /*   
                 * To ensure bank->start + bank->size is representable in
                 * 32 bits, we use ULONG_MAX as the upper limit rather than 4GB.
                 * This means we lose a page after masking.
                 */
                size = ULONG_MAX - aligned_start;
        }    
#endif

        if (aligned_start < PHYS_OFFSET) {
                if (aligned_start + size <= PHYS_OFFSET) {
                        pr_info("Ignoring memory below PHYS_OFFSET: 0x%08llx-0x%08llx\n",
                                aligned_start, aligned_start + size);
                        return -EINVAL;
                }

                pr_info("Ignoring memory below PHYS_OFFSET: 0x%08llx-0x%08llx\n",
                        aligned_start, (u64)PHYS_OFFSET);

                size -= PHYS_OFFSET - aligned_start;
                aligned_start = PHYS_OFFSET;
        }

        start = aligned_start;
        size = size & ~(phys_addr_t)(PAGE_SIZE - 1);

        /*   
         * Check whether this memory region has non-zero size or
         * invalid node number.
         */
        if (size == 0)
                return -EINVAL;

        memblock_add(start, size);
        return 0;
}
  • aligned_start = PAGE_ALIGN(start);
    • start 주소에 대해 4K round up 한다.
  • if (aligned_start > start + size)
    • 4K round up 한 aligned_start 주소가 start + size를 초과하는 경우 size를 0으로 변경하고 그렇지 않은 경우 4K round up으로 인해 발생한 그 차이만큼 size에서 뺀다.
    • 결국 4K align 되어 남는 하위 메모리는 버리게 된다.
    • 예) arm_add_memory(0x1234_5678, 0x1000_0000)
      • 물리 메모리 주소 0x1234_5678 부터 256M 크기의 메모리를 추가하라는 요청
      • 수행 후 물리 메모리 주소 0x1234_5000 부터 (256M – 0x678) 크기의 메모리를 추가
  • CONFIG_ARCH_PHYS_ADDR_T_64BIT
    • LPAE 설정 시 사용된다.
  • if (aligned_start + size > ULONG_MAX) {
    • 추가할 메모리 영역이 32비트 주소의 끝을 초과하는 경우 size를 32비트 이내에 들어갈 수 있도록 조정한다.
    • 예) arm_add_memory(0xf000_0000, 0x2000_0000)
      • 물리 메모리 주소 0xf000_0000 부터 512M 크기의 메모리를 추가하라는 요청
      • 수행 후 물리 메모리 주소 0xf000_0000 부터 0x0fff_ffff 크기의 메모리를 추가
  • if (aligned_start < PHYS_OFFSET) {
    • 요청한 시작 주소가 물리 메모리 시작 주소보다 작은 경우
  • if (aligned_start + size <= PHYS_OFFSET) {
    • size 까지 합친 요청 영역이 물리 메모리 시작 주소보다 작아 범위를 아예 벗어난 경우 에러를 경고하고 함수를 리턴한다.
    • 요청한 구간이 물리 메모리 이하에서 시작하였다는 것을 경고 출력하고 물리 메모리 시작 주소 이하의 요청 메모리를 제거한 범위를 시작 주소와 사이즈를 재 조정한다.
    • 예) arm_add_memory(0x1F00_0000, 0x1000_0000) 이 때 PHYS_OFFSET=0x2000_0000
      • aligned_start = 0x2000_0000
      • size = 0x0f00_000
  • size = size & ~(phys_addr_t)(PAGE_SIZE – 1);
    • 사이즈 또한 align 되어 있지 않으면 round down 하여 버린다.
    • 예) arm_add_memory(0x2000_0000, 0x1234_5678)
      • size = 0x1234_5000
  •  if (size == 0)
    • 추가 할 사이즈가 0이면 함수를 빠져나간다.
  • memblock_add(start, size);
    • memory memblock 에 메모리 영역을 추가한다.

 

참고

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( ) 함수를 사용하여 메모리 블록을 추가한다.

 

참고