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

댓글 남기기