<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) 영역에도 추가한다.
- 다음 2가지 설정에 의해 영역 크기가 할당될 수 있다.
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에 추가한다.
- 다음 3가지 영역을 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
- 디바이스 드라이버(dma for coherent/cma for dma)가 필요로 하는 DMA 영역을 reserve memblock에 추가하고 CMA(Contiguous Memory Allocator)에도 추가한다.
- memblock_dump_all();
- 커널 cmdline에 “debug” 옵션을 사용하는 경우 memory & reserve memblock 영역을 dump 한다.
아래 그림과 같은 순서로 몇 가지 영역을 reserved memblock에 추가한다.
- 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[] 배열에만 추가한다.
- 알아온 영역을 다음 2가지 경우에 따라 처리한다.
- 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[] 배열에만 추가한다.
- 첫 번째 depth의 “reserved-memory” 노드명을 찾아 “status” 속성값이 “ok” 또는 “okay”인 경우 알아온 영역을 다음 2가지 경우에 따라 처리한다.
- 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영역에서 해지
- “no-map” 속성이 찾아지면 true
- 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 속성을 읽어온다.
- rmem->size가 0인 경우는 base 주소가 dynamic하게 지정될 수 있게 __reserved_mem_alloc_size() 함수를 호출하여 reserve memblock에 영역을 추가한다.
- __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;
참고
- Memblock – (1) | 문c
- Memblock – (2) | 문c
- arm_memblock_init() | 문c – 현재 글
- arm64_memblock_init() | 문c

