io 매핑
정규 ioremap 함수는 빈 vmalloc 영역을 찾아 매핑하는데 매핑 종류에 따라 5개의 함수를 사용한다. 아키텍처마다 지원되는 캐시의 종류가 다르므로 특정 모드가 지원되지 않는 경우 하향 호환을 시킨다. ioremap_cache() 함수는 캐시를 사용하지만 아키텍처에 따라 읽기 캐시외에 쓰기 캐시까지도 사용할 수 있다. 쓰기 캐시에 대한 옵션을 가능하면 선택할 수 있도록 하기 위해 특정 아키텍처에서는 ioremap_wc() 함수와 ioremap_wt() 함수를 구분하여 사용하기도 한다. 참고로 다음 표와 같이 arm 및 arm64 에서 원래 함수 의도와는 다르게 약간 상이한 매핑 타입을 확인할 수 있다.
32bit arm ioremap 함수 흐름도
arm64 ioremap 함수 흐름도
ioremap() 등
arch/arm/include/asm/io.h
/* * ioremap and friends. * * ioremap takes a PCI memory address, as specified in * Documentation/io-mapping.txt. * */ #define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE) #define ioremap_nocache(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE) #define ioremap_cache(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE_CACHED) #define ioremap_wc(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE_WC)
arm64에서 ioremap(), ioremap_nocache(), ioremap_wt() 함수는 PROT_DEVICE_nGnRE 매핑을 사용하여 캐시를 사용하지 않는 디바이스 타입으로 매핑하고, ioremap_wc() 함수는 캐시를 사용하지 않는 것은 동일하지만 디바이스 타입이 아닌 메모리 타입으로 매핑하는 것이 다른 점이다.
__arm_ioremap()
arch/arm/mm/ioremap.c
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype) { return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0)); } EXPORT_SYMBOL(__arm_ioremap);
요청 물리주소 및 사이즈만큼 빈 vmalloc 공간을 찾아 mtype 속성으로 매핑하고 가상 주소를 반환한다.
전역 __arm_ioremap_caller 함수 포인터 변수
arch/arm/mm/ioremap.c
void __iomem * (*arch_ioremap_caller)(phys_addr_t, size_t, unsigned int, void *) = __arm_ioremap_caller;
컴파일 타임에 전역 arch_ioremap_caller 함수 포인터 변수에 __arm_ioremap_caller() 함수 주소를 대입한다.
__arm_ioremap_caller()
arch/arm/mm/ioremap.c
void __iomem *__arm_ioremap_caller(phys_addr_t phys_addr, size_t size, unsigned int mtype, void *caller) { phys_addr_t last_addr; unsigned long offset = phys_addr & ~PAGE_MASK; unsigned long pfn = __phys_to_pfn(phys_addr); /* * Don't allow wraparound or zero size */ last_addr = phys_addr + size - 1; if (!size || last_addr < phys_addr) return NULL; return __arm_ioremap_pfn_caller(pfn, offset, size, mtype, caller); }
요청 물리주소 및 사이즈만큼 빈 vmalloc 공간을 찾아 mtype 속성으로 매핑하고 가상 주소를 반환한다.
- 코드 라인 5~6에서 물리 주소에서 페이지 단위로 절삭하여 남은 offset 값과 pfn 값을 구한다.
- 코드 라인 11에서 페이지 단위로 매핑할 페이지의 끝 주소를 구한다.
- 예) phys_addr=0x1234_5000, size=0x2000
- last_addr=0x1234_6fff
- 예) phys_addr=0x1234_5000, size=0x2000
- 코드 라인 12~13에서 size가 0이거나 끝 주소가 시스템 범위를 초과하는 경우 null을 반환한다.
- 코드 라인 15에서 pfn에 대해 size 만큼 빈 vmalloc 공간을 찾아 mtype 속성으로 매핑하고 가상 주소를 반환한다.
__arm_ioremap_pfn_caller()
arch/arm/mm/ioremap.c
void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn, unsigned long offset, size_t size, unsigned int mtype, void *caller) { const struct mem_type *type; int err; unsigned long addr; struct vm_struct *area; phys_addr_t paddr = __pfn_to_phys(pfn); #ifndef CONFIG_ARM_LPAE /* * High mappings must be supersection aligned */ if (pfn >= 0x100000 && (paddr & ~SUPERSECTION_MASK)) return NULL; #endif type = get_mem_type(mtype); if (!type) return NULL; /* * Page align the mapping size, taking account of any offset. */ size = PAGE_ALIGN(offset + size); /* * Try to reuse one of the static mapping whenever possible. */ if (size && !(sizeof(phys_addr_t) == 4 && pfn >= 0x100000)) { struct static_vm *svm; svm = find_static_vm_paddr(paddr, size, mtype); if (svm) { addr = (unsigned long)svm->vm.addr; addr += paddr - svm->vm.phys_addr; return (void __iomem *) (offset + addr); } } /* * Don't allow RAM to be mapped - this causes problems with ARMv6+ */ if (WARN_ON(pfn_valid(pfn))) return NULL; area = get_vm_area_caller(size, VM_IOREMAP, caller); if (!area) return NULL; addr = (unsigned long)area->addr; area->phys_addr = paddr; #if !defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE) if (DOMAIN_IO == 0 && (((cpu_architecture() >= CPU_ARCH_ARMv6) && (get_cr() & CR_XP)) || cpu_is_xsc3()) && pfn >= 0x100000 && !((paddr | size | addr) & ~SUPERSECTION_MASK)) { area->flags |= VM_ARM_SECTION_MAPPING; err = remap_area_supersections(addr, pfn, size, type); } else if (!((paddr | size | addr) & ~PMD_MASK)) { area->flags |= VM_ARM_SECTION_MAPPING; err = remap_area_sections(addr, pfn, size, type); } else #endif err = ioremap_page_range(addr, addr + size, paddr, __pgprot(type->prot_pte)); if (err) { vunmap((void *)addr); return NULL; } flush_cache_vmap(addr, addr + size); return (void __iomem *) (offset + addr); }
요청 물리주소 및 사이즈만큼 빈 vmalloc 공간을 찾아 mtype 속성으로 매핑하고 가상 주소를 반환한다.
- 코드 라인 18~20에서 요청한 mtype에 해당하는 mem_type 구조체를 알아오는데 없는 경우 null을 반환한다.
- 코드 라인 25에서 매핑에 필요한 페이지 사이즈를 구하기 위해 size에 offset을 더한 값을 페이지 단위로 정렬한다.
- 예) offset=0x678, size=0x1000, PAGE_SIZE=4K
- size=0x2000 (2개 페이지)
- 예) offset=0x678, size=0x1000, PAGE_SIZE=4K
- 코드 라인 30에서 물리 주소가 4이면서 pfn이 0x10000(4G)를 초과하는 경우만 아니라면
- 코드 라인 33~38에서 vmalloc 공간에 이미 동일한 타입으로 static 매핑 영역이내에 포함된 경우 매핑 없이 가상 주소만 찾아 반환한다.
- 코드 라인 47~49에서 VM_IOREMAP 플래그를 사용하여 vmalloc 공간에서 빈 영역을 찾아 영역 정보를 vm_struct 구조체 형태로 알아오고 null인 경우 함수를 빠져나간다.
- 코드 라인 50~51에서 찾은 시작 가상 주소를 addr에 대입하고 영역의 시작 물리 주소에 paddr을 대입한다.
- 코드 라인 53~64에서 CONFIG_SMP 커널 옵션을 사용하지 않고 LPAE도 아닌 경우 수퍼 섹션 또는 섹션 매핑을 지원한다.
- 코드 라인 65~66에서 가상 주소 범위만큼 물리 주소 paddr부터 매핑한다.
- 코드 라인 68~71 매핑이 실패하는 경우 매핑해제를 시도한다.
- 코드 라인 73에서 매핑된 가상 주소 영역을 flush한다. 단 armv7 및 armv8 아키텍처와 같이 데이터 캐시가 PIPT 또는 VIPT non-aliasing을 사용하는 경우에는 flush할 필요없다
find_static_vm_paddr()
arch/arm/mm/ioremap.c
static struct static_vm *find_static_vm_paddr(phys_addr_t paddr, size_t size, unsigned int mtype) { struct static_vm *svm; struct vm_struct *vm; list_for_each_entry(svm, &static_vmlist, list) { vm = &svm->vm; if (!(vm->flags & VM_ARM_STATIC_MAPPING)) continue; if ((vm->flags & VM_ARM_MTYPE_MASK) != VM_ARM_MTYPE(mtype)) continue; if (vm->phys_addr > paddr || paddr + size - 1 > vm->phys_addr + vm->size - 1) continue; return svm; } return NULL; }
요청 영역이 vmalloc 공간에 매핑된 영역 엔트리 중 하나에 포함되고 같은 매핑 타입으로 이미 static 매핑된 경우 해당 vm_struct 정보를 반환한다. 그 외의 경우 null을 반환한다.
- 코드 라인 7에서 전역 static_vmlist의 모든 엔트리를 대상으로 루프를 돈다.
- 코드 라인 9~10에서 해당 영역이 VM_ARM_STATIC_MAPPING 플래그를 사용하지 않으면 skip 한다.
- 코드 라인 11~12에서 해당 영역에 매핑된 타입과 요청 매핑 타입이 다른 경우 skip 한다.
- 코드 라인 14~16에서 기존 영역의 범위내에 요청 범위가 포함되지 않은 경우 skip 한다.
remap_area_sections()
arch/arm/mm/ioremap.c
static int remap_area_sections(unsigned long virt, unsigned long pfn, size_t size, const struct mem_type *type) { unsigned long addr = virt, end = virt + size; pgd_t *pgd; pud_t *pud; pmd_t *pmd; /* * Remove and free any PTE-based mapping, and * sync the current kernel mapping. */ unmap_area_sections(virt, size); pgd = pgd_offset_k(addr); pud = pud_offset(pgd, addr); pmd = pmd_offset(pud, addr); do { pmd[0] = __pmd(__pfn_to_phys(pfn) | type->prot_sect); pfn += SZ_1M >> PAGE_SHIFT; pmd[1] = __pmd(__pfn_to_phys(pfn) | type->prot_sect); pfn += SZ_1M >> PAGE_SHIFT; flush_pmd_entry(pmd); addr += PMD_SIZE; pmd += 2; } while (addr < end); return 0; }
1M 단위 섹션 매핑이 가능한 경우 해당 pmd 엔트리를 섹션 페이지에 매핑한다.
- arm에서 pmd 엔트리는 pmd[0] 및 pmd[1] 2개로 구성되어 있다.
unmap_area_sections()
lib/ioremap.c
/* * Section support is unsafe on SMP - If you iounmap and ioremap a region, * the other CPUs will not see this change until their next context switch. * Meanwhile, (eg) if an interrupt comes in on one of those other CPUs * which requires the new ioremap'd region to be referenced, the CPU will * reference the _old_ region. * * Note that get_vm_area_caller() allocates a guard 4K page, so we need to * mask the size back to 1MB aligned or we will overflow in the loop below. */ static void unmap_area_sections(unsigned long virt, unsigned long size) { unsigned long addr = virt, end = virt + (size & ~(SZ_1M - 1)); pgd_t *pgd; pud_t *pud; pmd_t *pmdp; flush_cache_vunmap(addr, end); pgd = pgd_offset_k(addr); pud = pud_offset(pgd, addr); pmdp = pmd_offset(pud, addr); do { pmd_t pmd = *pmdp; if (!pmd_none(pmd)) { /* * Clear the PMD from the page table, and * increment the vmalloc sequence so others * notice this change. * * Note: this is still racy on SMP machines. */ pmd_clear(pmdp); init_mm.context.vmalloc_seq++; /* * Free the page table, if there was one. */ if ((pmd_val(pmd) & PMD_TYPE_MASK) == PMD_TYPE_TABLE) pte_free_kernel(&init_mm, pmd_page_vaddr(pmd)); } addr += PMD_SIZE; pmdp += 2; } while (addr < end); /* * Ensure that the active_mm is up to date - we want to * catch any use-after-iounmap cases. */ if (current->active_mm->context.vmalloc_seq != init_mm.context.vmalloc_seq) __check_vmalloc_seq(current->active_mm); flush_tlb_kernel_range(virt, end); }
vmalloc 공간에서 pmd 매핑된 엔트리를 클리어한다.
- 코드 라인 18에서 요청 영역을 pmd 섹션 단위만큼 데이터 캐시를 flush한다. 단 armv7 및 armv8 아키텍처와 같이 데이터 캐시가 PIPT 또는 VIPT non-aliasing을 사용하는 경우에는 flush할 필요없다
- 코드 라인 19~21에서 커널 페이지 테이블을 통해 요청 가상 주소에 해당하는 pgd, pud, pmd 엔트리 주소를 알아온다.
- 코드 라인 25~33에서 이미 pmd 매핑된 경우 매핑을 클리어한다.
- 코드 라인 34에서 커널에 메모리 context에서 vmalloc 시퀀스 값을 증가시켜 커널의 vmalloc 등록 정보가 갱신되었음을 나타내게 한다.
- 코드 라인 39~40에서 기존 pmd 엔트리가 테이블을 가리킨 경우 그 테이블을 할당 해제 시킨다.
- 코드 라인 43~45에서 끝 주소까지 pmd 단위를 증가시키며 루프를 돈다.
- 코드 라인 51~52에서 커널의 vmalloc 정보와 현재 태스크의 vmalloc 정보를 비교하여 갱신된 경우 커널의 pgd 매핑된 vmalloc 엔트리들을 현재 태스크 메모리 디스크립터의 pgd 테이블에 복사한다.
- 코드 라인 54에서 해당 커널 영역에 대한 TLB cache를 flush 한다
ioremap_page_range()
lib/ioremap.c
int ioremap_page_range(unsigned long addr, unsigned long end, phys_addr_t phys_addr, pgprot_t prot) { pgd_t *pgd; unsigned long start; unsigned long next; int err; BUG_ON(addr >= end); start = addr; phys_addr -= addr; pgd = pgd_offset_k(addr); do { next = pgd_addr_end(addr, end); err = ioremap_pud_range(pgd, addr, next, phys_addr+addr, prot); if (err) break; } while (pgd++, addr = next, addr != end); flush_cache_vmap(start, end); return err; } EXPORT_SYMBOL_GPL(ioremap_page_range);
주어진 가상 주소 영역에 물리 주소를 prot 속성으로 매핑한다.
- 코드 라인 11~12에서 요청 가상 주소를 start에 잠시 보관하고, 물리 주소에서 요청 가상 주소를 뺀다.
- 예) addr=0x1000_0000, phys_addr=0x1234_5000
- phys_addr=0x0234_5000
- 예) addr=0x1000_0000, phys_addr=0x1234_5000
- 코드 라인 13에서 가상 주소 addr에 해당하는 커널 pgd 엔트리 주소를 알아온다.
- 코드 라인 15에서 다음 pgd 엔트리가 관리하는 가상 주소를 가져오되 마지막인 경우 end 값을 가져온다.
- 코드 라인 16~18에서 pgd 엔트리 하나 범위내 addr ~ next 가상 주소 범위의 pud 엔트리들을 매핑하고 에러인 경우 루프를 탈출한다.
- 코드 라인 19에서 다음 pgd 엔트리 및 다음 가상 주소를 선택하고 end까지 루프를 돌며 반복한다.
- 코드 라인 21에서 매핑된 가상 주소 영역을 flush한다. 단 armv7 및 armv8 아키텍처와 같이 데이터 캐시가 PIPT 또는 VIPT non-aliasing을 사용하는 경우에는 flush할 필요없다.
ioremap_pud_range()
lib/ioremap.c
static inline int ioremap_pud_range(pgd_t *pgd, unsigned long addr, unsigned long end, phys_addr_t phys_addr, pgprot_t prot) { pud_t *pud; unsigned long next; phys_addr -= addr; pud = pud_alloc(&init_mm, pgd, addr); if (!pud) return -ENOMEM; do { next = pud_addr_end(addr, end); if (ioremap_pmd_range(pud, addr, next, phys_addr + addr, prot)) return -ENOMEM; } while (pud++, addr = next, addr != end); return 0; }
pud 단위로 주어진 가상 주소 영역에 물리 주소를 prot 속성으로 매핑한다.
- 코드 라인 7에서 물리 주소에서 요청 가상 주소를 미리 뺀다.
- 예) addr=0x1000_0000, phys_addr=0x1_1234_5000
- phys_addr=0x1_0234_5000
- 예) addr=0x1000_0000, phys_addr=0x1_1234_5000
- 코드 라인 8~10에서 pud용 테이블을 하나 할당 받아온다. 만일 실패하는 경우 -ENOMEM 에러로 함수를 빠져나간다.
- 코드 라인 12에서 다음 pud 엔트리가 관리하는 가상 주소를 가져오되 마지막인 경우 end 값을 가져온다.
- 코드 라인 13~14에서 pud 엔트리 하나 범위내 addr ~ next 가상 주소 범위의 pmd 엔트리들을 매핑한다. 실패하는 경우 -ENOMEM 결과를 가지고 함수를 빠져나간다.
- 코드 섹션 15에서 다음 pud 엔트리 및 다음 가상 주소를 선택하고 end까지 루프를 돌며 반복한다.
ioremap_pmd_range()
lib/ioremap.c
static inline int ioremap_pmd_range(pud_t *pud, unsigned long addr, unsigned long end, phys_addr_t phys_addr, pgprot_t prot) { pmd_t *pmd; unsigned long next; phys_addr -= addr; pmd = pmd_alloc(&init_mm, pud, addr); if (!pmd) return -ENOMEM; do { next = pmd_addr_end(addr, end); if (ioremap_pte_range(pmd, addr, next, phys_addr + addr, prot)) return -ENOMEM; } while (pmd++, addr = next, addr != end); return 0; }
pmd 단위로 주어진 가상 주소 영역에 물리 주소를 prot 속성으로 매핑한다.
- 코드 라인 7에서 물리 주소에서 요청 가상 주소를 미리 뺀다.
- 코드 라인 8~10에서 pmd용 테이블을 하나 할당 받아온다. 만일 실패하는 경우 -ENOMEM 에러로 함수를 빠져나간다.
- 코드 라인 12에서 다음 pmd 엔트리가 관리하는 가상 주소를 가져오되 마지막인 경우 end 값을 가져온다.
- 코드 라인 13~14에서 pmd 엔트리 하나 범위내 addr ~ next 가상 주소 범위의 pte 엔트리들을 매핑한다. 실패하는 경우 -ENOMEM 결과를 가지고 함수를 빠져나간다.
- 코드 섹션 15에서 다음 pmd 엔트리 및 다음 가상 주소를 선택하고 end까지 루프를 돌며 반복한다.
ioremap_pte_range()
lib/ioremap.c
static int ioremap_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end, phys_addr_t phys_addr, pgprot_t prot) { pte_t *pte; u64 pfn; pfn = phys_addr >> PAGE_SHIFT; pte = pte_alloc_kernel(pmd, addr); if (!pte) return -ENOMEM; do { BUG_ON(!pte_none(*pte)); set_pte_at(&init_mm, addr, pte, pfn_pte(pfn, prot)); pfn++; } while (pte++, addr += PAGE_SIZE, addr != end); return 0; }
pte 단위로 주어진 가상 주소 영역에 물리 주소 페이지를 prot 속성으로 매핑한다.
- 코드 라인 7에서 pfn을 알아온다.
- 코드 라인 8~10에서 pte용 테이블을 하나 할당 받아온다. 만일 실패하는 경우 -ENOMEM 에러로 함수를 빠져나간다.
- 코드 라인 13~14에서 가상 주소 addr에 해당하는 pte 엔트리를 pfn 물리페이지에 prot 타입으로 매핑시키고 pfn을 증가시킨다.
- 코드 라인 15에서 다음 pte 엔트리를 증가시키고, 가상 주소도 다음 페이지만큼 증가시키며 end까지 루프를 돌며 반복한다.
io 언매핑
iounmap()
arch/arm/include/asm/io.h
#define iounmap __arm_iounmap
요청 물리 주소의 io 매핑을 해제한다.
__arm_iounmap()
arch/arm/mm/ioremap.c
void __arm_iounmap(volatile void __iomem *io_addr) { arch_iounmap(io_addr); } EXPORT_SYMBOL(__arm_iounmap);
요청 물리 주소의 io 매핑을 해제한다.
arch_iounmap()
arch/arm/mm/ioremap.c
void (*arch_iounmap)(volatile void __iomem *) = __iounmap;
컴파일 타임에 전역 arch_iounmap 함수 포인터 변수에는 __iounmap() 함수의 주소가 담긴다.
__iounmap()
arch/arm/mm/ioremap.c
void __iounmap(volatile void __iomem *io_addr) { void *addr = (void *)(PAGE_MASK & (unsigned long)io_addr); struct static_vm *svm; /* If this is a static mapping, we must leave it alone */ svm = find_static_vm_vaddr(addr); if (svm) return; #if !defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE) { struct vm_struct *vm; vm = find_vm_area(addr); /* * If this is a section based mapping we need to handle it * specially as the VM subsystem does not know how to handle * such a beast. */ if (vm && (vm->flags & VM_ARM_SECTION_MAPPING)) unmap_area_sections((unsigned long)vm->addr, vm->size); } #endif vunmap(addr); }
요청 물리 주소의 io 매핑을 해제한다.
- 코드 라인 7~9에서 vmalloc 공간에 요청 주소에 대한 static 매핑이 있는 경우 그냥 함수를 빠져나간다.
- 코드 라인 11~25에서 시스템(빌드된 커널)이 SMP 및 LPAE를 지원하지 않는 경우 해당 주소로 섹션 매핑이 된 경우 해제한다.
- 코드 라인 27에서 vmalloc 공간에 매핑된 io 물리 주소를 vunmap() 함수를 호출하여 매핑을 해제한다.
vmalloc 공간의 매핑 상태 확인
다음과 같이 어떤 api를 통해 물리주소가 vmalloc 가상 주소 공간에 매핑되었는지 확인할 수 있다.
$ sudo cat /proc/vmallocinfo 0xba800000-0xbb000000 8388608 iotable_init+0x0/0xb8 phys=3a800000 ioremap 0xbb804000-0xbb806000 8192 raw_init+0x50/0x148 pages=1 vmalloc 0xbb806000-0xbb809000 12288 pcpu_mem_zalloc+0x44/0x80 pages=2 vmalloc 0xbb899000-0xbb89c000 12288 pcpu_mem_zalloc+0x44/0x80 pages=2 vmalloc 0xbb89c000-0xbb89e000 8192 dwc_otg_driver_probe+0x650/0x7a8 phys=3f006000 ioremap 0xbb89e000-0xbb8a0000 8192 devm_ioremap_nocache+0x40/0x7c phys=3f300000 ioremap 0xbbc00000-0xbbc83000 536576 bcm2708_fb_set_par+0x108/0x13c phys=3db79000 ioremap 0xbc9f7000-0xbc9ff000 32768 SyS_swapon+0x618/0xf6c pages=7 vmalloc 0xbc9ff000-0xbca01000 8192 SyS_swapon+0x850/0xf6c pages=1 vmalloc 0xbcc39000-0xbcc3b000 8192 SyS_swapon+0xaa0/0xf6c pages=1 vmalloc 0xbce7b000-0xbce7f000 16384 n_tty_open+0x20/0xe4 pages=3 vmalloc 0xbce83000-0xbce87000 16384 n_tty_open+0x20/0xe4 pages=3 vmalloc 0xbce8b000-0xbce8f000 16384 n_tty_open+0x20/0xe4 pages=3 vmalloc 0xbce93000-0xbce97000 16384 n_tty_open+0x20/0xe4 pages=3 vmalloc 0xbce9b000-0xbce9f000 16384 n_tty_open+0x20/0xe4 pages=3 vmalloc 0xbcea7000-0xbceab000 16384 n_tty_open+0x20/0xe4 pages=3 vmalloc 0xbceab000-0xbceaf000 16384 n_tty_open+0x20/0xe4 pages=3 vmalloc 0xbceb7000-0xbceb9000 8192 bpf_prog_alloc+0x44/0xb0 pages=1 vmalloc 0xbf509000-0xbf50d000 16384 n_tty_open+0x20/0xe4 pages=3 vmalloc 0xbf50d000-0xbf511000 16384 n_tty_open+0x20/0xe4 pages=3 vmalloc 0xf3000000-0xf3001000 4096 iotable_init+0x0/0xb8 phys=3f000000 ioremap 0xf3003000-0xf3004000 4096 iotable_init+0x0/0xb8 phys=3f003000 ioremap 0xf3007000-0xf3008000 4096 iotable_init+0x0/0xb8 phys=3f007000 ioremap 0xf300b000-0xf300c000 4096 iotable_init+0x0/0xb8 phys=3f00b000 ioremap 0xf3100000-0xf3101000 4096 iotable_init+0x0/0xb8 phys=3f100000 ioremap 0xf3200000-0xf3201000 4096 iotable_init+0x0/0xb8 phys=3f200000 ioremap 0xf3201000-0xf3202000 4096 iotable_init+0x0/0xb8 phys=3f201000 ioremap 0xf3215000-0xf3216000 4096 iotable_init+0x0/0xb8 phys=3f215000 ioremap 0xf3980000-0xf39a0000 131072 iotable_init+0x0/0xb8 phys=3f980000 ioremap 0xf4000000-0xf4001000 4096 iotable_init+0x0/0xb8 phys=40000000 ioremap 0xfea50000-0xff000000 5963776 pcpu_get_vm_areas+0x0/0x5e0 vmalloc
참고
- Early ioremam | 문c
- Vmap | 문c