메모리 타입별로 페이지 테이블을 구성하는 엔트리에 대한 속성을 구성한다.
커맨드라인 파라메터
- “cachepolicy=”
- 캐시 속성을 선택할 수 있다.
- “uncached”, “buffered”, “writethrough”, “writeback”, “writealloc” 중 하나를 지정할 수 있다.
- 관련 함수는 early_cachepolicy()
- “nocache”
- nocache 옵션은 deprecated되었고 buffered로 동작시킨다.
- 관련 함수는 early_nocache()
- “nowb”
- nowb 옵션은 deprecated되었고 uncached로 동작시킨다.
- 관련 함수는 early_nowrite()
- “ecc=”
- “on”, “off”를 사용할 수 있다.
- 관련 함수는 early_ecc()
build_mem_type_table()
arch/arm/mm/mmu.c
/* * Adjust the PMD section entries according to the CPU in use. */ static void __init build_mem_type_table(void) { struct cachepolicy *cp; unsigned int cr = get_cr(); pteval_t user_pgprot, kern_pgprot, vecs_pgprot; pteval_t hyp_device_pgprot, s2_pgprot, s2_device_pgprot; int cpu_arch = cpu_architecture(); int i; if (cpu_arch < CPU_ARCH_ARMv6) { #if defined(CONFIG_CPU_DCACHE_DISABLE) if (cachepolicy > CPOLICY_BUFFERED) cachepolicy = CPOLICY_BUFFERED; #elif defined(CONFIG_CPU_DCACHE_WRITETHROUGH) if (cachepolicy > CPOLICY_WRITETHROUGH) cachepolicy = CPOLICY_WRITETHROUGH; #endif } if (cpu_arch < CPU_ARCH_ARMv5) { if (cachepolicy >= CPOLICY_WRITEALLOC) cachepolicy = CPOLICY_WRITEBACK; ecc_mask = 0; } if (is_smp()) { if (cachepolicy != CPOLICY_WRITEALLOC) { pr_warn("Forcing write-allocate cache policy for SMP\n"); cachepolicy = CPOLICY_WRITEALLOC; } if (!(initial_pmd_value & PMD_SECT_S)) { pr_warn("Forcing shared mappings for SMP\n"); initial_pmd_value |= PMD_SECT_S; } }
- unsigned int cr = get_cr();
- cr <- read SCTLR
- cachepolicy
- 각 아키텍처가 지원하는 캐시 정책이 담긴다.
- rpi2: CPOLICY_WRITEALLOC
- if (is_smp()) {
- SMP 시스템인 경우
- cachepolicy = CPOLICY_WRITEALLOC
- initial_pmd_value |= PMD_SECT_S
- rpi2: PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | PMD_SECT_AF | PMD_SECT_TEX(1) | PMD_SECT_CACHEABLE | PMD_SECT_BUFFERABLE | PMD_SECT_S
- SMP 시스템인 경우
/* * Strip out features not present on earlier architectures. * Pre-ARMv5 CPUs don't have TEX bits. Pre-ARMv6 CPUs or those * without extended page tables don't have the 'Shared' bit. */ if (cpu_arch < CPU_ARCH_ARMv5) for (i = 0; i < ARRAY_SIZE(mem_types); i++) mem_types[i].prot_sect &= ~PMD_SECT_TEX(7); if ((cpu_arch < CPU_ARCH_ARMv6 || !(cr & CR_XP)) && !cpu_is_xsc3()) for (i = 0; i < ARRAY_SIZE(mem_types); i++) mem_types[i].prot_sect &= ~PMD_SECT_S; /* * ARMv5 and lower, bit 4 must be set for page tables (was: cache * "update-able on write" bit on ARM610). However, Xscale and * Xscale3 require this bit to be cleared. */ if (cpu_is_xscale() || cpu_is_xsc3()) { for (i = 0; i < ARRAY_SIZE(mem_types); i++) { mem_types[i].prot_sect &= ~PMD_BIT4; mem_types[i].prot_l1 &= ~PMD_BIT4; } } else if (cpu_arch < CPU_ARCH_ARMv6) { for (i = 0; i < ARRAY_SIZE(mem_types); i++) { if (mem_types[i].prot_l1) mem_types[i].prot_l1 |= PMD_BIT4; if (mem_types[i].prot_sect) mem_types[i].prot_sect |= PMD_BIT4; } }
- if (cpu_arch < CPU_ARCH_ARMv5)
- ARMv5 미만 아키텍처인 경우 mem_types[]에서 PMD_SECT_TEX 비트들을 제거한다.
- TEX(Type Extension)을 지원하지 않는 아키텍처이다.
- if ((cpu_arch < CPU_ARCH_ARMv6 || !(cr & CR_XP)) && !cpu_is_xsc3())
- ARMv6 미만 아키텍처이거나 xscale CPU가 아니면서 XP(Extended Page)를 지원하지 않으면 mem_types[]에서 PMD_SECT_S 비트를 제거한다.
- ARMv6 아키텍처 부터 Share 플래그를 지원한다.
- ARMv5 이하의 아키텍처는 mem_types[]에서 PMD_BIT4 플래그를 삭제하는데 xscale와 scale3에서는 PMD_BIT4 플래그를 제거해야 한다.
/* * Mark the device areas according to the CPU/architecture. */ if (cpu_is_xsc3() || (cpu_arch >= CPU_ARCH_ARMv6 && (cr & CR_XP))) { if (!cpu_is_xsc3()) { /* * Mark device regions on ARMv6+ as execute-never * to prevent speculative instruction fetches. */ mem_types[MT_DEVICE].prot_sect |= PMD_SECT_XN; mem_types[MT_DEVICE_NONSHARED].prot_sect |= PMD_SECT_XN; mem_types[MT_DEVICE_CACHED].prot_sect |= PMD_SECT_XN; mem_types[MT_DEVICE_WC].prot_sect |= PMD_SECT_XN; /* Also setup NX memory mapping */ mem_types[MT_MEMORY_RW].prot_sect |= PMD_SECT_XN; } if (cpu_arch >= CPU_ARCH_ARMv7 && (cr & CR_TRE)) { /* * For ARMv7 with TEX remapping, * - shared device is SXCB=1100 * - nonshared device is SXCB=0100 * - write combine device mem is SXCB=0001 * (Uncached Normal memory) */ mem_types[MT_DEVICE].prot_sect |= PMD_SECT_TEX(1); mem_types[MT_DEVICE_NONSHARED].prot_sect |= PMD_SECT_TEX(1); mem_types[MT_DEVICE_WC].prot_sect |= PMD_SECT_BUFFERABLE; } else if (cpu_is_xsc3()) { /* * For Xscale3, * - shared device is TEXCB=00101 * - nonshared device is TEXCB=01000 * - write combine device mem is TEXCB=00100 * (Inner/Outer Uncacheable in xsc3 parlance) */ mem_types[MT_DEVICE].prot_sect |= PMD_SECT_TEX(1) | PMD_SECT_BUFFERED; mem_types[MT_DEVICE_NONSHARED].prot_sect |= PMD_SECT_TEX(2); mem_types[MT_DEVICE_WC].prot_sect |= PMD_SECT_TEX(1); } else { /* * For ARMv6 and ARMv7 without TEX remapping, * - shared device is TEXCB=00001 * - nonshared device is TEXCB=01000 * - write combine device mem is TEXCB=00100 * (Uncached Normal in ARMv6 parlance). */ mem_types[MT_DEVICE].prot_sect |= PMD_SECT_BUFFERED; mem_types[MT_DEVICE_NONSHARED].prot_sect |= PMD_SECT_TEX(2); mem_types[MT_DEVICE_WC].prot_sect |= PMD_SECT_TEX(1); } } else { /* * On others, write combining is "Uncached/Buffered" */ mem_types[MT_DEVICE_WC].prot_sect |= PMD_SECT_BUFFERABLE; }
아키텍처에 맞게 디바이스 속성을 설정한다.
- if (cpu_is_xsc3() || (cpu_arch >= CPU_ARCH_ARMv6 && (cr & CR_XP))) {
- 아키텍처가 free-scale 이거나 ARMv7 이상이면서 TRE=1 인경우
- rpi2의 경우 ARMv7이면서 SCTLR.TRE=1
- if (!cpu_is_xsc3()) {
- free-scale CPU가 아닌경우 execute-never 플래그를 설정한다.
- if (cpu_arch >= CPU_ARCH_ARMv7 && (cr & CR_TRE)) {
- 아키텍처가 ARMv7 이상이면서 TRE=1인 경우 TEX 비트를 설정한다. DEVICE_WC의 경우는 Buffrable로 고정시킨다.
/* * Now deal with the memory-type mappings */ cp = &cache_policies[cachepolicy]; vecs_pgprot = kern_pgprot = user_pgprot = cp->pte; s2_pgprot = cp->pte_s2; hyp_device_pgprot = mem_types[MT_DEVICE].prot_pte; s2_device_pgprot = mem_types[MT_DEVICE].prot_pte_s2; #ifndef CONFIG_ARM_LPAE /* * We don't use domains on ARMv6 (since this causes problems with * v6/v7 kernels), so we must use a separate memory type for user * r/o, kernel r/w to map the vectors page. */ if (cpu_arch == CPU_ARCH_ARMv6) vecs_pgprot |= L_PTE_MT_VECTORS; /* * Check is it with support for the PXN bit * in the Short-descriptor translation table format descriptors. */ if (cpu_arch == CPU_ARCH_ARMv7 && (read_cpuid_ext(CPUID_EXT_MMFR0) & 0xF) == 4) { user_pmd_table |= PMD_PXNTABLE; } #endif
- 전역 변수 vecs_pgprot, kern_pgprot, user_pgprot에 아키텍처에 해당하는 캐시 속성을 부여한다.
- rpi2: WBWA 캐시속성
- .pmd= PMD_SECT_WBWA
- PMD_SECT_TEX(1) | PMD_SECT_CACHEABLE | PMD_SECT_BUFFERABLE
- .pte=L_PTE_MT_WRITEALLOC(7)
- .pmd= PMD_SECT_WBWA
- rpi2: WBWA 캐시속성
- if (cpu_arch == CPU_ARCH_ARMv7 && (read_cpuid_ext(CPUID_EXT_MMFR0) & 0xF) == 4) {
- ARMv7이면서 MMFR0.vmsa가 4이면 PMD_PXNTABLE(bit 0)을 사용한다.
- rpi2: MMFR0.vmsa=5 (Cortex-A7)
- 2015년 12월 커널 v4.5-rc1에서 “== 4″를 “>= 4″로 수정함
/* * ARMv6 and above have extended page tables. */ if (cpu_arch >= CPU_ARCH_ARMv6 && (cr & CR_XP)) { #ifndef CONFIG_ARM_LPAE /* * Mark cache clean areas and XIP ROM read only * from SVC mode and no access from userspace. */ mem_types[MT_ROM].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE; mem_types[MT_MINICLEAN].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE; mem_types[MT_CACHECLEAN].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE; #endif /* * If the initial page tables were created with the S bit * set, then we need to do the same here for the same * reasons given in early_cachepolicy(). */ if (initial_pmd_value & PMD_SECT_S) { user_pgprot |= L_PTE_SHARED; kern_pgprot |= L_PTE_SHARED; vecs_pgprot |= L_PTE_SHARED; s2_pgprot |= L_PTE_SHARED; mem_types[MT_DEVICE_WC].prot_sect |= PMD_SECT_S; mem_types[MT_DEVICE_WC].prot_pte |= L_PTE_SHARED; mem_types[MT_DEVICE_CACHED].prot_sect |= PMD_SECT_S; mem_types[MT_DEVICE_CACHED].prot_pte |= L_PTE_SHARED; mem_types[MT_MEMORY_RWX].prot_sect |= PMD_SECT_S; mem_types[MT_MEMORY_RWX].prot_pte |= L_PTE_SHARED; mem_types[MT_MEMORY_RW].prot_sect |= PMD_SECT_S; mem_types[MT_MEMORY_RW].prot_pte |= L_PTE_SHARED; mem_types[MT_MEMORY_DMA_READY].prot_pte |= L_PTE_SHARED; mem_types[MT_MEMORY_RWX_NONCACHED].prot_sect |= PMD_SECT_S; mem_types[MT_MEMORY_RWX_NONCACHED].prot_pte |= L_PTE_SHARED; } }
아키텍처에 맞게 메모리 타입에대한 접근권한, Share, 캐시 속성을 결정한다.
- if (cpu_arch >= CPU_ARCH_ARMv6 && (cr & CR_XP)) {
- ARMv6 이상이면서 SCTLR.CR_XP가 있으면 MT_ROM, MT_MINICLEAN 및 MT_CACHECLEAN 타입에서 svc 모드에서 읽기만 가능하고 userspace에서는 access를 할 수 없도록 한다.
- rpi2: CR_XP(extended page) 기능이 있다.
- if (initial_pmd_value & PMD_SECT_S) {
- 섹션 shared 비트가 있는 경우 관련 타입의 share 속성을 설정한다.
/* * Non-cacheable Normal - intended for memory areas that must * not cause dirty cache line writebacks when used */ if (cpu_arch >= CPU_ARCH_ARMv6) { if (cpu_arch >= CPU_ARCH_ARMv7 && (cr & CR_TRE)) { /* Non-cacheable Normal is XCB = 001 */ mem_types[MT_MEMORY_RWX_NONCACHED].prot_sect |= PMD_SECT_BUFFERED; } else { /* For both ARMv6 and non-TEX-remapping ARMv7 */ mem_types[MT_MEMORY_RWX_NONCACHED].prot_sect |= PMD_SECT_TEX(1); } } else { mem_types[MT_MEMORY_RWX_NONCACHED].prot_sect |= PMD_SECT_BUFFERABLE; } #ifdef CONFIG_ARM_LPAE /* * Do not generate access flag faults for the kernel mappings. */ for (i = 0; i < ARRAY_SIZE(mem_types); i++) { mem_types[i].prot_pte |= PTE_EXT_AF; if (mem_types[i].prot_sect) mem_types[i].prot_sect |= PMD_SECT_AF; } kern_pgprot |= PTE_EXT_AF; vecs_pgprot |= PTE_EXT_AF; /* * Set PXN for user mappings */ user_pgprot |= PTE_EXT_PXN; #endif
Non-cacheable Normal 메모리에 대한 속성을 설정한다.
- rpi2: MT_MEMORY_RWX_NONCACHED 타입에 대해 Buffered 속성을 추가한다.
- LPAE를 사용하는 경우 Access Flag Fault 기능을 사용하기 위해 PTE_EXT_AF 비트를 추가한다.
- user_pgprot에 PXN 비트도 추가한다.
for (i = 0; i < 16; i++) { pteval_t v = pgprot_val(protection_map[i]); protection_map[i] = __pgprot(v | user_pgprot); } mem_types[MT_LOW_VECTORS].prot_pte |= vecs_pgprot; mem_types[MT_HIGH_VECTORS].prot_pte |= vecs_pgprot; pgprot_user = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | user_pgprot); pgprot_kernel = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | kern_pgprot); pgprot_s2 = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | s2_pgprot); pgprot_s2_device = __pgprot(s2_device_pgprot); pgprot_hyp_device = __pgprot(hyp_device_pgprot); mem_types[MT_LOW_VECTORS].prot_l1 |= ecc_mask; mem_types[MT_HIGH_VECTORS].prot_l1 |= ecc_mask; mem_types[MT_MEMORY_RWX].prot_sect |= ecc_mask | cp->pmd; mem_types[MT_MEMORY_RWX].prot_pte |= kern_pgprot; mem_types[MT_MEMORY_RW].prot_sect |= ecc_mask | cp->pmd; mem_types[MT_MEMORY_RW].prot_pte |= kern_pgprot; mem_types[MT_MEMORY_DMA_READY].prot_pte |= kern_pgprot; mem_types[MT_MEMORY_RWX_NONCACHED].prot_sect |= ecc_mask; mem_types[MT_ROM].prot_sect |= cp->pmd; switch (cp->pmd) { case PMD_SECT_WT: mem_types[MT_CACHECLEAN].prot_sect |= PMD_SECT_WT; break; case PMD_SECT_WB: case PMD_SECT_WBWA: mem_types[MT_CACHECLEAN].prot_sect |= PMD_SECT_WB; break; } pr_info("Memory policy: %sData cache %s\n", ecc_mask ? "ECC enabled, " : "", cp->policy); for (i = 0; i < ARRAY_SIZE(mem_types); i++) { struct mem_type *t = &mem_types[i]; if (t->prot_l1) t->prot_l1 |= PMD_DOMAIN(t->domain); if (t->prot_sect) t->prot_sect |= PMD_DOMAIN(t->domain); } }
- protection_map[]에 user_pgprot를 추가한다.
-
- user_pgprot
- rpi2: Share, WriteAlloc(0111)
- user_pgprot
- MT_LOW_VECTORS 및 MT_HIGH_VECTORS 타입에는 vecs_pgprot를 추가한다.
- vecs_pgprot
- rpi2: Share, WriteAlloc(0111)
- vecs_pgprot
- pgprot_user = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | user_pgprot);
- pgprot_user에 user_pgprot, Present 및 Young 비트를 추가한다.
- pgprot_kernel = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | kern_pgprot);
- pgprot_kernel에 kern_pgprot, Present, Young 및 Dirty 비트를 추가한다
- 각 메모리 타입에 맞는 비트를 추가로 설정한다.
- 마지막으로 각 타입의 속성에 도메인 속성을 추가한다.
구조체 및 전역 변수
cachepolicy 구조체
arch/arm/mm/mmu.c
struct cachepolicy { const char policy[16]; unsigned int cr_mask; pmdval_t pmd; pteval_t pte; pteval_t pte_s2; };
- 캐시 타입에 따른 pmd와 pte 엔트리의 속성값이 설정되어 있다.
- pte_s2의 경우 hyper visor를 사용하는 경우 사용되는 pte 대신 사용하는 값이다.
cache_policies[]
static struct cachepolicy cache_policies[] __initdata = { { .policy = "uncached", .cr_mask = CR_W|CR_C, .pmd = PMD_SECT_UNCACHED, .pte = L_PTE_MT_UNCACHED, .pte_s2 = s2_policy(L_PTE_S2_MT_UNCACHED), }, { .policy = "buffered", .cr_mask = CR_C, .pmd = PMD_SECT_BUFFERED, .pte = L_PTE_MT_BUFFERABLE, .pte_s2 = s2_policy(L_PTE_S2_MT_UNCACHED), }, { .policy = "writethrough", .cr_mask = 0, .pmd = PMD_SECT_WT, .pte = L_PTE_MT_WRITETHROUGH, .pte_s2 = s2_policy(L_PTE_S2_MT_WRITETHROUGH), }, { .policy = "writeback", .cr_mask = 0, .pmd = PMD_SECT_WB, .pte = L_PTE_MT_WRITEBACK, .pte_s2 = s2_policy(L_PTE_S2_MT_WRITEBACK), }, { .policy = "writealloc", .cr_mask = 0, .pmd = PMD_SECT_WBWA, .pte = L_PTE_MT_WRITEALLOC, .pte_s2 = s2_policy(L_PTE_S2_MT_WRITEBACK), } };
기타 전역 변수
arch/arm/mm/mmu.c
static unsigned int cachepolicy __initdata = CPOLICY_WRITEBACK; static unsigned int ecc_mask __initdata = 0; static unsigned long initial_pmd_value __initdata = 0; pgprot_t pgprot_user; pgprot_t pgprot_kernel; pgprot_t pgprot_hyp_device; pgprot_t pgprot_s2; pgprot_t pgprot_s2_device;
- cachepolicy
- 캐시 정책으로 이미 setup_arch() -> setup_processor() -> init_default_cache_policy() 함수에서 설정되었다.
- rpi2: 4(CPOLICY_WRITEALLOC)
- ecc_mask
- 플래그 변수로 early_ecc() 함수에 의해 on으로 설정될 수 있다.
- cmdline에서 “ecc=on”으로 early_ecc() 함수를 호출할 수 있다.
- pmd 엔트리의 PMD_PROTECTION(bit 9) 비트를 의미한다.
- 플래그 변수로 early_ecc() 함수에 의해 on으로 설정될 수 있다.
- initial_pmd_value
- pmd 엔트리 초기 값으로 이미 setup_arch() -> setup_processor() -> init_default_cache_policy() 함수에서 설정되었다.
- rpi2: __cpu_mm_mmu_flags
- PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | PMD_SECT_AF | PMD_SECT_TEX(1) | PMD_SECT_CACHEABLE | PMD_SECT_BUFFERABLE | PMD_SECT_S
- pmd_user_table
- PMD_TYPE_TABLE | PMD_BIT4 | PMD_DOMAIN(DOMAIN_USER) | PMD_PXNTABLE
- rpi2: 2015년 12월 커널 v4.5-rc1을 적용할 때 PMD_PXNTABLE 추가된다.
- PMD_TYPE_TABLE | PMD_BIT4 | PMD_DOMAIN(DOMAIN_USER) | PMD_PXNTABLE
- pgprot_user
- L_PTE_SHARED | L_PTE_MT_WRITEALLOC | L_PTE_YOUNG | L_PTE_PRESENT
- pgprot_kern
- L_PTE_SHARED | L_PTE_DIRTY | L_PTE_MT_WRITEALLOC | L_PTE_YOUNG | L_PTE_PRESENT
- pgprot_hyp_device, pgprot_s2, pgprot_s2_device
- 하이퍼 바이저에서 사용하는 변수로 설명 생략
mem_types[]
다음 그림은 rpi2 시스템에서 build_mem_type_table() 함수가 수행된 후 메모리 타입에 대한 속성을 표시하였다.
- .prot_sect
- 1차 변환 수행 시 사용하는 섹션 페이지 descriptor 엔트리 속성
- .prot_l1
- 2차 변환 수행시 사용하는 1차 페이지 테이블 descriptor 엔트리 속성
- .prot_pte
- 2차 변환 수행시 사용하는 2차 페이지 테이블 descriptor 엔트리 속성
- .prot_pte_s2
- 하이퍼 바이저에서는 .prot_pte 대신 사용하는 속성인데 아래 그림에서는 생략하였다.
위의 비트 속성을 사용하여 아래 그림과 같이 속성 필드의 주요 내용을 별도로 표기하였다.
- TCM(Tight Coupled Memory)는 캐시와 같이 매우 빠른 메모리이므로 별도의 캐시 및 버퍼를 사용할 필요가 없다.
- SO(Strongly Ordered Memory)는 Out-of-order memory access를 막기 위해 캐시 및 버퍼를 사용하지 않는다.
- TRE를 사용하는 경우 PRRR/NMRR 레지스터에 매핑된 속성을 사용한다.
참고
- 페이지 테이블 엔트리 속성 | 문c
항상 잘보고 있습니다. 감사합니다.
별 말씀을요~ 저두 감사합니다.