[커널 17차] 49주차

2021.08.07 23:23

ㅇㅇㅇ 조회 수:216

resource 구조체
- 리소스의 descriptor 자료구조
- start, end, name, flags, desc 데이터를 포함
- 그 외 parent, sibiling, child 포인터로 구성됨
 
전역변수 resource_lock
- resource 접근을 위해 read-write lock인 arch_rwlock(rwlock_t)을 사용한다
- wait lock(spinlock)과 read lock인지 write lock인지 구별하는 필드, cnts 값으로 구성된다

 


request_standard_resources()
- 현재 memblock.memory 전체를 관리하기 위한 struct resource 영역을 memblock을 통해 새로 할당한다
- 그리고 각 memblock.memory region에 대해서 다음을 수행한다
1) nomap region이면 resource name을 "reserved"로 정하고 resource flag를 IORESOURCE_MEM으로 정한다
2) nomap region이 아니면 resource name을 "System RAM"으로 정하고 resource flag는 IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY로 지정한다
3) 각 memblock region의 시작주소와 끝 주소를 resource start/end로 지정하고 iomem_resource resource tree에 추가한다
4) 만약 kernel code가 memblock region에 포함되면 현재 memblock region resource tree에 kernel code resource node를 추가한다
5) 만약 kernel data가 memblock region에 포함되면 현재 memblock region resource tree에 kernel data resource node를 추가한다
6) CONFIG_KEXEC_CORE가 설정되어 있으면 현재 memblock region resource tree에 crash kernel resource node를 추가한다
 
 
request_resource(root, new)
- root는 resource tree 자료구조의 시작점
- new는 추가할 resource node이다
- tree에 new를 추가하고 성공하면 0, 실패하면 에러를 리턴한다
- request_resource_conflict() 함수로 노드 추가를 수행하고 성공 여부를 판단한다
 
request_resource_conflict()
- write lock을 건다
- __request_resource()로 resource 트리에 새로운 resource node를 추가한다. 결과는 conflict로 저장
- write lock을 푼다
- confilct를 리턴한다
 
__request_resource()
- root 부터 시작해서 child --> sibling --> ... --> sibling --> NULL로 이어지는 linked list를 탐색한다
- 각 노드들은 모두 start/end 주소가 오름차순으로 정렬되어 있다
- new가 가지는 start/end를 이용해서 전체 list가 오름차순이 되도록 올바른 위치에 new node를 삽입하는 코드이다
- start/end가 겹치는 경우 겹치는 기존 노드를 리턴한다
 
초기 상태
 
root[0, 100]
 |
child[10, 20] -> sibling[30, 40] -> sibling[70, 80] -> NULL
 
new[50,60]을 추가할 때
 
root[0, 100]
 |
child[10, 20] -> sibling[30, 40] -> new[50, 60] -> sibling[70, 80] -> NULL
 
new[35, 50]을 추가하는 경우에는 겹치는 sibling[30, 40]을 리턴한다
 
early_ioremap_reset()
- 전역 변수 after_paging_init를 1로 만들고 끝
 
ioremap
- 리눅스에서 하드웨어에 접근하기 위해 사용하는 방법
- 가상주소로 접근해야 하기 때문에 fixmap처럼 하드웨어의 임시 가상주소를 할당한다
- 할당된 임시 가상주소로 하드웨어에 접근한 뒤 임시 가상주소를 해제한다
- 디바이스 드라이버 등에서 많이 사용할 듯
- https://bradkim06.tistory.com/entry/%EB%A6%AC%EB%88%85%EC%8A%A4-%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84%EC%97%90%EC%84%9C-%ED%95%98%EB%93%9C%EC%9B%A8%EC%96%B4-%EC%9E%A5%EC%B9%98-%EC%A0%91%EA%B7%BC-%EB%B0%A9%EB%B2%95ARM
 
struct device_node : unflatten device tree에서 사용되는 device node 개체
struct of_device_id : device node 개체를 검색할 때 사용되는 자료 구조
 
psci_dt_init()
- unflattened device tree를 검색해서 "arm,psci", "arm,psci-0.2", "arm,psci-1.0" compatible에 가장 적합한 노드를 찾는다
- 찾은 노드의 compatible에 해당하는 data를 초기화 함수로 하여 초기화를 수행하고 인자로는 찾은 노드를 넣는다. 이는 찾은 노드를 인수로 psci 초기화 함수를 수행하는 것이다
- "arm,psci", "arm,psci-0.2", "arm,psci-1.0" compatible에 해당하는 초기화 함수는 각각 psci_0_1_init, psci_0_2_init, psci_1_0_init 이다
- 초기화가 끝나면 초기화 함수의 리턴값을 리턴하고 종료한다
 
of_find_matching_node_and_match(NULL, psci_of_match, matched_np)
- 노드들을 검색해서 조건에 가장 맞는 노드를 골라서 리턴하고, 그 특성은 matched_np로 리턴한다
- NULL은 검색을 of_root부터 시작한다는 것을 의미한다
- psci_of_match는 검색 조건을 나타내는 of_device_id 배열이다. 이 배열에 있는 compatible에 맞는 노드들을 검색한다
- matched_np는 검색된 노드의 of_device_id 정보를 리턴한다
- 검색된 노드는 함수값으로 리턴된다
- 검색 전후로 devtree_lock(spin lock)을 걸고 푸는 작업을 수행한다
 
 
PCSI 개요
- 예전에는 CPU/제조사마다 power up 및 전력 관리 방법이 모두 달랐음
- 따라서 ARM이 power up 및 관리 방법을 표준화하였고 이게 PCSI이다
- PCSI는 firmware level (EL3)의 구성요소로서 동작한다
- 규격과 인터페이스가 모두 표준으로 정해져 있음
- 일반적으로 SMC/HVC call을 통해서 PCSI 기능을 동작시킨다
- Hypervisor 설정에 따라서 EL1에 SMC하더라도 EL2로 갈 수도 있다
- EL3 호출이 빈번하면 firmware 구현이 lightweight하지 못하기 때문에 hypervisor가 존재하는 경우 보통 EL2에서 처리하려고 함
- 다만 물리적인 CPU power up/관리는 EL3 firmware에서만 처리 가능하다 (PSCI 표준 규약)
- 따라서 EL3 firmware에서 CPU power up을 하고 이후 EL1 커널에서 CPU 부팅 요청을 하면 EL2 hypervisor에서 vCPU
- 따라서 EL2 hypervisor에서 SMC를 통해 EL3 firmware로 하여금 물리적인 CPU 부팅 작업을 수행하도록 하고 vCPU 설정을 모두 만들어 놓은 뒤에 EL1 커널에서 CPU 부팅을 SMC을 통해 요청하면 SMC trap을 가로채서 EL2에서 vCPU를 커널에 연결해준다
 
PSCI 자료
- https://developer.arm.com/architectures/system-architectures/software-standards/psci
- https://developer.arm.com/documentation/den0022/latest/
 
psci_0_1_init()
- get_set_conduit_method() 함수로 가장 잘 매칭된 노드에서 "method" 속성을 읽어온다. 이는 HVC/SMC 둘 중 하나이다
- HVC/SMC에 따라서 set_conduit() 함수를 수행, psci 동작을 위한 함수 포인터 invoke_psci_fn를 __invoke_psci_fn_hvc/smc로 초기화한다
- 이후 psci_ops 구조체의 psci 호출 함수들을 초기화한다. 이 호출 함수들은 invoke_psci_fn를 이용하여 만들어진다
 
psci_0_2_init(), psci_1_0_init()도 세부사항은 다르나 비슷한 기능을 수행한다 (invoke_psci_fn를 이용하여 psci 호출 함수를 초기화함)
 
set_conduit()
- conduit(hvc/smc)에 따라서 invoke_psci_fn를 초기화한다
- 전역변수 psci_conduit를 conduit으로 초기화한다
 
__invoke_psci_fn_hvc/smc
- arm_smccc_hvc/smc로 정의되어 있음
- arm_smccc_hvc/smc는 __arm_smccc_hvc/smc로 정의됨
 
__arm_smccc_hvc/smc
- 어셈블리로 각각 hvc/smc를 호출한다
- 호출전에 스택에 x0~x3을 저장하고 (psci 함수가 x0~x3을 리턴값으로 쓰기 때문. 인수로는 x0 ~ x7를 사용한다) 필요한 경우 quirk라고 하는 값도 스택에 저장한다 (x6에 해당)
 
 
 
 
init_bootcpu_ops()
- Secondary CPU들을 부팅할 때 어떤 방식으로 할지 결정한다
- init_cpu_ops(0)을 호출한다
 
init_cpu_ops(n)
- cpu_read_enable_method(n)를 통해 n번 cpu가 어느 방법으로 cpu enable을 수행하도록 DTB에 쓰여 있는지 parsing한다
- enable 방식은 DTB와 ACPI의 경우가 다르며 DTB의 경우 spinning 방식과 PSCI 방식으로 나누어진다. Spinning 방식은 legacy이고 최근은 PSCI 방식을 사용한다
- enable 방식이 결정되면 cpu_init, cpu_prepare, cpu_boot 등 cpu 설정을 위한 함수들을 어떤 것을 쓸지 정하게 된다. 이는 전역 변수 함수 포인터를 모아놓은 cpu_operations 구조체 (cpu_psci_ops 또는 smp_spin_table_ops)를 cpu_operations 구조체 전역 변수 배열 cpu_ops[]에 할당함으로써 이루어진다. 이 배열의 인덱스는 cpu 인덱스이다.
 
- cpu_read_enable_method(cpu) : 해당 cpu가 사용하는 enable method를 가져온다
- cpu_ops[cpu] = cpu_get_ops(enable_method) : enable_method를 사용하는 경우 cpu 설정을 위해 사용해야 하는 함수포인터 구조체를 얻어서 cpu_ops[cpu] 배열 element에 저장한다
 
cpu_read_enable_method()
- 해당 cpu가 사용하는 enable method string을 parsing해서 리턴한다
- DTB를 쓰는 경우와 acpi를 쓰는 경우로 나누어지며 여기서는 DTB를 사용하는 경우로 한정한다
- dn = of_get_cpu_node(cpu, NULL) : DT를 검색해서 cpu 노드 중 "reg"가 cpu인 device node를 얻는다
- enable_method = of_get_property(dn, "enable-method", NULL) : 노드에서 "enable-method" property를 검색해서 enable_method로 리턴한다
- return enable_method : 구한 enable_method를 리턴한다
 
of_get_cpu_node()
- 특정 "reg" 값을 가지는 cpu를 DT에서 검색해서 리턴한다. 이때 어느 thread인지도 같이 리턴할 수 있다.
- for_each_of_cpu_node() 매크로로 cpu 노드에 대해 검색
- arch_find_n_match_cpu_physical_id() 함수로 각 검색된 cpu node에 대해 "reg" 값이 일치하는지 확인한다
- 일치하면 해당 노드를 리턴한다
 
for_each_of_cpu_node()
- of_get_next_cpu_node() 함수로 cpu 노드에 대해 iteration을 수행한다
 
of_get_next_cpu_node(prev)
- prev가 NULL이면 "/cpus" path에 있는 첫 노드의 child 부터 시작하여 node name/type이 "cpu"인 노드를 찾아 리턴한다
- prev가 NULL이 아니면 prev의 sibling에서 시작하여 node name/type이 "cpu"인 노드를 찾아 리턴한다
- 노드 검색 전후로 devtree_lock 스핀락을 사용한다
 
arch_find_n_match_cpu_physical_id(cpun, cpu)
- __of_find_n_match_cpu_property() 함수로 cpun 노드의 "reg" 값이 cpu인지 리턴한다
 
__of_find_n_match_cpu_property(cpun, prop_name, cpu)
- cpu 노드 cpun의 "prop_name" property를 검색해서 그 값이 cpu와 같은지 판단한다
- 같으면 true/다르면 false 리턴한다
 
cpu_get_ops(name = enable_method)
- enable_method가 pcsi면 전역 변수 cpu_psci_ops를 리턴하고 spin_table이면 전역변수 smp_spin_table_ops를 리턴한다
- 각 전역변수 구조체는 cpu 설정을 위한 함수를 포함하고 있다

XE Login