dma_contiguous_remap()

setup_arch() -> arm_memblock_init()  ->  dma_contiguous_reserve() 함수를 통해 reserve된 영역들은 CMA for DMA 용도의 메모리로 이 영역들을 순서대로 읽어 IO 매핑을 위해 다음과 같은 작업을 한다.

  • DMA_READY 메모리 타입으로 페이지 테이블을 매핑할 준비를 한다. (실제 매핑은 정규 메모리 관리자가 설정된 후에 한다)
  • IO 매핑 관리를 위해 static_vm 구조체 리스트인 static_vmlist에 static_vm 구조체를 추가한다. (vm->addr로 ascending 정렬)
    • 내부에서는 또 하나의 vm_struct 구조체 리스트인 vmlist에 vm_struct 구조체를 추가한다. (addr로 ascending 정렬)
      • early하게 추가된 이 리스트는 추후 mm_init() -> vmalloc_init()함수에 의해 다시 vmap_area 구조체로 변환되고 이 영역관리를 Red Black Tree 기법으로 관리하고 페이지 테이블에 매핑한다.

 

dma_contiguous_remap-1

 

dma_contiguous_remap()

arch/arm/mm/dma-mapping.c

void __init dma_contiguous_remap(void)
{
        int i;
        for (i = 0; i < dma_mmu_remap_num; i++) {
                phys_addr_t start = dma_mmu_remap[i].base;
                phys_addr_t end = start + dma_mmu_remap[i].size;
                struct map_desc map; 
                unsigned long addr;

                if (end > arm_lowmem_limit)
                        end = arm_lowmem_limit;
                if (start >= end) 
                        continue;

                map.pfn = __phys_to_pfn(start);
                map.virtual = __phys_to_virt(start);
                map.length = end - start;
                map.type = MT_MEMORY_DMA_READY;

                /*
                 * Clear previous low-memory mapping to ensure that the
                 * TLB does not see any conflicting entries, then flush
                 * the TLB of the old entries before creating new mappings.
                 *
                 * This ensures that any speculatively loaded TLB entries
                 * (even though they may be rare) can not cause any problems,
                 * and ensures that this code is architecturally compliant.
                 */
                for (addr = __phys_to_virt(start); addr < __phys_to_virt(end);
                     addr += PMD_SIZE)
                        pmd_clear(pmd_off_k(addr));

                flush_tlb_kernel_range(__phys_to_virt(start),
                                       __phys_to_virt(end));

                iotable_init(&map, 1);
        }   
}

dma_mmu_remap[] 배열에는 CMA for DMA 영역이 등록되어 있고 이 영역을 차례대로DMA_READY 메모리 타입 속성으로 페이지 매핑할 자료를 준비하고 iotable에 추가한다.

  • for (i = 0; i < dma_mmu_remap_num; i++) {
    • 리매핑이 필요한 수량 만큼 루프를 돈다.
  • phys_addr_t start = dma_mmu_remap[i].base;
    • 시작 물리 주소
  • phys_addr_t end = start + dma_mmu_remap[i].size;
    • 끝 물리 주소
  • if (end > arm_lowmem_limit) end = arm_lowmem_limit;
    • CMA for DMA 영역은 lowmem 영역 내에서만 매핑을 해야 하므로 끝 물리 주소가 lowmem 영역의 끝 주소인 arm_lowmem_limit를 초과하지 않게 조정한다.
  • if (start >= end) continue;
    • 시작 물리 주소가 lowmem 영역을 초과한 경우 무시하고 다음 리매핑 요청을 처리하기 위해 루프를 돈다.
  • for (addr = __phys_to_virt(start); addr < __phys_to_virt(end); addr += PMD_SIZE) pmd_clear(pmd_off_k(addr));
    • 가상 시작 주소부터 가상 끝 주소까지 pmd 엔트리를 삭제한다.
  • flush_tlb_kernel_range(__phys_to_virt(start), __phys_to_virt(end));
    • 영역 내의 1차, 2차 TLB 캐시 엔트리를 flush(삭제) 한다.
  • iotable_init(&map, 1);
    • 1개의 맵 영역을 iotable에 추가한다.
      • map 영역들을 여러 개의 static_vm 구조체로 변환하고 이 관리 구조체 영역을 할당 받아 static_vmlist에 추가한다.

 

iotable_init()

arch/arm/mm/mmu.c

/*
 * Create the architecture specific mappings
 */
void __init iotable_init(struct map_desc *io_desc, int nr)
{
        struct map_desc *md;
        struct vm_struct *vm;
        struct static_vm *svm;

        if (!nr)
                return;

        svm = early_alloc_aligned(sizeof(*svm) * nr, __alignof__(*svm));

        for (md = io_desc; nr; md++, nr--) {
                create_mapping(md); 

                vm = &svm->vm;
                vm->addr = (void *)(md->virtual & PAGE_MASK);
                vm->size = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
                vm->phys_addr = __pfn_to_phys(md->pfn); 
                vm->flags = VM_IOREMAP | VM_ARM_STATIC_MAPPING;
                vm->flags |= VM_ARM_MTYPE(md->type);
                vm->caller = iotable_init;
                add_static_vm_early(svm++);
        }            
}

요청 받은 맵 정보 배열 수 만큼 static_vm 구조체 배열로 reserve memblock에 할당 받는다. 그리고 요청 맵 영역들을 페이지 테이블에 매핑 구성하고 요청 맵 영역 각각을 관리하는 구조체 엔트리인 static_vm을 구성하고 그 엔트리들을 static_vmlist에 추가한다.

  • svm = early_alloc_aligned(sizeof(*svm) * nr, __alignof__(*svm));
    • nr개 만큼 static_vm 구조체 영역 크기를 reserve memblock으로 할당 받는다.
    • align 단위는 static_vm 구조체 하나 크기만큼 이다.
  • for (md = io_desc; nr; md++, nr–) {
    • 요청 맵 영역 수(nr)만큼 루프를 돈다.
  • create_mapping(md);
    • 해당 영역을 페이지 테이블에 섹션 또는 4K 페이지로 매핑한다.
  • static_vm 구조체에 영역에 대한 정보를 대입한다.
    • flags = VM_IOREMAP | VM_ARM_STATIC_MAPPING | VM_ARM_MTYPE(md->type);
      • IO 리매핑 속성, static 매핑 속성 및 메모리 타입을 플래그에 설정한다.
      • VM_ARM_MTYPE()
        • 지정된 메모리 타입을 좌측으로 20비트 쉬프트한 값
  • add_static_vm_early(svm++);
    • static_vmlist에 static_vm 구조체를 추가한다.

 

add_static_vm_early()

arch/arm/mm/ioremap.c

void __init add_static_vm_early(struct static_vm *svm)
{
        struct static_vm *curr_svm;
        struct vm_struct *vm;
        void *vaddr;

        vm = &svm->vm;
        vm_area_add_early(vm);
        vaddr = vm->addr;

        list_for_each_entry(curr_svm, &static_vmlist, list) {
                vm = &curr_svm->vm;

                if (vm->addr > vaddr)
                        break;
        }
        list_add_tail(&svm->list, &curr_svm->list);
}

vm_struct 구조체 리스트인 vmlist에 vm을 추가하고 static_vm 구조체 리스트인static_vmlist에도 svm 엔트리를 추가한다.

  • 2개의 리스트는 가상 주소가 작은 엔트리가 앞으로 순서대로 있다.

 

아래 그림은 static_vm 구조체 하나를 추가할 때의 모습을 나타낸다.

  • static_vm 구조체 내부에 vm이라는 이름의 vm_struct 구조체가 있는데 그 멤버변수 addr(시작 가상 주소)가 가장 작은 경우 리스트의 선두에 있고 순서대로 정렬되어 있다.

dma_contiguous_remap-3

 

 

vm_area_add_early()

mm/vmalloc.c

static struct vm_struct *vmlist __initdata;
/**
 * vm_area_add_early - add vmap area early during boot
 * @vm: vm_struct to add
 *
 * This function is used to add fixed kernel vm area to vmlist before
 * vmalloc_init() is called.  @vm->addr, @vm->size, and @vm->flags
 * should contain proper values and the other fields should be zero.
 *
 * DO NOT USE THIS FUNCTION UNLESS YOU KNOW WHAT YOU'RE DOING.
 */
void __init vm_area_add_early(struct vm_struct *vm) 
{
        struct vm_struct *tmp, **p; 

        BUG_ON(vmap_initialized);
        for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) {
                if (tmp->addr >= vm->addr) {
                        BUG_ON(tmp->addr < vm->addr + vm->size);
                        break;
                } else 
                        BUG_ON(tmp->addr + tmp->size > vm->addr);
        }    
        vm->next = *p;
        *p = vm;
}
  • for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) {
    • vm_struct 더블 포인터 값인 p의 초기값으로 vmlist의 주소를 담고 *p가 null이 아닌 동안 루프를 돈다.
    • 한 번 수행 시마다 p값을 현재 노드의 next 주소값으로 갱신한다.
  • if (tmp->addr >= vm->addr) {
    • 엔트리의 시작 가상 주소가 추가할 시작 가상 주소보다 크거나 같은 경우 그 엔트리 앞에 추가하기 위해 루프를 멈춘다.
  • vm->next = *p;  *p = vm
    • 루프가 중단되지 않고 끝까지 수행되는 경우는 추가한 시작 가상 주소가 가장 큰 경우이므로 리스트의 마지막에 추가하기 위해 추가 노드의 next(vm->next)에 null(*p)을 대입하고 마지막 노드의 next(*p)에 추가 노드(vm)를 대입한다.
    • 루프가 중간에 break 된 경우 현재 추가할 노드의 next(vm->next)에 현재 노드의 다음(*p) 주소를 대입하고 현재 노드의 next(*p)에 추가할 노드(vm)를 대입한다.

 

아래 그림은 vm_struct 하나를 추가할 때의 모습을 나타낸다.

  • addr(시작 가상 주소)이 가장 작은 경우 리스트의 선두에 있고 순서대로 정렬되어 있다.

dma_contiguous_remap-2c

 

구조체 및 전역 변수

 

static_vm 구조체

arch/arm/mm/mm.h

struct static_vm {
        struct vm_struct vm; 
        struct list_head list;
};

 

vm_struct 구조체

include/linux/vmalloc.h

struct vm_struct {
        struct vm_struct        *next;
        void                    *addr;
        unsigned long           size;
        unsigned long           flags;
        struct page             **pages;
        unsigned int            nr_pages;
        phys_addr_t             phys_addr;
        const void              *caller;
};
  • next
    • 다음 vm_struct를 가리킨다.
  • addr
    • 영역의 시작 가상 주소
  • size
    • 영역의 크기
  • flags
/* bits in flags of vmalloc's vm_struct below */
#define VM_IOREMAP              0x00000001      /* ioremap() and friends */
#define VM_ALLOC                0x00000002      /* vmalloc() */
#define VM_MAP                  0x00000004      /* vmap()ed pages */
#define VM_USERMAP              0x00000008      /* suitable for remap_vmalloc_range */
#define VM_VPAGES               0x00000010      /* buffer for pages was vmalloc'ed */
#define VM_UNINITIALIZED        0x00000020      /* vm_struct is not fully initialized */
#define VM_NO_GUARD             0x00000040      /* don't add guard page */
#define VM_KASAN                0x00000080      /* has allocated kasan shadow memory */
/* bits [20..32] reserved for arch specific ioremap internals */
  • pages
    • 해당 영역에서 사용하고 있는 페이지를 가리키는 포인터 배열을 가리키는 이중 page 포인터이다.
  • nr_pages
    • 해당 영역에서 사용하고 있는 페이지  수
  • phys_addr
    • 영역의 시작 물리 주소
  • caller
    • 함수 포인터

 

전역 변수

arch/arm/mm/ioremap.c

LIST_HEAD(static_vmlist);
  • static vm 엔트리들을 검색하고 관리하는 리스트이다.

 

mm/vmalloc.c

static struct vm_struct *vmlist __initdata;
  •  이 리스트에 추가된 엔트리들은 커널이 설정되는 동안 early하게 추가되어 커널 정규 메모리 관리자가 정상적으로 동작할 때 vmalloc_init()에서 다시 allocation 된다.

 

참고

페이지 테이블 매핑 – ARM32

리눅스는 페이지 테이블 관리에 4단계 테이블(pgd -> pud -> pmd -> pte)을 사용하는데 32bit ARM은 3단계 테이블(pgd -> pud(x) -> pmd -> pte)을 사용한다.

  • LPAE를 사용하지 않을 경우에는 pmd 테이블이 실제로 없기 때문에 pgd 엔트리 하나를 pmd 엔트리 포인터 2개에 대응하여 사용한다.
  • 실제 LPAE를 사용하지 않는 32bit ARM h/w는 2단계 테이블매핑만 사용한다.

 

create_mapping()

 

create_mapping-3

 

arch/arm/mm/mmu.c

/*
 * Create the page directory entries and any necessary
 * page tables for the mapping specified by `md'.  We
 * are able to cope here with varying sizes and address
 * offsets, and we take full advantage of sections and
 * supersections.
 */
static void __init create_mapping(struct map_desc *md) 
{
        unsigned long addr, length, end; 
        phys_addr_t phys;
        const struct mem_type *type;
        pgd_t *pgd;

        if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
                pr_warn("BUG: not creating mapping for 0x%08llx at 0x%08lx in user region\n",
                        (long long)__pfn_to_phys((u64)md->pfn), md->virtual);
                return;
        }

        if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
            md->virtual >= PAGE_OFFSET &&
            (md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {
                pr_warn("BUG: mapping for 0x%08llx at 0x%08lx out of vmalloc space\n",
                        (long long)__pfn_to_phys((u64)md->pfn), md->virtual);
        }

        type = &mem_types[md->type];

#ifndef CONFIG_ARM_LPAE
        /*
         * Catch 36-bit addresses
         */
        if (md->pfn >= 0x100000) {
                create_36bit_mapping(md, type);
                return;
        }
#endif

        addr = md->virtual & PAGE_MASK;
        phys = __pfn_to_phys(md->pfn);
        length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));

        if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
                pr_warn("BUG: map for 0x%08llx at 0x%08lx can not be mapped using pages, ignoring.\n",
                        (long long)__pfn_to_phys(md->pfn), addr);
                return;
        }
  • if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
    • 가상 주소가 벡터 주소가 아니면서 user space 영역이면 경고를 출력 후 매핑을 포기하고 그냥 리턴한다.
  • if ((md->type == MT_DEVICE || md->type == MT_ROM) && md->virtual >= PAGE_OFFSET && (md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {
    • 메모리 타입이 디바이스나 ROM 이면서 가상 주소가 kernel space 이고 vmalloc 영역을 벗어난 경우 경고를 출력 후 매핑을 포기하고 그냥 리턴한다.
  • if (md->pfn >= 0x100000) { create_36bit_mapping(md, type);
    • LPAE를 사용하지 않으면서 pfn이 0x100000 이상이면 즉, 4G를 초과하는 경우 36비트 매핑을 사용한다.
    • LPAE 없이 xscale 아키텍처 등에서 large-section(16M) 페이지를 사용하여 36비트 매핑을 사용할 수 있다.
  • addr = md->virtual & PAGE_MASK;
    • 가상 주소를 page 단위로 round down 한다.
  • phys = __pfn_to_phys(md->pfn);
    • pfn 값으로 물리 주소를 구한다.
  • length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
    • 가상 주소를 4K 단위로 round down할 때 남는 주소 영역에 요청 길이만큼을 4K round up 하여 길이를 산출한다.
    • 예) rpi2: md->virtual=0x8050_1b00, md->pfn=0x501, md->length=0x1500
      • addr=0x8050_1000, phys=0x0050_1000,  length=0x2000
    • 예) 위의 조건에서 md->length=0x1504인 경우
      • addr=0x8050_1000, phys=0x0050_1000,  length=0x3000
  • if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
    • 요청 메모리 타입이 1차 테이블을 사용하지 않는 경우 즉 섹션 타입을 사용해야 하는 하는 경우 addr, phys, length가 섹션 align이 안된 경우 경고를 출력 후 매핑을 포기하고 그냥 리턴한다.

 

        pgd = pgd_offset_k(addr);
        end = addr + length;
        do {
                unsigned long next = pgd_addr_end(addr, end);

                alloc_init_pud(pgd, addr, next, phys, type);

                phys += next - addr;
                addr = next;
        } while (pgd++, addr != end);
}
  • pgd = pgd_offset_k(addr);
    • 가상 주소 값으로 pgd 엔트리 가상 주소를 알아온다.
  • end = addr + length;
    • 매핑할 가상 주소의 끝 주소
  • unsigned long next = pgd_addr_end(addr, end);
    • addr 값에 2M를 더한 값을 구해오는데 초과하는 경우 end 주소를 리턴한다.
  • alloc_init_pud(pgd, addr, next, phys, type);
    • pgd 엔트리 주소에 addr 주소부터 next 주소까지를 물리주소(phys)와 타입 정보로 매핑한다.
  • phys += next – addr;
    • 매핑한 사이즈만큼 추가
  • addr = next;
    • 다음 매핑을 위해 addr 준비
  • } while (pgd++, addr != end);
    • pgd 인덱스를 증가(8 bytes)시키고 addr 주소가 끝날 때까지 루프를 돈다.

 

alloc_init_pud()

arch/arm/mm/mmu.c

static void __init alloc_init_pud(pgd_t *pgd, unsigned long addr,
                                  unsigned long end, phys_addr_t phys,
                                  const struct mem_type *type)
{
        pud_t *pud = pud_offset(pgd, addr);
        unsigned long next;

        do {
                next = pud_addr_end(addr, end);
                alloc_init_pmd(pud, addr, next, phys, type);
                phys += next - addr;
        } while (pud++, addr = next, addr != end);
}

addr 주소부터 end 주소까지를 pud 엔트리가 커버하는 크기만큼 증가시키며 pud 엔트리를 매핑한다. 단 LPAE를 사용하지 않는 경우 pud 테이블이 없으므로 요청된 인수 그대로 alloc_init_pmd() 함수를 1회 호출하고 리턴한다.

  • pud_t *pud = pud_offset(pgd, addr);
    • pud 테이블이 없기 때문에 pgd를 리턴한다.
  • next = pud_addr_end(addr, end);
    • pud 테이블이 없기 때문에 end를 리턴한다.
  • alloc_init_pmd(pud, addr, next, phys, type);
    • pud 엔트리 주소에 addr 주소부터 next 주소까지를 물리주소(phys)와 타입 정보로 매핑한다.
  • phys += next – addr;
    • 매핑한 사이즈만큼  추가
  • } while (pud++, addr = next, addr != end);
    • pud 인덱스를 증가(4 bytes)시키고 addr 주소가 끝날 때까지 루프를 돌게 되어 있지만 ARM 32bit에서는 pud 테이블이 없기 때문에 조건이 자동으로 만료되어 루프를 탈출하게 된다.

 

alloc_init_pmd()

arch/arm/mm/mmu.c

static void __init alloc_init_pmd(pud_t *pud, unsigned long addr,
                                      unsigned long end, phys_addr_t phys,
                                      const struct mem_type *type)
{
        pmd_t *pmd = pmd_offset(pud, addr);
        unsigned long next;

        do {
                /*
                 * With LPAE, we must loop over to map
                 * all the pmds for the given range.
                 */
                next = pmd_addr_end(addr, end);
                
                /*
                 * Try a section mapping - addr, next and phys must all be
                 * aligned to a section boundary.
                 */
                if (type->prot_sect && 
                                ((addr | next | phys) & ~SECTION_MASK) == 0) {
                        __map_init_section(pmd, addr, next, phys, type);
                } else {
                        alloc_init_pte(pmd, addr, next,
                                                __phys_to_pfn(phys), type);
                }

                phys += next - addr;

        } while (pmd++, addr = next, addr != end);
}

addr 주소부터 end 주소까지를 2M씩 증가시키며 pmd 엔트리를 매핑한다. 단 LPAE를 사용하지 않는 경우 pmd 테이블이 없으므로 요청된 인수 그대로 __map_init_section() 또는 alloc_init_pte() 함수를 1회 호출하고 리턴한다.

  • pmd_t *pmd = pmd_offset(pud, addr);
    • LPAE를 사용하지 않는 경우 pud(실제 pgd) 인수가 그대로 리턴된다.
    • LPAE를 사용하는 경우 pud(실제 pgd) 테이블에서 가상주소로 pmd 인덱스를 계산하여 pmd 엔트리 주소를 알아온다.
  • next = pmd_addr_end(addr, end);
    • LPAE를 사용하지 않는 경우 end를 그대로 리턴한다.
    • LPAE를 사용하는 경우 다음 2M 단위로 align 된 주소를 리턴하는데 end를 넘어가는 경우 end를 리턴한다.
  • if (type->prot_sect && ((addr | next | phys) & ~SECTION_MASK) == 0) {
    • 요청 메모리 타입이 섹션용 매핑이 가능하고 요청 인수들이 섹션 크기로 align된 경우
  • __map_init_section(pmd, addr, next, phys, type);
    • pmd 엔트리를 섹션으로 매핑한다.
  • alloc_init_pte(pmd, addr, next, __phys_to_pfn(phys), type);
    • 그 외에는 2차 PTE 테이블을 만든다.

 

__map_init_section()

arch/arm/mm/mmu.c

static void __init __map_init_section(pmd_t *pmd, unsigned long addr,
                        unsigned long end, phys_addr_t phys,
                        const struct mem_type *type)
{
        pmd_t *p = pmd;

#ifndef CONFIG_ARM_LPAE
        /*
         * In classic MMU format, puds and pmds are folded in to
         * the pgds. pmd_offset gives the PGD entry. PGDs refer to a
         * group of L1 entries making up one logical pointer to
         * an L2 table (2MB), where as PMDs refer to the individual
         * L1 entries (1MB). Hence increment to get the correct
         * offset for odd 1MB sections.
         * (See arch/arm/include/asm/pgtable-2level.h)
         */
        if (addr & SECTION_SIZE)
                pmd++;
#endif
        do {
                *pmd = __pmd(phys | type->prot_sect);
                phys += SECTION_SIZE;
        } while (pmd++, addr += SECTION_SIZE, addr != end);

        flush_pmd_entry(p);
}

섹션을 매핑하기 위한 함수이다.

  • if (addr & SECTION_SIZE) pmd++
    • LPAE를 사용하지 않는 경우 pmd 엔트리 하나는 두 개로 이루어져 있는데 각각 1M를 담당한다. 만일 요청 가상 주소를 1M 단위의 인덱스만을 남겼을 때 홀 수인 경우 pmd 엔트리를 증가시켜 pmd+1 번째를 지정해야 한다.
      • 예) 0x0030_0000(홀 수), 0x0040_0000(짝 수)
  • *pmd = __pmd(phys | type->prot_sect);
    • pmd 엔트리에 물리 주소와 요청 메모리 타입의 섹션 속성을 설정한다.
  • phys += SECTION_SIZE;
    • 1M 증가
  • } while (pmd++, addr += SECTION_SIZE, addr != end);
    • pmd 인덱스를 증가(4 bytes)시키고 addr 주소가 끝날 때까지 루프를 돈다.
  • flush_pmd_entry(p);
    • 1차, 2차 TLB 테이블에서 pmd 엔트리를 삭제한다.

 

alloc_init_pte()

arch/arm/mm/mmu.c

static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
                                  unsigned long end, unsigned long pfn,
                                  const struct mem_type *type)
{
        pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);
        do {
                set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
                pfn++;
        } while (pte++, addr += PAGE_SIZE, addr != end);
}
  • pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);
    • pte 테이블을 할당한다.
  • set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
    • pte 엔트리를 설정한다.
  • } while (pte++, addr += PAGE_SIZE, addr != end);
    • pte 엔트리를 증가 , 주소를 4K 증가하고 end 주소까지 루프를 돈다.

 

early_pte_alloc()

arch/arm/mm/mmu.c

static pte_t * __init early_pte_alloc(pmd_t *pmd, unsigned long addr, unsigned long prot)
{
        if (pmd_none(*pmd)) {
                pte_t *pte = early_alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);
                __pmd_populate(pmd, __pa(pte), prot);
        }
        BUG_ON(pmd_bad(*pmd));
        return pte_offset_kernel(pmd, addr);
}
  • if (pmd_none(*pmd)) {
    • pmd 엔트리에 값이 있어 pte 테이블이 필요한 경우
  • pte_t *pte = early_alloc(PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE);
    • memblock에서 free 4K 공간을 할당 받고 해당 페이지의 가상 주소를 알아온다.
  • __pmd_populate(pmd, __pa(pte), prot);
    • 해당 pte 테이블을 populate로 기록 한다.
  • pte_offset_kernel(pmd, addr);
    • pte 테이블에서 가상주소로 pte 엔트리 주소를 구한다.

 

__pmd_populate()

arch/arm/include/asm/pgalloc.h

static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte,
                                  pmdval_t prot)
{
        pmdval_t pmdval = (pte + PTE_HWTABLE_OFF) | prot;
        pmdp[0] = __pmd(pmdval); 
#ifndef CONFIG_ARM_LPAE
        pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));
#endif
        flush_pmd_entry(pmdp);
}
  • pmdval_t pmdval = (pte + PTE_HWTABLE_OFF) | prot;
    • pte 테이블 주소 + 2K한 후 속성값을 추가한 값
  • pmdp[0] = __pmd(pmdval);
    • pmdp 엔트리에 pmdval 엔트리 값을 기록한다.
    • ARM h/w pt #0 테이블을 가리키는 주소
  • pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));
    • 다음 pmdp 엔트리에 pmdval + 1K한 값
      • pte 테이블 주소 + 3K한 후 속성값을 추가한 값과 동일
    • ARM h/w pt #1 테이블을 가리키는 주소

 

페이지 테이블 매핑

아래 그림과 같이 16K의 영역을 매핑하는 경우 LPAE를 사용하지 않는 32bit ARM 아키텍처에서의 호출되는 함수의 흐름을 나타내 보았다.

create_mapping-1

 

아래는 위의 함수 호출을 통하여 완성된 페이지 테이블 형태를 나타내었다.

create_mapping-2

 

참고

 

 

map_lowmem()

lowmem 영역을 페이지 테이블에 매핑하는데 각 영역에 따라 MT_MEMORY_RW 또는 MT_MEMORY_RWX 타입으로 매핑한다.

map_lowmem()

map_lowmem-1a

 

arch/arm/mm/mmu.c

static void __init map_lowmem(void)
{
        struct memblock_region *reg;
        phys_addr_t kernel_x_start = round_down(__pa(_stext), SECTION_SIZE);
        phys_addr_t kernel_x_end = round_up(__pa(__init_end), SECTION_SIZE);

        /* Map all the lowmem memory banks. */
        for_each_memblock(memory, reg) {
                phys_addr_t start = reg->base;
                phys_addr_t end = start + reg->size;
                struct map_desc map; 

                if (end > arm_lowmem_limit)
                        end = arm_lowmem_limit;
                if (start >= end) 
                        break;
  • phys_addr_t kernel_x_start = round_down(__pa(_stext), SECTION_SIZE);
    • 커널의 물리 시작 주소를 1M round down
  • phys_addr_t kernel_x_end = round_up(__pa(__init_end), SECTION_SIZE);
    • 커널의 __init_end 주소를 1M round up
      • __init_end
        • 커널의 코드 영역 끝나고 .data 섹션 안에 위치한 init data 종료 주소

 

                if (end < kernel_x_start) {
                        map.pfn = __phys_to_pfn(start);
                        map.virtual = __phys_to_virt(start);
                        map.length = end - start;
                        map.type = MT_MEMORY_RWX;

                        create_mapping(&map);
  • 다음 그림과 같이 비교할 memblock이 커널보다 아래에 위치하는 경우 이 memblock을 MT_MEMORY_RWX 타입으로 매핑한다.

map_lowmem-2b

 

                } else if (start >= kernel_x_end) {
                        map.pfn = __phys_to_pfn(start);
                        map.virtual = __phys_to_virt(start);
                        map.length = end - start;
                        map.type = MT_MEMORY_RW;

                        create_mapping(&map);
  • 다음 그림과 같이 비교할 memblock이 커널보다 위에 위치하는 경우 이 memblock을 MT_MEMORY_RW 타입으로 매핑한다.

map_lowmem-3b

 

                } else {
                        /* This better cover the entire kernel */
                        if (start < kernel_x_start) {
                                map.pfn = __phys_to_pfn(start);
                                map.virtual = __phys_to_virt(start);
                                map.length = kernel_x_start - start;
                                map.type = MT_MEMORY_RW;

                                create_mapping(&map);
                        }

                        map.pfn = __phys_to_pfn(kernel_x_start);
                        map.virtual = __phys_to_virt(kernel_x_start);
                        map.length = kernel_x_end - kernel_x_start;
                        map.type = MT_MEMORY_RWX;

                        create_mapping(&map);

                        if (kernel_x_end < end) {
                                map.pfn = __phys_to_pfn(kernel_x_end);
                                map.virtual = __phys_to_virt(kernel_x_end);
                                map.length = end - kernel_x_end;
                                map.type = MT_MEMORY_RW;

                                create_mapping(&map);
                        }
                }
        }
}
  • 다음 그림과 같이 비교할 memblock이 커널을 포함하는 경우 이 memblock을 3등분 하여 각각의 영역을 아래와 같은 타입으로 매핑한다.

map_lowmem-4a

 

참고

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

 

참고