해당 머신의 바뀐 메모리 정보를 위해 초기화를 수행한다. 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()

