devicemaps_init()

아래와 같이 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()

devicemaps_init-1

 

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 속성

Cache Flushing Regions – CACHECLEAN

  • 시스템이 FLUSH_BASE 옵션을 지원하면 사용할 수 있다.
  • 지원되는 시스템에서 FLUSH_BASE_PHYS가 매핑될 시작 물리 주소가 지정되어 있고 길이는 1M로 고정되어 있다.
    • rpi2: 사용하지 않음.
  • 매핑 메모리 타입을 MT_CACHECLEAN으로 하여 매핑을 한다.
    • MT_CACHECLEAN 타입의 특징
      • 섹션 매핑만 허용
      • 메모리 접근 권한은 privileged level에서 read만 가능
      • share
      • 캐시는 사용하지 못하고 버퍼만 사용 가능
      • Device 속성
      • 코드 실행 금지(XN)

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)

 

        /*
         * 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 캐시 속성
  • if (!vectors_high()) {
    • 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

 

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의 매핑을 한다.

 

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에 추가한다.

fill_pmd_gaps-1

 

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

 

벡터 매핑

devicemaps_init-2

  • 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
    • ARM 고정 매핑할 가상 주소
      • 0xf300_0000

 

구조체 및 전역 변수

map_desc 구조체

arch/arm/include/asm/mach/map.h

struct map_desc {
        unsigned long virtual;
        unsigned long pfn;
        unsigned long length;
        unsigned int type;
};

 

참고

댓글 남기기