tcm_init()

TCM(Tightly Coupled Memory)이 내장된 SoC에서 DTCM 및 ITCM 메모리 영역의 갯 수와 크기를 알아내서 다음과 같이 매핑하고 enable 한다.

  • DTCM: 0xfffe_8000 가상 주소에 전체 DTCM 영역을 MT_MEMORY_RW_DTCM으로 매핑 예약(static_vmlist에 추가)
  • ITCM: 0xfffe_0000 가상 주소에 전체 ITCM 영역을 MT_MEMORY_RWX_ITCM으로 매핑 예약(static_vmlist에 추가)
  • rpi2: TCM이 없음.

32bit ARM 리눅스에서는 TCM 메모리의 최대 처리 용량은 각각 32K이다.

tcm_init-1

 

tcm_init()

arch/arm/kernel/tcm.c

/*
 * This initializes the TCM memory
 */
void __init tcm_init(void)
{
        u32 tcm_status;
        u8 dtcm_banks;
        u8 itcm_banks;
        size_t dtcm_code_sz = &__edtcm_data - &__sdtcm_data;
        size_t itcm_code_sz = &__eitcm_text - &__sitcm_text;
        char *start;
        char *end;
        char *ram;
        int ret;
        int i;

        /*
         * Prior to ARMv5 there is no TCM, and trying to read the status
         * register will hang the processor.
         */
        if (cpu_architecture() < CPU_ARCH_ARMv5) {
                if (dtcm_code_sz || itcm_code_sz)
                        pr_info("CPU TCM: %u bytes of DTCM and %u bytes of "
                                "ITCM code compiled in, but no TCM present "
                                "in pre-v5 CPU\n", dtcm_code_sz, itcm_code_sz);
                return;
        }

        tcm_status = read_cpuid_tcmstatus();
        dtcm_banks = (tcm_status >> 16) & 0x03;
        itcm_banks = (tcm_status & 0x03);

        /* Values greater than 2 for D/ITCM banks are "reserved" */
        if (dtcm_banks > 2)
                dtcm_banks = 0;
        if (itcm_banks > 2)
                itcm_banks = 0;
  • if (cpu_architecture() < CPU_ARCH_ARMv5) {
    • ARMv5 미만 아키텍처는 TCM이 없으므로 루틴을 빠져나간다.
  • tcm_status = read_cpuid_tcmstatus();
    • DTCM 뱅크 수와 ITCM 뱅크 수를 알아온다.
      • ARMv6 format
        • DTCM: CPUID_TCM[bit17:16]
        • ITCM: CPUID_TCM[bit1:0]
      • ARMv7 format
        • 현재 리눅스 코드가 ARMv7 format을 지원하지 않는다.
  • if (dtcm_banks > 2)
    • 뱅크 수는 0~2까지 인데 3이 나오는 경우는 PB11MPCore가 TCM이 없는데도 잘못 보고를 하는 case가 발견되어 이를 수정한 것이다.
    • 참고: ARM: 6984/1: enhance TCM robustness
        /* Setup DTCM if present */
        if (dtcm_banks > 0) {
                for (i = 0; i < dtcm_banks; i++) {
                        ret = setup_tcm_bank(0, i, dtcm_banks, &dtcm_end);
                        if (ret)
                                return;
                }
                /* This means you compiled more code than fits into DTCM */
                if (dtcm_code_sz > (dtcm_end - DTCM_OFFSET)) {
                        pr_info("CPU DTCM: %u bytes of code compiled to "
                                "DTCM but only %lu bytes of DTCM present\n",
                                dtcm_code_sz, (dtcm_end - DTCM_OFFSET));
                        goto no_dtcm;
                }
                dtcm_res.end = dtcm_end - 1;
                request_resource(&iomem_resource, &dtcm_res);
                dtcm_iomap[0].length = dtcm_end - DTCM_OFFSET;
                iotable_init(dtcm_iomap, 1);
                /* Copy data from RAM to DTCM */
                start = &__sdtcm_data;
                end   = &__edtcm_data;
                ram   = &__dtcm_start;
                memcpy(start, ram, dtcm_code_sz);
                pr_debug("CPU DTCM: copied data from %p - %p\n",
                         start, end);
                dtcm_present = true;
        } else if (dtcm_code_sz) {
                pr_info("CPU DTCM: %u bytes of code compiled to DTCM but no "
                        "DTCM banks present in CPU\n", dtcm_code_sz);
        }
  • if (dtcm_banks > 0) {
    • DTCM 뱅크가 발견되면
  • for (i = 0; i < dtcm_banks; i++) {
    • DTCM 뱅크 수 만큼 루프를 돈다.
  • ret = setup_tcm_bank(0, i, dtcm_banks, &dtcm_end);
    • TCM 관련 레지스터를 설정한다.
    • dtcm_end는 초기에 TCM의 base 주소가 담겨서 함수에 진입하고 함수를 빠져 나올때엔  TCM 사이즈만큼 증가되어 다음 TCM 메모리의 주소가 될 위치를 가리킨다.
  • if (dtcm_code_sz > (dtcm_end – DTCM_OFFSET)) {
    • 커널에 있는 DTCM용 데이터 사이즈가 실제 DTCM 영역 크기보다 더 큰 경우 에러를 출력하고 루틴을 빠져 나온다.
  • request_resource(&iomem_resource, &dtcm_res)
    • iomem_resource에 TCM 영역을 추가한다.
  • iotable_init(dtcm_iomap, 1);
    • static_vmlist에 영역을 추가하여 매핑 예약을 한다.
  • 커널에 있는 DTCM 데이터 영역을 TCM 영역에 복사한다.
  • dtcm_present = true;
    • DTCM이 준비되었음을 알리는 전역 변수이다.
no_dtcm:
        /* Setup ITCM if present */
        if (itcm_banks > 0) {
                for (i = 0; i < itcm_banks; i++) {
                        ret = setup_tcm_bank(1, i, itcm_banks, &itcm_end);
                        if (ret)
                                return;
                }
                /* This means you compiled more code than fits into ITCM */
                if (itcm_code_sz > (itcm_end - ITCM_OFFSET)) {
                        pr_info("CPU ITCM: %u bytes of code compiled to "
                                "ITCM but only %lu bytes of ITCM present\n",
                                itcm_code_sz, (itcm_end - ITCM_OFFSET));
                        return;
                }
                itcm_res.end = itcm_end - 1;
                request_resource(&iomem_resource, &itcm_res);
                itcm_iomap[0].length = itcm_end - ITCM_OFFSET;
                iotable_init(itcm_iomap, 1);
                /* Copy code from RAM to ITCM */
                start = &__sitcm_text;
                end   = &__eitcm_text;
                ram   = &__itcm_start;
                memcpy(start, ram, itcm_code_sz);
                pr_debug("CPU ITCM: copied code from %p - %p\n",
                         start, end);
                itcm_present = true;
        } else if (itcm_code_sz) {
                pr_info("CPU ITCM: %u bytes of code compiled to ITCM but no "
                        "ITCM banks present in CPU\n", itcm_code_sz);
        }
}
  • ITCM의 코드는 DTCM의 코드와 거의 동일하다.

 

다음 그림은 DTCM 및 ITCM 메모리가 각각 2개씩 내장된 ARM SoC를 대상으로 가상 주소에 매핑되는 예를 들었다.

  • DTCM 및 ITCM이 각각 2개씩인 상황에 대해 메모리를 매핑하였지만 실제 SoC에는 DTCM 및 ITCM 메모리가 없거나 있는 경우 각각 1개가 내장되어 있다.

tcm_init-2a

 

read_cpuid_tcmstatus()

arch/arm/include/asm/cputype.h

static inline unsigned int __attribute_const__ read_cpuid_tcmstatus(void)
{
        return read_cpuid(CPUID_TCM);
}
  • TCMTR(TCM Type Register) 레지스터를 통해 DTCM 뱅크 수와 ITCM 뱅크 수를 알아낼 수 있다.
    • ARMv6 format
      • DTCM: CPUID_TCM[bit17:16]
      • ITCM: CPUID_TCM[bit1:0]
    • CPUID_TCM=2
    • 두 개의 비트를 사용하여 0=no TCM, 1=1개 TCM, 2=2개 TCM, 3=undefined

 

setup_tcm_bank()

arch/arm/kernel/tcm.c

static int __init setup_tcm_bank(u8 type, u8 bank, u8 banks,
                                  u32 *offset)
{
        const int tcm_sizes[16] = { 0, -1, -1, 4, 8, 16, 32, 64, 128,
                                    256, 512, 1024, -1, -1, -1, -1 };
        u32 tcm_region;
        int tcm_size;

        /*
         * If there are more than one TCM bank of this type,
         * select the TCM bank to operate on in the TCM selection
         * register.
         */
        if (banks > 1)
                asm("mcr        p15, 0, %0, c9, c2, 0"
                    : /* No output operands */
                    : "r" (bank));

        /* Read the special TCM region register c9, 0 */
        if (!type)
                asm("mrc        p15, 0, %0, c9, c1, 0"
                    : "=r" (tcm_region));
        else
                asm("mrc        p15, 0, %0, c9, c1, 1"
                    : "=r" (tcm_region));
        tcm_size = tcm_sizes[(tcm_region >> 2) & 0x0f];
        if (tcm_size < 0) {
                pr_err("CPU: %sTCM%d of unknown size\n",
                       type ? "I" : "D", bank);
                return -EINVAL;
        } else if (tcm_size > 32) {
                pr_err("CPU: %sTCM%d larger than 32k found\n",
                       type ? "I" : "D", bank);
                return -EINVAL;
        } else {
                pr_info("CPU: found %sTCM%d %dk @ %08x, %senabled\n",
                        type ? "I" : "D",
                        bank,
                        tcm_size,
                        (tcm_region & 0xfffff000U),
                        (tcm_region & 1) ? "" : "not ");
        }

        /* Not much fun you can do with a size 0 bank */
        if (tcm_size == 0)
                return 0;

        /* Force move the TCM bank to where we want it, enable */
        tcm_region = *offset | (tcm_region & 0x00000ffeU) | 1;

        if (!type)
                asm("mcr        p15, 0, %0, c9, c1, 0"
                    : /* No output operands */
                    : "r" (tcm_region));
        else
                asm("mcr        p15, 0, %0, c9, c1, 1"
                    : /* No output operands */
                    : "r" (tcm_region));

        /* Increase offset */
        *offset += (tcm_size << 10);

        pr_info("CPU: moved %sTCM%d %dk to %08x, enabled\n",
                type ? "I" : "D",
                bank,
                tcm_size,
                (tcm_region & 0xfffff000U));
        return 0;
}
  • if (banks > 1)
    • 뱅크 수가 1개를 넘어가는 경우
  • asm(“mcr        p15, 0, %0, c9, c2, 0”
    • TCMSR(TCM Selection Register)을 사용하여 TCM 선택을 한다.
  • if (!type)
    • type=0(DTCM)이면
  • asm(“mrc        p15, 0, %0, c9, c1, 0”
    • DTCMRR(Data TCM Region Register)을 통해 DTCM 정보를 읽어온다.
  •  asm(“mrc        p15, 0, %0, c9, c1, 1”
    • ITCMRR(Instruction TCM Region Register)을 통해 ITCM 정보를 읽어온다.
  • tcm_size = tcm_sizes[(tcm_region >> 2) & 0x0f];
    • 알아온 TCM 정보에서 사이즈를 지정하는 5비트 중 4비트만 읽어낸다.
  • tcm_size가 0보다 작거나 32보다 큰 경우 에럴를 출력하고 루틴을 빠져나간다.
  • tcm_size가 0인 경우 TCM이 없는 것으로 인식하고 루틴을 빠져나간다.
  • tcm_region = *offset | (tcm_region & 0x00000ffeU) | 1;
    • tcm_region 속성 값에 TCM base 주소를 가리키는 offset을 갱신하고 TCM enable 비트를 설정한다.
  • asm(“mcr        p15, 0, %0, c9, c1, 0”
    • DTCMRR에 tcm_region 속성값을 기록한다.
  • asm(“mcr        p15, 0, %0, c9, c1, 1”
    • ITCMRR에 tcm_region 속성값을 기록한다
  • *offset += (tcm_size << 10);
    • offset에 tcm_size * 1K 만큼을 추가하여 다음 TCM base 주소를 리턴한다.

 

request_resource()

kernel/resource.c

/**
 * request_resource - request and reserve an I/O or memory resource
 * @root: root resource descriptor
 * @new: resource descriptor desired by caller
 *
 * Returns 0 for success, negative error code on error.
 */
int request_resource(struct resource *root, struct resource *new)
{
        struct resource *conflict;

        conflict = request_resource_conflict(root, new);
        return conflict ? -EBUSY : 0;
}
  • new 리소스를 root에 등록할 때 리소스 영역들끼리 겹치는 영역이 있으면 에러가 리턴된다.

 

request_resource_conflict()

kernel/resource.c

/**
 * request_resource_conflict - request and reserve an I/O or memory resource
 * @root: root resource descriptor
 * @new: resource descriptor desired by caller
 *
 * Returns 0 for success, conflict resource on error.
 */
struct resource *request_resource_conflict(struct resource *root, struct resource *new)
{
        struct resource *conflict;

        write_lock(&resource_lock);
        conflict = __request_resource(root, new);
        write_unlock(&resource_lock);
        return conflict;
}
  • write_lock(&resource_lock);
    • resource들간에 연결을 위해 write lock을 수행
  • conflict = __request_resource(root, new);
    • root가 관리하는 영역에 new 영역을 추가한다.
      • root 영역의 범위에 포함되지 않는 경우 root를 리턴한다.
      • 기존 리소스들과 영역이 겹치는 경우 해당 리소스를 리턴한다.
      • 기존 리소스들과 영역이 겹치지 않는 경우 new 영역을 추가하고 null을 리턴한다.
  • write_unlock(&resource_lock);
    • write unlock을 수행

 

__request_resource()

kernel/resource.c

/* Return the conflict entry if you can't request it */
static struct resource * __request_resource(struct resource *root, struct resource *new)
{
        resource_size_t start = new->start;
        resource_size_t end = new->end;
        struct resource *tmp, **p;

        if (end < start)
                return root;
        if (start < root->start)
                return root;
        if (end > root->end)
                return root;
        p = &root->child;
        for (;;) {
                tmp = *p;
                if (!tmp || tmp->start > end) {
                        new->sibling = tmp;
                        *p = new;
                        new->parent = root;
                        return NULL;
                }
                p = &tmp->sibling;
                if (tmp->end < start)
                        continue;
                return tmp;
        }
}
  • if (end < start) return root
    • new 영역의 끝 주소가 시작 주소보다 큰 경우는 root를 리턴
  • if (start < root->start)
    • new 영역이 root의 하단부를 벗어난 경우 root를 리턴
  • if (end > root->end) return root;
    • new 영역이 root의 상단부를 벗어난 경우 root를 리턴

resource-1

  • if (!tmp || tmp->start > end) {
    • new 리소스의 영역이 기존 리소스 영역과 겹치지 않으면서 하단에 위치하는 경우 앞쪽에 추가한다.

resource-3

  • p = &tmp->sibling;
    • 비교할 노드를 다음 옆 리소스로 선택한다.
  • if (tmp->end < start) continue;
    • new 리소스의 영역이 현재 리소스 영역보다 크면 루프를 계속 진행한다.

resource-4

  • new 리소스의 영역이 현재 리소스 영역과 겹치면 현재 리소스를 리턴한다.

resource-2

TCMSR, DTCMRR, ITCMRR 레지스터

  • TCMSR
    • 0~3 번 TCM 메모리 영역(뱅크)를 선택한다.
  • DTCMRR and ITCMRR
    • 현재 선택된 TCM 메모리의 물리 주소, 사이즈를 알아오거나 설정할 수 있다.
      • 물리주소를 바꾸거나 사용 가능 사이즈도 줄일 수 있다.
    • Enable bit를 사용하여 사용 여부를 결정할 수 있다.
    • BaseAddress
      • TCM 메모리의 물리 주소로 size에 align되어 사용된다.
      • Size는 아래 테이블 표를 참고한다.
        • ARM 리눅스는 현재 32K까지 지원한다.

tcm_init-3

 

구조체 및 전역 변수

 

resource 구조체

include/linux/ioport.h

struct resource {
        resource_size_t start;
        resource_size_t end;
        const char *name;
        unsigned long flags;
        struct resource *parent, *sibling, *child;
};
  • start
    • 리소스의 시작 물리 주소
  • end
    • 리소스의 끝 물리 주소
  • name
    • 리소스 명
  • flags
    • TCM 메모리의 플래그는 IORESOURCE_MEM(0x00000200) 이다.
  • parent
    • 리소스의 상위와 연결
  • sibling
    • 다음 리소스와 연결(ascending order)
  • child
    • 하위 리소스와 연결

 

map_desc 구조체

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

struct map_desc {
        unsigned long virtual;
        unsigned long pfn;
        unsigned long length;
        unsigned int type;
};
  • virtual
    • 가상 시작 주소
  • pfn
    • 물리 프레임 번호
  • length
    • 길이
  • type
    • 매핑 타입

 

dtcm_res & itcm_res

arch/arm/kernel/tcm.c

/*
 * TCM memory resources
 */
static struct resource dtcm_res = {
        .name = "DTCM RAM",
        .start = DTCM_OFFSET,
        .end = DTCM_OFFSET,
        .flags = IORESOURCE_MEM
};

static struct resource itcm_res = {
        .name = "ITCM RAM",
        .start = ITCM_OFFSET,
        .end = ITCM_OFFSET,
        .flags = IORESOURCE_MEM
};

 

dtcm_iomap[] & itcm_iomap[]

arch/arm/kernel/tcm.c

static struct map_desc dtcm_iomap[] __initdata = {
        {
                .virtual        = DTCM_OFFSET,
                .pfn            = __phys_to_pfn(DTCM_OFFSET),
                .length         = 0,
                .type           = MT_MEMORY_RW_DTCM
        }
};      
        
static struct map_desc itcm_iomap[] __initdata = {
        {
                .virtual        = ITCM_OFFSET,
                .pfn            = __phys_to_pfn(ITCM_OFFSET),
                .length         = 0,
                .type           = MT_MEMORY_RWX_ITCM,
        }
};

 

기타 전역 변수

static struct gen_pool *tcm_pool;
static bool dtcm_present;
static bool itcm_present;

/* TCM section definitions from the linker */
extern char __itcm_start, __sitcm_text, __eitcm_text;
extern char __dtcm_start, __sdtcm_data, __edtcm_data;

/* These will be increased as we run */
u32 dtcm_end = DTCM_OFFSET;
u32 itcm_end = ITCM_OFFSET;

 

참고

kmap_init()

pkmap과 fixmap을 사용한 매핑을 하기 위해 각각의 PTE 테이블을 할당 받아 초기화한다.

kmap_init-1

kmap_init()

arch/arm/mm/mmu.c

static void __init kmap_init(void)
{
#ifdef CONFIG_HIGHMEM
        pkmap_page_table = early_pte_alloc(pmd_off_k(PKMAP_BASE),
                PKMAP_BASE, _PAGE_KERNEL_TABLE);
#endif

        early_pte_alloc(pmd_off_k(FIXADDR_START), FIXADDR_START,
                        _PAGE_KERNEL_TABLE);
}
  • pkmap_page_table = early_pte_alloc(pmd_off_k(PKMAP_BASE), PKMAP_BASE, _PAGE_KERNEL_TABLE);
    • CONFIG_HIGHMEM 커널 옵션을 사용할 수 있으면 pkmap 사용을 위해 pkmap_page_table 전역 변수에 pkmap 시작 주소에 해당하는 pmd 엔트리 주소를 알아온다.
      • 만일 pmd 엔트리 값이 없는 경우 pte 테이블을 할당 받는다.
    • _PAGE_KERNEL_TABLE
      • (PMD_TYPE_TABLE | PMD_BIT4 | PMD_DOMAIN(DOMAIN_KERNEL))
    • PKMAP_BASE(PAGE_OFFSET-2M)
  •   early_pte_alloc(pmd_off_k(FIXADDR_START), FIXADDR_START, _PAGE_KERNEL_TABLE);
    • fixmap 사용을 위해 pte 테이블을 준비한다.
      • FIXADDR_START(0xffc0_0000)

pkmap용 L2 테이블이 없는 경우 할당 받아 초기화 한다. pkmap_page_table은 pkmap용 L2테이블을 가리킨다.

kmap_init-2

fixmap용 L2 테이블이 없는 경우 할당 받아 초기화한다.

kmap_init-3

 

참고

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;
};

 

참고

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

 

참고