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 기법으로 관리하고 페이지 테이블에 매핑한다.
- 내부에서는 또 하나의 vm_struct 구조체 리스트인 vmlist에 vm_struct 구조체를 추가한다. (addr로 ascending 정렬)
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에 추가한다.
- 1개의 맵 영역을 iotable에 추가한다.
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비트 쉬프트한 값
- flags = VM_IOREMAP | VM_ARM_STATIC_MAPPING | VM_ARM_MTYPE(md->type);
- 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(시작 가상 주소)가 가장 작은 경우 리스트의 선두에 있고 순서대로 정렬되어 있다.
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(시작 가상 주소)이 가장 작은 경우 리스트의 선두에 있고 순서대로 정렬되어 있다.
구조체 및 전역 변수
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 된다.