prepare_page_table()

다음의 영역들을 1차 페이지 테이블에서 초기화 하고 해당 영역에 대한 1차 및 2차 TLB 엔트리를 제거(invalidate)한다.

  • 0x0 ~ MODULES_VADDR 영역
    • 모듈 시작 영역은 커널 시작 가상 주소(PAGE_OFFSET) – 16M이다.
  • MODULES_VADDR ~ PAGE_OFFSET 영역
  • lowmem 남은 공간 부터 VMALLOC_START 영역
    • VMALLOC 영역이 가장 작아졌을 때 VMALLOC_START 값은 0xf000_0000(ef80_0000 + 0x80_0000)이다.
    • arm_lowmem_limit 주소를 가상주소로 변환한 주소가 high_memory이며 VMALLOC_START는 여기에 VMALLOC_OFFSET(8M)를 더한 후 8M round up한 주소이다.

 

prepare_page_table()

prepare_page_table-1a

 

arch/arm/mm/mmu.c

static inline void prepare_page_table(void)
{
        unsigned long addr;
        phys_addr_t end; 

        /*
         * Clear out all the mappings below the kernel image.
         */
        for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE)
                pmd_clear(pmd_off_k(addr));

#ifdef CONFIG_XIP_KERNEL
        /* The XIP kernel is mapped in the module area -- skip over it */
        addr = ((unsigned long)_etext + PMD_SIZE - 1) & PMD_MASK;
#endif
        for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE)
                pmd_clear(pmd_off_k(addr));

        /*
         * Find the end of the first block of lowmem.
         */
        end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
        if (end >= arm_lowmem_limit)
                end = arm_lowmem_limit;

        /*
         * Clear out all the kernel space mappings, except for the first
         * memory bank, up to the vmalloc region.
         */
        for (addr = __phys_to_virt(end);
             addr < VMALLOC_START; addr += PMD_SIZE)
                pmd_clear(pmd_off_k(addr));
}
  • for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE)
    • 페이지 테이블의 0x0000_0000 ~ MODULES_VADDR 가상 주소까지에 대응하는 2M 단위의 엔트리를 clear한다.
    • MODULES_VADDR
      • PAGE_OFFSET – 16M
      • rpi2: 0x7f00_0000
  • pmd_clear(pmd_off_k(addr));
    • 가상주소에 해당하는 pmd 엔트리 주소를 알아온 후 그 주소의 pmd 엔트리를 clear 한다.
    • 가상주소에 해당하는 TLB 캐시 엔트리도 clean 한다.
    • 참고: TLB Cache (API) | 문c
  • addr = ((unsigned long)_etext + PMD_SIZE – 1) & PMD_MASK;
    • XIP 커널의 경우 시작 주소에 따라 모듈 영역에 대한 페이지 테이블 초기화는 다음과 같이 처리된다.
      • _etext는 PAGE_OFFSET 하단에 위치 즉, 모듈 영역에 위치하므로 addr 주소를 _etext의 2M round up한 주소로 한다.
  • for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE)
    • XIP 커널이 아닌 경우 모듈 시작 주소부터 PAGE_OFFSET 까지의 가상 주소까지에 대응하는 2M 단위의 엔트리를 clear 한다.
    • 만일 XIP 커널인 경우는 재 산정된 addr 주소 부터 PAGE_OFFSET 까지의 가상 주소까지에 대응하는 2M 단위의 pmd 엔트리를 clear 한다.
  • end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
    • end = 물리 메모리의 끝 주소
  • if (end >= arm_lowmem_limit) end = arm_lowmem_limit;
    • 물리 메모리가 lowmem 영역 한계치(arm_lowmem_limit)를 초과하는 경우 끝 주소를 lowmem 영역의 한계치로 대입한다.
  • for (addr = __phys_to_virt(end); addr < VMALLOC_START; addr += PMD_SIZE)
    • lowmem 영역 내에서의 끝 주소를 가상주소로 바꾼 주소부터 VMALLOC_START 주소까지 2M 단위로 증가시키며 이에 대응하는 2M 단위의 pmd 엔트리를 clear 한다.

 

참고

build_mem_type_table()

메모리 타입별로 페이지 테이블을 구성하는 엔트리에 대한 속성을 구성한다.

 

커맨드라인 파라메터

  • “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()

build_mem_type_table-3

 

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

 

        /*
         * 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)
  • if (cpu_arch == CPU_ARCH_ARMv7 && (read_cpuid_ext(CPUID_EXT_MMFR0) & 0xF) == 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)
  • MT_LOW_VECTORS 및 MT_HIGH_VECTORS 타입에는 vecs_pgprot를 추가한다.
    • vecs_pgprot
      • rpi2: Share, WriteAlloc(0111)
  • 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) 비트를 의미한다.
  • 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 추가된다.
  • 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 대신 사용하는 속성인데 아래 그림에서는 생략하였다.

build_mem_type_table-1b

 

위의 비트 속성을 사용하여 아래 그림과 같이 속성 필드의 주요 내용을 별도로 표기하였다.

  • TCM(Tight Coupled Memory)는 캐시와 같이 매우 빠른 메모리이므로 별도의 캐시 및 버퍼를 사용할 필요가 없다.
  • SO(Strongly Ordered Memory)는 Out-of-order memory access를 막기 위해 캐시 및 버퍼를 사용하지 않는다.
  • TRE를 사용하는 경우 PRRR/NMRR 레지스터에 매핑된 속성을 사용한다.

build_mem_type_table-2a

 

참고

CMA(Contiguous Memory Allocator) for DMA

디바이스 드라이버가 사용하는 DMA 영역을 CMA(Contiguous Memory Allocator)에 등록하고 reserve memblock 추가한 후 해당 영역의 메모리 할당관리를 수행한다.

 

dma_contiguous_reserve()

drivers/base/dma-contiguous.c

/**
 * dma_contiguous_reserve() - reserve area(s) for contiguous memory handling
 * @limit: End address of the reserved memory (optional, 0 for any).
 *
 * This function reserves memory from early allocator. It should be
 * called by arch specific code once the early allocator (memblock or bootmem)
 * has been activated and all other subsystems have already allocated/reserved
 * memory.          
 */
void __init dma_contiguous_reserve(phys_addr_t limit)
{
        phys_addr_t selected_size = 0;
        phys_addr_t selected_base = 0;
        phys_addr_t selected_limit = limit;
        bool fixed = false;

        pr_debug("%s(limit %08lx)\n", __func__, (unsigned long)limit);

        if (size_cmdline != -1) {
                selected_size = size_cmdline;
                selected_base = base_cmdline;
                selected_limit = min_not_zero(limit_cmdline, limit);
                if (base_cmdline + size_cmdline == limit_cmdline)
                        fixed = true;
        } else {
#ifdef CONFIG_CMA_SIZE_SEL_MBYTES
                selected_size = size_bytes;
#elif defined(CONFIG_CMA_SIZE_SEL_PERCENTAGE)
                selected_size = cma_early_percent_memory();
#elif defined(CONFIG_CMA_SIZE_SEL_MIN)
                selected_size = min(size_bytes, cma_early_percent_memory());
#elif defined(CONFIG_CMA_SIZE_SEL_MAX)
                selected_size = max(size_bytes, cma_early_percent_memory());
#endif
        }

        if (selected_size && !dma_contiguous_default_area) {
                pr_debug("%s: reserving %ld MiB for global area\n", __func__,
                         (unsigned long)selected_size / SZ_1M);

                dma_contiguous_reserve_area(selected_size, selected_base,
                                            selected_limit,
                                            &dma_contiguous_default_area,
                                            fixed);
        }
}

커널 파라메터 또는 커널 옵션으로 요청한 메모리 사이즈만큼의 영역을 CMA(Contigugou Memory Allocator)에 관리 항목으로 추가하되 한 번만 요청을 받는다.

  • 커널 파라메터에 “cma=”가 설정되어 early_cma() 루틴이 호출되는 경우 size_cmdline, base_cmdline, limit_cmdline 등을 알아온다.
    • 요청 영역이 limit와 동일한 경우 fixed는 true로 설정하여 정확히 base(시작주소)에 할당 할 수 있도록 요청한다.
    • fixed가 false인 경우 base 부터 limit 사이즈 크기로 요청한다.
  • if (size_cmdline != -1) {
    • size_cmdline 정보를 알아온 경우 파싱된 전역변수 값들을 사용한다.
  • CONFIG_CMA_SIZE_SEL_MBYTES
    • size_cmdline 정보를 알아오지 못한 경우에 커널 옵션으로 설정한 값을 사용한다.
    • size_bytes = CMA_SIZE_MBYTES * SZ_1M
      • CMA_SIZE_MBYTES = CONFIG_CMA_SIZE_MBYTES
  • if (selected_size && !dma_contiguous_default_area) {
    • 사이즈가 0보다 크면서 cma 관리 영역을 처음 설정되는 경우(null)
  • dma_contiguous_reserve_area(selected_size, selected_base, selected_limit, &dma_contiguous_default_area, fixed);
    • cma를 구성할 메모리 시작 주소, 사이즈, limit 및 fixed 값으로 cma에 관리 항목을 추가하고 추후 리매핑을 하기 위해 리매핑 정보도 구성한다.
    • cma 관리 항목에 추가하고 reserve memblock에도 추가된다.

 

dma_contiguous_reserve_area()

drivers/base/dma-contiguous.c

/**
 * dma_contiguous_reserve_area() - reserve custom contiguous area
 * @size: Size of the reserved area (in bytes),
 * @base: Base address of the reserved area optional, use 0 for any
 * @limit: End address of the reserved memory (optional, 0 for any).
 * @res_cma: Pointer to store the created cma region.
 * @fixed: hint about where to place the reserved area
 *
 * This function reserves memory from early allocator. It should be
 * called by arch specific code once the early allocator (memblock or bootmem)
 * has been activated and all other subsystems have already allocated/reserved
 * memory. This function allows to create custom reserved areas for specific
 * devices.
 *
 * If @fixed is true, reserve contiguous area at exactly @base.  If false,
 * reserve in range from @base to @limit.
 */
int __init dma_contiguous_reserve_area(phys_addr_t size, phys_addr_t base,
                                       phys_addr_t limit, struct cma **res_cma,
                                       bool fixed)
{
        int ret;

        ret = cma_declare_contiguous(base, size, limit, 0, 0, fixed, res_cma);
        if (ret)
                return ret;

        /* Architecture specific contiguous memory fixup. */
        dma_contiguous_early_fixup(cma_get_base(*res_cma),
                                cma_get_size(*res_cma));

        return 0;
}
  • ret = cma_declare_contiguous(base, size, limit, 0, 0, fixed, res_cma);
    • cma_areas[]에 관리 항목을 추가하고 관리할 메모리 크기 정보인 base, size, limit, alignment 및 order_per_bit를 사용하여 base_pfn, count, bitmap 할당, alignment 및 order_per_bit 를 설정한다.
      • cma 에서 사용하는 비트맵은 각 비트가 1개 페이지를 표현하도록 하게 한다.
      • 영역의 alignment 단위는 1 페이지로 한다.
      • 해당 영역은 reserve memblock에도 등록된다.
    • 실패하는 경우 에러를 리턴한다.
    • 추가한 엔트리들은 CMA 드라이버가 로드될 때 호출되어 초기화한다.
  • dma_contiguous_early_fixup(cma_get_base(*res_cma), cma_get_size(*res_cma));
    • cma에서 관리할 메모리 영역을 dma_mmu_remap[] 배열에 추가한다.
    • 이렇게 배열에 정보만 단순히 추가한 후 나중에 setup_arch() -> paging_init() -> dma_contiguous_remap() 순서대로 함수를 호출하여 리매핑을 하게 한다.

 

dma_contiguous_early_fixup()

arch/arm/mm/dma-mapping.c

void __init dma_contiguous_early_fixup(phys_addr_t base, unsigned long size)
{
        dma_mmu_remap[dma_mmu_remap_num].base = base;
        dma_mmu_remap[dma_mmu_remap_num].size = size;
        dma_mmu_remap_num++;
}
  • dma_mmu_remap[] 배열에 추가한다.

 

CMA 메모리 early 설정 루틴

early_cma()

“cma= ” 커널 파라메터에 의해 호출되어 사용된다.

drivers/base/dma-contiguous.c

/*
 * Default global CMA area size can be defined in kernel's .config.
 * This is useful mainly for distro maintainers to create a kernel
 * that works correctly for most supported systems.
 * The size can be set in bytes or as a percentage of the total memory
 * in the system.
 *
 * Users, who want to set the size of global CMA area for their system
 * should use cma= kernel parameter.
 */
static const phys_addr_t size_bytes = CMA_SIZE_MBYTES * SZ_1M;
static phys_addr_t size_cmdline = -1;
static phys_addr_t base_cmdline;
static phys_addr_t limit_cmdline;

static int __init early_cma(char *p)
{
        pr_debug("%s(%s)\n", __func__, p);
        size_cmdline = memparse(p, &p);
        if (*p != '@')
                return 0;
        base_cmdline = memparse(p + 1, &p);
        if (*p != '-') {
                limit_cmdline = base_cmdline + size_cmdline;
                return 0;
        }
        limit_cmdline = memparse(p + 1, &p);

        return 0;
}
early_param("cma", early_cma);

다음과 같이 사용예를 알아본다.

  • cma=4M
    • 0x0000_0000 ~ arm_dma_limit(DMA 사이즈) 영역내에서 4M의 CMA용 공간을 할당한다.
  • cma=4M@0x00c00000
    • 0x00c0_0000 ~ arm_dma_limit(DMA 사이즈) 영역내에서 4M의 CMA용 공간을 할당한다.
  • cma=4M@0x10000000-0x40000000
    • 0x1000_0000 ~ 0x4000_0000 영역내에서 4M의 CMA용 공간을 할당한다.
  • cma=4M@0x10000000-0x10400000
    • 정확히 0x1000_0000 위치에서 4M의 CMA용 공간을 할당한다. (fix된 위치)

 

구조체 및 전역 변수

dma_contig_early_reserve 구조체

arch/arm/mm/dma-mapping.c

struct dma_contig_early_reserve {
        phys_addr_t base;
        unsigned long size;
};

 

전역변수

arch/arm/mm/dma-mapping.c

static struct dma_contig_early_reserve dma_mmu_remap[MAX_CMA_AREAS] __initdata;
static int dma_mmu_remap_num __initdata;
  • dma_mmu_remap[]
    • MAX_CMA_AREAS는 CONFIG_CMA_AREAS 커널 옵션 + 1 값이다.
  • dma_mmu_remap_num
    • 리매핑할 항목의 수

 

참고

CMA(Contiguous Memory Allocator)

버디 시스템이 연속된 물리 메모리의 할당 관리를 하는 시스x템인데, 그 중 특별히 디바이스들이 사용하는 연속된 물리 메모리에 대해 우선권을 주기 위해 사용하는 시스템이 CMA이다.
디바이스 장치를 위해 특별히 영역을 한정지어 CMA 영역으로 설정을 하면 버디 시스템은 보통 때에는 이 영역을 movable 페이지로 할당 관리한다. 이 영역이 movable 페이지로 가득 찰 때 디바이스가 연속된 물리 메모리를 요청하는 경우 CMA 영역의 일부 페이지들을 CMA 영역이 아닌 다른 영역으로 이주시키고 CMA 메모리 할당자가 디바이스가 필요로 하는 크기만큼의 메모리 페이지를 할당해준다.

 

  • 2010년 삼성이 만든 연속 메모리 할당자이며 IBM 및 LG가 참여하여 발전시켰다.
  • CONFIG_CMA 커널 옵션을 사용한다.
  • CMA 기능은 주로 DMA 서브시스템에서 필요로하는 물리적으로 연속된 메모리를 커널 실행 시 별도로 reserve하지 않고 할당받아 사용할 수 있도록 관리하기 위해 사용된다.
    • CONFIG_DMA_CMA 커널 옵션을 사용한다.
    • 그외 powerpc 아키텍처에서는 kvm을 위해서도 사용된다.
    • “cma=” 커널 파라메터로 cma 영역을 지정하고 이 영역에는 cma 및 movable 페이지만 사용되도록 제한되며 cma 영역이 부족한 경우 movable 페이지를 다른 곳으로 이주시킨다.
  • 버디 할당자와는 다르게 2의 차수 단위의 사이즈로 할당을 제한하지 않는다. (10M 영역 할당 등)
  • cma 할당자는 dma 서브 시스템과 완전 통합되어 dma를 사용하는 디바이스 드라이버 개발자가 별도의 cma api를 사용할 필요 없다.

다음 그림은 cma 가  cma_area[] 구조체 배열에서 관리되는 것을 나타내며 각각의 cma area는 비트맵으로 관리되는 것을 나타낸다.

cma-1

 

Cache-Coherent 문제

  • h/w가 캐시 coherent 기능을 제공하지 않는 경우 s/w 자체적으로  캐시 coherent 기능이 필요한데 이를 위해 페이지 할당 시 dma_alloc_coherent() 함수를 통해 캐시를 사용하지 않는 매핑을 사용한다.
    • dma_map_single() 함수를 통해 사용하는 경우 캐시 coherent 없이 빠르게 access하는 버퍼를 만들 수도 있다.
      • DMA Pool 할당자를 통해 dma_pool_create() 및 dma_pool_alloc() 함수도 사용할 수 있다.
  • 불행하게도 캐시 매핑에 대해 현재 cma 구조가 완전하지 않는 문제가 있다. 캐시 coherent 기능을 위해 언캐시 매핑이 필요한데 이미 커널의 lowmem 영역을 매핑하여 캐시가 사용되게 사용되므로 dma 영역으로 할당하는 언캐시된 메모리 페이지에 대해서 두 개의 매핑이 충돌하는 문제가 있다.
    • 이에 대해 2가지 해법으로 진행이되고 있다. 하나는 highmem에 cma 영역을 할당하게 하는 방법이다.(단 메모리가 작거나 64비트 시스템은 highmem이 없음…^^;) 두 번째는 dma-buffer로 할당되어 사용되는 영역은 커널의 리니어 매핑에서 제거하는 방법이다.
      • 현재는 두 번째의 방법을 밀고있다. 이 방법을 사용하므로 커널 메모리 영역(lowmem)이 huge 페이지를 사용하여 크게 리니어 매핑되는데 이 영역의 일부가 분할되어 매핑되면서 TLB 캐시 성능이 저하되는 것은 피할 수 없다.
      • CMA and ARM | LWN.net

 

CMA 관리 영역 추가

이 함수에서 정의된 cma 관리 영역들은 추후 cma 드라이버가 로드되면서 초기화 된 후 할당 관리를 사용할 수 있다. CONFIG_MODULE 커널 옵션을 사용하지 않을 경우에는 아래의 루틴에 의해 초기화된다.

  • core_initcall(cma_init_reserved_areas);
    • 등록되는 모든 initcall 함수들은 kernel_init 스레드의 do_initcalls() 함수에서 호출된다.

 

cma_declare_contiguous()

mm/cma.c

/**
 * cma_declare_contiguous() - reserve custom contiguous area
 * @base: Base address of the reserved area optional, use 0 for any
 * @size: Size of the reserved area (in bytes),
 * @limit: End address of the reserved memory (optional, 0 for any).
 * @alignment: Alignment for the CMA area, should be power of 2 or zero
 * @order_per_bit: Order of pages represented by one bit on bitmap.
 * @fixed: hint about where to place the reserved area
 * @res_cma: Pointer to store the created cma region.
 *
 * This function reserves memory from early allocator. It should be
 * called by arch specific code once the early allocator (memblock or bootmem)
 * has been activated and all other subsystems have already allocated/reserved
 * memory. This function allows to create custom reserved areas.
 *
 * If @fixed is true, reserve contiguous area at exactly @base.  If false,
 * reserve in range from @base to @limit.
 */
int __init cma_declare_contiguous(phys_addr_t base,
                        phys_addr_t size, phys_addr_t limit,
                        phys_addr_t alignment, unsigned int order_per_bit,
                        bool fixed, struct cma **res_cma)
{
        phys_addr_t memblock_end = memblock_end_of_DRAM();
        phys_addr_t highmem_start;
        int ret = 0;

#ifdef CONFIG_X86
        /*
         * high_memory isn't direct mapped memory so retrieving its physical
         * address isn't appropriate.  But it would be useful to check the
         * physical address of the highmem boundary so it's justfiable to get
         * the physical address from it.  On x86 there is a validation check for
         * this case, so the following workaround is needed to avoid it.
         */
        highmem_start = __pa_nodebug(high_memory);
#else
        highmem_start = __pa(high_memory);
#endif
        pr_debug("%s(size %pa, base %pa, limit %pa alignment %pa)\n",
                __func__, &size, &base, &limit, &alignment);

        if (cma_area_count == ARRAY_SIZE(cma_areas)) {
                pr_err("Not enough slots for CMA reserved regions!\n");
                return -ENOSPC;
        }

        if (!size)
                return -EINVAL;

        if (alignment && !is_power_of_2(alignment))
                return -EINVAL;

CMA로 관리하고자 하는 메모리 영역을 설정한다. 메모리 영역은 최대 MAX_CMA_AREAS 갯수 이내로 제한된다.

  • phys_addr_t memblock_end = memblock_end_of_DRAM();
    • memory memblock에 등록된 마지막 항목의 base + size를 하여 끝 주소를 알아온다.
  • highmem_start = __pa(high_memory);
    • high_memory
      • 물리 메모리 사이즈를 최대 lowmem 영역의 사이즈와 비교하여
        • 초과하는 경우 high_memory는 max lowmem 영역의 의 끝 가상 주소
        • 초과하지 않는 경우 high_memory는 lowmem 영역의 끝 가상 주소
  • if (cma_area_count == ARRAY_SIZE(cma_areas)) {
    • cma_areas 배열이 가득 찬 경우 에러 메시지를 출력하고 에러를 리턴한다.
  • if (alignment && !is_power_of_2(alignment))
    • alignment가 지정된 경우 2의 차수가 아닌 경우 그냥 에러를 리턴한다.
        /*
         * Sanitise input arguments.
         * Pages both ends in CMA area could be merged into adjacent unmovable
         * migratetype page by page allocator's buddy algorithm. In the case,
         * you couldn't get a contiguous memory, which is not what we want.
         */
        alignment = max(alignment,
                (phys_addr_t)PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order));
        base = ALIGN(base, alignment);
        size = ALIGN(size, alignment);
        limit &= ~(alignment - 1);

        if (!base)
                fixed = false;

        /* size should be aligned with order_per_bit */
        if (!IS_ALIGNED(size >> PAGE_SHIFT, 1 << order_per_bit))
                return -EINVAL;

        /*
         * If allocating at a fixed base the request region must not cross the
         * low/high memory boundary.
         */
        if (fixed && base < highmem_start && base + size > highmem_start) {
                ret = -EINVAL;
                pr_err("Region at %pa defined on low/high memory boundary (%pa)\n",
                        &base, &highmem_start);
                goto err;
        }
  • alignment = max(alignment, (phys_addr_t)PAGE_SIZE << max(MAX_ORDER – 1, pageblock_order))
    • pageblock_order
      • 32bit ARM은 CONFIG_HUGETLB_PAGE 옵션을 지원하지 않는다.
    • MAXORDER(11=버디에서 사용)-1과 같다.
  • alignment와 4M(PAGE_SIZE << 10) 중 큰 수를 대입
  • base = ALIGN(base, alignment);
    • base를 alignment 단위로round up
  • size = ALIGN(size, alignment);
    • size를 alignment 단위로 round up
  • limit &= ~(alignment – 1);
    • limit를 alignment 단위로 round down
  • if (!base)
    • base가 0이면 인수 fixed 값을 false로 변경한다.
    • 결국 fixed를 true로 하여 이 함수를 호출한 경우에도 base가 0일 때만 true로 유지된다.
  • if (!IS_ALIGNED(size >> PAGE_SHIFT, 1 << order_per_bit))
    • size가 2^order_per_bit 페이지(4K)로 align되지 않으면 에러를 리턴한다.
  • if (fixed && base < highmem_start && base + size > highmem_start) {
    • fixed가 true이면서 영역이 highmem 영역과 겹치면
      • 영역이 low/high 메모리 경계에 정의되었다고 에러 메시지를 출력하고 에러를 리턴한다.
        /*
         * If the limit is unspecified or above the memblock end, its effective
         * value will be the memblock end. Set it explicitly to simplify further
         * checks.
         */
        if (limit == 0 || limit > memblock_end)
                limit = memblock_end;

        /* Reserve memory */
        if (fixed) {
                if (memblock_is_region_reserved(base, size) ||
                    memblock_reserve(base, size) < 0) {
                        ret = -EBUSY;
                        goto err;
                }
        } else {
                phys_addr_t addr = 0;

                /*
                 * All pages in the reserved area must come from the same zone.
                 * If the requested region crosses the low/high memory boundary,
                 * try allocating from high memory first and fall back to low
                 * memory in case of failure.
                 */
                if (base < highmem_start && limit > highmem_start) {
                        addr = memblock_alloc_range(size, alignment,
                                                    highmem_start, limit);
                        limit = highmem_start;
                }

                if (!addr) {
                        addr = memblock_alloc_range(size, alignment, base,
                                                    limit);
                        if (!addr) {
                                ret = -ENOMEM;
                                goto err;
                        }
                }

                /*
                 * kmemleak scans/reads tracked objects for pointers to other
                 * objects but this address isn't mapped and accessible
                 */
                kmemleak_ignore(phys_to_virt(addr));
                base = addr;
        }

        ret = cma_init_reserved_mem(base, size, order_per_bit, res_cma);
        if (ret)
                goto err;

        pr_info("Reserved %ld MiB at %pa\n", (unsigned long)size / SZ_1M,
                &base);
        return 0;

err:
        pr_err("Failed to reserve %ld MiB\n", (unsigned long)size / SZ_1M);
        return ret;
}
  • if (limit == 0 || limit > memblock_end)
    • limit가 0이거나 limit가 memblock_end를 초과하는 경우 limit를 memblock_end로 설정한다.
    • CMA에 메모리 만큼의 사이즈를 등록한다.
  • if (fixed) {
    • 추가 시 lowmem 내에서 고정적으로 base 주소부터 추가를 하려는 경우
  • if (memblock_is_region_reserved(base, size) || memblock_reserve(base, size) < 0) {
    • 영역이 reserve memblock에 등록되었었거나 영역을 reserve memblock에 등록 에러된 경우 에러를 리턴한다.

fixed가 false인 경우 추가할 영역이 lowmem과 highmem영역의 경계인 경우 먼저 highmem 영역에 할당을 시도하고 실패하는 경우 lowmem 영역에 할당한다.

  • if (base < highmem_start && limit > highmem_start) {
    • 추가할 영역이 highmem 영역과 lowmem 영역의 경계인 경우
  • addr = memblock_alloc_range(size, alignment, highmem_start, limit);
    • highmem_start 부터 limit 범위 사이에서 요청 사이즈를 할당한다.
  • if (!addr) {
    • highmem 영역에 할당을 실패한 경우
  • addr = memblock_alloc_range(size, alignment, base, limit);
    • base 부터 limit 범위 사이에 요청 사이즈를 할당하고 실패하면 에러를 리턴한다.
  • kmemleak_ignore(phys_to_virt(addr));
    • 이 주소가 scan되고 leak로 리포트되지 않도록 무시하게 한다.
  • ret = cma_init_reserved_mem(base, size, order_per_bit, res_cma);
    • reserve된 영역을 다시 한 번 sanity check를 수행하고 cma 관리 항목을 설정한다.
    • cma 관리 항목(cma area 정보)은 추후 각 항목이 초기화되어야 한다..
      • CMA 모듈이 로드될 때 cma_init_reserved_area()가 호출되며 이 때 각 영역이 초기화된다.

 

cma_init_reserved_mem()

mm/cma.c

/**
 * cma_init_reserved_mem() - create custom contiguous area from reserved memory
 * @base: Base address of the reserved area
 * @size: Size of the reserved area (in bytes),
 * @order_per_bit: Order of pages represented by one bit on bitmap.
 * @res_cma: Pointer to store the created cma region.
 *
 * This function creates custom contiguous area from already reserved memory.
 */
int __init cma_init_reserved_mem(phys_addr_t base, phys_addr_t size,
                                 int order_per_bit, struct cma **res_cma)
{
        struct cma *cma;
        phys_addr_t alignment;

        /* Sanity checks */
        if (cma_area_count == ARRAY_SIZE(cma_areas)) {
                pr_err("Not enough slots for CMA reserved regions!\n");
                return -ENOSPC;
        }

        if (!size || !memblock_is_region_reserved(base, size))
                return -EINVAL;

        /* ensure minimal alignment requied by mm core */
        alignment = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);

        /* alignment should be aligned with order_per_bit */
        if (!IS_ALIGNED(alignment >> PAGE_SHIFT, 1 << order_per_bit))
                return -EINVAL;

        if (ALIGN(base, alignment) != base || ALIGN(size, alignment) != size)
                return -EINVAL;

        /*
         * Each reserved area must be initialised later, when more kernel
         * subsystems (like slab allocator) are available.
         */
        cma = &cma_areas[cma_area_count];
        cma->base_pfn = PFN_DOWN(base);
        cma->count = size >> PAGE_SHIFT;
        cma->order_per_bit = order_per_bit;
        *res_cma = cma;
        cma_area_count++;
        totalcma_pages += (size / PAGE_SIZE);

        return 0;
}
  • if (cma_area_count == ARRAY_SIZE(cma_areas)) {
    • cma_areas[]에 더 이상 할당할 공간이 없는 경우 에러를 출력하고 리턴한다.
  • if (!size || !memblock_is_region_reserved(base, size))
    • size가 0이거나 요청 영역이 reserve memblock에 없는 경우 에러를 리턴한다.
  •  alignment = PAGE_SIZE << max(MAX_ORDER – 1, pageblock_order);
    • pageblock_order
      • 32bit ARM은 CONFIG_HUGETLB_PAGE 옵션을 지원하지 않는다.
      • MAXORDER(11=버디에서 사용)-1과 같다.
    • 4M = PAGE_SIZE(4K) << (MAXORDER(11) – 1)
  • if (!IS_ALIGNED(alignment >> PAGE_SHIFT, 1 << order_per_bit))
    • alignment가 order_per_bit 단위로 align 되어 있지 않은 경우 에러를 리턴한다.
  • if (ALIGN(base, alignment) != base || ALIGN(size, alignment) != size)
    • 요청 시작 주소와 사이즈가 alignment 단위로 align 되어 있지 않은 경우 에러를 리턴한다.
  • cma_areas[]에 요청 항목을 추가, cma_area_count를 1 증가시키고 totalcma_pages에 추가된 페이지 수를 증가시킨다.

아래 그림은 cma_init_reserved_mem() 함수를 통하여 만들어질 때 영역에 대한 size와 base가 4M 단위 및 2^order_per_bit 페이지로 align되어야 만들어질 수 있는 것을 나타낸다.

cma-4

 

cma 할당과 해제

cma_alloc()

mm/cma.c

/**
 * cma_alloc() - allocate pages from contiguous area
 * @cma:   Contiguous memory region for which the allocation is performed.
 * @count: Requested number of pages.
 * @align: Requested alignment of pages (in PAGE_SIZE order).
 *
 * This function allocates part of contiguous memory on specific
 * contiguous memory area.
 */
struct page *cma_alloc(struct cma *cma, int count, unsigned int align)
{
        unsigned long mask, offset, pfn, start = 0;
        unsigned long bitmap_maxno, bitmap_no, bitmap_count;
        struct page *page = NULL;
        int ret;

        if (!cma || !cma->count)
                return NULL;

        pr_debug("%s(cma %p, count %d, align %d)\n", __func__, (void *)cma,
                 count, align);

        if (!count)
                return NULL;

        mask = cma_bitmap_aligned_mask(cma, align);
        offset = cma_bitmap_aligned_offset(cma, align);
        bitmap_maxno = cma_bitmap_maxno(cma);
        bitmap_count = cma_bitmap_pages_to_bits(cma, count);

        for (;;) {
                mutex_lock(&cma->lock);
                bitmap_no = bitmap_find_next_zero_area_off(cma->bitmap,
                                bitmap_maxno, start, bitmap_count, mask,
                                offset);
                if (bitmap_no >= bitmap_maxno) {
                        mutex_unlock(&cma->lock);
                        break;
                }
                bitmap_set(cma->bitmap, bitmap_no, bitmap_count);
                /*
                 * It's safe to drop the lock here. We've marked this region for
                 * our exclusive use. If the migration fails we will take the
                 * lock again and unmark it.
                 */
                mutex_unlock(&cma->lock);

                pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit);
                mutex_lock(&cma_mutex);
                ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA);
                mutex_unlock(&cma_mutex);
                if (ret == 0) {
                        page = pfn_to_page(pfn);
                        break;
                }

                cma_clear_bitmap(cma, pfn, count);
                if (ret != -EBUSY)
                        break;

                pr_debug("%s(): memory range at %p is busy, retrying\n",
                         __func__, pfn_to_page(pfn));
                /* try again with a bit different memory target */
                start = bitmap_no + mask + 1;
        }

        pr_debug("%s(): returned %p\n", __func__, page);
        return page;
}
  • if (!cma || !cma->count)
    • cma가 null이거나 cma에 사용할 수 있는 비트가 없는 경우 null 리턴
  • if (!count)
    • 요청 count(bit)가 없는 경우 null 리턴
  • mask = cma_bitmap_aligned_mask(cma, align);
    • align이 cma->order_per_bit 보다 큰 경우 그 차이만큼의 비트 수를 마스크 비트 1로 만들어 리턴
      • 예) align=4(64K), cma->order_per_bit=2(16K)
        • 3 (두 개의 bit를 1로 하여 만든 마스크)
  • offset = cma_bitmap_aligned_offset(cma, align);
    • 시작 주소를 2 ^ align page로 align할 때 사용할 수 없는 사이즈를 offset으로 알아온다.
  • bitmap_maxno = cma_bitmap_maxno(cma);
    • 비트맵에서 최대 사용할 수 있는 비트 수를 알아온다.
  • bitmap_count = cma_bitmap_pages_to_bits(cma, count);
    • 요청 count(페이지)에 대해 필요한 비트맵 비트 수
  • bitmap_no = bitmap_find_next_zero_area_off(cma->bitmap, bitmap_maxno, start, bitmap_count, mask, offset);
    • 비트맵에서 빈자리를 찾아 bitmap_no를 알아온다.
      • cma->bitmap 부터 검색을 하여 size만큼의 bit를 mask하여 0이 나오면 빈 자리라 판단하여 bitmap_no를 알아온다. (offset은 할당하지 않는 영역)
  • if (bitmap_no >= bitmap_maxno) {
    • 추가할 공간이 없으면 null을 return 한다.
  •  bitmap_set(cma->bitmap, bitmap_no, bitmap_count);
    • bitmap_no 위치에 bitmap_count bit 수 만큼 1로 설정한다.
  • pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit);
    • 할당한 위치에 대한 pfn
    • 예) base_pfn=0x1000, bitmap_no=1, order_per_bit=1
      • 0x1002 = 0x1000 + (1 << 1)
  • ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA);
    • 필요한 만큼의 페이지들을 모두 MIGRATE_CMA로 할당하고 성공하는 경우 page 구조체 포인터를 리턴한다. 이 영역에 들어가 있는 기존 movable 페이지들은 모두 compaction 과정을 통해 이주시킨다.
  •  cma_clear_bitmap(cma, pfn, count);
    • 할당이 실패한 경우 bitmap을 다시 지운다.
    • 만일 실패 사요가 -EBUSY가 아닌 경우 null을 리턴한다.
  • start = bitmap_no + mask + 1;
    • 다음 비트맵 위치에서 다시 한 번 할당을 시도한다.

아래 그림에서는 align 단위를 다르게 하여 cma_alloc() 함수를 호출하였을 때 페이지가 할당되는 모습을 나타낸다.

cma-5

 

cma_bitmap_aligned_mask()

mm/cma.c

static unsigned long cma_bitmap_aligned_mask(struct cma *cma, int align_order)
{
        if (align_order <= cma->order_per_bit)
                return 0;
        return (1UL << (align_order - cma->order_per_bit)) - 1;
}
  • align_order와 cma->order_per_bit의 차이만큼의 비트 수를 1로 만들어 리턴
    • 해당 cma 항목에서 비트가 관리하는 페이지 단위가 요청한 align_order보다 크거나 같으면 0을 리턴한다.
    • 예) align_order = 4(64K 단위), cma->order_per_bit = 2(16K 단위)
      • 3 (2개의 비트를 1로 설정하여 마스크로 만든다.)

아래 그림은 요청 단위를 2^3 page로 할당하고자 할 때 기존 cma 영역이 2 페이지 단위로 관리되던 것에 대하여 그 비트 차이만큼의 mask 값을 알아온다.

cma-3

 

cma_bitmap_aligned_offset()

mm/cma.c

/*
 * Find a PFN aligned to the specified order and return an offset represented in
 * order_per_bits.
 */
static unsigned long cma_bitmap_aligned_offset(struct cma *cma, int align_order)
{
        if (align_order <= cma->order_per_bit)
                return 0;

        return (ALIGN(cma->base_pfn, (1UL << align_order))
                - cma->base_pfn) >> cma->order_per_bit;
}
  • cma->base_pfn이 요청한 align_order로 되어 있지 않은 경우 align_order 단위의 검색을 위해 필요한 offset를 리턴한다.
    • 해당 cma 항목에서 비트가 관리하는 페이지 단위가 요청한 align_order보다 크거나 같으면 0을 리턴한다.
    • 시작 pfn을 align_order만큼 좌측으로 shift한 후 시작 pfn을 뺀 후 cma->order_per_bit 만큼 우측 쉬프트
      • 예) 0x0001_2344, cma->order_per_bit = 2(16K 단위), align_order = 4(64K 단위)
        • 0xC(0x0001_2350 – 0x0001_2344) >> 2 = 3

아래 그림은 cma->base_pfn이 2^3 page 수 만큼 align이 되어 있지 않을 경우 그 차이만큼 offset을 알아오는 것을 나타낸다.

cma-2

 

cma_bitmap_maxno()

mm/cma.c

static unsigned long cma_bitmap_maxno(struct cma *cma)
{
        return cma->count >> cma->order_per_bit;
}
  • 비트맵에서 사용하는 최대 비트 수
    • 예) cma->count=800, cma->order_per_bit=2
      • 200개 비트

 

cma_bitmap_pages_to_bits()

mm/cma.c

static unsigned long cma_bitmap_pages_to_bits(struct cma *cma,
                                                unsigned long pages)
{
        return ALIGN(pages, 1UL << cma->order_per_bit) >> cma->order_per_bit;
}
  • 요청 페이지에 대해 필요한 비트맵에서 사용할 비트 수
    • 비트맵이 사용하는 최소 단위의 사이즈로 align하였을 때 필요한 비트 수를 리턴한다.
    • 예) pages=5(20K 요청), cma->order_per_bit=2(16K 단위)
      • 2(16K x 2) = 8 >> 2

 

cma_release()

mm/cma.c

/**
 * cma_release() - release allocated pages
 * @cma:   Contiguous memory region for which the allocation is performed.
 * @pages: Allocated pages.
 * @count: Number of allocated pages.
 *
 * This function releases memory allocated by alloc_cma().
 * It returns false when provided pages do not belong to contiguous area and
 * true otherwise.
 */
bool cma_release(struct cma *cma, struct page *pages, int count)
{
        unsigned long pfn;

        if (!cma || !pages)
                return false;

        pr_debug("%s(page %p)\n", __func__, (void *)pages);

        pfn = page_to_pfn(pages);

        if (pfn < cma->base_pfn || pfn >= cma->base_pfn + cma->count)
                return false;

        VM_BUG_ON(pfn + count > cma->base_pfn + cma->count);

        free_contig_range(pfn, count);
        cma_clear_bitmap(cma, pfn, count);

        return true;
}
  • if (!cma || !pages)
    • cma가 null이거나 pages 포인터가 null 인경우 false를 리턴한다.
  • 요청 페이지의 시작 물리 주소가 cma 영역을 벗어난 경우 false를 리턴한다.
  • free_contig_range(pfn, count)
    • cma 영역의 pfn 주소에서 count(페이지 수) 만큼 해제한다.
  • cma_clear_bitmap(cma, pfn, count);
    • 해당 영역의 페이지를 비트맵에서 clear한다.

 

free_contig_range()

mm/page_alloc.c

void free_contig_range(unsigned long pfn, unsigned nr_pages)
{
        unsigned int count = 0; 

        for (; nr_pages--; pfn++) {
                struct page *page = pfn_to_page(pfn);

                count += page_count(page) != 1;
                __free_page(page);
        }
        WARN(count != 0, "%d pages are still in use!\n", count);
}
  • pfn부터 요청한 페이지 수 만큼 루프를 돌며 페이지를 해제 한다.

 

cma_clear_bitmap()

mm/cma.c

static void cma_clear_bitmap(struct cma *cma, unsigned long pfn, int count)
{
        unsigned long bitmap_no, bitmap_count;

        bitmap_no = (pfn - cma->base_pfn) >> cma->order_per_bit;
        bitmap_count = cma_bitmap_pages_to_bits(cma, count);

        mutex_lock(&cma->lock);
        bitmap_clear(cma->bitmap, bitmap_no, bitmap_count);
        mutex_unlock(&cma->lock);
}
  • bitmap_no = (pfn – cma->base_pfn) >> cma->order_per_bit;
    • bitmap 번호를 구한다.
  • bitmap_count = cma_bitmap_pages_to_bits(cma, count)
    • count(페이지)로 필요한 비트 수를 구한다.
  • bitmap_clear(cma->bitmap, bitmap_no, bitmap_count);
    • 비트맵에서 bitmap_no 위치부터 bitmap_count bit 수 만큼 clear 한다.

 

CMA 영역 초기화

cma_init_reserved_areas()

mm/cma.c

static int __init cma_init_reserved_areas(void)
{
        int i;

        for (i = 0; i < cma_area_count; i++) {
                int ret = cma_activate_area(&cma_areas[i]);

                if (ret)
                        return ret;
        }

        return 0;
}
core_initcall(cma_init_reserved_areas);
  • core_initcall(cma_init_reserved_areas);
    • CONFIG_MODULE 커널 옵션에 따라 호출되는 위치가 달라진다.
      • CONFIG_MODULE 설정이 없는 경우
        • 등록되는 모든 initcall 함수들은 kernel_init 스레드의 do_initcalls() 함수에서 호출된다.
      • CONFIG_MODULE 설정이 있는 경우
        • 드라이버들 내부에 있는 module_init(cma_init_reserved_areas) 선언과 동일하다. 따라서 cma 드라이버 모듈이 로드되는 경우 cma_init_reserved_areas() 함수가 호출된다.

 

cma_activate_area()

mm/cma.c

static int __init cma_activate_area(struct cma *cma)
{
        int bitmap_size = BITS_TO_LONGS(cma_bitmap_maxno(cma)) * sizeof(long);
        unsigned long base_pfn = cma->base_pfn, pfn = base_pfn;
        unsigned i = cma->count >> pageblock_order;
        struct zone *zone;

        cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL);

        if (!cma->bitmap)
                return -ENOMEM;

        WARN_ON_ONCE(!pfn_valid(pfn));
        zone = page_zone(pfn_to_page(pfn));

        do {
                unsigned j;

                base_pfn = pfn;
                for (j = pageblock_nr_pages; j; --j, pfn++) {
                        WARN_ON_ONCE(!pfn_valid(pfn));
                        /*
                         * alloc_contig_range requires the pfn range
                         * specified to be in the same zone. Make this
                         * simple by forcing the entire CMA resv range
                         * to be in the same zone.
                         */
                        if (page_zone(pfn_to_page(pfn)) != zone)
                                goto err;
                }
                init_cma_reserved_pageblock(pfn_to_page(base_pfn));
        } while (--i);

        mutex_init(&cma->lock);
        return 0;

err:
        kfree(cma->bitmap);
        cma->count = 0;
        return -EINVAL;
}

 

모듈 관련

아래 매크로 코드들은 2015년 5월 커널 v4.2-rc3에서 include/linux/init.h  -> include/linux/module.h 로 옮겼다.

 

core_initcall()

include/linux/init.h

#define core_initcall(fn)               module_init(fn)
  • CONFIG_MODULE이 선언되어 있는 경우 module_init()을 호출한다.

 

module_init()

include/linux/init.h

/* Each module must use one module_init(). */
#define module_init(initfn)                                     \
        static inline initcall_t __inittest(void)               \
        { return initfn; }                                      \
        int init_module(void) __attribute__((alias(#initfn)));
  • initcall_t
    • 다음과 같이 인수 없는 함수 포인터이다.
    • typedef int (*initcall_t)(void);
  • 인수 initfn 함수를 호출하고 결과를 int 타입으로 리턴한다.
  • int init_module(void) __attribute__((alias(#initfn)));
    • alias 컴파일러 속성을 사용하여 init_module() 함수명을 alias명으로 사용할 때 인수 initfn을 호출한다.

 

구조체 및 주요 변수

struct cma

mm/cma.c

struct cma {
        unsigned long   base_pfn;
        unsigned long   count;
        unsigned long   *bitmap;
        unsigned int order_per_bit; /* Order of pages represented by one bit */
        struct mutex    lock;
};
  • base_pfn
    • 시작 물리 주소의 pfn 번호
  • count
    • 관리 페이지 수
  • bitmap
    • 비트맵 데이터
  • order_per_bit
    • 비트맵의 1비트가  표현하는 order 페이지(2^N 페이지, 0=1페이지, 1=2페이지, 2=4페이지, …)
  • lock
    • 비트맵 조작시 동기화를 위해 사용

 

전역 변수

mm/cma.c

static struct cma cma_areas[MAX_CMA_AREAS];
static unsigned cma_area_count;
static DEFINE_MUTEX(cma_mutex);
  • cma_areas[]
    • CONFIG_CMA_AREAS 커널 옵션이 설정된 경우 (1 + CONFIG_CMA_AREAS), 그렇지 않은 경우 0 이다.
  • cma_area_count
    • cma_areas[] 배열에서 사용하는 항목 수
  • cma_mutex
    • cma 영역 할당 시 동기화를 위해 사용

 

mm/page_alloc.c

unsigned long totalcma_pages __read_mostly;
  • totalcma_pages
    • cma에 사용된 페이지 수

 

참고

페이지 테이블 API – ARM32

페이지 테이블 관련 명령(pgd, pud, pmd, pte)은 32bit ARM 리눅스용에 대해 제한하여 설명한다.

  • ARM 리눅스에서는 3단계 페이지 테이블 관리를 사용
    • pgd -> pmd -> pte
  • ARM h/w에서는 2단계 페이지 테이블 관리를 사용한다.
    • pgd(pmd) -> pte

 

엔트리 타입

  • pgd_t
    • typedef struct { pmdval_t pgd[2]; } pgd_t;
    • pmdval_t는 u32형이다.
  • pmd_t
    • typedef struct { pmdval_t pmd; } pmd_t;
  • pte_t
    • typedef struct { pteval_t pte; } pte_t;
    • pteval_t는 u32형이다.

 

pgd 관련

pgd_val()

arch/arm/include/asm/pgtable-2level-types.h

#define pgd_val(x)      ((x).pgd[0])
  • 두 개의 pgd 엔트리 중 첫 번째 pgd 엔트리 값을 리턴한다.

 

pgd_index()

arch/arm/include/asm/pgtable.h

/* to find an entry in a page-table-directory */
#define pgd_index(addr)         ((addr) >> PGDIR_SHIFT)
  • 주어진 주소의 pgd 인덱스 값(0~2047)을 리턴한다.
  • PGDIR_SHIFT=21 (=2M round down)

addr[31:21] 값을 우측으로 PGDIR_SHIFT(21) 비트 만큼 쉬프트하여 인덱스로 활용 (0~2047(0x7ff))

  • rpi2 예) pgd_index(0x8a00_0000) = 0x450

pgd_index-1

 

pgd_offset()

arch/arm/include/asm/pgtable.h

#define pgd_offset(mm, addr)    ((mm)->pgd + pgd_index(addr))
  • mm_struct 구조체가 가리키는 pgd 주소 + pgd 인덱스를 하여 pgd의 offset 주소를 알아올 수 있다.

pgd_offset-1

 

pgd_offset_k()

arch/arm/include/asm/pgtable.h

/* to find an entry in a kernel page-table-directory */
#define pgd_offset_k(addr)      pgd_offset(&init_mm, addr)
  • “_k” suffix(접미) 는 커널이라는 뜻이다. 즉 커널이 사용하는 level-1 PGD 에서 pgd 엔트리 가상 주소를 알아온다.

 

pgd_offset_k-1

 

pgd_addr_end()

include/asm-generic/pgtable.h

/*
 * When walking page tables, get the address of the next boundary,
 * or the end address of the range if that comes earlier.  Although no
 * vma end wraps to 0, rounded up __boundary may wrap to 0 throughout.
 */

#define pgd_addr_end(addr, end)                                         \
({      unsigned long __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK;  \
        (__boundary - 1 < (end) - 1)? __boundary: (end);                \
})
  • addr 값에 2M를 더한 값을 round down 하고 end 주소 보다 작은 경우 리턴한다. 그렇지 않은 경우 end를 리턴한다.
  • 예) addr=0x1000_0000, end=0x1040_0000
    • 호출1=0x1020_0000, 호출2=0x1040_0000
  • 예) addr=0x1000_0004, end=0x1040_0004
    • 호출1=0x1020_0000, 호출2=0x1040_0000, 호출3=0x1040_0004

 

pud 관련

pud_val()

include/asm-generic/pgtable-nopud.h

#define pud_val(x)                              (pgd_val((x).pgd))
  • pud 엔트리 값을 리턴한다.
  • 실제 pud 테이블이 없으므로 pgd 값을 사용한다.

 

pud_offset()

include/asm-generic/pgtable-nopud.h

static inline pud_t * pud_offset(pgd_t * pgd, unsigned long address)
{
        return (pud_t *)pgd;
}
  • pud의 offset은 pud 테이블이 없으므로 그냥 첫 번째 인수인 pgd만 리턴한다.

 

pmd 관련

pmd_val()

arch/arm/include/asm/pgtable-2level-types.h

#define pmd_val(x)      ((x).pmd)
  • pmd 엔트리 값을 리턴한다.

 

pmd_index()

  • 사용하지 않음.

 

pmd_offset()

arch/arm/include/asm/pgtable-2level.h

static inline pmd_t *pmd_offset(pud_t *pud, unsigned long addr)
{
        return (pmd_t *)pud;
}
  • pmd의 offset은 pmd 테이블이 없으므로 그냥 첫 번째 인수인 pud만 리턴한다.

 

pmd_off_k()

arch/arm/mm/mm.h

static inline pmd_t *pmd_off_k(unsigned long virt)
{
        return pmd_offset(pud_offset(pgd_offset_k(virt), virt), virt);
}

가상 주소에 대응하는 pmd 엔트리 가상 주소를 알아온다.

  •  pgd_offset_k()
    • 커널이 사용하는 level-1 PGD 에서 가상 주소에 대응하는 pgd 엔트리 가상 주소를 알아온다
  • pud_offset()
    • 첫 번째 인수인 pgd 엔트리 가상 주소를 리턴한다.
  • pmd_offset()
    • 첫 번째 인수가 가리키는 pmd 테이블에서 가상 주소에 대응하는 pmd 엔트리 가상 주소를 알아온다.

pmd_off_k-1

 

pmd_page_vaddr()

arch/arm/include/asm/pgtable.h

arch/arm/include/asm/pgtable.h

static inline pte_t *pmd_page_vaddr(pmd_t pmd)
{
        return __va(pmd_val(pmd) & PHYS_MASK & (s32)PAGE_MASK);
}
  • pmd 테이블의 가상 주소값을 알아온다.
  • pmd 값을 4K round down 한 후 가상 주소로 변환한다.

pmd[31:12] 값을 가상주소로 변환하여 pte 테이블을 가리키는 가상 주소를 알아낸다.

  • 예) rpi2: pmd_page_vaddr(0x0234_5678) = 0x8234_5000

pmd_page_vaddr-1

 

pmd_clear()

arch/arm/include/asm/pgtable-2level.h

#define pmd_clear(pmdp)                 \
        do {                            \
                pmdp[0] = __pmd(0);     \
                pmdp[1] = __pmd(0);     \
                clean_pmd_entry(pmdp);  \
        } while (0)
  • pmd 엔트리 2개를 삭제한다.
    • pgd 엔트리 하나가 pmd 엔트리 2개 이므로 결국 pgd 엔트리를 지우는 것과 동일하다.
  • clean_pmd_entry(pmdp);
    • TLB를 삭제한다.

 

clean_pmd_entry()

arch/arm/include/asm/tlbflush.h

static inline void clean_pmd_entry(void *pmd)
{
        const unsigned int __tlb_flag = __cpu_tlb_flags;
        
        tlb_op(TLB_DCLEAN, "c7, c10, 1  @ flush_pmd", pmd);
        tlb_l2_op(TLB_L2CLEAN_FR, "c15, c9, 1  @ L2 flush_pmd", pmd);
}
  • L1-TLB 엔트리와 L2-TLB 엔트리를 flush(여기서는 invalidate) 한다.

 

pte 관련

pte_val()

arch/arm/include/asm/pgtable-2level-types.h

#define pte_val(x)      ((x).pte)

  • pte 엔트리 값을 리턴한다.

 

pte_index()

arch/arm/include/asm/pgtable.h

#define pte_index(addr)         (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
  • 주어진 주소의 pte 인덱스 값(0~511)을 리턴한다.
  • PTRS_PER_PTE=512
  • PAGE_SHIFT=12 (=4K round down)

addr[12:20] 값을 우측으로 12bit 쉬프트하여 인덱스로 활용 (0~511(0x1ff))

  • rpi2 예) pte_index(0x8a12_0000) = 0x120

pte_index-1

 

pte_offset_kernel()

arch/arm/include/asm/pgtable.h

#define pte_offset_kernel(pmd,addr)     (pmd_page_vaddr(*(pmd)) + pte_index(addr))

level-1 테이블에서 pmd 포인터가 가리키는 pmd_t 값에 연동된 level-2 테이블에서 addr 값으로 pte index를 알아내어 해당 pte_t의 주소를 알아낸다.

  • 예) rpi2: *pmd=0x0a00_0000
    • pte_offset_kernel(0x8000_6008, 0x8234_5000) = 0x8a00_0514

pte_offset_kernel-1

 

__pte_map() & __pte_unmap()

CONFIG_HIGHPTE

  • pte 테이블을 highmem에서 운용하고자 할 때 사용하는 커널 옵션이다.
  • highmem에 있는 페이지 테이블을 access  할 때 kmap_atomic()을 사용하여 highmem에 위치한 페이지 테이블을 fixmap에 매핑 후 그 페이지 access 할 수 있다. 사용한 후에는 kunmap_atomic()을 사용하여 페이지 테이블을 fixmap에서 언매핑하여야 한다.

pte 페이지 테이블을 lowmem에서 운영하는 경우에는 별도의 매핑을 할 필요가 없으므로 곧바로 해당 영역의 주소를 알아와서 access할 수 있다.

arch/arm/include/asm/pgtable.h

#ifndef CONFIG_HIGHPTE
#define __pte_map(pmd)          pmd_page_vaddr(*(pmd))
#define __pte_unmap(pte)        do { } while (0)
#else
#define __pte_map(pmd)          (pte_t *)kmap_atomic(pmd_page(*(pmd)))
#define __pte_unmap(pte)        kunmap_atomic(pte)
#endif

level-1 테이블에서 pmd 포인터가 가리키는 pmd_t 값에 연동된 pte 테이블의 가상 주소 값을 알아낸다.

  • 예) rpi2: *pmd = 0x0a10_0000
    • __pte_map(0x8000_6004) = 0x8a10_0000

__pte_map-1

 

pte_offset_map()

arch/arm/include/asm/pgtable.h

#define pte_offset_map(pmd,addr)        (__pte_map(pmd) + pte_index(addr))

level-1 테이블에서 pmd 포인터가 가리키는 pmd_t 값에 연동된 level-2 테이블에서 addr 값으로 pte index를 알아내어 해당 pte_t의 가상 주소를 알아낸다.

  • 예) rpi2: *pmd=0x0a00_0000
    • pte_offset_kernel(0x8000_6008, 0x8234_5000) = 0x8a00_0514

pte_offset_map-1

 

set_pte_ext()

arch/arm/include/asm/pgtable-2level.h

#define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext)

 

pte_clear()

arch/arm/include/asm/pgtable.h

#define pte_clear(mm,addr,ptep) set_pte_ext(ptep, __pte(0), 0)

 

cpu_set_pte_ext()

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

#define cpu_set_pte_ext                 processor.set_pte_ext
cpu_v7_set_pte_ext()

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

/*
 *      cpu_v7_set_pte_ext(ptep, pte)
 *
 *      Set a level 2 translation table entry.
 *
 *      - ptep  - pointer to level 2 translation table entry
 *                (hardware version is stored at +2048 bytes)
 *      - pte   - PTE value to store
 *      - ext   - value for extended PTE bits
 */
ENTRY(cpu_v7_set_pte_ext)
#ifdef CONFIG_MMU
        str     r1, [r0]                        @ linux version

        bic     r3, r1, #0x000003f0
        bic     r3, r3, #PTE_TYPE_MASK
        orr     r3, r3, r2
        orr     r3, r3, #PTE_EXT_AP0 | 2

        tst     r1, #1 << 4
        orrne   r3, r3, #PTE_EXT_TEX(1)

        eor     r1, r1, #L_PTE_DIRTY
        tst     r1, #L_PTE_RDONLY | L_PTE_DIRTY
        orrne   r3, r3, #PTE_EXT_APX

        tst     r1, #L_PTE_USER
        orrne   r3, r3, #PTE_EXT_AP1

        tst     r1, #L_PTE_XN
        orrne   r3, r3, #PTE_EXT_XN

        tst     r1, #L_PTE_YOUNG
        tstne   r1, #L_PTE_VALID
        eorne   r1, r1, #L_PTE_NONE
        tstne   r1, #L_PTE_NONE
        moveq   r3, #0

 ARM(   str     r3, [r0, #2048]! )
 THUMB( add     r0, r0, #2048 )
 THUMB( str     r3, [r0] )
        ALT_SMP(W(nop))
        ALT_UP (mcr     p15, 0, r0, c7, c10, 1)         @ flush_pte
#endif
        bx      lr  
ENDPROC(cpu_v7_set_pte_ext)

ptep 주소에 위치한 pte 엔트리를 설정하는 경우 ptep로부터 2K byte 윗 쪽에 위치한 hw pte 엔트리 역시 linux pte 속성 값을 h/w pte 속성 값으로 변환하여 설정한다.

set_pte_ext-1

r1(linux pte 엔트리 값)

set_pte_ext-2

  • MT(Memory Type)
    • 0: MT_UNCACHED
    • 1: MT_BUFFERABLE
    • 2: MT_WRITETHROUGH
    • 3: MT_WRITEBACK
    • 4: MT_DEVICE_SHARED
    • 6: MT_MINICACHE
    • 9: MT_DEV_WC
    • B: MT_DEV_CACHED
    • C: MT_DEV_NONSHARED
    • F: MT_VECTORS

r3(hw pte 엔트리 값), r2(ext, hw용 확장 pte 플래그)

  • r3 값의 h/w pte AP, TEX, Table, XN 비트는 linux pte 값을 사용하여 재 설정하기 위해 일단 clear 한다.
    • r3 → 비트클리어(0x3f3)

set_pte_ext-3

  • r3에 인수로 받은 h/w용 pte 플래그를 합치고 privileged access 권한에서 read 또는 read/write 권한을 주기위해 EXT_AP0을 설정하고 2차 테이블을 지원하기 위해 TABLE 비트를 설정한다.
    • r3 | ext(hw용 확장 pte 플래그) | PTE_EXT_AP0(0x10) | 2

set_pte_ext-4

  • pte의 bit4가 1인 경우 TRE를 설정한다.
    • r3 | PTE_EXT_TEX(0x40)

set_pte_ext-5

  • pte의 L_PTE_DIRTY가 없거나 L_PTE_RDONLY가 있으면 EXT_APX를 설정하여 read only로 만든다.
    • r3 | PTX_EXT_APX(0x200)

set_pte_ext-6

  • pte의 L_PTE_USER가 있으면 unprivileged access 권한에서도 read 또는 read/write 권한을 주기 위해 EXT_AP1을 설정한다.
    • r3 | PTE_EXT_AP1(0x20)

set_pte_ext-7

  • pte의 L_PTE_XN가 있으면 페이지에서 실행코드가 동작하지 않도록 XN을 설정한다.
    • r3 | PTEX_EXT_XN(0x1)

set_pte_ext-8

  • pte의 L_PTE_YOUNG이 없거나 L_PTE_VALID(L_PTE_PRESENT)가 없거나 L_PTE_NONE이 있으면 r3을 모두 0으로 변경
    • 리눅스 PTE 엔트리에만 있는 L_PTE_YOUNG이 있으면서 L_PTE_PRESENT가 없는 경우 pte 엔트리가 아니라 swap 엔트리를 의미한다. (H/W PTE 값은 0을 대입)
    • 리눅스 PTE 엔트리에만 있는 L_PTE_NONE은 PAGE_NONE 매핑으로 automatic NUMA balancing을 위해 일부러 유저 페이지에 대한 fault를 발생시켜 사용하기 위해 이용한다. (H/W PTE 값은 0을 대입)

 

  • 리눅스용 pte 엔트리 속성 -> h/w pte 엔트리 속성 변환 예)
    • linux pte 엔트리=0x1234_565f(0b0001_0010_0011_0100_0101_0110_0101_1111)
      • pfn=0x12345, prot=MT_MEMORY_RW = SHARE | XN | DIRTY | WA(0b0111) | YOUNG | PRESENT
    • h/w pte 엔트리=0x1234_545f(0b0001_0010_0011_0100_0101_0100_0101_1111)
      • pfn=0x12345, prot=SHARE | AP(0b001=RW,NA) | TEX(0b001) | C | B | TABLE | XN

 

실제 연결된 페이지 테이블 예)

pgd-1

 

xxx_page() 관련

pgd_page()

include/asm-generic/pgtable-nopud.h

#define pgd_page(pgd)                           (pud_page((pud_t){ pgd }))
  • pud page()를 호출 -> pmd_page() 호출하여 pmd 테이블에 대응하는 page를 리턴한다.

 

pud_page()

  • 사용하지 않음.

 

pmd_page()

arch/arm/include/asm/pgtable.h

#define pmd_page(pmd)           pfn_to_page(__phys_to_pfn(pmd_val(pmd) & PHYS_MASK))
  • pmd 테이블에 대응하는 page를 리턴한다

 

pte_page()

arch/arm/include/asm/pgtable.h

#define pte_page(pte)           pfn_to_page(pte_pfn(pte))
  • pte 값에 대응하는 pfn 값을 알아온 후 해당 page를 리턴한다.

 

pte_pfn()

arch/arm/include/asm/pgtable.h

#define pte_pfn(pte)            ((pte_val(pte) & PHYS_MASK) >> PAGE_SHIFT)
  • pte 엔트리 값을 12 bit 우측 쉬프트하여 pfn(물리 주소 frame 번호) 값으로 변환한다.

 

pfn_to_page()

include/asm-generic/memory_model.h

#define pfn_to_page __pfn_to_page
#define __pfn_to_page(pfn)      (mem_map + ((pfn) - ARCH_PFN_OFFSET))
  • pfn 번호에 대응하는 page 구조체 주소를 알아온다.
  • __pfn_to_page
    • 메모리 모델에 따라 구현이 다르다.
    • CONFIG_FLATMEM
      • ARCH_PFN_OFFSET = 물리주소 PFN
      • rpi2: mem_map + pfn
    • mem_map은 PFN0부터 시작되는 물리메모리 페이지의 정보인 page[] 구조체 배열을 가리킨다.

 

참고