아래와 같이 IO 디바이스 영역에 대한 매핑을 한다.
- 벡터 페이지 8K를 할당받고 초기 데이터를 copy한다.
- XIP 커널 영역을 MT_ROM으로 매핑
- Cache Flushing Region이 필요한 아키텍처의 경우 해당 주소부터 1M를 MT_CACHECLEAN으로 매핑하고 더 필요 시 1M를 MT_MINICLEAN으로 매핑
- 벡터테이블을 MT_LOW_VECTORS/MT_HIGH_VECTORS로 매핑
- 머신 아키텍처가 map_io 함수를 지원하는 경우 해당 함수를 호출하여 디바이스 영역을 정적으로 매핑하게 한다. 아키텍처가 별도로 요구하지 않는 경우 CONFIG_DEBUG_LL 커널 옵션이 설정된 경우 콘솔 디바이스 영역을 MT_DEVICE로 static_vmlist에 추가하여 매핑 예약
- UART, I2C, GPIO, …. 등
- 매핑된 영역에 대해 짝이 맞지 않는 pmd 엔트리를 1M 추가 매핑하여 설정한다.
- PCI 영역 0xFEE0_0000 부터 2M를 static_vmlist에 추가하여 매핑 예약.
- TLB 등을 포함한 i-cache, d-cache를 flush 한다.
devicemaps_init()
vmalloc 영역에 대한 매핑용 페이지 테이블 엔트리를 삭제하므로 모든 디버그용 디바이스 매핑이 삭제될 것이다. 따라서 이 함수를 수정하거나 디버깅 하는 경우 매우 조심해야 한다는 의미이다.
arch/arm/mm/mmu.c
/* * Set up the device mappings. Since we clear out the page tables for all * mappings above VMALLOC_START, we will remove any debug device mappings. * This means you have to be careful how you debug this function, or any * called function. This means you can't use any function or debugging * method which may touch any device, otherwise the kernel _will_ crash. */ static void __init devicemaps_init(const struct machine_desc *mdesc) { struct map_desc map; unsigned long addr; void *vectors; /* * Allocate the vector page early. */ vectors = early_alloc(PAGE_SIZE * 2); early_trap_init(vectors); for (addr = VMALLOC_START; addr; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr)); /* * Map the kernel if it is XIP. * It is always first in the modulearea. */ #ifdef CONFIG_XIP_KERNEL map.pfn = __phys_to_pfn(CONFIG_XIP_PHYS_ADDR & SECTION_MASK); map.virtual = MODULES_VADDR; map.length = ((unsigned long)_etext - map.virtual + ~SECTION_MASK) & SECTION_MASK; map.type = MT_ROM; create_mapping(&map); #endif /* * Map the cache flushing regions. */ #ifdef FLUSH_BASE map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS); map.virtual = FLUSH_BASE; map.length = SZ_1M; map.type = MT_CACHECLEAN; create_mapping(&map); #endif #ifdef FLUSH_BASE_MINICACHE map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS + SZ_1M); map.virtual = FLUSH_BASE_MINICACHE; map.length = SZ_1M; map.type = MT_MINICLEAN; create_mapping(&map); #endif
- vectors = early_alloc(PAGE_SIZE * 2);
- 벡터 테이블 용도로 사용하기 위해 4K 페이지 두 개를 memblock을 통해 할당 받는다.
- early_trap_init(vectors);
- 할당된 벡터 영역에 커널에 존재하는 벡터 데이터를 복사하여 준비한다.
- for (addr = VMALLOC_START; addr; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr));
- vmalloc 공간에 대한 1차 페이지 테이블 매핑을 모두 지운다.
XIP 커널 매핑
- XIP 커널 옵션을 사용하는 경우 XIP 커널의 시작 물리 주소로 pfn 값을 구한다.
- 매핑할 가상 주소 값은 MODULES_ADDR(PAGE_OFFSET)이고 길이는 커널의 코드 영역 끝에서 커널 시작 주소를 뺸 후 섹션 단위로 round up한다.
- 매핑 메모리 타입을 MT_ROM으로 하여 매핑을 한다.
- MT_ROM 타입의 특징
- 섹션 매핑만 허용
- 메모리 접근 권한은 privileged level에서 read만 가능
- non-share
- WBWA 캐시 속성
- Normal Memory 속성
- MT_ROM 타입의 특징
Cache Flushing Regions – CACHECLEAN
- 시스템이 FLUSH_BASE 옵션을 지원하면 사용할 수 있다.
- 지원되는 시스템에서 FLUSH_BASE_PHYS가 매핑될 시작 물리 주소가 지정되어 있고 길이는 1M로 고정되어 있다.
- rpi2: 사용하지 않음.
- 매핑 메모리 타입을 MT_CACHECLEAN으로 하여 매핑을 한다.
- MT_CACHECLEAN 타입의 특징
- 섹션 매핑만 허용
- 메모리 접근 권한은 privileged level에서 read만 가능
- share
- 캐시는 사용하지 못하고 버퍼만 사용 가능
- Device 속성
- 코드 실행 금지(XN)
- MT_CACHECLEAN 타입의 특징
Cache Flushing Regions – MINICACHE
- 시스템이 FLUSH_BASE_MINICACHE 옵션을 지원하면 사용할 수 있다.
- 지원되는 시스템에서 FLUSH_BASE_PHYS + 1M가 매핑될 시작 물리 주소가 지정되어 있고 길이는 1M로 고정되어 있다.
- rpi2: 사용하지 않음.
- 매핑 메모리 타입을 MT_MINICLEAN으로 하여 매핑을 한다.
- MT_MINICLEAN 타입의 특징
- 섹션 매핑만 허용
- 메모리 접근 권한은 privileged level에서 read만 가능
- Non-share
- 캐시와 버퍼를 사용하지 않음
- Strong Ordered Memory 속성
- 코드 실행 금지(XN)
- MT_MINICLEAN 타입의 특징
/* * Create a mapping for the machine vectors at the high-vectors * location (0xffff0000). If we aren't using high-vectors, also * create a mapping at the low-vectors virtual address. */ map.pfn = __phys_to_pfn(virt_to_phys(vectors)); map.virtual = 0xffff0000; map.length = PAGE_SIZE; #ifdef CONFIG_KUSER_HELPERS map.type = MT_HIGH_VECTORS; #else map.type = MT_LOW_VECTORS; #endif create_mapping(&map); if (!vectors_high()) { map.virtual = 0; map.length = PAGE_SIZE * 2; map.type = MT_LOW_VECTORS; create_mapping(&map); } /* Now create a kernel read-only mapping */ map.pfn += 1; map.virtual = 0xffff0000 + PAGE_SIZE; map.length = PAGE_SIZE; map.type = MT_LOW_VECTORS; create_mapping(&map); /* * Ask the machine support to map in the statically mapped devices. */ if (mdesc->map_io) mdesc->map_io(); else debug_ll_io_init(); fill_pmd_gaps(); /* Reserve fixed i/o space in VMALLOC region */ pci_reserve_io(); /* * Finally flush the caches and tlb to ensure that we're in a * consistent state wrt the writebuffer. This also ensures that * any write-allocated cache lines in the vector page are written * back. After this point, we can start to touch devices again. */ local_flush_tlb_all(); flush_cache_all(); }
할당 받은 벡터 페이지를 CPU가 사용하는 가상 주소에 맞게 매핑하는데 CPU는 SCTLR에서 벡터의 주소를 0x0(low vector address) 또는 0xffff_0000(high vector) address의 둘 중 하나로 설정할 수 있다.
- 벡터 영역의 첫 페이지를 0xffff_0000 가상 주소에 MT_HIGH_VECTORS 메모리 타입으로 매핑한다.
- MT_HIGH_VECTORS 메모리 타입 속성
- 섹션을 사용하지 못하고 small page 매핑만 가능
- 메모리 접근 권한은 privileged level(kernel space)과 unprivileged level(user space)에서 read만 가능
- Share
- WBWA 캐시 속성
- MT_HIGH_VECTORS 메모리 타입 속성
- if (!vectors_high()) {
- SCTLR의 벡터 모드가 high가 아닌 경우 벡터 영역의 두 개 페이지를 0x0 가상 주소에 MT_LOW_VECTORS 메모리 타입으로 매핑한다.
- MT_LOW_VECTORS는 MT_HIGH_VECTORS와 다른 점이 unprivileged level 에서 access를 할 수 없는 점이다.
- 참고: Why two vector table addresses on ARM?
- SCTLR의 벡터 모드가 high가 아닌 경우 벡터 영역의 두 개 페이지를 0x0 가상 주소에 MT_LOW_VECTORS 메모리 타입으로 매핑한다.
- 벡터 영역의 두 번째 페이지를 0xffff_1000 가상 주소에 MT_LOW_VECTORS 메모리 타입으로 매핑한다.
- if (mdesc->map_io) mdesc->map_io();
- 머신 아키텍처가 map_io 함수를 지원하는 경우 해당 함수를 호출하여 디바이스 영역을 정적으로 매핑하게 한다.
- rpi2 예) bcm2709_map_io()
- debug_ll_io_init();
- 디버그 출력용 디바이스 장치가 사용하는 주소 1 페이지를 static_vmlist에 추가하여 매핑 예약한다.
- fill_pmd_gaps();
- 매핑된 영역에 대해 짝이 맞지 않는 pmd 엔트리를 1M 추가 매핑하여 설정한다.
- pci_reserve_io();
- PCI에서 사용하는 주소 영역을 static_vmlist에 추가하여 매핑 예약한다.
- 마지막으로 모든 TLB 캐시 엔트리를 flush하고 모든 캐시를 flush 한다.
early_trap_init()
arch/arm/kernel/traps.c
void __init early_trap_init(void *vectors_base) { #ifndef CONFIG_CPU_V7M unsigned long vectors = (unsigned long)vectors_base; extern char __stubs_start[], __stubs_end[]; extern char __vectors_start[], __vectors_end[]; unsigned i; vectors_page = vectors_base; /* * Poison the vectors page with an undefined instruction. This * instruction is chosen to be undefined for both ARM and Thumb * ISAs. The Thumb version is an undefined instruction with a * branch back to the undefined instruction. */ for (i = 0; i < PAGE_SIZE / sizeof(u32); i++) ((u32 *)vectors_base)[i] = 0xe7fddef1; /* * Copy the vectors, stubs and kuser helpers (in entry-armv.S) * into the vector page, mapped at 0xffff0000, and ensure these * are visible to the instruction stream. */ memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start); kuser_init(vectors_base); flush_icache_range(vectors, vectors + PAGE_SIZE * 2); modify_domain(DOMAIN_USER, DOMAIN_CLIENT); #else /* ifndef CONFIG_CPU_V7M */ /* * on V7-M there is no need to copy the vector table to a dedicated * memory area. The address is configurable and so a table in the kernel * image can be used. */ #endif }
- for (i = 0; i < PAGE_SIZE / sizeof(u32); i++) ((u32 *)vectors_base)[i] = 0xe7fddef1;
- 할당 받은 벡터 페이지를 0xe7fd_def1으로 설정한다.
- 이 값은 ARM 또는 THUMB instruction에 없는 코드를 사용하여 이 영역의 코드로 jump되는 경우 exception이 발생하게 하였다.
- memcpy((void *)vectors, __vectors_start, __vectors_end – __vectors_start);
- 할당 받은 벡터 페이지에 커널의 vectors_start ~ vectors_end 까지의 영역을 복사한다.
- memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end – __stubs_start);
- 할당 받은 벡터 페이지 + 4K에 커널의 stubs_start ~ stubs_end 까지의 영역을 복사한다
- kuser_init(vectors_base);
- CONFIG_KUSER_HELPERS 커널 옵션을 사용하는 경우 할당 받은 벡터 영역 + 4K 바로 밑에 kuser_helper 코드를 복사하고 필요 시 vectors + 0xfe8(hardware TLS instruction)의 4바이트를 vectors + 0xfe0(kuser_get_tls) 주소에 복사한다.
- flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
- 벡터 2개 페이지를 i-cache flush 한다.
- modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
- DOMAIN_USER 영역을 DOMAIN_CLIENT(0b01)로 설정한다.
kuser_init()
arch/arm/kernel/traps.c
static void __init kuser_init(void *vectors) { extern char __kuser_helper_start[], __kuser_helper_end[]; int kuser_sz = __kuser_helper_end - __kuser_helper_start; memcpy(vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz); /* * vectors + 0xfe0 = __kuser_get_tls * vectors + 0xfe8 = hardware TLS instruction at 0xffff0fe8 */ if (tls_emu || has_tls_reg) memcpy(vectors + 0xfe0, vectors + 0xfe8, 4); }
- memcpy(vectors + 0x1000 – kuser_sz, __kuser_helper_start, kuser_sz);
- 할당 받은 벡터 영역 + 4K 바로 밑에 kuser_helper 코드를 복사한다.
- if (tls_emu || has_tls_reg) memcpy(vectors + 0xfe0, vectors + 0xfe8, 4);
- tls_emu와 has_tls_reg가 하나라도 설정된 경우 vectors + 0xfe8(hardware TLS instruction)의 4바이트를 vectors + 0xfe0(kuser_get_tls) 주소에 복사한다.
- user space에서 TLS(Thread Local Storage) 값의 access를 하기 위해 사용
- HWCAP_TLS 기능이 있는 일부 ARMv6 이상의 아키텍처에서 TPIDRURO 및 TPIDRURW(TLS 레지스터로 이용)를 사용
- HWCAP_TLS 기능이 없는 아키텍처는 vectors + 0xff0(kuser_get_tls) 주소를 사용한다.
- TPIDRPRW, TPIDRURO, TPIDRURW 레지스터는 ARM에서 계획했던 TPID의 access 용도로 사용하지 않고 다음과 같이 per-cpu나 TLS base 주소를 access 하는데에 사용한다.
- TPIDRPRW는 per-cpu offset 용도로 사용
- TPIDRURO는 user space에서의 TLS 값을access
- TPIDRURW는 kernel space에서의 TLS 값을 access
- 참고: Kernel-provided User Helpers | kernel.org
- user space에서 TLS(Thread Local Storage) 값의 access를 하기 위해 사용
- tls_emu와 has_tls_reg가 하나라도 설정된 경우 vectors + 0xfe8(hardware TLS instruction)의 4바이트를 vectors + 0xfe0(kuser_get_tls) 주소에 복사한다.
bcm2709_map_io()
arch/arm/mach-bcm2709/bcm2709.c
void __init bcm2709_map_io(void) { iotable_init(bcm2709_io_desc, ARRAY_SIZE(bcm2709_io_desc)); }
rpi2에서 사용하는 bcm2709 칩에서 기본적으로 사용되는 IO device의 매핑을 한다.
- 참고: dma_contiguous_remap() | 문c
bcm2709_io_desc[] 배열
arch/arm/mach-bcm2709/bcm2709.c
static struct map_desc bcm2709_io_desc[] __initdata = { { .virtual = IO_ADDRESS(ARMCTRL_BASE), .pfn = __phys_to_pfn(ARMCTRL_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(UART0_BASE), .pfn = __phys_to_pfn(UART0_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(UART1_BASE), .pfn = __phys_to_pfn(UART1_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(DMA_BASE), .pfn = __phys_to_pfn(DMA_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(MCORE_BASE), .pfn = __phys_to_pfn(MCORE_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(ST_BASE), .pfn = __phys_to_pfn(ST_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(USB_BASE), .pfn = __phys_to_pfn(USB_BASE), .length = SZ_128K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(PM_BASE), .pfn = __phys_to_pfn(PM_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(GPIO_BASE), .pfn = __phys_to_pfn(GPIO_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(ARM_LOCAL_BASE), .pfn = __phys_to_pfn(ARM_LOCAL_BASE), .length = SZ_4K, .type = MT_DEVICE}, };
- rpi2에서 사용하는 각 device 장치의 매핑을 위해 가상 주소, 물리주소, 길이, 매핑할 메모리 타입이 배열로 제공된다.
IO_ADDRESS()
arch/arm/mach-bcm2709/include/mach/platform.c
/* macros to get at IO space when running virtually */ #define IO_ADDRESS(x) (((x) & 0x00ffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000)
IO 디바이스의 물리 주소를 가상 주소로 변경한다.
- IO 장치의 0xABxx_xxxx 물리 주소를 가상 주소로 변경하면 A를 B의 자리로 옮기고 그 자리에 F를 사용한다.
- 0xFAxx_xxxx
#define BCM2708_PERI_BASE 0x3F000000
- bcm2708 및 bcm2709 둘 다 장치의 base 물리 주소를 0x3f00_0000을 사용한다.
#define UART0_BASE (BCM2708_PERI_BASE + 0x201000) /* Uart 0 */
- UART0의 물리주소는
- 0x3f20_1000
- 이 주소를 가상주소로 변경하면
- 0xf320_1000
- 참고로 이 주소를 rpi2의 main core인 VC(Video Core)가 사용하는 bus 주소로 변경하면 다(rpi2의 device의 버스 주소는 DTB에서 사용)
- 0x7e20_1000
debug_ll_addr()
arch/arm/mm/mmu.c
#ifdef CONFIG_DEBUG_LL void __init debug_ll_io_init(void) { struct map_desc map; debug_ll_addr(&map.pfn, &map.virtual); if (!map.pfn || !map.virtual) return; map.pfn = __phys_to_pfn(map.pfn); map.virtual &= PAGE_MASK; map.length = PAGE_SIZE; map.type = MT_DEVICE; iotable_init(&map, 1); } #endif
- debug_ll_addr(&map.pfn, &map.virtual);
- 디버그용 디바이스 장치의 물리 주소와 가상 주소를 알아온다.
- map
- 1개 페이지를 MT_DEVICE 타입으로 매핑할 구조체를 준비하였다.
- iotable_init(&map, 1);
- 1개 맵 구조체 정보대로 static_vmlist에 추가하여 매핑 예약을 한다.
debug_ll_addr()
arch/arm/kernel/debug.S
ENTRY(debug_ll_addr) addruart r2, r3, ip str r2, [r0] str r3, [r1] ret lr ENDPROC(debug_ll_addr)
- uart 포트의 물리주소를 r0 레지스터가 가리키는 주소에 저장한다.
- uart 포트의 가상 주소를 r1 레지스터가 가기키는 주소에 저장한다.
addruart()
mach-bcm2709/include/mach/debug-macro.S
.macro addruart, rp, rv, tmp ldr \rp, =UART0_BASE ldr \rv, =IO_ADDRESS(UART0_BASE) .endm
rpi2의 uart0 포트를 디버그 출력용으로 사용할 때를 예로 들었다.
- UART0_BASE=0x3F00_0000 + 0x20_1000
- IO_ADDRESS()
- #define IO_ADDRESS(x) (((x) & 0x00ffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000)
- IO_ADDRESS(0x3f20_1000)=0xf320_1000
- rp
- uart0의 물리주소가 담긴다.
- rpi2: 0x3f20_1000
- rv
- uart0의 가상주소가 담긴다.
- rpi2: f320_1000
fill_pmd_gaps()
arch/arm/mm/mmu.c
/* * The Linux PMD is made of two consecutive section entries covering 2MB * (see definition in include/asm/pgtable-2level.h). However a call to * create_mapping() may optimize static mappings by using individual * 1MB section mappings. This leaves the actual PMD potentially half * initialized if the top or bottom section entry isn't used, leaving it * open to problems if a subsequent ioremap() or vmalloc() tries to use * the virtual space left free by that unused section entry. * * Let's avoid the issue by inserting dummy vm entries covering the unused * PMD halves once the static mappings are in place. */ static void __init fill_pmd_gaps(void) { struct static_vm *svm; struct vm_struct *vm; unsigned long addr, next = 0; pmd_t *pmd; list_for_each_entry(svm, &static_vmlist, list) { vm = &svm->vm; addr = (unsigned long)vm->addr; if (addr < next) continue; /* * Check if this vm starts on an odd section boundary. * If so and the first section entry for this PMD is free * then we block the corresponding virtual address. */ if ((addr & ~PMD_MASK) == SECTION_SIZE) { pmd = pmd_off_k(addr); if (pmd_none(*pmd)) pmd_empty_section_gap(addr & PMD_MASK); } /* * Then check if this vm ends on an odd section boundary. * If so and the second section entry for this PMD is empty * then we block the corresponding virtual address. */ addr += vm->size; if ((addr & ~PMD_MASK) == SECTION_SIZE) { pmd = pmd_off_k(addr) + 1; if (pmd_none(*pmd)) pmd_empty_section_gap(addr); } /* no need to look at any vm entry until we hit the next PMD */ next = (addr + PMD_SIZE - 1) & PMD_MASK; } }
- list_for_each_entry(svm, &static_vmlist, list) {
- static_vmlist를 루프를 돌며 수행
- if ((addr & ~PMD_MASK) == SECTION_SIZE) {
- 요청 가상 주소가 홀수 섹션인 경우 즉 pmd가 홀수 엔트리인경우
- pmd = pmd_off_k(addr);
- 가상 주소로 pmd 엔트리 가상 주소를 알아온다.
- if (pmd_none(*pmd)) pmd_empty_section_gap(addr & PMD_MASK);
- 만일 pmd 엔트리가 없는 경우 해당 1M를 그 전 섹션 주소로 매핑 요청(예약)을 한다.
- 예) 0x3f10_0000 (홀수 섹션)의 pmd 엔트리가 없는 경우 0x3f00_0000(pair 구성된 짝수 섹션 주소)로 매핑 요청을 한다.
- addr += vm->size;
- 이 번에는 사이즈를 더하여 끝 가상 주소를 알아온다.
- if ((addr & ~PMD_MASK) == SECTION_SIZE) {
- 역시 끝 가상 주소가 홀수 섹션인 경우 즉 pmd가 홀수 엔트리인 경우
- pmd = pmd_off_k(addr) + 1;
- 가상 주소로 pmd 엔트리 주소를 알아온 후 1을 더해 다음 pmd 엔트리 주소를 알아온다.
- if (pmd_none(*pmd)) pmd_empty_section_gap(addr);
- 해당 주소의 pmd 엔트리가 비어 있는 경우 해당 주소로 매핑 요청(예약)을 한다.
- next = (addr + PMD_SIZE – 1) & PMD_MASK;
- 다음 찾을 주소는 현재 매핑한 주소를 pmd 크기(2M) round up 한 주소 이상을 찾게 설정한다.
- 예) addr=0x3f10_5000
- next=3f20_0000이 되고 다음 엔트리의 주소는 3f20_0000 이상 부터 매핑확인을 한다.
아래 그림과 같이 LPAE를 사용하지 않는 32bit ARM에서는 pmd 엔트리들이 항상 페어를 이루어야 하기 때문에 영역의 시작 주소와 끝 주소를 체크하여 pmd 엔트리가 짝을 이루지 못한 엔트리들에 대해서도 나중에 매핑을 할 수 있도록 static_vmlist에 추가한다.
pmd_empty_section_gap()
arch/arm/mm/mmu.c
static void __init pmd_empty_section_gap(unsigned long addr) { vm_reserve_area_early(addr, SECTION_SIZE, pmd_empty_section_gap); }
- 요청 영역을 static_vmlist에 추가한다.
pci_reserve_io()
arch/arm/mm/mmu.c
#if defined(CONFIG_PCI) && !defined(CONFIG_NEED_MACH_IO_H) static void __init pci_reserve_io(void) { struct static_vm *svm; svm = find_static_vm_vaddr((void *)PCI_IO_VIRT_BASE); if (svm) return; vm_reserve_area_early(PCI_IO_VIRT_BASE, SZ_2M, pci_reserve_io); } #else #define pci_reserve_io() do { } while (0) #endif
- svm = find_static_vm_vaddr((void *)PCI_IO_VIRT_BASE);
- static_vmlist에 PCI IO 주소가 이미 등록이 되어 있는지 확인하여 등록 되어 있는 경우 함수를 빠져나간다.
- vm_reserve_area_early(PCI_IO_VIRT_BASE, SZ_2M, pci_reserve_io);
- 등록되어 있지 않은 경우 PCI IO 주소로 2M를 매핑 요청(예약)을 한다.
- PCI_IO_VIRT_BASE=0xfee0_0000
벡터 매핑
- MT_LOW_VECTORS는 read only, MT_HIGH_VECTORS는 read/write access 권한을 갖는 메모리 속성
- CONFIG_KUSER_HELPERS 커널 옵션을 사용하지는 않는 경우 MT_HIGH_VECTORS로 매핑하지 않는다.
find_static_vm_vaddr()
arch/arm/mm/ioremap.c
struct static_vm *find_static_vm_vaddr(void *vaddr) { struct static_vm *svm; struct vm_struct *vm; list_for_each_entry(svm, &static_vmlist, list) { vm = &svm->vm; /* static_vmlist is ascending order */ if (vm->addr > vaddr) break; if (vm->addr <= vaddr && vm->addr + vm->size > vaddr) return svm; } return NULL; }
static_vmlist의 등록된 엔트리들의 영역 중 요청 주소가 포함되는 경우 해당 엔트리를 리턴한다.
- list_for_each_entry(svm, &static_vmlist, list) {
- static_vmlist 엔트리를 루프를 돌며 하나씩 가져온다.
- if (vm->addr > vaddr)
- 엔트리의 영역 시작 주소가 요청 주소보다 큰 경우 null을 리턴
- if (vm->addr <= vaddr && vm->addr + vm->size > vaddr)
- 엔트리의 영역 범위에 요청 주소가 있는 경우 현재 엔트리를 리턴한다.
BCM2709 IO 테이블 매핑
bcm2709_map_io()
arch/arm/mm/mmu.c
void __init bcm2709_map_io(void) { iotable_init(bcm2709_io_desc, ARRAY_SIZE(bcm2709_io_desc)); }
bcm2709용 io 주소 페이지들을 static 매핑하도록 요청한다.
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++); } }
bcm2709용 io 주소 페이지들을 static 매핑하도록 요청한다.
- 코드 라인 13에서 요청한 갯 수만큼 static_vm 구조체를 할당해온다.
- 코드 라인 15~16에서 요청한 갯 수 만큼 io 디스크립터들에 대해 루프를 돌며 고정 매핑을 수행한다.
- 매핑 시 vmalloc 공간 중 상위 부분에 고정 매핑 한다.
- 코드 라인 18~21에서 주소, 사이즈, 물리 주소 등을 svm에 대입한다.
- 코드 라인 22에서 vm_struct 구성 시 VM_IOREMAP, VM_ARM_STATIC_MAPPING을 포함시켜 io용 static 매핑을 하였음을 알린다.
- 코드 라인 23에서 매핑 디스크립터의 타입을 20bit 좌측으로 쉬프트하여 플래그에 대입한다.
- 코드 라인 24에서 caller로 iotable_init() 함수에서 추가하였음을 알린다.
- 코드 라인 25에서 &static_vmlist에서 가상 주소순으로 static_vm을 해당 위치에 insert 한다.
- 가장 높은 가상 주소가 리스트의 선두이다.
bcm2709_io_desc IO 맵 디스크립터
arch/arm/mm/mmu.c
static struct map_desc bcm2709_io_desc[] __initdata = { { .virtual = IO_ADDRESS(ARMCTRL_BASE), .pfn = __phys_to_pfn(ARMCTRL_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(UART0_BASE), .pfn = __phys_to_pfn(UART0_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(UART1_BASE), .pfn = __phys_to_pfn(UART1_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(DMA_BASE), .pfn = __phys_to_pfn(DMA_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(MCORE_BASE), .pfn = __phys_to_pfn(MCORE_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(ST_BASE), .pfn = __phys_to_pfn(ST_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(USB_BASE), .pfn = __phys_to_pfn(USB_BASE), .length = SZ_128K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(PM_BASE), .pfn = __phys_to_pfn(PM_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(GPIO_BASE), .pfn = __phys_to_pfn(GPIO_BASE), .length = SZ_4K, .type = MT_DEVICE}, { .virtual = IO_ADDRESS(ARM_LOCAL_BASE), .pfn = __phys_to_pfn(ARM_LOCAL_BASE), .length = SZ_4K, .type = MT_DEVICE}, };
BCM2709에서 사용할 IO에 대한 고정 매핑 항목들이다.
- 예) ARMCTL_BASE
- Video Core 물리 주소
- 0x7e00_0000
- ARM 물리 주소
- 0x3F00_0000
- 주의) rpi: 0x2000_0000 -> rpi2: 0x3f00_0000
- 0x3F00_0000
- ARM 고정 매핑할 가상 주소
- 0xf300_0000
- Video Core 물리 주소
구조체 및 전역 변수
map_desc 구조체
arch/arm/include/asm/mach/map.h
struct map_desc { unsigned long virtual; unsigned long pfn; unsigned long length; unsigned int type; };
참고
- Kernel-provided User Helpers | kernel.org
- Kernel-provided User Helpers | 문c
- ARM Exception Vector | 문c
- 페이지 테이블 | 문c
- 페이지 테이블 관련 명령(pgd, pud, pmd, pte) | 문c
- 페이지 테이블 매핑(create_mapping()) | 문c
- 페이지 테이블 엔트리 속성 | 문c