Kernel/理解

Device Tree 분석 (Kernel Source)

暻煥 2024. 2. 2. 14:29

.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