setup_arch()

  • early_paging_init()의 경우 특별히 메모리 정보가 변경되는 머신의 경우만 메모리 재설정 루틴이 동작된다. LPAE 설정을 사용하는 경우 phisical to virtual transalation에 대한 추가 루틴도 수행된다.
    • 관련 머신: arch/arm/mach-keystone

setup_arch

 

arm_memblock_init()

<kernel v4.0>

reserve memblock 영역에 다음 영역들을 등록한다.

  • 커널 영역 (XIP 커널인 경우 코드를 제외한 커널 영역)
  • initrd 영역
  • 페이지 테이블 영역
  • 아키텍처 머신이 지정하는 reserve 영역
  • DTB 영역 및 DTB가 지정하는 reserved mem 영역
  • CMA 영역(연속된 메모리 핸들링이 필요한 영역)
    • 다음 2가지 설정에 의해 영역 크기가 할당될 수 있다.
      • “cma=” 커널 cmdline 문자열에 의해 호출되어 지정된다.
      • CONFIG_CMA_SIZE_SEL_MBYTES 옵션을 사용하여 요청 영역을 받아 등록
    • cma(Contigugos Memory Allocator) 영역에도 추가한다.

arm_memblock_init

 

arm_memblock_init()

arch/arm/mm/init.c

void __init arm_memblock_init(const struct machine_desc *mdesc)
{
        /* Register the kernel text, kernel data and initrd with memblock. */
#ifdef CONFIG_XIP_KERNEL
        memblock_reserve(__pa(_sdata), _end - _sdata);
#else
        memblock_reserve(__pa(_stext), _end - _stext);
#endif
#ifdef CONFIG_BLK_DEV_INITRD
        /* FDT scan will populate initrd_start */
        if (initrd_start && !phys_initrd_size) {
                phys_initrd_start = __virt_to_phys(initrd_start);
                phys_initrd_size = initrd_end - initrd_start;
        }   
        initrd_start = initrd_end = 0;
        if (phys_initrd_size &&
            !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
                pr_err("INITRD: 0x%08llx+0x%08lx is not a memory region - disabling initrd\n",
                       (u64)phys_initrd_start, phys_initrd_size);
                phys_initrd_start = phys_initrd_size = 0;
        }   
        if (phys_initrd_size &&
            memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
                pr_err("INITRD: 0x%08llx+0x%08lx overlaps in-use memory region - disabling initrd\n",
                       (u64)phys_initrd_start, phys_initrd_size);
                phys_initrd_start = phys_initrd_size = 0;
        }           
        if (phys_initrd_size) {
                memblock_reserve(phys_initrd_start, phys_initrd_size);

                /* Now convert initrd to virtual addresses */
                initrd_start = __phys_to_virt(phys_initrd_start);
                initrd_end = initrd_start + phys_initrd_size;
        }           
#endif      

        arm_mm_memblock_reserve();

        /* reserve any platform specific memblock areas */
        if (mdesc->reserve)
                mdesc->reserve();

        early_init_fdt_scan_reserved_mem();

        /* reserve memory for DMA contiguous allocations */
        dma_contiguous_reserve(arm_dma_limit);

        arm_memblock_steal_permitted = false;
        memblock_dump_all();
}
  • 커널 영역을 reserve memblock에 등록한다.
    • XIP_KERNEL 옵션을 사용하는 경우 code 영역을 제외한 나머지 커널 영역을 등록한다.
  • CONFIG_BLK_DEV_INITRD
    • 특정 메모리 영역을 램디스크로 사용할 수 있다.
  • if (initrd_start && !phys_initrd_size) {
    • initrd_start 가 지정되었고 phys_initrd_size가 0이 아니면 phys_initrd_start 와 phys_initrd_size를 지정한다.
  • if (phys_initrd_size && !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
    • initrd 영역이 memory memblock 영역에 포함되어 있지 않은 경우 에러 메시지를 출력하고 initrd 영역을 memblock에 추가하는 것을 포기하기 위해 크기를 0으로 설정한다.
  • if (phys_initrd_size && memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
    • initrd 영역이 이미 reserved memblock 영역에 겹친 경우 에러 메시지를 출력하고 initrd 영역을 memblock에 추가하는 것을 포기하기 위해 크기를 0으로 설정한다.
  • memblock_reserve(phys_initrd_start, phys_initrd_size);
    • initrd 영역을 reserve memblock에 추가한다.
  • arm_mm_memblock_reserve();
    • 페이지 테이블을 reserve memblock에 추가한다.
  • if (mdesc->reserve) mdesc->reserve();
    • 아키텍처의 지정된 reserve() 함수를  호출한다.
  •  early_init_fdt_scan_reserved_mem();
    • 다음 3가지 영역을 reserve memblock에 추가한다.
      • DTB 영역
      • DTB 헤더의 off_mem_rsvmap 필드가 가리키는 memory reserve 블럭(바이너리)에서 읽은 메모리 영역들
      • DTB reserved-mem 노드 영역이 요청하는 영역을 reserve memblock에 추가한다.
  • dma_contiguous_reserve(arm_dma_limit);
    • 디바이스 드라이버(dma for coherent/cma for dma)가 필요로 하는 DMA 영역을 reserve memblock에 추가하고 CMA(Contiguous Memory Allocator)에도 추가한다.
      • cma_areas[]에 추가된 엔트리는 CMA 드라이버가 로드되면서 초기화될 때 사용한다.
      • dma_mmu_remap[]에 추가된 엔트리는 추후 dma_contiguous_remap() 함수를 통해 페이지 테이블에 IO 속성으로 매핑할 때 사용된다.
      • 참고: CMA(Contiguous Memory Allocator) for DMA | 문c
  • memblock_dump_all();
    • 커널 cmdline에 “debug” 옵션을 사용하는 경우 memory & reserve memblock 영역을 dump 한다.

아래 그림과 같은 순서로 몇 가지 영역을 reserved memblock에 추가한다.

arm_memblock_init-2

  • CMA 관련 테이블은 다음과 같이 2개이다.
    • dma_mmu_remap[] 배열은 추후 테이블에 IO 속성으로 매핑할 때 사용된다.
    • cma_areas[] 배열은 CMA 드라이버가 로드되면서 이 항목을 사용하여 초기화한다.

 

arm_mm_memblock_reserve()

arch/arm/mm/mmu.c

/*
 * Reserve the special regions of memory
 */
void __init arm_mm_memblock_reserve(void)
{
        /*   
         * Reserve the page tables.  These are already in use,
         * and can only be in node 0.
         */
        memblock_reserve(__pa(swapper_pg_dir), SWAPPER_PG_DIR_SIZE);

#ifdef CONFIG_SA1111
        /*   
         * Because of the SA1111 DMA bug, we want to preserve our
         * precious DMA-able memory...
         */
        memblock_reserve(PHYS_OFFSET, __pa(swapper_pg_dir) - PHYS_OFFSET);
#endif
}
  • 커널 1차 페이지 테이블 영역을 reserve memblock에 추가한다.

 

DTB for reserved memory region

일반적인 메모리 영역에서 특정 목적으로 제외(reserve)시켜야 하는 메모리 영역을 지정한다. 아래 예를 살펴본다.

  • reserved-memory 노드로 구성한다.
    • 구성 시 #address-cells와 #size-cells는 루트 노드의 것과 동일해야 한다.
    • ranges 속성이 존재해야 한다.
  • 서브 노드들에는 영역의 시작과 사이즈에 대한 정보가 포함되어 있다.
    • compatible 속성을 두어 연동되는 디바이스 드라이버를 지정할 수 있다.
    • cma 및 dma 드라이버의 경우는 디바이스명으로 “shared-cma-pool” 및 “shared-dma-pool” 로 지정되어 있고 이를 통해서 별도의 지정된 설정 함수를 실행시켜 초기화를 수행한다.
      • 아직 실제 dtb 사례에서 cma용 shared-cma-pool이나 dma용 shared-dma-pool 디바이스 명을 사용하여 구동시킨 사례는 찾아 볼 수 없었지만 설계는 이미 아래 예와 같이 되어 있고 코드도 준비되어 있다.
    • 영역의 크기 정보는 reg와 size를 통해서 한다.
      • reg 속성을 사용하는 경우 static한 방법으로 지정된 메모리 영역을 reserve memblock에 추가한다.
      • size 속성을 사용하는 경우 dynamic 한 방법을 사용하여 디바이스 드라이버가 시작 주소를 구해 영역을 reserve memblock에 추가한다.
        • alloc-range 속성 값으로 지정된 범위내에서 요청한 사이즈 공간을 찾아 reserve memblock에 추가한다.
        • alloc-range 속성을 지정하지 않은 경우 전체 메모리를 대상으로 요청된 사이즈 만큼 reserve memblock에 추가한다.
      • reg와 size를 동시에 지정하는 경우 size는 무시된다.
    • no-map 속성이 있는 경우에는 reserve 영역에서 오히려 제거한다.
/ {
	#address-cells = <1>;
	#size-cells = <1>;

	memory {
		reg = <0x40000000 0x40000000>;
	};

	reserved-memory {
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

		/* global autoconfigured region for contiguous allocations */
		linux,cma {
			compatible = "shared-dma-pool";
			reusable;
			size = <0x4000000>;
			alignment = <0x2000>;
			linux,cma-default;
		};

		display_reserved: framebuffer@78000000 {
			reg = <0x78000000 0x800000>;
		};

		multimedia_reserved: multimedia@77000000 {
			compatible = "acme,multimedia-memory";
			reg = <0x77000000 0x4000000>;
		};
	};

	/* ... */

	fb0: video@12300000 {
		memory-region = <&display_reserved>;
		/* ... */
	};

	scaler: scaler@12500000 {
		memory-region = <&multimedia_reserved>;
		/* ... */
	};

	codec: codec@12600000 {
		memory-region = <&multimedia_reserved>;
		/* ... */
	};
};

 

early_init_fdt_scan_reserved_mem()

drivers/of/fdt.c

/**
 * early_init_fdt_scan_reserved_mem() - create reserved memory regions
 *
 * This function grabs memory from early allocator for device exclusive use
 * defined in device tree structures. It should be called by arch specific code
 * once the early allocator (i.e. memblock) has been fully activated.
 */
void __init early_init_fdt_scan_reserved_mem(void)
{
        int n;
        u64 base, size;

        if (!initial_boot_params)
                return;

        /* Reserve the dtb region */
        early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
                                          fdt_totalsize(initial_boot_params),
                                          0);

        /* Process header /memreserve/ fields */
        for (n = 0; ; n++) {
                fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
                if (!size)
                        break; 
                early_init_dt_reserve_memory_arch(base, size, 0);
        }   

        of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
        fdt_init_reserved_mem();
}
  • early_init_dt_reserve_memory_arch(__pa(initial_boot_params), fdt_totalsize(initial_boot_params), 0);
    • DTB 영역을 reserve memblock에 추가한다.
  • fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
    • DTB 헤더에서 reservedmem 필드가 가리키는 바이너리 영역을 읽어 reserve할 base(시작주소)및 size를 알아온다.
    • 사이즈가 0이 아닐때 까지 반복된다.
      • DTB에서 reserved memory block은 8바이트 숫자로 시작주소 및 사이즈가 필요한 만큼 반복된다.
  • early_init_dt_reserve_memory_arch(base, size, 0);
    • 알아온 영역을 다음 2가지 경우에 따라 처리한다.
      • static 방법: DTB에서 reg를 읽은 경우 base, size 정보로 reserve memblock에 추가한다.
      • dynamic 방법: DTB에서 size 속성을 읽은 경우 2번에 나누어 초기화 하는데 먼저 reserved_mem[] 배열에만 추가한다.
  • of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
    • 첫 번째 depth의 “reserved-memory” 노드명을 찾아 “status” 속성값이 “ok” 또는 “okay”인 경우 알아온 영역을 다음 2가지 경우에 따라 처리한다.
      • static 방법: DTB에서 reg를 읽은 경우 base, size 정보로 reserve memblock에 추가한다.
      • dynamic 방법: DTB에서 size 속성을 읽은 경우 2번에 나누어 초기화 하는데 먼저 reserved_mem[] 배열에만 추가한다.
  • fdt_init_reserved_mem();
    • dynamic 방법의 연장 즉 2nd phase 초기화로 reserved_mem[] 배열에 등록된 수 만큼 루프를 돌며 읽어와서 DTB의 alloc-range 속성이 요청하는 메모리 range 들에서 reserve할 영역을 찾은 경우 reserve memblock에 추가하고 등록된 디바이스를 찾아 초기화 함수를 실행하게 한다.

 

early_init_dt_reserve_memory_arch()

drivers/of/fdt.c

int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base,
                                        phys_addr_t size, bool nomap)
{
        if (nomap)
                return memblock_remove(base, size);
        return memblock_reserve(base, size);
}
  • nomap이 true인 경우 해당 영역을 reserve memblock에서 삭제한다.
  • nomap이 false인 경우 해당 영역을 reserve memblock에 추가한다.

 

__fdt_scan_reserved_mem()

drivers/of/fdt.c”

/**
 * fdt_scan_reserved_mem() - scan a single FDT node for reserved memory
 */
static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname,
                                          int depth, void *data)
{
        static int found;
        const char *status;
        int err;

        if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) {
                if (__reserved_mem_check_root(node) != 0) {
                        pr_err("Reserved memory: unsupported node format, ignoring\n");
                        /* break scan */
                        return 1;
                }
                found = 1;
                /* scan next node */
                return 0;
        } else if (!found) {
                /* scan next node */
                return 0;
        } else if (found && depth < 2) {
                /* scanning of /reserved-memory has been finished */
                return 1;
        }

        status = of_get_flat_dt_prop(node, "status", NULL);
        if (status && strcmp(status, "okay") != 0 && strcmp(status, "ok") != 0)
                return 0;

        err = __reserved_mem_reserve_reg(node, uname);
        if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL))
                fdt_reserved_mem_save_node(node, uname, 0, 0);

        /* scan next node */
        return 0;
}
  • if (!found && depth == 1 && strcmp(uname, “reserved-memory”) == 0) {
    • 1 depth 인 노드명이 “reserved-memory” 이면
    •  아래 예) arch/arm/boot/dts/atlas7-evb.dts
      • 0x5e80_0000 부터 8M를 reserve memblock에 추가
      • 0x4600_0000 부터 2M를 reserve memblock에서 삭제
        reserved-memory {
                #address-cells = <1>;
                #size-cells = <1>;
                ranges;

                vpp_reserved: vpp_mem@5e800000 {
                        compatible = "sirf,reserved-memory";
                        reg = <0x5e800000 0x800000>;
                };

                nanddisk_reserved: nanddisk@46000000 {
                        reg = <0x46000000 0x200000>;
                        no-map;
                };
        };
  • if (__reserved_mem_check_root(node) != 0) {
    • 현재 노드의 #address-cells와 #size-cells가 root 노드에 있는 #address-cells와 #size-cells와 같고 “ranges” 속성값이 제공되는 경우 성공리에 0이 리턴된다. 그 외에는 음수의 에러(-EINVAL)를 리턴한다.
  • status = of_get_flat_dt_prop(node, “status”, NULL);
    • reserved-memory 노드를 찾은 다음 depth의 노드에서 “status” 속성을 찾는다.
  • if (status && strcmp(status, “okay”) != 0 && strcmp(status, “ok”) != 0)
    • status 속성이 있는 경우 속성 값이 “okay”가 아니면서 “ok”도 아닌 경우 리턴한다.
  • err = __reserved_mem_reserve_reg(node, uname);
    • 해당 노드의 reg 속성 값으로 reserve memblock에 추가한다.
    • base 주소가 지정되지 않아 dynamic 할당을 시도하는 경우에는 reg 속성 대신 size 속성을 사용하므로 이 때에는 실패로 리턴하게 된다.
  • if (err == -ENOENT && of_get_flat_dt_prop(node, “size”, NULL))
    • 호출한 함수가 에러이면서 “size” 속성을 찾을 수 있는 경우
  • fdt_reserved_mem_save_node(node, uname, 0, 0);
    • 시작 주소와 사이즈를 0으로 해서 전역 변수 reserved_mem[] 배열에 추가한다.
    • 배열에 추가된 이 정보는 추후 fdt_init_reserved_mem() 함수를 호출하여 2nd phase 초기화를 수행하게 한다.

 

__reserved_mem_reserve_reg()

해당 함수는 CONFIG_OF_EARLY_FLATTREE 옵션을 사용한 경우에만 동작한다. (DTB 기본 옵션)

drivers/of/fdt.c

/**
 * res_mem_reserve_reg() - reserve all memory described in 'reg' property
 */
static int __init __reserved_mem_reserve_reg(unsigned long node,
                                             const char *uname)
{
        int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
        phys_addr_t base, size;
        int len;
        const __be32 *prop;
        int nomap, first = 1;

        prop = of_get_flat_dt_prop(node, "reg", &len);
        if (!prop)
                return -ENOENT;

        if (len && len % t_len != 0) {
                pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n",
                       uname);
                return -EINVAL;
        }

        nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

        while (len >= t_len) {
                base = dt_mem_next_cell(dt_root_addr_cells, &prop);
                size = dt_mem_next_cell(dt_root_size_cells, &prop);

                if (size &&
                    early_init_dt_reserve_memory_arch(base, size, nomap) == 0)
                        pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %ld MiB\n",
                                uname, &base, (unsigned long)size / SZ_1M);
                else
                        pr_info("Reserved memory: failed to reserve memory for node '%s': base %pa, size %ld MiB\n",
                                uname, &base, (unsigned long)size / SZ_1M);

                len -= t_len;
                if (first) {
                        fdt_reserved_mem_save_node(node, uname, base, size);
                        first = 0;
                }
        }
        return 0;
}

DTB를 분석하여 다음 두 가지 방법 중 하나를 사용하여 memblock에 등록한다.

  • static
    • reserved-mem 노드의 reg 속성에 지정된 크기로 memblock에 곧장 등록한다.
  • dynamic
    • reserved-mem 노드의 size 속성이 지정된 경우 reserved_mem[] 배열에 추가하고 추 후 second pass initialization 루틴(fdt_init_reserved_mem())이 호출될 때 초기화를 진행하게 한다.

 

  • int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
    • 루트 노드에 있는 #addr-cells + #size-cells를 4로 곱한 값이 처리할 데이터 길이다.
  • prop = of_get_flat_dt_prop(node, “reg”, &len);
    • “reg” 속성을 찾는다.
  • nomap = of_get_flat_dt_prop(node, “no-map”, NULL) != NULL;
    • “no-map” 속성이 찾아지면 true
      • reserve영역에서 해지
  • base = dt_mem_next_cell(dt_root_addr_cells, &prop);
    • 루트노드의 #addr-cells x 4 바이트 만큼 읽어서 base에 저장한다.
  • size = dt_mem_next_cell(dt_root_size_cells, &prop);
    • 루트노드의 #size-cells x 4 바이트 만큼 읽어서 size에 저장한다.
  • if (size && early_init_dt_reserve_memory_arch(base, size, nomap) == 0)
    • size가 주어진 경우 해당 영역을 static하게 reserve memblock에 추가하거나 삭제한다.
  •  fdt_reserved_mem_save_node(node, uname, base, size);
    • 처음 한 번만 이루틴이 호출된다.
    • 전역 구조체 reserved_mem[] 배열에 노드 포인터, 노드명, 시작주소, 사이즈 등을 추가한다.
    • 이 배열은 추후에 fdt_init_reserved_mem() 함수가 호출될 때 초기화를 수행한다.

 

fdt_reserved_mem_save_node()

drivers/of/of_reserved_mem.c

/**
 * res_mem_save_node() - save fdt node for second pass initialization
 */
void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
                                      phys_addr_t base, phys_addr_t size)
{
        struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];

        if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) {
                pr_err("Reserved memory: not enough space all defined regions.\n");
                return;
        }

        rmem->fdt_node = node;
        rmem->name = uname;
        rmem->base = base;
        rmem->size = size;

        reserved_mem_count++;
        return;
}
  • 전역 구조체 reserved_mem[] 배열에 노드 포인터, 노드명, 시작주소, 사이즈 등을 추가하고 전역 변수 reserved_mem_count를 증가시킨다.
  • 배열은 MAX_RESERVED_REGIONS(16)개로 초기화되어 있다.

 

reserved mem 초기화

fdt_init_reserved_mem()

drivers/of/of_reserved_mem.c

/**
 * fdt_init_reserved_mem - allocate and init all saved reserved memory regions
 */
void __init fdt_init_reserved_mem(void)
{
        int i;
        for (i = 0; i < reserved_mem_count; i++) {
                struct reserved_mem *rmem = &reserved_mem[i];
                unsigned long node = rmem->fdt_node;
                int len;
                const __be32 *prop;
                int err = 0;

                prop = of_get_flat_dt_prop(node, "phandle", &len);
                if (!prop)
                        prop = of_get_flat_dt_prop(node, "linux,phandle", &len);
                if (prop)
                        rmem->phandle = of_read_number(prop, len/4);

                if (rmem->size == 0)
                        err = __reserved_mem_alloc_size(node, rmem->name,
                                                 &rmem->base, &rmem->size);
                if (err == 0)               
                        __reserved_mem_init_node(rmem);
        }            
}

dynamic 방법에 의해 등록된 reserved_mem[] 배열을 읽어 DTB alloc-range 속성이 요청한 메모리 범위에서 size 속성 만큼 reserve 할 수 있는 영역을 찾고 성공한 경우 reserve memblock을 추가한다. 그런 후 각 디바이스 드라이버에 지정된  callback 함수(of_device_id->data)를 호출하여 해당 디바이스 드라이버를 초기화한다.

  • reserved_mem[] 배열에 등록된 항목들을 모두 조회한다.
  • unsigned long node = rmem->fdt_node;
    • rmem->fdt_node는 DTB에 reserved-mem 노드로 등록된 노드이다.
  •  prop = of_get_flat_dt_prop(node, “phandle”, &len);
    • 해당 노드에서 “phandle” 속성을 찾는다. 없으면 “linux,phandle”에서도 찾아본다.
  • if (prop) rmem->phandle = of_read_number(prop, len/4);
    • 속성이 발견되면 rmem->phandle에 값을 읽어 udpate 한다.
  •  err = __reserved_mem_alloc_size(node, rmem->name, &rmem->base, &rmem->size);
    • rmem->size가 0인 경우는 base 주소가 dynamic하게 지정될 수 있게 __reserved_mem_alloc_size() 함수를 호출하여 reserve memblock에 영역을 추가한다.
      • 추가할 사이즈 정보는 DTB의 size 속성을 읽어오고 범위 정보는 DTB의 alloc-range 속성을 읽어온다.
  • __reserved_mem_init_node(rmem);
    • 에러가 없는 경우 __reserved_mem_init_node() 함수를 호출하여 관련 reserved memory에 대한 해당 디바이스의 초기화 함수를 __rmem_of_table_sentinel 테이블에서 검색하여 호출한다.

 

__reserved_mem_init_node()

drivers/of/of_reserved_mem.c

/**
 * res_mem_alloc_size() - allocate reserved memory described by 'size', 'align'
 *                        and 'alloc-ranges' properties
 */
static int __init __reserved_mem_alloc_size(unsigned long node,
        const char *uname, phys_addr_t *res_base, phys_addr_t *res_size)
{
        int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
        phys_addr_t start = 0, end = 0;
        phys_addr_t base = 0, align = 0, size;
        int len;
        const __be32 *prop;
        int nomap;
        int ret;

        prop = of_get_flat_dt_prop(node, "size", &len);
        if (!prop)
                return -EINVAL;

        if (len != dt_root_size_cells * sizeof(__be32)) {
                pr_err("Reserved memory: invalid size property in '%s' node.\n",
                                uname);
                return -EINVAL;
        }
        size = dt_mem_next_cell(dt_root_size_cells, &prop);

        nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

        prop = of_get_flat_dt_prop(node, "alignment", &len);
        if (prop) {
                if (len != dt_root_addr_cells * sizeof(__be32)) {
                        pr_err("Reserved memory: invalid alignment property in '%s' node.\n",
                                uname);
                        return -EINVAL;
                }
                align = dt_mem_next_cell(dt_root_addr_cells, &prop);
        }

dynamic 방법으로 alloc-ranges 속성이 지정한 범위 내에서 지정한 영역 크기를 reserve memblock에 추가한다.

  • int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
    • 노드에서 읽어와야 할 바이트 수를 알아낸다.
  • prop = of_get_flat_dt_prop(node, “size”, &len);
    • size 속성을 읽어와서 없으면 에러를 리턴한다.
  • if (len != dt_root_size_cells * sizeof(__be32)) {
    • 루트 노드의 #size-cells와 reserved-mem 노드의 #size-cells가 다른 경우 메시지를 출력하고 에러로 리턴한다.
  • size = dt_mem_next_cell(dt_root_size_cells, &prop);
    • size 속성에서 값(byte)을 읽어온다.
  • nomap = of_get_flat_dt_prop(node, “no-map”, NULL) != NULL;
    • no-map 속성이 있으면 true가 된다.
  • prop = of_get_flat_dt_prop(node, “alignment”, &len);
    • alignment 속성을 찾는다.
  • if (len != dt_root_addr_cells * sizeof(__be32)) {
    • 루트 노드의 #addr-cells와 reserved-mem 노드의 #addr-cells가 다른 경우 메시지를 출력하고 에러로 리턴한다.
  • align = dt_mem_next_cell(dt_root_addr_cells, &prop);
    • align 값을 읽어온다.
        prop = of_get_flat_dt_prop(node, "alloc-ranges", &len);
        if (prop) {

                if (len % t_len != 0) {
                        pr_err("Reserved memory: invalid alloc-ranges property in '%s', skipping node.\n",
                               uname);
                        return -EINVAL;
                }

                base = 0;

                while (len > 0) {
                        start = dt_mem_next_cell(dt_root_addr_cells, &prop);
                        end = start + dt_mem_next_cell(dt_root_size_cells,
                                                       &prop);

                        ret = early_init_dt_alloc_reserved_memory_arch(size,
                                        align, start, end, nomap, &base);
                        if (ret == 0) {
                                pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n",
                                        uname, &base,
                                        (unsigned long)size / SZ_1M);
                                break;
                        }
                        len -= t_len;
                }

        } else {
                ret = early_init_dt_alloc_reserved_memory_arch(size, align,
                                                        0, 0, nomap, &base);
                if (ret == 0)
                        pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n",
                                uname, &base, (unsigned long)size / SZ_1M);
        }

        if (base == 0) {
                pr_info("Reserved memory: failed to allocate memory for node '%s'\n",
                        uname);
                return -ENOMEM;
        }

        *res_base = base;
        *res_size = size;

        return 0;
}
  • prop = of_get_flat_dt_prop(node, “alloc-ranges”, &len);
    • alloc_ranges 속성을 찾는다.
  • if (len % t_len != 0) {
    • 루트 노드의 #addr-cells 및 #size-cells 가 reserved-mem 노드의 #addr-cells 및 #size-cells가 다른 경우 메시지를 출력하고 에러를 리턴한다.
  • while (len > 0) {
    • alloc_ranges 속성에서 영역을 여러 개의 배열로 지정한 경우를 위해 루프를 돈다.
  • start = dt_mem_next_cell(dt_root_addr_cells, &prop);
    • 시작 주소를 알아온다.
  • end = start + dt_mem_next_cell(dt_root_size_cells, &prop);
    • 끝 주소를 알아온다.
  • ret = early_init_dt_alloc_reserved_memory_arch(size, align, start, end, nomap, &base);
    • 읽어온 검색 범위에서 reserve memblock을 시도하고 성공하면 루프를 빠져나온다.
  • ret = early_init_dt_alloc_reserved_memory_arch(size, align, 0, 0, nomap, &base);
    • alloc-ranges 속성이 없는 경우에는 검색 범위를 메모리 전체로 지정하여 reserve memblock을 수행한다.
  • if (base == 0) {
    • reserve할 공간이 없어 실패한 경우 메시지를 출력하고 에러를 리턴한다.

 

__reserved_mem_init_node()

drivers/of/of_reserved_mem.c

/**
 * res_mem_init_node() - call region specific reserved memory init code
 */
static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
{
        extern const struct of_device_id __reservedmem_of_table[];
        const struct of_device_id *i;

        for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {
                reservedmem_of_init_fn initfn = i->data;
                const char *compat = i->compatible;

                if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
                        continue;

                if (initfn(rmem) == 0) {
                        pr_info("Reserved memory: initialized node %s, compatible id %s\n",
                                rmem->name, compat);
                        return 0;
                }
        }
        return -ENOENT;
}

DTB의 reserved-mem 노드의 sub 노드가 사용하는 디바이스명(compat)으로 커널에 등록된 __reservedmem_of_table 에서 검색하여 해당 초기화 함수를 호출한다.

  • __reservedmem_of_table에서 __rmem_of_table_sentinel까지 루프를 돌며 of_device_id 구조체 값을 가져온다.
  • if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
    • 가져온 구조체의 compat(드라이버명)이 요청한 노드와 같은 드라이버명을 사용하지 않는 경우 continue를 호출하여 다음을 검색한다.
  •  if (initfn(rmem) == 0) {
    • 드라이버명이 같은 경우 해당 구조체의 data에 등록된 함수를 호출한다.
  • 전체를 검색하여 실패한 경우  에러를 리턴한다.

 

__reservedmem_of_table

현재 커널에는 아래와 같이 두 개의 디바이스 드라이버 코드가 준비되어 있다.

  • rpi2: __of_device_cma와 __of_device_dma 두 개 구조체명이 __reservedmem_of_table에 등록된다.

 

struct __of_device_cma

CMA for DMA mapping framework 용도의 디바이스 드라이버로 사용된다.

drivers/base/dma-contiguous.c

static const struct reserved_mem_ops rmem_cma_ops = { 
        .device_init    = rmem_cma_device_init,
        .device_release = rmem_cma_device_release,
};

static int __init rmem_cma_setup(struct reserved_mem *rmem)
{
        phys_addr_t align = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);
        phys_addr_t mask = align - 1;
        unsigned long node = rmem->fdt_node;
        struct cma *cma;
        int err;

        if (!of_get_flat_dt_prop(node, "reusable", NULL) ||
            of_get_flat_dt_prop(node, "no-map", NULL))
                return -EINVAL;

        if ((rmem->base & mask) || (rmem->size & mask)) {
                pr_err("Reserved memory: incorrect alignment of CMA region\n");
                return -EINVAL;
        }

        err = cma_init_reserved_mem(rmem->base, rmem->size, 0, &cma);
        if (err) {
                pr_err("Reserved memory: unable to setup CMA region\n");
                return err;
        }
        /* Architecture specific contiguous memory fixup. */
        dma_contiguous_early_fixup(rmem->base, rmem->size);

        if (of_get_flat_dt_prop(node, "linux,cma-default", NULL))
                dma_contiguous_set_default(cma);

        rmem->ops = &rmem_cma_ops;
        rmem->priv = cma;

        pr_info("Reserved memory: created CMA memory pool at %pa, size %ld MiB\n",
                &rmem->base, (unsigned long)rmem->size / SZ_1M);

        return 0;
}
RESERVEDMEM_OF_DECLARE(cma, "shared-cma-pool", rmem_cma_setup);

RESERVEDMEM_OF_DECLARE를 통해서 __of_table_cma 이름의 of_device_id 구조체가 __reservedmem_of_table에 등록된다.

  • 디바이스명(compat)은 “shared-cma-pool”이다.
  • 이 디바이스의 초기화 함수는 rmem_cma_setup() 함수이다.

 

struct __of_device_dma

DMA for Coherent per-device 메모리 핸들링을 위한 디바이스 드라이버에 사용된다.

drivers/base/dma-coherent.c

static const struct reserved_mem_ops rmem_dma_ops = { 
        .device_init    = rmem_dma_device_init,
        .device_release = rmem_dma_device_release,
};

static int __init rmem_dma_setup(struct reserved_mem *rmem)
{
        unsigned long node = rmem->fdt_node;

        if (of_get_flat_dt_prop(node, "reusable", NULL))
                return -EINVAL;

#ifdef CONFIG_ARM
        if (!of_get_flat_dt_prop(node, "no-map", NULL)) {
                pr_err("Reserved memory: regions without no-map are not yet supported\n");
                return -EINVAL;
        }   
#endif

        rmem->ops = &rmem_dma_ops;
        pr_info("Reserved memory: created DMA memory pool at %pa, size %ld MiB\n",
                &rmem->base, (unsigned long)rmem->size / SZ_1M);
        return 0;
}
RESERVEDMEM_OF_DECLARE(dma, "shared-dma-pool", rmem_dma_setup);
  • RESERVEDMEM_OF_DECLARE를 통해서 __of_table_dma 이름의 of_device_id 구조체가 __reservedmem_of_table에 등록된다.
    • 디바이스명(compat) “shared-dma-pool”이다.
    • 이 디바이스의 초기화 함수는 rmem_dma_setup() 함수이다.

 

구조체 및 전역 변수

 

reserved_mem 구조체

include/linux/of_reserved_mem.h

struct reserved_mem {
        const char                      *name;
        unsigned long                   fdt_node;
        unsigned long                   phandle;
        const struct reserved_mem_ops   *ops;
        phys_addr_t                     base;
        phys_addr_t                     size;
        void                            *priv;
};

 

reserved_mem_ops 구조체

include/linux/of_reserved_mem.h

struct reserved_mem_ops {
        int     (*device_init)(struct reserved_mem *rmem,
                               struct device *dev);
        void    (*device_release)(struct reserved_mem *rmem,
                                  struct device *dev);
};

 

전역변수

drivers/of/of_reserved_mem.c

static const struct of_device_id __rmem_of_table_sentinel
        __used __section(__reservedmem_of_table_end);

 

#define MAX_RESERVED_REGIONS    16
static struct reserved_mem reserved_mem[MAX_RESERVED_REGIONS];
static int reserved_mem_count;

 

참고

 

sanity_check_meminfo()

이 함수에서는 등록된 memory memblock에 대해 미리 사전 체크를 하여 early memory allocator로 동작할 수 있도록 다음과 같이 준비한다.

  • lowmem 영역만 사용해야 하는 case에 대해 memblock 영역 삭제
    • HIGHMEM을 사용하지 않을 경우 또는 캐시가 VIPT aliasing을 사용하는 경우 memory 영역에 등록된 memblock 들이 lowmem 영역을 초과하는 경우 해당 초과 영역들을 제거한다.
  • arm_lowmem_limithigh_memory 설정
    • memory block의 끝 주소를 arm_lowmem_limit으로 하되  vmalloc_limit(vmalloc_min의 물리주소)을 초과하지 않도록 한다.
    • high_memory는 arm_lowmem_limit의 가상 주소 값이다.
  • memblock.current_limit 설정
    • memory memblock들이 2M 단위로 align되어 있어야 커널 설정 초기에 사용되는 early memory allocator에서 2M 영역을 할당하여 사용하는 reserve memblock을 운영하여야 하므로 각 memory memblock 들이 2M align되어 있지 않은 memblock이 있는 경우 그 지점의 2M round down 주소까지로 사용을 제한하도록 memblock_limit를 설정한다.

sanity_check_meminfo_1a

 

sanity_check_meminfo()

arch/arm/mm/mmu.c

void __init sanity_check_meminfo(void)
{
        phys_addr_t memblock_limit = 0; 
        int highmem = 0; 
        phys_addr_t vmalloc_limit = __pa(vmalloc_min - 1) + 1; 
        struct memblock_region *reg;

        for_each_memblock(memory, reg) {
                phys_addr_t block_start = reg->base;
                phys_addr_t block_end = reg->base + reg->size;
                phys_addr_t size_limit = reg->size;

                if (reg->base >= vmalloc_limit)
                        highmem = 1; 
                else
                        size_limit = vmalloc_limit - reg->base;


                if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) {

                        if (highmem) {
                                pr_notice("Ignoring RAM at %pa-%pa (!CONFIG_HIGHMEM)\n",
                                          &block_start, &block_end);
                                memblock_remove(reg->base, reg->size);
                                continue;
                        }

                        if (reg->size > size_limit) {
                                phys_addr_t overlap_size = reg->size - size_limit;

                                pr_notice("Truncating RAM at %pa-%pa to -%pa",
                                          &block_start, &block_end, &vmalloc_limit);
                                memblock_remove(vmalloc_limit, overlap_size);
                                block_end = vmalloc_limit;
                        }
                }
  • phys_addr_t vmalloc_limit = __pa(vmalloc_min – 1) + 1;
    • vmalloc_min
      • VMALLOC_END(0xff00_0000) – (240 << 20) – VMALLOC_OFFSET(8M)
      • 0xff00_000 – 240M – 8M = 0xef80_0000
      • VMALLOC 영역이 최소 보장되어야 하는 하한 주소로 아키텍처 마다 다르다.
        • 32 bit ARM 에서는 0xef80_0000으로 고정되어 사용된다.
    • vmalloc_limit
      • vmalloc_min의 물리 주소가 담긴다.
      • rpi2: 0x6f80_0000
  • for_each_memblock(memory, reg) {
    • 등록된 전체 memory memblock 영역을 루프로 돈다.
  • if (reg->base >= vmalloc_limit)
    • 영역의 시작 물리 주소가 vmalloc_limit을 초과한 경우 highmem 영역이라 판단한다.
  • if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) {
    • HIGHMEM 설정이 안되어 있거나 d-cache가 vipt aliasing을 사용하는 경우
      • rpi2: d-cache는 CACHEID_VIPT_NONALIASING
  • if (highmem) {
    • highmem 영역을 사용하는 블럭은 삭제한다.
  • if (reg->size > size_limit) {
    • size가 lowmem 영역을 넘어가는 경우 넘어가는 부분 만큼을 제거한다.

HIGHMEM을 사용하지 않는 경우 memblock들이 vmalloc_limit을 초과하는 경우 해당 영역을 제거한다.

sanity_check_meminfo_3b

 

                if (!highmem) {
                        if (block_end > arm_lowmem_limit) {
                                if (reg->size > size_limit)
                                        arm_lowmem_limit = vmalloc_limit;
                                else
                                        arm_lowmem_limit = block_end;
                        }
  • if (block_end > arm_lowmem_limit) {
    • 블럭이 arm_lowmem_limit를 초과한 경우
  • if (reg->size > size_limit)
    • 블럭 사이즈가 lowmem 영역까지 남은 공간을 초과하는 경우 arm_lowmem_limit에 vmalloc_limit을 대입하고 그렇지 않은 경우 블럭의 끝을 지정한다.

sanity_check_meminfo_4a

 

                        /*
                         * Find the first non-pmd-aligned page, and point
                         * memblock_limit at it. This relies on rounding the
                         * limit down to be pmd-aligned, which happens at the
                         * end of this function.
                         *
                         * With this algorithm, the start or end of almost any
                         * bank can be non-pmd-aligned. The only exception is
                         * that the start of the bank 0 must be section-
                         * aligned, since otherwise memory would need to be
                         * allocated when mapping the start of bank 0, which
                         * occurs before any free memory is mapped.
                         */
                        if (!memblock_limit) {
                                if (!IS_ALIGNED(block_start, PMD_SIZE))
                                        memblock_limit = block_start;
                                else if (!IS_ALIGNED(block_end, PMD_SIZE))
                                        memblock_limit = arm_lowmem_limit;
                        }

                }
        }

        high_memory = __va(arm_lowmem_limit - 1) + 1;

        /*
         * Round the memblock limit down to a pmd size.  This
         * helps to ensure that we will allocate memory from the
         * last full pmd, which should be mapped.
         */
        if (memblock_limit)
                memblock_limit = round_down(memblock_limit, PMD_SIZE);
        if (!memblock_limit)
                memblock_limit = arm_lowmem_limit;

        memblock_set_current_limit(memblock_limit);
}
  • if (!memblock_limit) {
    • memblock_limit값이 설정되지 않았으면
  • if (!IS_ALIGNED(block_start, PMD_SIZE))
    • 블럭의 시작 주소가 2M align되어 있지 않은 경우 memblock_limit에 블럭 시작 주소를 대입한다.
  • else if (!IS_ALIGNED(block_end, PMD_SIZE))
    • 블럭의 끝 주소가 2M align되어 있지 않은 경우 memblock_limit에 블럭 끝 주소를 대입한다.
  • memblock_limit = round_down(memblock_limit, PMD_SIZE);
    • memblock_limit 주소를 2M round down 한다.
  • memblock_set_current_limit(memblock_limit);
    • 전역 변수 memblock.current_limit를 설정한다.
  • 등록된 memblock은 커널 설정 초기에 2M 단위의 메모리를 할당 받아 사용한다. 따라서 align되지 않은 메모리가 배열에 등록된 경우 align 된 영역까지만 사용하고 나머지 메모리는 사용하지 않도록 memblock_limit를 설정한다.

sanity_check_meminfo_5a

 

lowmem 영역

  • lowmem 영역은 물리 메모리가 1:1로 커널 영역에 매핑되어 사용할 수 있는 영역이다.
    • vmalloc_limit
      • 현재 커널에서 lowmem 영역을 최대 키울 수 있는 한도내의 물리 메모리 끝 주소
      • 메모리 크기와 관계 없이 커널 영역의 크기에 따라 계산되는 물리 주소
      • 예)
        • VM_SPLIT_3G: 0x2f80_0000 (max lowmem=760M)
        • VM_SPLIT_2G: 0x6f80_0000 (max lowmem=1G+760M)
    • arm_lowmem_limit
      • 물리 메모리 크기가 max lowmem을 초과하는 경우 arm_lowmem_limit는 vmalloc_limit 값과 동일하다.
      • 물리 메모리 크기가 max lowmem보다 작은 경우 arm_lowmem_limit는 물리 메모리의 끝 주소가 대입된다.
    • high_memory
      • arm_lowmem_limit의 가상 주소와 동일하다.

sanity_check_meminfo_2b

 

참고

early_paging_init()

해당 머신의 바뀐 메모리 정보를 위해 초기화를 수행한다. LPAE의 경우 phisical to virtual transalation이 필요하여 추가로 몇 개의 루틴들이 수행되어야 한다.

  • mdesc→init_meminfo() 수행
  • LPAE의 경우 추가로 다음 항목들 수행
    • fixup_pv_table() 수행
    • page table  수정
    • 캐시 플러쉬
    • 해당 CPU 아키텍처의 MMU에 페이지 테이블 설정 변경
    • TTBR1 레지스터 재 설정
    • BP 및 TLB 캐시 플러쉬

early_paging_init

 

early_paging_init()

  • CONFIG_ARM_LPAE가 설정된 경우 두 개의 구현된 함수 중 윗 부분 함수를 수행하고 그렇지 않은 경우 아랫 부분 함수를 수행한다.
  • 머신 구조체의 init_meminfo 콜백 함수가 등록되어 있지 않은 경우 early하게 메모리 정보를 초기화(주로 패치 목적) 할 필요가 없는 것으로 간주하고 빠져나간다.
  • init_mm은 커널이 사용하는 mm_struct 구조체 포인터 변수이다.
  • 커널 코드의 시작과 끝을 map_start, map_end에 저장
  • pgd_offset_k(0)은 커널이 사용하는 pgd(페이지 글로벌 디렉토리)에 있는 첫 번째 엔트리 주소를 알아온다.
  • mdesc->init_meminfo() 콜백 함수를 사용하여 메모리 정보를 초기화한다. 이를 통해 메모리 기초 정보가 바뀌었으므로 관련 정보를 모두 수정하여야 한다. 커널 4.2에서 이 멤버 변수는 pv_fixup으로 변경된다.
    • fixup_pv_table()을 호출하여 각 pv_table 엔트리들을 모두 패치한다.
    • pv_table의 내용이 바뀌었으므로 flush_cache_louis() 함수를 사용하여 명령 캐시(i-cache) 를 flush 한다.
    • 레벨1과 레벨2의 페이지 테이블을 다시 매핑한다.
    • flush_cache_all()을 사용하여 모든 캐시를 비운다.
    • cpu_switch_mm()
      • ARMv7:
        • TTBR0에 pgd0를 설정한다.
        • CONTEXTIDR에 Context ID를 설정한다.
    • cpu_set_ttbr()을 사용하여 TTBR 1레지스터를 설정한다.
    • 마지막으로 branch predict 캐시와 TLB 캐시를 모두 비운다.
#ifdef CONFIG_ARM_LPAE
/*
 * early_paging_init() recreates boot time page table setup, allowing machines
 * to switch over to a high (>4G) address space on LPAE systems
 */
void __init early_paging_init(const struct machine_desc *mdesc,
                              struct proc_info_list *procinfo)
{
        pmdval_t pmdprot = procinfo->__cpu_mm_mmu_flags;
        unsigned long map_start, map_end;
        pgd_t *pgd0, *pgdk;
        pud_t *pud0, *pudk, *pud_start;
        pmd_t *pmd0, *pmdk;
        phys_addr_t phys;
        int i;

        if (!(mdesc->init_meminfo))
                return;

        /* remap kernel code and data */
        map_start = init_mm.start_code & PMD_MASK;
        map_end   = ALIGN(init_mm.brk, PMD_SIZE);

        /* get a handle on things... */
        pgd0 = pgd_offset_k(0);
        pud_start = pud0 = pud_offset(pgd0, 0);
        pmd0 = pmd_offset(pud0, 0);

        pgdk = pgd_offset_k(map_start);
        pudk = pud_offset(pgdk, map_start);
        pmdk = pmd_offset(pudk, map_start);

        mdesc->init_meminfo();

        /* Run the patch stub to update the constants */
        fixup_pv_table(&__pv_table_begin,
                (&__pv_table_end - &__pv_table_begin) << 2);

        /*
         * Cache cleaning operations for self-modifying code
         * We should clean the entries by MVA but running a
         * for loop over every pv_table entry pointer would
         * just complicate the code.
         */
        flush_cache_louis();
        dsb(ishst);
        isb();

        /*
         * FIXME: This code is not architecturally compliant: we modify
         * the mappings in-place, indeed while they are in use by this
         * very same code.  This may lead to unpredictable behaviour of
         * the CPU.
         *
         * Even modifying the mappings in a separate page table does
         * not resolve this.
         *
         * The architecture strongly recommends that when a mapping is
         * changed, that it is changed by first going via an invalid
         * mapping and back to the new mapping.  This is to ensure that
         * no TLB conflicts (caused by the TLB having more than one TLB
         * entry match a translation) can occur.  However, doing that
         * here will result in unmapping the code we are running.
         */
        pr_warn("WARNING: unsafe modification of in-place page tables - tainting kernel\n");
        add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);

        /*
         * Remap level 1 table.  This changes the physical addresses
         * used to refer to the level 2 page tables to the high
         * physical address alias, leaving everything else the same.
         */
        for (i = 0; i < PTRS_PER_PGD; pud0++, i++) {
                set_pud(pud0,
                        __pud(__pa(pmd0) | PMD_TYPE_TABLE | L_PGD_SWAPPER));
                pmd0 += PTRS_PER_PMD;
        }

        /*
         * Remap the level 2 table, pointing the mappings at the high
         * physical address alias of these pages.
         */
        phys = __pa(map_start);
        do {
                *pmdk++ = __pmd(phys | pmdprot);
                phys += PMD_SIZE;
        } while (phys < map_end);

        /*
         * Ensure that the above updates are flushed out of the cache.
         * This is not strictly correct; on a system where the caches
         * are coherent with each other, but the MMU page table walks
         * may not be coherent, flush_cache_all() may be a no-op, and
         * this will fail.
         */
        flush_cache_all();

        /*
         * Re-write the TTBR values to point them at the high physical
         * alias of the page tables.  We expect __va() will work on
         * cpu_get_pgd(), which returns the value of TTBR0.
         */
        cpu_switch_mm(pgd0, &init_mm);
        cpu_set_ttbr(1, __pa(pgd0) + TTBR1_OFFSET);

        /* Finally flush any stale TLB values. */
        local_flush_bp_all();
        local_flush_tlb_all();
}
#else

void __init early_paging_init(const struct machine_desc *mdesc,
 struct proc_info_list *procinfo)
{
 if (mdesc->init_meminfo)
 mdesc->init_meminfo();
}

#endif

 

cpu_switch_mm() 매크로

  • cpu_do_switch_mm 매크로를 호출하여 MMU에 페이지 디렉토리 설정 변경을 요청한다.
  • cpu_do_switch_mm 매크로는 각 아키텍처에 따라 수행 방법이 다르다.
    • MULTI_CPU를 사용하는 경우
      • processor->switch_mm() 콜백 함수를 호출한다.
        • switch_mm() 콜백 함수는 페이지 테이블을 설정한다.
      • ARMv7:
        • CONFIG_CPU_V7을 사용하므로 MULTI_CPU 이다.
        • switch_mm은 cpu_v7_switch_mm() 함수가 연결되어 있다.
    • MULTI_CPU를 사용하지 않는 경우
      • 각 아키텍처 이름에 맞게 호출 함수가 존재한다.

arch/arm/include/asm/proc-fns.h

#define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm)
  • 아래와 같이 두 개의 루틴 중 빌드 구성에 따라 선택하여 호출한다.

arch/arm/include/asm/glue-proc.h

#define cpu_do_switch_mm                __glue(CPU_NAME,_switch_mm)
  • __glue() 매크로는 두 개의 인수를 합친다.

arch/arm/include/asm/proc-fns.h

#define cpu_do_switch_mm                processor.switch_mm
  • MULTI_CPU로 설정된 경우 해당 프로세서 구조체를 통해 호출

 

cpu_v7_switch_mm()

  • CONTEXTIDR 레지스터에 context ID(tsk) 설정
  • TTBR0에 pgd0 설정

arch/arm/mm/proc-v7-2level.S

/*
 *      cpu_v7_switch_mm(pgd_phys, tsk)
 *
 *      Set the translation table base pointer to be pgd_phys
 *
 *      - pgd_phys - physical address of new TTB
 *
 *      It is assumed that:
 *      - we are not using split page tables
 */
ENTRY(cpu_v7_switch_mm)
#ifdef CONFIG_MMU
        mov     r2, #0
        mmid    r1, r1                          @ get mm->context.id
        ALT_SMP(orr     r0, r0, #TTB_FLAGS_SMP)
        ALT_UP(orr      r0, r0, #TTB_FLAGS_UP)
#ifdef CONFIG_ARM_ERRATA_430973
        mcr     p15, 0, r2, c7, c5, 6           @ flush BTAC/BTB
#endif
#ifdef CONFIG_PID_IN_CONTEXTIDR
        mrc     p15, 0, r2, c13, c0, 1          @ read current context ID
        lsr     r2, r2, #8                      @ extract the PID
        bfi     r1, r2, #8, #24                 @ insert into new context ID
#endif
#ifdef CONFIG_ARM_ERRATA_754322
        dsb
#endif
        mcr     p15, 0, r1, c13, c0, 1          @ set context ID
        isb
        mcr     p15, 0, r0, c2, c0, 0           @ set TTB 0
        isb
#endif
        bx      lr
ENDPROC(cpu_v7_switch_mm)

 

 

__glue() 매크로

  • 2 개의 인수를 합쳐 하나의 이름으로 만든다.

arch/arm/include/asm/glue.h

#define ____glue(name,fn)       name##fn
#define __glue(name,fn)         ____glue(name,fn)

 

 

 

flush_cache_louis()

  • setup_arch() → early_paging_init() – flush_cache_louis()

flush_cache_louis