QUESTION
Secure/Non-secure CPU context 전환 단계에서 실제로 바뀌는 상태는 무엇인가?
world switch는 PC만 바꾸는 branch가 아니다. security state별 SCR_EL3, translation regime, timer, FP/SVE state의 owner를 정확히 나눠야 한다.
ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.
STRUCTURE
객체와 주소가 놓이는 구조
ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다.
cpu_context_t / per_cpu_context를 중심에 놓고 왼쪽의 입력이 어떤 검사를 거쳐 오른쪽 결과로 공개되는지 표시했다. 실제 디버깅에서는 각 블록의 주소와 크기를 로그에 대입한다.
CALL PATH
실행 흐름
화살표는 단순 호출 순서만 뜻하지 않는다. 각 단계가 성공을 반환할 때 다음 단계가 읽을 수 있는 상태가 무엇인지 함께 확인한다. 오류 반환이 발생하면 바로 다음 화살표로 진행하지 않고 해당 단계의 정리 경로를 따라간다.
STATE LEDGER
단계별 입력과 출력
호출 순서를 함수 이름으로만 외우지 않고, 각 단계가 무엇을 받아 무엇을 공개하는지 적은 표다. 실제 소스에서 생산 필드가 다르면 표를 고치는 방식으로 사용한다.
| # | 단계 | 진입 시 신뢰할 상태 | 성공 뒤 남아야 할 상태 | 다음 소비자 |
|---|---|---|---|---|
| 01 | SMC/interrupt entry | EL3 exception context | cpu_context_t | save outgoing context |
| 02 | save outgoing context | SMC/interrupt entry 완료 상태 | el3_state_t | select security state |
| 03 | select security state | save outgoing context 완료 상태 | el1_sysregs_t | restore target sysregs |
| 04 | restore target sysregs | select security state 완료 상태 | gp_regs_t | ERET |
| 05 | ERET | restore target sysregs 완료 상태 | gp_regs_t | 최종 최종 부트로더 이미지 또는 다음 stage |
공통 불변 조건: ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다. 한 단계의 출력이 다음 단계의 입력 조건을 만족하지 않으면 오류가 실제로 드러난 위치보다 앞의 생산 단계부터 조사한다.
VISUAL WALKTHROUGH
주소와 객체의 이동을 그림으로 읽기
아래 그림은 호출 이름보다 주소, 객체 수명과 handoff 경계를 먼저 볼 수 있도록 구성했다. 실제 주소와 크기는 사용 중인 보드의 bdinfo, map과 linker symbol을 대입한다.
각 칸은 제어권이 다음 단계로 넘어가기 전에 확정되어야 하는 상태를 나타낸다.
두 열은 실행 주체와 접근 권한, 객체를 해제할 책임이 다른 경계를 비교한다.
Non-secure context
01SCR_EL3.NS=1
02NS EL1/EL2 system registers
03Linux/U-Boot general registers
04NS timer·FP owner
Secure context
01SCR_EL3.NS=0
02S-EL1 system registers
03OP-TEE general registers
04secure timer·FP owner
UPSTREAM SOURCE
원본 코드
아래 코드는 Trusted Firmware-A LTS 2.14.3의 lib/el3_runtime/aarch64/context_mgmt.c에서 1126-1214줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.
1126 write_hstr_el2(HSTR_EL2_RESET_VAL & ~(HSTR_EL2_T_MASK));
1127
1128 /*
1129 * Initialise CNTHP_CTL_EL2. All fields are architecturally UNKNOWN on
1130 * reset.
1131 *
1132 * CNTHP_CTL_EL2:ENABLE: Set to zero to disable the EL2 physical timer
1133 * and prevent timer interrupts.
1134 */
1135 write_cnthp_ctl_el2(CNTHP_CTL_RESET_VAL & ~(CNTHP_CTL_ENABLE_BIT));
1136
1137 manage_extensions_nonsecure_el2_unused();
1138#endif /* INIT_UNUSED_NS_EL2 */
1139}
1140
1141/*******************************************************************************
1142 * Prepare the CPU system registers for first entry into realm, secure, or
1143 * normal world.
1144 *
1145 * If execution is requested to EL2 or hyp mode, SCTLR_EL2 is initialized
1146 * If execution is requested to non-secure EL1 or svc mode, and the CPU supports
1147 * EL2 then EL2 is disabled by configuring all necessary EL2 registers.
1148 * For all entries, the EL1 registers are initialized from the cpu_context
1149 ******************************************************************************/
1150void cm_prepare_el3_exit(size_t security_state)
1151{
1152 u_register_t sctlr_el2, scr_el3;
1153 cpu_context_t *ctx = cm_get_context(security_state);
1154
1155 assert(ctx != NULL);
1156
1157 if (security_state == NON_SECURE) {
1158 uint64_t el2_implemented = el_implemented(2);
1159
1160 scr_el3 = read_ctx_reg(get_el3state_ctx(ctx),
1161 CTX_SCR_EL3);
1162
1163 if (el2_implemented != EL_IMPL_NONE) {
1164
1165 /*
1166 * If context is not being used for EL2, initialize
1167 * HCRX_EL2 with its init value here.
1168 */
1169 if (is_feat_hcx_supported()) {
1170 write_hcrx_el2(HCRX_EL2_INIT_VAL);
1171 }
1172
1173 /*
1174 * Initialize Fine-grained trap registers introduced
1175 * by FEAT_FGT so all traps are initially disabled when
1176 * switching to EL2 or a lower EL, preventing undesired
1177 * behavior.
1178 */
1179 if (is_feat_fgt_supported()) {
1180 /*
1181 * Initialize HFG*_EL2 registers with a default
1182 * value so legacy systems unaware of FEAT_FGT
1183 * do not get trapped due to their lack of
1184 * initialization for this feature.
1185 */
1186 write_hfgitr_el2(HFGITR_EL2_INIT_VAL);
1187 write_hfgrtr_el2(HFGRTR_EL2_INIT_VAL);
1188 write_hfgwtr_el2(HFGWTR_EL2_INIT_VAL);
1189 }
1190
1191 /* Condition to ensure EL2 is being used. */
1192 if ((scr_el3 & SCR_HCE_BIT) != 0U) {
1193 /* Initialize SCTLR_EL2 register with reset value. */
1194 sctlr_el2 = SCTLR_EL2_RES1;
1195
1196 /*
1197 * If workaround of errata 764081 for Cortex-A75
1198 * is used then set SCTLR_EL2.IESB to enable
1199 * Implicit Error Synchronization Barrier.
1200 */
1201 if (errata_a75_764081_applies()) {
1202 sctlr_el2 |= SCTLR_IESB_BIT;
1203 }
1204
1205 write_sctlr_el2(sctlr_el2);
1206 } else {
1207 /*
1208 * (scr_el3 & SCR_HCE_BIT==0)
1209 * EL2 implemented but unused.
1210 */
1211 init_nonsecure_el2_unused(ctx);
1212 }
1213 }
1214
LINE BY LINE
1126-1214줄 해설
원본에 보이는 모든 줄을 순서대로 설명한다. 빈 줄도 block 경계로 남겨, 코드와 설명의 위치가 어긋나지 않게 했다.
write_hstr_el2(HSTR_EL2_RESET_VAL & ~(HSTR_EL2_T_MASK));write_hstr_el2(HSTR_EL2_RESET_VAL & ~(HSTR_EL2_T_MASK))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 cpu_context_t 항목과 대조한다.
(빈 줄)write_hstr_el2(HSTR_EL2_RESET_VAL & ~(HSTR_EL2_T_MASK));까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 el3_state_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Initialise CNTHP_CTL_EL2. All fields are architecturally UNKNOWN on원본 주석이 'Initialise CNTHP_CTL_EL2. All fields are architecturally UNKNOWN on'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* reset.원본 주석이 'reset.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* CNTHP_CTL_EL2:ENABLE: Set to zero to disable the EL2 physical timer원본 주석이 'CNTHP_CTL_EL2:ENABLE: Set to zero to disable the EL2 physical timer'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* and prevent timer interrupts.원본 주석이 'and prevent timer interrupts.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
write_cnthp_ctl_el2(CNTHP_CTL_RESET_VAL & ~(CNTHP_CTL_ENABLE_BIT));write_cnthp_ctl_el2(CNTHP_CTL_RESET_VAL & ~(CNTHP_CTL_ENABLE_BIT))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
(빈 줄)write_cnthp_ctl_el2(CNTHP_CTL_RESET_VAL & ~(CNTHP_CTL_ENABLE_BIT));까지의 동작과 manage_extensions_nonsecure_el2_unused();에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
manage_extensions_nonsecure_el2_unused();manage_extensions_nonsecure_el2_unused(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
#endif /* INIT_UNUSED_NS_EL2 */#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /*******************************************************************************에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*******************************************************************************원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Prepare the CPU system registers for first entry into realm, secure, or원본 주석이 'Prepare the CPU system registers for first entry into realm, secure, or'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* normal world.원본 주석이 'normal world.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* If execution is requested to EL2 or hyp mode, SCTLR_EL2 is initialized원본 주석이 'If execution is requested to EL2 or hyp mode, SCTLR_EL2 is initialized'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* If execution is requested to non-secure EL1 or svc mode, and the CPU supports원본 주석이 'If execution is requested to non-secure EL1 or svc mode, and the CPU supports'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* EL2 then EL2 is disabled by configuring all necessary EL2 registers.원본 주석이 'EL2 then EL2 is disabled by configuring all necessary EL2 registers.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* For all entries, the EL1 registers are initialized from the cpu_context원본 주석이 'For all entries, the EL1 registers are initialized from the cpu_context'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
******************************************************************************/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
void cm_prepare_el3_exit(size_t security_state)cm_prepare_el3_exit 함수 정의가 시작된다. 입력은 size_t security_state이며, EL3 exception context에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 cpu_context_t / per_cpu_context의 수명에 맞춰 묶어 읽는다.
u_register_t sctlr_el2, scr_el3;원본 1152번 줄의 u_register_t sctlr_el2, scr_el3;는 앞의 { 결과를 받아 다음 cpu_context_t *ctx = cm_get_context(security_state);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
cpu_context_t *ctx = cm_get_context(security_state);cpu_context_t *ctx에 cm_get_context(security_state)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)cpu_context_t *ctx = cm_get_context(security_state);까지의 동작과 assert(ctx != NULL);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
assert(ctx != NULL);assert(ctx !에 NULL)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)assert(ctx != NULL);까지의 동작과 if (security_state == NON_SECURE) {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
if (security_state == NON_SECURE) {security_state == NON_SECURE를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
uint64_t el2_implemented = el_implemented(2);uint64_t el2_implemented에 el_implemented(2)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)uint64_t el2_implemented = el_implemented(2);까지의 동작과 scr_el3 = read_ctx_reg(get_el3state_ctx(ctx),에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
scr_el3 = read_ctx_reg(get_el3state_ctx(ctx),get_el3state_ctx(ctx)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
CTX_SCR_EL3);원본 1161번 줄의 CTX_SCR_EL3);는 앞의 scr_el3 = read_ctx_reg(get_el3state_ctx(ctx), 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
(빈 줄)CTX_SCR_EL3);까지의 동작과 if (el2_implemented != EL_IMPL_NONE) {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
if (el2_implemented != EL_IMPL_NONE) {el2_implemented != EL_IMPL_NONE를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
(빈 줄)if (el2_implemented != EL_IMPL_NONE) {까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* If context is not being used for EL2, initialize원본 주석이 'If context is not being used for EL2, initialize'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* HCRX_EL2 with its init value here.원본 주석이 'HCRX_EL2 with its init value here.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
if (is_feat_hcx_supported()) {is_feat_hcx_supported()를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
write_hcrx_el2(HCRX_EL2_INIT_VAL);write_hcrx_el2(HCRX_EL2_INIT_VAL)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Initialize Fine-grained trap registers introduced원본 주석이 'Initialize Fine-grained trap registers introduced'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* by FEAT_FGT so all traps are initially disabled when원본 주석이 'by FEAT_FGT so all traps are initially disabled when'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* switching to EL2 or a lower EL, preventing undesired원본 주석이 'switching to EL2 or a lower EL, preventing undesired'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* behavior.원본 주석이 'behavior.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
if (is_feat_fgt_supported()) {is_feat_fgt_supported()를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Initialize HFG*_EL2 registers with a default원본 주석이 'Initialize HFG*_EL2 registers with a default'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* value so legacy systems unaware of FEAT_FGT원본 주석이 'value so legacy systems unaware of FEAT_FGT'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* do not get trapped due to their lack of원본 주석이 'do not get trapped due to their lack of'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* initialization for this feature.원본 주석이 'initialization for this feature.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
write_hfgitr_el2(HFGITR_EL2_INIT_VAL);write_hfgitr_el2(HFGITR_EL2_INIT_VAL)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
write_hfgrtr_el2(HFGRTR_EL2_INIT_VAL);write_hfgrtr_el2(HFGRTR_EL2_INIT_VAL)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
write_hfgwtr_el2(HFGWTR_EL2_INIT_VAL);write_hfgwtr_el2(HFGWTR_EL2_INIT_VAL)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /* Condition to ensure EL2 is being used. */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Condition to ensure EL2 is being used. */원본 주석이 'Condition to ensure EL2 is being used.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
if ((scr_el3 & SCR_HCE_BIT) != 0U) {(scr_el3 & SCR_HCE_BIT) != 0U를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
/* Initialize SCTLR_EL2 register with reset value. */원본 주석이 'Initialize SCTLR_EL2 register with reset value.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
sctlr_el2 = SCTLR_EL2_RES1;sctlr_el2에 SCTLR_EL2_RES1를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)sctlr_el2 = SCTLR_EL2_RES1;까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* If workaround of errata 764081 for Cortex-A75원본 주석이 'If workaround of errata 764081 for Cortex-A75'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* is used then set SCTLR_EL2.IESB to enable원본 주석이 'is used then set SCTLR_EL2.IESB to enable'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Implicit Error Synchronization Barrier.원본 주석이 'Implicit Error Synchronization Barrier.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
if (errata_a75_764081_applies()) {errata_a75_764081_applies()를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
sctlr_el2 |= SCTLR_IESB_BIT;sctlr_el2에 SCTLR_IESB_BIT를 |= 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 write_sctlr_el2(sctlr_el2);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
write_sctlr_el2(sctlr_el2);write_sctlr_el2(sctlr_el2)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
} else {원본 1206번 줄의 } else {는 앞의 write_sctlr_el2(sctlr_el2); 결과를 받아 다음 /*로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* (scr_el3 & SCR_HCE_BIT==0)원본 주석이 '(scr_el3 & SCR_HCE_BIT==0)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* EL2 implemented but unused.원본 주석이 'EL2 implemented but unused.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
init_nonsecure_el2_unused(ctx);init_nonsecure_el2_unused(ctx)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
RELATED SOURCE
함께 읽어야 하는 원본 코드
첫 코드 조각만으로 동작이 완성되지 않는 경우 호출 매크로, 객체 정의와 실제 실행 목록을 같은 페이지에 묶었다. 각 조각은 같은 기준 commit에서 가져왔다.
01. outgoing EL1 system register context 저장
lib/el3_runtime/aarch64/context_mgmt.c 1954-2034줄이다.
1954
1955 if (is_feat_ls64_accdata_supported()) {
1956 write_accdata_el1(read_el1_ctx_ls64(ctx, accdata_el1));
1957 }
1958}
1959
1960/*******************************************************************************
1961 * The next couple of functions are used by runtime services to save and restore
1962 * EL1 context on the 'cpu_context' structure for the specified security state.
1963 ******************************************************************************/
1964void cm_el1_sysregs_context_save(uint32_t security_state)
1965{
1966 cpu_context_t *ctx;
1967
1968 ctx = cm_get_context(security_state);
1969 assert(ctx != NULL);
1970
1971 el1_sysregs_context_save(get_el1_sysregs_ctx(ctx));
1972
1973#if IMAGE_BL31
1974 if (security_state == SECURE) {
1975 PUBLISH_EVENT(cm_exited_secure_world);
1976 } else {
1977 PUBLISH_EVENT(cm_exited_normal_world);
1978 }
1979#endif
1980}
1981
1982void cm_el1_sysregs_context_restore(uint32_t security_state)
1983{
1984 cpu_context_t *ctx;
1985
1986 ctx = cm_get_context(security_state);
1987 assert(ctx != NULL);
1988
1989 el1_sysregs_context_restore(get_el1_sysregs_ctx(ctx));
1990
1991#if IMAGE_BL31
1992 if (security_state == SECURE) {
1993 PUBLISH_EVENT(cm_entering_secure_world);
1994 } else {
1995 PUBLISH_EVENT(cm_entering_normal_world);
1996 }
1997#endif
1998}
1999
2000#endif /* ((IMAGE_BL1) || (IMAGE_BL31 && (!CTX_INCLUDE_EL2_REGS))) */
2001
2002/*******************************************************************************
2003 * This function populates ELR_EL3 member of 'cpu_context' pertaining to the
2004 * given security state with the given entrypoint
2005 ******************************************************************************/
2006void cm_set_elr_el3(uint32_t security_state, uintptr_t entrypoint)
2007{
2008 cpu_context_t *ctx;
2009 el3_state_t *state;
2010
2011 ctx = cm_get_context(security_state);
2012 assert(ctx != NULL);
2013
2014 /* Populate EL3 state so that ERET jumps to the correct entry */
2015 state = get_el3state_ctx(ctx);
2016 write_ctx_reg(state, CTX_ELR_EL3, entrypoint);
2017}
2018
2019/*******************************************************************************
2020 * This function populates ELR_EL3 and SPSR_EL3 members of 'cpu_context'
2021 * pertaining to the given security state
2022 ******************************************************************************/
2023void cm_set_elr_spsr_el3(uint32_t security_state,
2024 uintptr_t entrypoint, uint32_t spsr)
2025{
2026 cpu_context_t *ctx;
2027 el3_state_t *state;
2028
2029 ctx = cm_get_context(security_state);
2030 assert(ctx != NULL);
2031
2032 /* Populate EL3 state so that ERET jumps to the correct entry */
2033 state = get_el3state_ctx(ctx);
2034 write_ctx_reg(state, CTX_ELR_EL3, entrypoint);
1954-2034줄 해설
(빈 줄)이전 block 경계까지의 동작과 if (is_feat_ls64_accdata_supported()) {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 cpu_context_t 상태가 아래 블록의 입력으로 사용되는 경계다.
if (is_feat_ls64_accdata_supported()) {is_feat_ls64_accdata_supported()를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
write_accdata_el1(read_el1_ctx_ls64(ctx, accdata_el1));read_el1_ctx_ls64(ctx, accdata_el1))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 el1_sysregs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /*******************************************************************************에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*******************************************************************************원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* The next couple of functions are used by runtime services to save and restore원본 주석이 'The next couple of functions are used by runtime services to save and restore'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* EL1 context on the 'cpu_context' structure for the specified security state.원본 주석이 'EL1 context on the 'cpu_context' structure for the specified security state.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
******************************************************************************/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
void cm_el1_sysregs_context_save(uint32_t security_state)cm_el1_sysregs_context_save 함수 정의가 시작된다. 입력은 uint32_t security_state이며, EL3 exception context에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 cpu_context_t / per_cpu_context의 수명에 맞춰 묶어 읽는다.
cpu_context_t *ctx;원본 1966번 줄의 cpu_context_t *ctx;는 앞의 { 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
(빈 줄)cpu_context_t *ctx;까지의 동작과 ctx = cm_get_context(security_state);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
ctx = cm_get_context(security_state);ctx에 cm_get_context(security_state)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 assert(ctx != NULL);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
assert(ctx != NULL);assert(ctx !에 NULL)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)assert(ctx != NULL);까지의 동작과 el1_sysregs_context_save(get_el1_sysregs_ctx(ctx));에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
el1_sysregs_context_save(get_el1_sysregs_ctx(ctx));get_el1_sysregs_ctx(ctx))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
(빈 줄)el1_sysregs_context_save(get_el1_sysregs_ctx(ctx));까지의 동작과 #if IMAGE_BL31에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
#if IMAGE_BL31#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
if (security_state == SECURE) {security_state == SECURE를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
PUBLISH_EVENT(cm_exited_secure_world);PUBLISH_EVENT(cm_exited_secure_world)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
} else {원본 1976번 줄의 } else {는 앞의 PUBLISH_EVENT(cm_exited_secure_world); 결과를 받아 다음 PUBLISH_EVENT(cm_exited_normal_world);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
PUBLISH_EVENT(cm_exited_normal_world);PUBLISH_EVENT(cm_exited_normal_world)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 void cm_el1_sysregs_context_restore(uint32_t security_state)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
void cm_el1_sysregs_context_restore(uint32_t security_state)cm_el1_sysregs_context_restore 함수 정의가 시작된다. 입력은 uint32_t security_state이며, EL3 exception context에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 cpu_context_t / per_cpu_context의 수명에 맞춰 묶어 읽는다.
cpu_context_t *ctx;원본 1984번 줄의 cpu_context_t *ctx;는 앞의 { 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
(빈 줄)cpu_context_t *ctx;까지의 동작과 ctx = cm_get_context(security_state);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
ctx = cm_get_context(security_state);ctx에 cm_get_context(security_state)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 assert(ctx != NULL);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
assert(ctx != NULL);assert(ctx !에 NULL)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)assert(ctx != NULL);까지의 동작과 el1_sysregs_context_restore(get_el1_sysregs_ctx(ctx));에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
el1_sysregs_context_restore(get_el1_sysregs_ctx(ctx));get_el1_sysregs_ctx(ctx))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
(빈 줄)el1_sysregs_context_restore(get_el1_sysregs_ctx(ctx));까지의 동작과 #if IMAGE_BL31에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
#if IMAGE_BL31#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
if (security_state == SECURE) {security_state == SECURE를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
PUBLISH_EVENT(cm_entering_secure_world);PUBLISH_EVENT(cm_entering_secure_world)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
} else {원본 1994번 줄의 } else {는 앞의 PUBLISH_EVENT(cm_entering_secure_world); 결과를 받아 다음 PUBLISH_EVENT(cm_entering_normal_world);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
PUBLISH_EVENT(cm_entering_normal_world);PUBLISH_EVENT(cm_entering_normal_world)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 #endif /* ((IMAGE_BL1) || (IMAGE_BL31 && (!CTX_INCLUDE_EL2_REGS))) */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
#endif /* ((IMAGE_BL1) || (IMAGE_BL31 && (!CTX_INCLUDE_EL2_REGS))) */#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif /* ((IMAGE_BL1) || (IMAGE_BL31 && (!CTX_INCLUDE_EL2_REGS))) */까지의 동작과 /*******************************************************************************에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*******************************************************************************원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* This function populates ELR_EL3 member of 'cpu_context' pertaining to the원본 주석이 'This function populates ELR_EL3 member of 'cpu_context' pertaining to the'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* given security state with the given entrypoint원본 주석이 'given security state with the given entrypoint'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
******************************************************************************/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
void cm_set_elr_el3(uint32_t security_state, uintptr_t entrypoint)cm_set_elr_el3 함수 정의가 시작된다. 입력은 uint32_t security_state, uintptr_t entrypoint이며, EL3 exception context에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 cpu_context_t / per_cpu_context의 수명에 맞춰 묶어 읽는다.
cpu_context_t *ctx;원본 2008번 줄의 cpu_context_t *ctx;는 앞의 { 결과를 받아 다음 el3_state_t *state;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
el3_state_t *state;원본 2009번 줄의 el3_state_t *state;는 앞의 cpu_context_t *ctx; 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
(빈 줄)el3_state_t *state;까지의 동작과 ctx = cm_get_context(security_state);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
ctx = cm_get_context(security_state);ctx에 cm_get_context(security_state)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 assert(ctx != NULL);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
assert(ctx != NULL);assert(ctx !에 NULL)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)assert(ctx != NULL);까지의 동작과 /* Populate EL3 state so that ERET jumps to the correct entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Populate EL3 state so that ERET jumps to the correct entry */원본 주석이 'Populate EL3 state so that ERET jumps to the correct entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
state = get_el3state_ctx(ctx);state에 get_el3state_ctx(ctx)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 write_ctx_reg(state, CTX_ELR_EL3, entrypoint);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
write_ctx_reg(state, CTX_ELR_EL3, entrypoint);write_ctx_reg(state, CTX_ELR_EL3, entrypoint)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /*******************************************************************************에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*******************************************************************************원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* This function populates ELR_EL3 and SPSR_EL3 members of 'cpu_context'원본 주석이 'This function populates ELR_EL3 and SPSR_EL3 members of 'cpu_context''라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* pertaining to the given security state원본 주석이 'pertaining to the given security state'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
******************************************************************************/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
void cm_set_elr_spsr_el3(uint32_t security_state,cm_set_elr_spsr_el3(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
uintptr_t entrypoint, uint32_t spsr)원본 2024번 줄의 uintptr_t entrypoint, uint32_t spsr)는 앞의 void cm_set_elr_spsr_el3(uint32_t security_state, 결과를 받아 다음 {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 cpu_context_t / per_cpu_context의 수명에 맞춰 묶어 읽는다.
cpu_context_t *ctx;원본 2026번 줄의 cpu_context_t *ctx;는 앞의 { 결과를 받아 다음 el3_state_t *state;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
el3_state_t *state;원본 2027번 줄의 el3_state_t *state;는 앞의 cpu_context_t *ctx; 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
(빈 줄)el3_state_t *state;까지의 동작과 ctx = cm_get_context(security_state);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
ctx = cm_get_context(security_state);ctx에 cm_get_context(security_state)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 assert(ctx != NULL);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
assert(ctx != NULL);assert(ctx !에 NULL)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)assert(ctx != NULL);까지의 동작과 /* Populate EL3 state so that ERET jumps to the correct entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Populate EL3 state so that ERET jumps to the correct entry */원본 주석이 'Populate EL3 state so that ERET jumps to the correct entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
state = get_el3state_ctx(ctx);state에 get_el3state_ctx(ctx)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 write_ctx_reg(state, CTX_ELR_EL3, entrypoint);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
write_ctx_reg(state, CTX_ELR_EL3, entrypoint);write_ctx_reg(state, CTX_ELR_EL3, entrypoint)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
02. target world의 EL1 system register context 복원
lib/el3_runtime/aarch64/context_mgmt.c 1972-2040줄이다.
1972
1973#if IMAGE_BL31
1974 if (security_state == SECURE) {
1975 PUBLISH_EVENT(cm_exited_secure_world);
1976 } else {
1977 PUBLISH_EVENT(cm_exited_normal_world);
1978 }
1979#endif
1980}
1981
1982void cm_el1_sysregs_context_restore(uint32_t security_state)
1983{
1984 cpu_context_t *ctx;
1985
1986 ctx = cm_get_context(security_state);
1987 assert(ctx != NULL);
1988
1989 el1_sysregs_context_restore(get_el1_sysregs_ctx(ctx));
1990
1991#if IMAGE_BL31
1992 if (security_state == SECURE) {
1993 PUBLISH_EVENT(cm_entering_secure_world);
1994 } else {
1995 PUBLISH_EVENT(cm_entering_normal_world);
1996 }
1997#endif
1998}
1999
2000#endif /* ((IMAGE_BL1) || (IMAGE_BL31 && (!CTX_INCLUDE_EL2_REGS))) */
2001
2002/*******************************************************************************
2003 * This function populates ELR_EL3 member of 'cpu_context' pertaining to the
2004 * given security state with the given entrypoint
2005 ******************************************************************************/
2006void cm_set_elr_el3(uint32_t security_state, uintptr_t entrypoint)
2007{
2008 cpu_context_t *ctx;
2009 el3_state_t *state;
2010
2011 ctx = cm_get_context(security_state);
2012 assert(ctx != NULL);
2013
2014 /* Populate EL3 state so that ERET jumps to the correct entry */
2015 state = get_el3state_ctx(ctx);
2016 write_ctx_reg(state, CTX_ELR_EL3, entrypoint);
2017}
2018
2019/*******************************************************************************
2020 * This function populates ELR_EL3 and SPSR_EL3 members of 'cpu_context'
2021 * pertaining to the given security state
2022 ******************************************************************************/
2023void cm_set_elr_spsr_el3(uint32_t security_state,
2024 uintptr_t entrypoint, uint32_t spsr)
2025{
2026 cpu_context_t *ctx;
2027 el3_state_t *state;
2028
2029 ctx = cm_get_context(security_state);
2030 assert(ctx != NULL);
2031
2032 /* Populate EL3 state so that ERET jumps to the correct entry */
2033 state = get_el3state_ctx(ctx);
2034 write_ctx_reg(state, CTX_ELR_EL3, entrypoint);
2035 write_ctx_reg(state, CTX_SPSR_EL3, spsr);
2036}
2037
2038/*******************************************************************************
2039 * This function updates a single bit in the SCR_EL3 member of the 'cpu_context'
2040 * pertaining to the given security state using the value and bit position
1972-2040줄 해설
(빈 줄)이전 block 경계까지의 동작과 #if IMAGE_BL31에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 cpu_context_t 상태가 아래 블록의 입력으로 사용되는 경계다.
#if IMAGE_BL31#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
if (security_state == SECURE) {security_state == SECURE를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
PUBLISH_EVENT(cm_exited_secure_world);PUBLISH_EVENT(cm_exited_secure_world)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
} else {원본 1976번 줄의 } else {는 앞의 PUBLISH_EVENT(cm_exited_secure_world); 결과를 받아 다음 PUBLISH_EVENT(cm_exited_normal_world);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
PUBLISH_EVENT(cm_exited_normal_world);PUBLISH_EVENT(cm_exited_normal_world)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 void cm_el1_sysregs_context_restore(uint32_t security_state)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
void cm_el1_sysregs_context_restore(uint32_t security_state)cm_el1_sysregs_context_restore 함수 정의가 시작된다. 입력은 uint32_t security_state이며, EL3 exception context에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 cpu_context_t / per_cpu_context의 수명에 맞춰 묶어 읽는다.
cpu_context_t *ctx;원본 1984번 줄의 cpu_context_t *ctx;는 앞의 { 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
(빈 줄)cpu_context_t *ctx;까지의 동작과 ctx = cm_get_context(security_state);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
ctx = cm_get_context(security_state);ctx에 cm_get_context(security_state)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 assert(ctx != NULL);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
assert(ctx != NULL);assert(ctx !에 NULL)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)assert(ctx != NULL);까지의 동작과 el1_sysregs_context_restore(get_el1_sysregs_ctx(ctx));에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
el1_sysregs_context_restore(get_el1_sysregs_ctx(ctx));get_el1_sysregs_ctx(ctx))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
(빈 줄)el1_sysregs_context_restore(get_el1_sysregs_ctx(ctx));까지의 동작과 #if IMAGE_BL31에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
#if IMAGE_BL31#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
if (security_state == SECURE) {security_state == SECURE를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
PUBLISH_EVENT(cm_entering_secure_world);PUBLISH_EVENT(cm_entering_secure_world)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
} else {원본 1994번 줄의 } else {는 앞의 PUBLISH_EVENT(cm_entering_secure_world); 결과를 받아 다음 PUBLISH_EVENT(cm_entering_normal_world);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
PUBLISH_EVENT(cm_entering_normal_world);PUBLISH_EVENT(cm_entering_normal_world)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 #endif /* ((IMAGE_BL1) || (IMAGE_BL31 && (!CTX_INCLUDE_EL2_REGS))) */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
#endif /* ((IMAGE_BL1) || (IMAGE_BL31 && (!CTX_INCLUDE_EL2_REGS))) */#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif /* ((IMAGE_BL1) || (IMAGE_BL31 && (!CTX_INCLUDE_EL2_REGS))) */까지의 동작과 /*******************************************************************************에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*******************************************************************************원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* This function populates ELR_EL3 member of 'cpu_context' pertaining to the원본 주석이 'This function populates ELR_EL3 member of 'cpu_context' pertaining to the'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* given security state with the given entrypoint원본 주석이 'given security state with the given entrypoint'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
******************************************************************************/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
void cm_set_elr_el3(uint32_t security_state, uintptr_t entrypoint)cm_set_elr_el3 함수 정의가 시작된다. 입력은 uint32_t security_state, uintptr_t entrypoint이며, EL3 exception context에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 cpu_context_t / per_cpu_context의 수명에 맞춰 묶어 읽는다.
cpu_context_t *ctx;원본 2008번 줄의 cpu_context_t *ctx;는 앞의 { 결과를 받아 다음 el3_state_t *state;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
el3_state_t *state;원본 2009번 줄의 el3_state_t *state;는 앞의 cpu_context_t *ctx; 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
(빈 줄)el3_state_t *state;까지의 동작과 ctx = cm_get_context(security_state);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
ctx = cm_get_context(security_state);ctx에 cm_get_context(security_state)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 assert(ctx != NULL);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
assert(ctx != NULL);assert(ctx !에 NULL)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)assert(ctx != NULL);까지의 동작과 /* Populate EL3 state so that ERET jumps to the correct entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Populate EL3 state so that ERET jumps to the correct entry */원본 주석이 'Populate EL3 state so that ERET jumps to the correct entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
state = get_el3state_ctx(ctx);state에 get_el3state_ctx(ctx)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 write_ctx_reg(state, CTX_ELR_EL3, entrypoint);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
write_ctx_reg(state, CTX_ELR_EL3, entrypoint);write_ctx_reg(state, CTX_ELR_EL3, entrypoint)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /*******************************************************************************에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*******************************************************************************원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* This function populates ELR_EL3 and SPSR_EL3 members of 'cpu_context'원본 주석이 'This function populates ELR_EL3 and SPSR_EL3 members of 'cpu_context''라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* pertaining to the given security state원본 주석이 'pertaining to the given security state'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
******************************************************************************/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
void cm_set_elr_spsr_el3(uint32_t security_state,cm_set_elr_spsr_el3(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
uintptr_t entrypoint, uint32_t spsr)원본 2024번 줄의 uintptr_t entrypoint, uint32_t spsr)는 앞의 void cm_set_elr_spsr_el3(uint32_t security_state, 결과를 받아 다음 {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 cpu_context_t / per_cpu_context의 수명에 맞춰 묶어 읽는다.
cpu_context_t *ctx;원본 2026번 줄의 cpu_context_t *ctx;는 앞의 { 결과를 받아 다음 el3_state_t *state;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
el3_state_t *state;원본 2027번 줄의 el3_state_t *state;는 앞의 cpu_context_t *ctx; 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다' 조건이 유지되는지 확인한다.
(빈 줄)el3_state_t *state;까지의 동작과 ctx = cm_get_context(security_state);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
ctx = cm_get_context(security_state);ctx에 cm_get_context(security_state)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 assert(ctx != NULL);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
assert(ctx != NULL);assert(ctx !에 NULL)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 ERET 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)assert(ctx != NULL);까지의 동작과 /* Populate EL3 state so that ERET jumps to the correct entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Populate EL3 state so that ERET jumps to the correct entry */원본 주석이 'Populate EL3 state so that ERET jumps to the correct entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
state = get_el3state_ctx(ctx);state에 get_el3state_ctx(ctx)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 write_ctx_reg(state, CTX_ELR_EL3, entrypoint);가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
write_ctx_reg(state, CTX_ELR_EL3, entrypoint);write_ctx_reg(state, CTX_ELR_EL3, entrypoint)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
write_ctx_reg(state, CTX_SPSR_EL3, spsr);write_ctx_reg(state, CTX_SPSR_EL3, spsr)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 cpu_context_t / per_cpu_context의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 gp_regs_t 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /*******************************************************************************에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 gp_regs_t 상태가 아래 블록의 입력으로 사용되는 경계다.
/*******************************************************************************원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* This function updates a single bit in the SCR_EL3 member of the 'cpu_context'원본 주석이 'This function updates a single bit in the SCR_EL3 member of the 'cpu_context''라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* pertaining to the given security state using the value and bit position원본 주석이 'pertaining to the given security state using the value and bit position'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
DETAILS
내부 동작을 더 깊게 읽기
진입 조건을 먼저 고정한다
SMC/interrupt entry에서 들어온 실행은 EL3 exception context에 놓여 있다. 이때 interrupt, MMU/cache, stack, heap 중 무엇이 이미 준비되었는지 소스의 호출자까지 올라가 확인한다. 같은 함수라도 SPL, relocation 전후, app thread처럼 호출 문맥이 달라지면 허용되는 API와 지연 시간이 달라진다.
world switch는 PC만 바꾸는 branch가 아니다. security state별 SCR_EL3, translation regime, timer, FP/SVE state의 owner를 정확히 나눠야 한다.
중심 객체의 생성과 공개를 나눈다
이 글의 중심 객체는 cpu_context_t / per_cpu_context다. 메모리를 확보한 시점, 필드를 채운 시점, 전역 list나 다른 subsystem에 공개한 시점을 구분한다. 공개 뒤 오류가 발생한다면 목록에서 제거하고 child, buffer, reference를 역순으로 정리하는지 확인한다.
빌드 산출물 관점에서는 최종 부트로더 이미지 안에 해당 symbol과 section이 실제로 포함되었는지도 map과 objdump로 검증한다.
주소, 크기와 정렬을 계산한다
부트 코드의 오류는 논리보다 주소 계산에서 먼저 드러나는 경우가 많다. source range, destination range, header가 말하는 payload size, block 또는 page 단위를 표로 적고 각 구간의 끝 주소를 직접 계산한다. 끝 주소는 start + size - 1인지 exclusive end인지 API 계약을 확인한다.
ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다.
성공 flag와 실제 완료 시점을 맞춘다
save outgoing context → select security state → restore target sysregs 구간에서는 부분 초기화 상태가 생길 수 있다. flag, list insertion, callback 등록, storage write 완료 중 무엇이 성공의 기준인지 찾는다. hardware write나 DMA가 포함되면 함수 반환과 장치 완료가 같은 시점인지도 확인한다.
다른 CPU, interrupt handler, USB completion 또는 shell command가 상태를 관찰할 수 있다면 memory ordering과 lock 범위도 함께 읽는다.
마지막 handoff의 계약을 적는다
정상 경로는 ERET에서 끝난다. 이 단계가 함수 반환인지, scheduler 전환인지, 다른 image로의 비복귀 분기인지 구분한다. 비복귀 handoff라면 cache clean/invalidate, interrupt disable, 장치 quiesce, argument register와 FDT 또는 image address가 최종 점검 항목이다.
반환하는 경로라면 caller가 오류와 부분 성공을 구분하고 다음 후보 또는 복구 경로를 선택하는지 확인한다.
IMPLEMENTATION NOTES
구현을 읽을 때 놓치기 쉬운 부분
SMC/interrupt entry에서 ERET까지 제어권이 이동하는 조건
SCR_EL3, SPSR, EL1/EL2 system register와 general register context를 저장·복원해 world를 전환하는 경로를 읽습니다. 이 경로는 함수 호출 목록만 외워서는 연결되지 않는다. SMC/interrupt entry → save outgoing context → select security state → restore target sysregs → ERET 순서에서 각 단계가 읽는 입력, 새로 확정하는 상태, 다음 단계에 넘기는 값을 구분해야 한다. 특히 EL3 exception context에서는 이전 단계가 남긴 register와 memory attribute가 C 코드의 전제 조건이 된다.
world switch는 PC만 바꾸는 branch가 아니다. security state별 SCR_EL3, translation regime, timer, FP/SVE state의 owner를 정확히 나눠야 한다. 따라서 첫 지점에서 cpu_context_t / per_cpu_context의 주소와 owner를 기록하고, 마지막 지점에서 같은 값이 그대로 유지되는지 아니면 새 객체로 교체되는지를 확인한다. 중간 함수가 성공을 반환해도 ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다.
cpu_context_t와 gp_regs_t의 생성 시점과 수명
이 글에서 함께 나타나는 객체는 cpu_context_t, el3_state_t, el1_sysregs_t, gp_regs_t이다. 이름이 비슷해도 저장 위치와 수명은 다르다. build-time descriptor인지, boot 단계의 임시 객체인지, world switch 뒤에도 남는 runtime 객체인지 나눠야 pointer를 따라가다 다른 단계의 구조체를 같은 것으로 오해하지 않는다.
cpu_context_t / per_cpu_context을 기준으로 allocation 또는 정적 배치 위치, list/table에 공개되는 시점, 참조가 끊기는 시점을 적는다. 그 다음 source와 destination 범위, per-CPU 여부, secure/non-secure 접근 권한을 map과 runtime log로 대조한다. 이 절차를 거치면 단순한 호출 순서가 아니라 실제 소유권 이동이 보인다.
성공 로그만으로 놓치기 쉬운 실패 경계
대표적인 실패 조건은 CPU index 혼동: 다른 core context 복원; SCR.NS 오류: 잘못된 world 진입; sysreg 누락: secure/non-secure state leakage이다. 이 문제들은 대개 fault가 발생한 함수보다 앞에서 만들어진 잘못된 주소, size, security state 또는 refcount 때문에 생긴다. 마지막 출력만 보지 말고 각 경계 직전의 상태를 한 줄씩 남겨 최초 불일치 지점을 찾는다.
재현에는 SMC 전후 SCR/SPSR/TTBR 기록; 두 CPU 동시 SMC로 per-CPU 분리 확인; 의도적으로 context field를 바꿔 fault 위치 확인를 사용한다. 정상 경로와 실패 경로에서 같은 필드를 같은 위치에 출력하고, 실패가 검증 단계에서 차단되는지 아니면 다음 context까지 전파되는지 비교한다. firmware와 secure world에서는 실패 뒤의 cleanup 또는 reset 경로도 정상 경로만큼 중요하다.
DEEP ANALYSIS
레지스터에서 오류 판정까지 상세 분석
EL3 world switch는 general register를 저장하고 SCR_EL3.NS만 바꾸는 작업이 아니다. target world가 사용할 EL1/EL2 translation, timer, exception mask, stack, vector와 optional architectural state를 CPU별 context에서 복원해야 한다.
특히 secure와 non-secure context는 같은 cpu_context_t 형식을 사용해도 서로 다른 slot에 놓인다. 현재 CPU index와 security_state key가 맞지 않으면 값 자체는 그럴듯해 보여도 다른 core 또는 다른 world의 TTBR과 SP를 복원하게 된다.
현재 exception frame의 gp_regs와 EL3 state를 가리켜야 한다.
SECURE/NON_SECURE/REALM 범위를 명확히 구분한다.
NS, RW, HCE, IRQ/FIQ/EA routing이 target과 일치해야 한다.
target EL, mask, execution width와 PC alignment가 유효해야 한다.
TTBR/TCR/MAIR/SCTLR/VBAR/SP가 같은 address-space 묶음이어야 한다.
각 계약은 앞 단계가 생산하고 현재 단계가 검증한 뒤 다음 소비자에게 넘기는 상태다. 한 항목이라도 확인되지 않으면 뒤 단계의 fault를 그 지점의 문제로 단정하지 않는다.
진입 레지스터와 메모리 계약
함수 첫 줄에 도달했을 때 이미 참이라고 가정하는 값과, 그 값이 틀렸을 때 영향을 받는 범위를 함께 적었다.
| # | 입력 상태 | 생산자 | 정상 조건 | 확인 이유 |
|---|---|---|---|---|
| 01 | context handle | EL3 vector/SMC framework | 현재 exception frame의 gp_regs와 EL3 state를 가리켜야 한다. | handler 인자와 return register를 읽고 쓴다. |
| 02 | security_state | SMC caller 또는 next image | SECURE/NON_SECURE/REALM 범위를 명확히 구분한다. | per-CPU context slot 선택 key다. |
| 03 | SCR_EL3 | saved el3_state | NS, RW, HCE, IRQ/FIQ/EA routing이 target과 일치해야 한다. | ERET 뒤 execution world와 exception route를 결정한다. |
| 04 | SPSR_EL3/ELR_EL3 | entry point 또는 exception frame | target EL, mask, execution width와 PC alignment가 유효해야 한다. | ERET가 실제로 소비한다. |
| 05 | EL1/EL2 sysregs | outgoing save/target restore | TTBR/TCR/MAIR/SCTLR/VBAR/SP가 같은 address-space 묶음이어야 한다. | 부분 복원으로 인한 translation·stack fault를 막는다. |
핵심 구조체 필드의 생산자, 소비자와 수명
구조체 이름만 나열하지 않고 어떤 코드가 값을 쓰고, 어느 코드가 처음 읽으며, 언제까지 주소와 내용이 유지되어야 하는지 구분했다.
| # | 객체 또는 필드 | 생산자 | 소비자 | 수명과 불변 조건 |
|---|---|---|---|---|
| 01 | cpu_context_t | context setup | save/restore와 SPD | CPU별 security state slot에 resident하며 world 수명 동안 유지된다. |
| 02 | gp_regs_t | exception entry assembly | SMC handler와 exit assembly | 한 EL3 exception 동안 x0-x30 값을 소유하며 return 값이 같은 frame에 기록된다. |
| 03 | el3_state_t | entry/ep_info | cm_prepare_el3_exit | SCR/SPSR/ELR 같은 EL3-owned return state를 담는다. |
| 04 | el1_sysregs_t | cm_el1_sysregs_context_save | restore path | world별 translation, exception, thread pointer 상태를 유지한다. |
| 05 | per-world context pointer | cm_set_context | cm_get_context | 현재 CPU의 slot만 가리키며 hotplug 초기화 전에는 사용하면 안 된다. |
| 06 | optional FP/SVE/PAUTH state | lazy/eager context policy | world switch extension | feature가 켜진 경우 owner 전환과 sanitize 규칙이 필요하다. |
함수 내부 실행 순서
소스의 큰 분기와 side effect를 실행 순서대로 다시 펼쳤다. breakpoint는 이 목록의 경계에 두고, 다음 번호로 넘어갈 때 새로 유효해진 객체를 기록한다.
- 01
EL3 vector가 outgoing world의 general register와 exception return state를 frame에 저장한다.
- 02
caller security state와 현재 CPU index를 이용해 outgoing cpu_context_t를 선택한다.
- 03
필요한 EL1/EL2 system register와 timer, thread pointer, fault state를 outgoing slot에 저장한다.
- 04
SMC/interrupt handler가 x0-x4와 context handle을 사용해 service 동작과 target world를 결정한다.
- 05
target security state의 per-CPU context pointer를 얻고 초기화 여부와 entry state를 확인한다.
- 06
target EL1/EL2 sysreg 묶음을 restore하고 translation register 사이에 필요한 barrier를 수행한다.
- 07
SCR_EL3, SPSR_EL3, ELR_EL3를 target state로 준비하고 return general register를 frame에 반영한다.
- 08
optional architecture state와 routing bit를 전환한 뒤 active context pointer를 갱신한다.
- 09
exit assembly가 frame을 복원하고 ISB 뒤 ERET하여 target PC와 stack에서 실행을 재개한다.
빌드 설정이 바꾸는 실제 코드 경로
동일한 함수 이름이라도 아래 설정에 따라 포함되는 source, 구조체 크기, 인자 의미와 failure path가 달라진다.
| # | 설정 | 바뀌는 동작 | 확인 방법 |
|---|---|---|---|
| 01 | CTX_INCLUDE_AARCH32_REGS | AArch32 EL1 banked register 저장 범위가 추가된다. | target execution width와 save area 크기를 확인한다. |
| 02 | CTX_INCLUDE_FPREGS/SVE | FP/SIMD 또는 SVE state를 context에 포함한다. | lazy owner 정책과 secure data leakage 방지를 확인한다. |
| 03 | ENABLE_PAUTH | API key register save/restore가 필요하다. | world별 key가 초기화되고 다른 world에 남지 않는지 본다. |
| 04 | EL2 context 관련 option | Normal World가 EL2를 사용할 때 EL2 sysreg 묶음이 추가된다. | BL33 target EL과 hypervisor 존재를 맞춘다. |
증상에서 최초 불일치 지점까지 추적하기
마지막 panic 메시지가 아니라 어디에서 멈추고 무엇을 읽어 어떤 결론을 내릴지 정리했다. 정상값과 실패값은 같은 build와 같은 위치에서 비교한다.
| # | 관찰 증상 | 중단 위치 | 기록할 값 | 판정 |
|---|---|---|---|---|
| 01 | ERET 직후 translation fault | cm_prepare_el3_exit와 target first instruction | TTBR, TCR, MAIR, SCTLR, ELR | target address-space register가 한 묶음으로 복원됐는지 확인한다. |
| 02 | 두 CPU에서만 간헐적 context 오염 | cm_get_context | MPIDR→linear index, context pointer | per-CPU index 계산 또는 초기화 race를 찾는다. |
| 03 | secure interrupt가 Normal World handler로 감 | SCR_EL3 routing과 GIC acknowledge | SCR IRQ/FIQ, interrupt group, active context | context 복원과 GIC routing 중 어느 쪽이 틀렸는지 분리한다. |
| 04 | SMC 반환값은 맞지만 caller stack 손상 | exit assembly frame restore | SP_EL0/SP_EL1, gp_regs offsets, frame size | 구조체 offset과 assembly macro 불일치를 검사한다. |
OBJECT LIFETIME
객체와 수명
| 대상 | 만들어지는 시점 | 유효 범위 | 확인할 조건 |
|---|---|---|---|
cpu_context_t / per_cpu_context | save outgoing context | ERET 또는 오류 정리 완료까지 | ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다. |
| 입력 buffer / metadata | SMC/interrupt entry | parse와 검증이 끝날 때까지 | 길이, 정렬, 소유권, 변조 가능성 |
| 등록된 list / descriptor | select security state | subsystem 종료 또는 image handoff까지 | 이중 등록, dangling pointer, 오류 unwind |
| hardware 또는 persistent state | 실제 write/probe가 완료된 뒤 | reset 또는 명시적 해제·갱신까지 | flush, timeout, 전원 차단, rollback |
최종 부트로더 이미지 | link/image 생성 시점 | 다음 stage가 새 image로 교체할 때까지 | load address, entry, section과 header 일치 |
FAILURE PATH
실패 지점과 증상
| # | 조건 | 관찰되는 증상 | 먼저 볼 단계 |
|---|---|---|---|
| 01 | CPU index 혼동 | 다른 core context 복원 | save outgoing context |
| 02 | SCR.NS 오류 | 잘못된 world 진입 | select security state |
| 03 | sysreg 누락 | secure/non-secure state leakage | restore target sysregs |
로그가 끊긴 마지막 함수만 고치지 않는다. 그 함수가 받은 주소, size, flag가 만들어진 앞 단계까지 올라가고, 오류 뒤 등록 객체와 hardware 상태가 남았는지도 확인한다.
EVIDENCE
소스 밖에서 확인할 증거
소스 해석은 실제 빌드 산출물과 target 로그로 닫아야 한다. 아래 명령의 보드 이름과 toolchain prefix는 사용 중인 빌드 환경에 맞게 바꾼다.
| # | 목적 | 명령 또는 계측 | 판정 기준 |
|---|---|---|---|
| 01 | 빌드 산출물 | make PLAT=<platform> DEBUG=1 all fip | BL1, BL2, BL31과 FIP가 같은 설정으로 만들어졌는지 전체 빌드 명령부터 기록한다. |
| 02 | FIP 구성 | tools/fiptool/fiptool info build/<platform>/debug/fip.bin | FIP 안의 BL31, BL32, BL33 UUID와 offset, 크기를 확인해 실제 적재 입력을 고정한다. |
| 03 | 심볼과 주소 | ${CROSS_COMPILE}nm -n build/<platform>/debug/bl31/bl31.elf | grep 'void cm_prepare_el3_exit' | 대상 함수가 BL31의 어느 주소와 섹션에 놓였는지 확인한다. |
| 04 | EL3 명령 추적 | ${CROSS_COMPILE}objdump -drS build/<platform>/debug/bl31/bl31.elf | C 코드가 EL3 system register 접근과 eret 경로로 어떻게 번역됐는지 대조한다. |
| 05 | 실행 시점 증거 | TF-A DEBUG log + CurrentEL/SCR_EL3/SPSR_EL3/x0-x3 기록 | 어느 exception level에서 어떤 security state와 인자를 다음 이미지에 넘겼는지 serial log로 남긴다. |
LAB
직접 확인할 실험
- 01SMC 전후 SCR/SPSR/TTBR 기록
save outgoing context진입 전후에 cpu_context_t의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다. 조건이 처음 깨지는 줄을 기록한다. - 02두 CPU 동시 SMC로 per-CPU 분리 확인
select security state진입 전후에 el3_state_t의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다. 조건이 처음 깨지는 줄을 기록한다. - 03의도적으로 context field를 바꿔 fault 위치 확인
restore target sysregs진입 전후에 el1_sysregs_t의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 ERET 직전 active context는 현재 CPU와 target security state에 대응하고 target EL의 system register가 모두 복원되어야 한다. 조건이 처음 깨지는 줄을 기록한다.
PRIMARY REFERENCES