해당 머신의 바뀐 메모리 정보를 위해 초기화를 수행한다. LPAE의 경우 phisical to virtual transalation이 필요하여 추가로 몇 개의 루틴들이 수행되어야 한다.
- mdesc→init_meminfo() 수행
- LPAE의 경우 추가로 다음 항목들 수행
- fixup_pv_table() 수행
- page table 수정
- 캐시 플러쉬
- 해당 CPU 아키텍처의 MMU에 페이지 테이블 설정 변경
- TTBR1 레지스터 재 설정
- BP 및 TLB 캐시 플러쉬
early_paging_init()
- CONFIG_ARM_LPAE가 설정된 경우 두 개의 구현된 함수 중 윗 부분 함수를 수행하고 그렇지 않은 경우 아랫 부분 함수를 수행한다.
- 머신 구조체의 init_meminfo 콜백 함수가 등록되어 있지 않은 경우 early하게 메모리 정보를 초기화(주로 패치 목적) 할 필요가 없는 것으로 간주하고 빠져나간다.
- 대부분의 머신들은 이 init_meminfo 콜백 함수를 수행하지 않는다.
- init_meminfo 콜백 함수를 사용하는 머신은 다음의 소스에서 찾을 수 있었다.
- arch/arm/mach-keystone/keystone.c
- DT_MACHINE_START(KEYSTONE, “Keystone”)
- .init_meminfo = keystone_init_meminfo
- 참고: ARM: keystone: Switch over to coherent memory address space
- 2015년 4월 커널 v4.1-rc1에서 init_meminfo는 pv_fixup이라는 명칭으로 바뀌었다.
- 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를 설정한다.
- ARMv7:
- 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() 함수가 연결되어 있다.
- processor->switch_mm() 콜백 함수를 호출한다.
- MULTI_CPU를 사용하지 않는 경우
- 각 아키텍처 이름에 맞게 호출 함수가 존재한다.
- 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()