[커널 17차] 50주차

2021.08.15 01:44

ㅇㅇㅇ 조회 수:171

ARM bootchain
- 여러 개의 bootloader가 각 level별로 존재한다
- BL1(Bootloader 1, boot ROM, secure) --> BL2(Trusted Boot firwmare, secure) --> BL3-1(EL3 firmware, Secure Monitor, secure) --> BL3-2(Trusted OS, booted by BL2, secure) --> BL3-3(UEFI, uboot 등, non-secure) --> OS (Linux 등) 등의 순으로 부팅된다
- https://www.cnx-software.com/2016/10/06/hacking-arm-trustzone-secure-boot-on-amlogic-s905-soc/
- https://www.slideshare.net/linaroorg/secure-boot-on-arm-systems-building-a-complete-chain-of-trust-upon-existing-industry-standards-using-opensource-firmware-sfo17201
 
OPTEE
- Secure OS 중 하나로 Linaro 프로젝트
- https://optee.readthedocs.io/en/latest/general/about.html
 
PCSI
- https://developer.arm.com/architectures/system-architectures/software-standards/psci
 
FSBL
- 1st stage bootloader
- https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842107/Arm+Trusted+Firmware
- Xilinx Zynq/Versal의 경우 BL1 --> FSBL(BL2)로 진행
 
NVSRAM
- SRAM에 일종의 flash memory(FN-tunneling 기반)를 backup으로 사용하여 power off시 data migration해서 non-volatile로 만든 메모리
 
psci_init_migrate()
- Trusted OS가 존재하는지 확인하고 존재하면 Trusted OS를 돌리는 CPU를 구한다
- resident_cpu에 Trusted OS CPU값을 저장함
- psci_migrate_info_up_cpu() 함수는 Trusted OS를 돌리는 CPU를 MPIDR 값으로 리턴해준다
 
SMC를 안쓰면 DT상에서 PSCI를 쓰면 안됨
 

=======================================================================================
 
smp_init_cpus()
- 함수 of_parse_and_init_cpus()로 각 cpu node에 대해 __cpu_logical_map[]을 각 cpu의 mpidr값으로 업데이트하고 함수 smp_cpu_setup()으로 cpu ops 초기화 및 cpu possible bitmap을 업데이트한다  
 
of_parse_and_init_cpus()
 
개요
- 각 cpu 노드에 대해 mpidr(hwid = "reg" property)을 구해서 __cpu_logical_map[] 배열에 저장한다
 
- for_each_of_cpu_node() 매크로로 각 cpu node에 대해 아래 iteration 수행
- of_get_cpu_mpidr() 함수로 각 cpu node의 mpidr을 읽어온다
- mpidr이 invalid이면 에러 처리
- mpidr이 이전 iteration에서 처리한 cpu의 mpidr과 중복인지 함수 is_mpidr_duplicate()로 파악하고 중복이면 에러처리
- 현재 iteration이 booting cpu 차례인 경우 bootcpu_valid 전역 변수가 false인것을 확인하고 true로 변경한다(처음부터 true였으면 에러처리). 그리고 early_map_cpu_to_node() 함수로 cpu_to_node_map[] 배열에 cpu to node map을 저장한다. 또한 set_cpu_numa_node() 함수로 percpu 변수 numa_node에 cpu node id를 저장한다
- Booting cpu가 아닌 경우에는 cpu_count가 NR_CPUS보다 같거나 크면 에러 처리하고 아니면 mpidr을 커널 출력한뒤 set_cpu_logical_map() 함수로 __cpu_logical_map[]에 mpidr을 저장한다. 그리고 함수 early_map_cpu_to_node()로 node id를 cpu_to_node_map[] 배열에 저장한다
- 전역 변수 cpu_count를 1 증가시킨다
 
of_get_cpu_mpidr()
- cpu node에서 "reg" property(DT상의 CPU 물리주소값)를 읽어서 MPIDR register의 [29:24] bit가 0이 아니면 에러 처리한다. 이 부분은 Multi Thread 필드이다.
- 0이면 읽은 값을 리턴한다
 
of_n_addr_cells()
- Device node의 최상위 parent node로 가서 "#address-cells" property를 읽어서 리턴한다
 
of_bus_n_size_cells()
- Device node의 최상위 parent node의 "#size-cells" property를 읽어서 리턴한다
 
is_mpidr_duplicate()
- cpu_logical_map[]에 저장된 이전 CPU들과 동일한 물리주소(mpidr)값을 가지는지 확인하고 같으면 true 다르면 false를 리턴한다
- cpu를 1번부터 체크하기 때문에 boot cpu와는 비교하지 않는다
 
set_cpu_logical_map()
- __cpu_logical_map[]에 mpidr값을 저장한다
 
early_map_cpu_to_node()
- cpu 번호와 nid를 받아서 cpu_to_node_map[]을 업데이트한다
- cpu가 0번이면 set_cpu_numa_node() 함수로 percpu의 numa_node 변수도 업데이트한다
 
smp_cpu_setup()
- 함수 init_cpu_ops()로 DT로부터 cpu operation을 초기화한다 (psci/apci)
- cpu ops 함수 중 cpu_init()를 실행한다 (psci의 경우 빈 함수임)
- set_cpu_possible() 함수로 possible cpu bitmask를 1로 set한다
 
 
smp_build_mpidr_hash()
- mpidr을 입력으로 하는 collision free hash를 위해 collision free hash bucket bit를 구성한다
- 이는 cpu suspend/resume 등에서 사용된다

- Suspend context를 저장하는 배열이 있는데 배열 어느 element에 저장해야 할지 index를 MPIDR hashing을 해서 결정한다
- mpidr register 중 cpu affinity bitfield 32 bit [39:32], [23:16], [15:8], [7:0]만 사용된다
- 1, 2, 3번 affinity와 0번 affinity를 XOR한 값 3개를 계산하고 이를 모두 XOR하여  
값이 동일한 bit를 걸러내는 mask를 만든다
- 0, 1, 2, 3번 affinity에 대해 첫 setbit이 나오는 bit index값과 유효한 bit 길이를 저장한다 (fs[i] : index, bits[i] : 유효 길이)
- 위 값들을 사용해서 mpidr로부터 hash bucket을 생성하기 위해 필요한 shift 값을 계산한다 (shift_aff[i])
- hash bucket 총 길이도 계산한다
- 구조체 mpidr_hash에 위에서 계산한 shift_aff[i], mask, bits를 저장한다
- hash bucket 길이가 너무 길면 경고한다
 
for_each_possible_cpu() 매크로
- 주어진 bitmask 조건을 만족하는 cpu index를 iterate한다
- cpumask_next() 함수가 사용되며 이 함수는 find_next_bit()을 이용하여 현재 index + 1 지점부터 bitmask를 탐색하여 1값을 가지는 바로 다음의 bit index를 리턴한다
 
MPIDR_AFFINITY_LEVEL() 매크로
- 입력값을 MPIDR로 생각하고  affinity 0, 1, 2, 3 값을 추출해서 리턴한다
 
fls() 함수
- Leftmost 1값을 가지는 bit index를 리턴한다
 
ffs() 함수
- Rightmost 1값을 가지는 bit index를 리턴한다
 
 
CPU 상태
- http://jake.dothome.co.kr/boot_cpu_init/
 
=========================================================
 
cpu_suspend() 함수의 context 복원 문제
- suspend 후 resume할 때 caller-saved register가 온전히 보전되는가?
- __cpu_suspend_enter() 함수 내에서 caller-saved register 외의 모든 register context는 구조체 sleep_stack_data 내부에 보존되고 resume시에 복원되는 것이 확인됨
- 그러나 register를 저장하는 함수 __cpu_suspend_enter()와 실제 SMC를 통해 suspend를 수행하는 함수 fn()이 분리되어 있기 때문에 caller-saved register가 항상 보존되는 것인지 불분명함
- 현재는 두 함수 사이에 아무것도 없기 때문에 __cpu_suspend_enter() 함수에서 리턴하더라도 sp 아래쪽 값들(__cpu_suspend_enter() 함수에 진입했을 때 스택에 올린 caller-saved register들 정보)이 살아있고 보존되는 것으로 생각되며 그래서 문제 없이 동작하는 것으로 보임
- 그러나 엄밀히 따지면 하나의 함수에서 context를 저장하고 SMC를 호출하는 것이 정석으로 생각됨
- 실제로 이전 패치에서는 하나의 함수 __cpu_suspend_enter()에서 context 저장과 SMC 호출이 모두 이루어지므로 문제가 없음
 
SMCCC (calling convention)
- https://developer.arm.com/documentation/den0028/d/?lang=en
 
관련 패치
- https://github.com/torvalds/linux/commit/adc9b2dfd00924e9e9b98613f36a6cb8c51f0dc6
- https://lwn.net/Articles/682293/

XE Login