본문 바로가기
Kernel/理解

Device Tree 분석 (Kernel Source)

by 暻煥 2024. 2. 2.

.dts파일은 컴파일 후, HexFile로 생성된다.

해당 HexFile을 Linux Kernel이 Parsing 하면 아래그림과 같이 Tree 구조를 가진다.

 

Linux Source Code 흐름을 따라가보면 아래 그림과 같다.

 

동일한 내용을 텍스트로 작성.

setup_arch(&command_line)
	setup_machine_fdt(__atags_pointer)
		| 인수로 전달받은 디바이스 트리(fdt)가 물리 주소에 있는지 검색
		| 해당 machine을 찾고 machine_desc구조체 포인터로 알아온다		
		| 디바이스 트리로부터 초기 부트업 과정에 미리(early) 설정해야 정보를 얻어온다
		| 초기 정보에는 커맨드 라인 정보나 메모리 정보가 있다
		early_init_dt_verify(phys_to_virt(dt_phys))
			| 전역변수 initiali_boot_params의 값에 device tree의 가상주소 저장
			| fdt magic value 및 CRC를 체크하여 유효성 검사
		of_flat_dt_match_machine(mdesc_best, arch_get_next_mach) : 디바이스 트리 항목들의 compatible속성을 읽어들인다
			arch_get next_mach_(&compat))
				| 다음 항목에 대하여 compatible string을 반환한다
				| 디바이스 트리의 모든 항목들에 대하여 Loop를 돈다
				| bootarg와 device tree의 모든 항목들에 대하여 compatible을 비교하는 이 루틴은 MULTIPATFORM을 지원하기 위함이다
				| 당사의 제품의 경우 하나의 platform이 올라가기 때문에 루프한번을 돌고 compatile value를 얻어온다
					of flat_dt_match(dt_root, compat)
						of_fdt_match(initial_boot_params, node, compat)
		| machine을 찾지 못하여 machine_desc 구조체 포인터를 알아오지 못한 경우, machine table을 덤프하고 종료
		| 만약 firmware가 kernel level에서의 device tree조작을 요청한 경우, 즉, machine_dexc->dt_fixup 함수 포인터에 값이 저장되어 있는 경우, 해당 함수를 수행한다
		early_init_dt_scan_nodes()
			| 본 함수에서는 of_scan_flat_dt( ) 함수를 인자를 다르게 하여 3번 호출한다
			| of_scan_flat_dt( )는 해당하는 node를 찾아서 call back 함수를 호출하는 역할을 한다
			of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line)
				| fdt_next_node( ) 함수를 통해서 dtb blob을 순휘한다
				kbasename(pathp)
					| device tree는 파일처럼 root에서 시작되는 경로로 이루어져 있는데, 경로의 마지막 이름을 얻어온다
				it(offset, pathp,depth, data)
					| 콜백 함수 early_init_dt_scan_chosen(unsigned long node, const char *uname, int depth,void *data)를 실행한다 (dtb의bootarg를 읽어와 cmdline 설정)
					| 현재 찾는 노드는 chosen이 맞는가 문자열 비교를 통하여 확인한다
					| chosen 노드는 bootarg와 bootmode가 설정되어 있는 노드이다
					of_get_flat_dt_prop(node, "bootargs", &l)
						fdt_getprop(initial_boot_params, node, name, size)
							fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp)
								| property name string을 비교하여 fdt_property 구조체 포인터 반환
						| fdt_porperty->data 반환
					| 전역변수 boot_command_lene에 chosen node의 bootargs property의 data 저장
					| CONFIG_CMDLINE 정의 & CONFIG_CMDLINE_FORCE 비정의 -> config파일의 CMDLINE 사용
			of_scan_flat_dt(early_init_dt_scan_root, NULL)
				| 콜백 함수 early_init_dt_scan_root(unsigned long node, const char *uname, int depth, void *data) 실행
				| root node (top level)의 address-cell 크기와 size-cell 크기를 알아와서 전역변수 dt-root_size_cells, dt_root_addr_cells에 각각 저장
				| cell size의 값은 1이 4byte를 나타낸다
			of_scan_flat_dt(early_init_dt_scan_memory, NULL)
				| 콜백 함수 early_init_dt_scan_memory(unsigned long node, const char *uname, intdepth, void *data) 실행
				of_get_flat_dt_prop(node, "device_type", NULL)
					| node name이 "device_type인 node의 fdt_property->data를 반환한다
					| node의 property가 memory가 아니라면 함수를 종료한다
					| node 내의 각각의 cell을 모두 스캔하여 dt_mem_next_cell(dt_root_addr-cells, &reg), dt_mem_next_cell(dt_root_ssize-cells,&reg) 두개의 함수를 이용하여 지정된 cell size만큰 base와 size를 read한다
					early_init_dt_add_memory_arch(base, size)
						| memblock에 해당 base와 size를 PAGE_ALIGN 하여 추가한다
						memblock_add_region(base, size, MAX_NUMNODES, 0)
							memblock_add_range(_rgn, base, size, nid, flags)
								| 전역변수 memblock->memory->region[i]에 메모리 구역을 추가한다
								| type이 동일한 영역들을 merging하거나, memblock entry갯수가 모자란 경우를 위해 적어도 두번 이상 loop를 동면서 merging을 수행한다
								┖memblock_double_array(type, obase, size)
									| first round의 경우 기존 memblock들과 끼워 넣을memblock들의 합이 최대 관리 개수를 넘어가는 경우에 한해 memblock 영역의 엔트리 수를 두 배 더 크게 넓히기 위해 memblock_double_array( ) 함수를 호출
									| 이 떄 충분한 엔트리 개수가 준비될 때 까지 반복한다.
								if/else
								┖memblock_merge_regions(type)
									| second round에서 끼워 넣은 memblock들에 대해 주변 memblock들과 인접하고 flag타입이 동일한 memblock들을 memvlock_merge_regions( ) 함수를 사용하여 merge한다.
		| 만약 multi platform인 경우에는 인식한 architecture의 갯수를 전역변수 __machine_arch_type에 기록한다
	| machine_desc를 반환하여 setup_arch( )의 나머지 부분에 계속해서 사용하여 초기화를 진행한다
......
	parse_early_param()
		| 전역 변수 boot_command_line의 내용을 tmp_cmdline에 복사
		parse_early_options(tmp_cmdline)
			| 파라미터 블록, 개수, 범위가 지정되는 경우 그 파라미터 범위에 해당하는 토큰과 매치되는 경우 해당 파라미터에 값을 대입
			| 파라미터 블록, 개수 및 범위가 0으로 전달되는 경우 각 토큰을 파싱하게 되면 param과 val 값을 가지고 항상 unknown hanler인 do_early_param() 함수가 호출
			do early param(char *oaram, char *val, const char *unused)
				| 다음 조건에 해당되는 early 커널 파라미터를 발견하면 @val 값 인수를 가지고 해당 커널 파라미터에 등록된 함수를 호출
				| 요청한 커맨드 라인 파라미터가 early 커널 셋업 파라미터와 매치된 경우
				| 요청한 커맨드 라인 파라미터가 "console"로 시작한 경우
				| early_param("earlycon")으로 등록한 셋업 함수
				early_mem(char *p)
					arm_add_memory(start, size)
						memblock_add(start, size)
							| bootarg애 있는 값 또한 memblock에 추가한다
......
	arm_memblock_init(mdesc)
		......
		early_init_fdt_scan_reserved_mem()
			early_init_dt_reserve_memory_arch(__pa(initial_voot_params), fdt_totalsize(initial_boot_params),0)
				| DTB Blob이 저장되어 있는 영역을 전역변수 memblock->reserved->region[i] 추가한다
				| DTB 시작위치에서 fdt_header->off_mem_rsvmap 만큼 떨어진 필드에 reservation이 필요한 메모리 영역이 시작위치(32bit), 크기(32bit) 순서대로 위치해 있다. 해당 pair list의 마지막은 size가 0으로 저장되어 있다.
			fdt_get_mem_rsv(initial_boot_params, n, &base, &size)
				| pair list를 돌면서 base와 size를 얻어온다
			early_init_dt_reserve_memory_

reserved mem 초기화 함수를 등록할 수 있다

#define RESERVEDMEM_OF_DECLARE(name, compat, init)  _OF_DECLARE(reservedmem, name, compat, init reserved_of_init_fn)

예를 들어 아래와 같이 사용한 경우

RESERVEDMEM_OF_DECLARE(cma, "shared-dma-pool", rmem_cma_setup)

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

디바이스명(compat)은 "shared-cma-pool"이다.

이 디바이스의 초기화 함수는 rmem_cma_setup() 함수이다.


참고 및 출처:

 

http://jake.dothome.co.kr/arm_memblock_init/

 

arm_memblock_init()

<kernel v4.0> reserve memblock 영역에 다음 영역들을 등록한다. 커널 영역 (XIP 커널인 경우 코드를 제외한 커널 영역) initrd 영역 페이지 테이블 영역 아키텍처 머신이 지정하는 reserve 영역 DTB 영역 및 DT

jake.dothome.co.kr

http://jake.dothome.co.kr/setup_machine_fdt/

 

setup_machine_fdt()

<kernel v5.0> 머신 설정 시스템을 설정하는 방법은 다음과 같이 두 가지로 나뉜다. Legacy 머신 디스크립터를 사용하여 아키텍처 또는 머신 specific한 코드를 사용한다. 기존 ARM32를 사용한 대부분의

jake.dothome.co.kr

https://www.cnblogs.com/downey-blog/p/10485596.html

 

linux设备驱动程序-设备树(1)-dtb转换成device_node - 牧野星辰 - 博客园

linux设备驱动程序 设备树(1) dtb转换成device_node 本设备树解析基于arm平台 从start_kernel开始 linux最底层的初始化部分在HEAD.s中,这是汇编代码,我们暂且不作过多讨论,在head.s完成部分初始化之后

www.cnblogs.com

http://jake.dothome.co.kr/unflatten_device_tree/

 

unflatten_device_tree()

<kernel v5.10> 디바이스 트리(FDT) -> Expanded 포맷으로 변환 device_node와 property 구조체를 사용하여 트리 구조로 각 노드와 속성을 연결한다. 기존에 사용하던 DTB 바이너리들도 문자열등을 그대로 사용

jake.dothome.co.kr

 

'Kernel > 理解' 카테고리의 다른 글

Scatter Gather List  (0) 2024.04.10
Block IO 분석  (0) 2024.03.24
Device Tree 문법  (0) 2024.02.02
ZRAM 분석  (0) 2024.02.02
ELF 실행 & execve  (0) 2024.02.02