https://developer.arm.com/documentation/198123/latest/
책은 ARMv7과 ARMv8 양쪽 모두를 다루고 있습니다.
이미 이전 포스트들에서 ARMv7에 대해서 충분히 공부했기 때문에, 이번 포스트에서는 ARMv8에 집중해서 다룹니다.
ARMv7 관련 내용 중 일부는 해당 내용을 자세히 다룬 포스트를 주석으로 링크 달고 생략하도록 하겠습니다
7. GIC - 인터럽트
7.1. 인터럽트 컨트롤러?
외부 I/O 디바이스 (이하 페리퍼럴)는 프로세서와 인터럽트를 통해 통신합니다.
SoC 업체는 고객들이 다양한 페리퍼럴과 연결해 사용할 수 있도록 인터럽트 컨트롤러를 설계해 탑재합니다.
인터럽트 컨트롤러는 수많은 인터럽트를 통합하고 관리하고 분배하는 역할을 합니다.
- 각 인터럽트의 활성/비활성
- 각 인터럽트의 우선순위 설정 및 우선순위 그룹 지정
- 각 인터럽트의 논-시큐어 또는 시큐어 지정
- 특정 인터럽트를 특정 CPU 코어에 종속 (affinity)
- 각 인터럽트의 트리거 조건 지정 (level-sensitive or edge-triggered)
7.2. 인터럽트 종류
인터럽트 소스 | 인터럽트 번호 | 특징 |
SPI (Shared Peripheral Interrupt) |
32~1019 4096~5119 |
일반적인 페리퍼럴 인터럽트 |
PPI (Private Peripheral Interrupt) |
16~31 1056~1119 |
특정 코어에 종속된 페리퍼럴 인터럽트 |
SGI (SW Generated Interrupt) |
0~15 | 프로세서 코어 간 통신(IPI)에 사용되는 인터럽트 SGI 레지스터에 값을 써서 (SW적으로) 발생하는 인터럽트 |
LPI (Locality-specific PI) |
8192 이상 | GICv3부터 도입된 인터럽트, 위 3개와는 완전히 다름 |
※ 특정 코어에 특정 인터럽트를 종속시키는 affinity, PPI의 장점은 무엇일까요?
- 해당 인터럽트는 무조건 해당 코어가 처리합니다.
- 그리고, 각 코어는 내부에 캐시를 갖고있습니다.
- 따라서, 해당 인터럽트를 처리할 때 그 캐시를 충분히 이용할 수 있기 때문에 미미하지만 성능면에서 좋습니다.
※ SGI는 무슨 용도일까요?
- SW적으로 트리거되는 인터럽트지만, SGI는 svc 같은 그런 SW triggered 인터럽트가 아닌 엄연히 HW 인터럽트입니다.
- 일반적으로 IPI 용도(IPI란, 이쪽 프로세서 코어에서 저쪽 코어로 요청/명령을 보내 서로 소통하는 것을 의미로 사용합니다.
7.3. 인터럽트 상태
인터럽트는 진행에 따라 위와같은 4가지 상태 중 하나로 나뉩니다.
상태변화는 { ① 이벤트 발생 → ② 특정 레지스터를 읽거나 값을 씀 → ③ 상태가 변경됨 } 이런 흐름으로 이뤄집니다.
사용되는 레지스터는 ICC_IARn_EL1 레지스터와 ICC_EOIAR_EL1 레지스터입니다.
- ICC_IAR0_EL1: 인터럽트 ACK 레지스터입니다. 이 레지스터를 읽기만 해도 인터럽트 컨트롤러에게 인터럽트 잘 전달받았다는 신호를 보냅니다.
- ICC_EOIR0_EL1: 인터럽트 종료 (End Of Interrupt) 레지스터입니다. 활성화된 인터럽트의 인터럽트 번호를 쓰면 CPU 인터페이스에게 해당 인터럽트가 종료됐다는 신호를 보내고 인터럽트 실행이 종료됩니다.
- IAR과 EOIR 뒤에 붙은 숫자는 0과 1이 오는데, 인터럽트 그룹을 의미합니다. 인터럽트 그룹은 아래 7.4절에서 다룹니다.
Level-Sensitive 인터럽트 상태 변화
- Inactive: 인터럽트가 발생(Assert)하지 않은 상태를 의미합니다.
- Pending: 인터럽트가 발생하면 인터럽트 컨트롤러는 ARM 코어한테 인터럽트가 발생했다고 알립니다. 별다른 이상 (기준 우선순위보다 낮다던지)이 없다면 인터럽트 활성화 및 Pending 상태로 변경됩니다.
- Active and Pending
- 인터럽트를 PE가 받아 'ICC_IAR_EL1' 레지스터를 읽습니다. ARM 코어가 인터럽트 컨트롤러에게 ACK를 보냅니다.
- ACK를 받은 인터럽트 컨트롤러는 다음 인터럽트를 받기 위해 해당 인터럽트를 de-assert합니다.
- Active
- Inactive: 'ICC_IAR_EL1' 레지스터를 읽어서 얻은 인터럽트 번호를 'ICC_EOIR_EL1' 레지스터에 써 인터럽트가 종료됩니다.
Edge-triggered 인터럽트 상태 변화
위의 level-sensitive 방식과 큰 내용은 비슷하지만, 세부적으로는 약간 다릅니다.
- Inactive
- Pending
- Active
- 이번에는 active가 먼저 나옵니다. 내용은 똑같습니다.
- 'ICC_IAR_EL1' 레지스터 읽으면 Pending에서 Active 상태가 됩니다. ARM 코어는 인터럽트 컨트롤러에게 ACK를 보냅니다.
- GIC는 해당 인터럽트를 de-assert 합니다.
- Active and Pending: level-sensitive 방식과 가장 다른 점인데, 페리퍼럴이 다시 인터럽트 시그널을 assert 합니다.
- Pending: ICC_EOIR_EL1 레지스터에 인터럽트 번호를 써서 실행을 종료합니다. Pending 상태가 됩니다.
7.4. 인터럽트 그룹
트러스트존 덕분에 논-시큐어존과 시큐어존으로 실행영역이 분리돼있으며
ARMv8의 FIQ는 시큐어 인터럽트를 의미한다는것 또한 배웠습니다.
GIC는 각 인터럽트를 IRQ(논-시큐어 인터럽트)로 등록할지, FIQ(시큐어 인터럽트)로 등록할지 설정합니다.
이렇게 인터럽트를 논-시큐어/시큐어로, 그리고 익셉션 레벨별로 나눈것을 인터럽트 그룹이라고 합니다.
익셉션 레벨 (EL) | Group 0 | Group 1 | |
시큐어 | 논-시큐어 | ||
시큐어 EL0 / EL1 | FIQ | IRQ | FIQ |
논-시큐어 EL0 / EL1 / EL2 | FIQ | FIQ | IRQ |
EL3 | FIQ | FIQ | FIQ |
SGI / SPI / PPI 모두 그룹핑을 통해 IRQ 또는 FIQ로 나눌 수 있습니다.
표는 복잡하지만, 일반적으로 러프하게 Group 0 = FIQ, Group 1 = IRQ라고 생각하면 됩니다.
현재 프로세서의 시큐어 상태 여부는 SCR_EL3 레지스터의 NS bit에서 확인할 수 있습니다. (0이면 시큐어, 1이면 논-시큐어)
7.5. GIC 개요
GIC(Generic Interrupt Controller)는 ARM에서 제공하는 인터럽트 컨트롤러 IP입니다.
SoC 업체는 이 GIC를 칩에 탑재하거나 또는 GIC를 활용해 자신들만의 인터럽트 컨트롤러를 만들 수도 있습니다.
GIC는 4가지 버전이 있는데, ARMv7은 GICv1, 2를, ARMv8부터는 GICv3, 4를 사용합니다.
GIC ver | Key Feature | 사용 프로세서 |
GICv1 | 최대 8개의 PE(Processing Element)를 지원 1,020개의 인터럽트 ID 지원 2가지 Security State 지원 |
Cortex-A5, A9 |
GICv2 | GICv1 + 가상화 지원 | Cortex-A7, A15, A17 |
GICv3 | GICv2 호환 8개 이상의 PE를 지원 메시지 시그널 인터럽트 지원 시큐어, 논시큐어 Group 1 인터럽트 지원 'CPU 인터페이스'에 접근하는 시스템 레지스터 추가 |
Cortex-A3x, A5x, A7x |
GICv4 | GICv3 + 가상 인터럽트 지원 | Cortex-A5x, A7x |
7.6. GIC의 프로그래머 모델 (SW 관점)
GIC는 크게 3가지 부분 'Distributor', 'Redistributor', 'CPU Interface' 3가지 부분으로 나뉩니다.
- Distributor: GIC에 1개 있는 distributor는 SPI를 관리하고 redistributor로 인터럽트를 뿌려주는 역할을 합니다. 여기서 관리란, 앞서 살펴본 인터럽트 컨트롤러의 역할 ( 인터럽트 활성화/비활성화, 인터럽트 우선순위 설정 등 )을 수행함을 의미합니다.
- Redistributor: Distributor가 SPI를 관리한다면, Redistributor는 PPI와 SGI를 관리합니다.
- CPU Interface: ARM 코어당 하나씩 붙어있는 CPU interface는 ARM 코어와 GIC 사이를 중재하고, 최종적으로 인터럽트를 필터링하는 역할을 수행합니다.
정리하자면,
- GIC는 우체국입니다. Distributor에서 SPI를, Redistributor에서 PPI/ SGI를 관리합니다.
- CPU Interface는 코어에게 우편물을 전달하는 집배원입니다.
- 목적지 ARM 코어를 PE (Processing Element)라고 부릅니다.
※ GIC의 레지스터들 (링크, GIC로 검색하기)
Distributor의 레지스터들 (접두사 GICD_, SPI 설정)
레지스터 | 이름 뜻 | n (총 인터럽트 1,024개) | 설명 |
GICD_ISENABLER<n> | Interrupt Set-Enable | n = 0~31 각 bit당 인터럽트 설정 32-bit * 32개 = 1,024개 |
각 인터럽트 활성화 여부 |
GICD_ICFGR<n> | Interrupt Configuration | n = 0~63 2-bit당 인터럽트 설정 32-bit / 2 * 64개 = 1,024개 |
각 인터럽트 트리거 방식 |
GICD_IPRIORITYR<n> | Interrupt Priority | n = 0~254 8-bit당 인터럽트 설정 32-bit / 8 * 256개 = 1,024개 |
각 인터럽트 우선순위 설정 |
GICD_IROUTER<n> | Interrupt Routing | n = 0~31 각 bit당 인터럽트 설정 32-bit * 32개 = 1,024개 |
각 인터럽트 (SPI)를 특정 core에 묶는 affinity 설정 |
GICD_IGROUPR<n> | Interrupt Group | n = 0~31 각 bit당 인터럽트 설정 32-bit * 32개 = 1,024개 |
각 인터럽트가 어느 그룹에 속할지 결정 (0 = FIQ, 1 = IRQ) |
GICD_IGRPMODR<n> | Interrupt Group Modifier | n = 0~31 각 bit당 인터럽트 설정 32-bit * 32개 = 1,024개 |
각 인터럽트가 그룹 내에서 어떤 특징을 가지는지 결정 (0 = secure, 1 = non-secure) |
GICD_CTRL | Distributor Control | 1개 | GIC의 전체적인 속성을 설정. |
Redistributor의 레지스터들 (접두사 GICR_, SGI, PPI 설정)
레지스터 | 이름 뜻 | 설명 |
GICR_ISENABLER0 | Interrupt Set-Enable | 각 인터럽트 활성화 여부 |
GICR_ICFGR0 | Interrupt Configuration | 각 인터럽트 트리거 방식 |
GICR_IPRIORITYR<n> (n=0~7) | Interrupt Priority | 각 인터럽트 우선순위 설정 |
GICR_IGROUPR0 | Interrupt Group | 각 인터럽트가 어느 그룹에 속할지 결정 (0 = FIQ, 1 = IRQ) |
GICR_IGRPMODR0 | Interrupt Group Modifier | 각 인터럽트가 그룹 내에서 어떤 특징을 가지는지 결정 (0 = secure, 1 = non-secure) |
CPU Interface의 레지스터들 (접두사 ICC_)
(링크, ICC_ 로 검색하기)
레지스터 | 이름 뜻 | 설명 |
ICC_PMR_EL1 | Interrupt Priority Mask | ARM Core(PE)로 인터럽트를 전달하기 전에 필터링함. 설정된 우선순위보다 낮은 인터럽트는 전달되지 않음. |
ICC_IAR1_EL1 | Interrupt ACK | 인터럽트 잘 전달받음 신호 |
ICC_EOIR1_EL1 | End Of Interrupt | 인터럽트 실행 종료 신호 |
ICC_RPR_EL1 | Running Priority | 현재 활성화된 인터럽트의 우선순위를 나타냄 |
ICC_BPR1_EL1 | Binary Point | 우선순위를 어떻게 분배할지 (선점/서브 몇 bit씩) |
ICC_CTRL_EL1 | Interrupt Controller Control | CPU Interface의 세부 속성을 설정 |
ICC_SRE_EL1 | System Register Enable | CPU Interface와 관련된 시스템 레지스터 (ICC_*) 활성화 여부 |
단순히 레지스터를 외우는것보다 GIC를 어떻게 설정하고 사용하는지 배우는게 더 중요할겁니다.
시스템이 부팅하는 과정에서 위 레지스터들을 제어해서 GIC를 설정합니다.
- GICD_CTRL 설정: GIC의 전반적인 속성을 설정합니다.
- Redistributor 설정: SGI, PPI의 활성화, 트리거 방식, 우선순위, 그룹을 설정합니다.
- CPU Interface 설정: ICC_SRE_ELn 레지스터의 SRE bit (시스템 레지스터 접근방식 활성화)를 비롯해서 ICC_로 시작하는 CPU Interface 관련 레지스터들을 설정합니다.
- Distributor 및 인터럽트 설정: 인터럽트 (SPI)를 설정합니다. 우선순위, 그룹, 트리거방식, affinity 등을 설정합니다.
- 인터럽트 핸들러 구현 + 익셉션 벡터 테이블 (VBAL_EL1)에 인터럽트(익셉션) 핸들러 시작 주소 지정
7.7. 실제 코드로 보는 GIC 동작
- 인터럽트(IRQ 익셉션) 발생
- 익셉션 레벨에 따라 익셉션 벡터 테이블의 el0_irq 또는 el1_irq 레이블로 진입
- 익셉션 핸들러 `gic_handle_irq()` 함수를 호출
- `gic_read_iar()` 함수를 호출해 ICC_IAR_EL1 레지스터 값 읽습니다.
그러면 GIC에게 ACK신호 전달함과 동시에 활성화된 인터럽트 번호가 반환됩니다. - 이 번호를 근거로 SPI/ PPI/ SGI를 판단한 뒤 처리합니다.
(`handle_domain_irq()`에서 SPI/PPi를, `handle_IPI()`에서 SGI를) - `gic_write_eoir()` 함수를 호출해 ICC_EOIR_EL1 레지스터에 인터럽트 번호를 써 실행을 종료합니다.
8. 트러스트존 (TrustZone)
8.1. 트러스트존이란?
※ 정의
트러스트존은 보안을 위해 유출되서는 안되는 중요한/민감한 정보를 다루거나 메모리에 접근하는 애플리케이션 또는 OS가 실행될 수 있는 독립적인 실행환경을 CPU 아키텍처 레벨부터 설계한 구조를 말합니다.
※ 배경
- 해킹 기술이 발전하며 디바이스 드라이버, 네트워크 패킷 등 침투경로 다양화
- 방어를 위해 다양한 외부 HW 부품을 제품에 적용
- 그러나 침투경로가 다양해 시스템의 복잡도와 가격이 함께 증가
- 높은 보안을 요구하는 애플리케이션을 전담해서 실행하는 아예 독립된 실행공간에 대한 수요가 등장
- 보호해야하는 것들을 한 공간에 몰아넣어 공격/침투경로를 한 곳에 집중시킴
- 또한 이 실행공간에는 여러 보안과 관련된 기능(데이터 암호/복호화)이 제공
논-시큐어 환경에선 리눅스 커널 같은 OS와 계산기같은 일반 애플리케이션이 실행되고,
시큐어 환경에선 시큐어OS 위에서 인터넷 뱅킹, 공인인증서 같은 애플리케이션이 실행됩니다.
논-시큐어 영역과 시큐어 영역이 어느정도 독립돼있는가 하면,
- 각 영역이 페이지 테이블에 익셉션 테이블조차 따로따로 갖고있습니다.
- 논-시큐어 영역에서는 시큐어 영역에서 사용하는 메모리 영역에 접근할 수 없습니다.
- SVC 명령어를 이용해서 EL0에서 EL1으로 접근했던 것처럼,
SMC 명령어를 이용해서 논-시큐어 영역에서 시큐어 영역에 자원을 요청합니다.
8.2. (★) 논-시큐어 영역 & 시큐어 영역 전환과정
- 보안성 높은 작업 요청 (e.g. 인터넷뱅킹) (ARMv7: 동작모드 usr, ARMv8: 익셉션레벨 EL0)
- SVC 명령어로 실행흐름 커널영역으로 이동 (ARMv7: 동작모드 svc, ARMv8: 익셉션레벨 EL1)
- SMC (Secure Monitor Call) 명령어를 실행 - 익셉션 발생
SCR 레지스터 (ARMv8: SCR_EL3)의 NS(Non-Secure) bit가 `0`으로 바뀌며 시큐어 모니터 진입 - SP_EL3 스택포인터를 움직여 기존에 논-시큐어 영역의 context를 백업, 기존 시큐어-영역의 context가 있었다면, 복원
- 시큐어 영역의 커널영역 EL1으로 진입해서 요청받은 보안성 높은 작업을 수행합니다.
- 요청받은 작업이 완료되면, 2번 동작과 똑같이 SMC 명령어로 시큐어 모니터로 진입합니다.
이때 SCR 레지스터의 NS bit는 `1`로 업데이트 됩니다. - SP_EL3 스택포인터를 움직여 시큐어 영역의 context를 백업하고, 논-시큐어 영역의 context를 복원해 원래 실행흐름으로 복귀합니다.
※ ARMv7이든 ARMv8이든 상관없이 트러스트존이 적용됐다면 거의 같은 구조를 보입니다.
※ 시큐어모니터가 각 영역 사이의 게이트 역할을 하는 것을 확인합시다.
8.3. SCR (SCR_EL3) 레지스터
(ARMv8의 SCR_EL3 레지스터: 링크, ARMv7 SCR과 호환됩니다)
SCR 레지스터는 보안상태에 대한 여러 가지 설정을 제공하는 트러스트존 핵심 레지스터입니다.
- [0] - NS bit: 위 전환과정에서도 언급된 bit로, 현재 실행흐름이 논-시큐어 영역인지 여부를 알려줍니다.
- [1] - IRQ bit: 일반 IRQ 익셉션을 EL3로 라우팅 할지 설정합니다. `1`로 활성화하면, IRQ 발생 시 EL1이 아니라 EL3로 라우팅 돼 시큐어 모니터에서 인터럽트를 처리합니다.
- [2] - FIQ bit: FIQ 익셉션 EL3 라우팅 설정
- [3] - EA bit : External Abort, 외부 어보트 EL3 라우팅 설정
- [7] - SCD bit: Secure-monitor Call Disable, Set 하면 SMC 명령어 사용이 비활성화됩니다.
- [8] - HCE bit: Hypervisor Call Disable, Set 하면 하이퍼바이저 진입 명령어 HVC 사용이 비활성화됩니다. 이후 9절에서 하이퍼바이저를 배울 때 아마 언급될 겁니다.
- [9] - SIF bit: Secure Instruction Fetch, 시큐어 상태에서 논-시큐어 메모리 공간에 접근할 수 있는지 여부를 설정합니다.
8.4 SMC 익셉션 핸들러로 분기하려면?
트러스트존이 제대로 성립하기 위해서는 각 영역의 익셉션 벡터 테이블에 SMC call에 대한 synchronous 익셉션 핸들러가 구현돼있어야 합니다.
- 논-시큐어 영역의 EL1 익셉션 벡터 테이블 (Base 주소: VBAL, VBAL_EL1)
- 시큐어모니터 EL3 익셉션 벡터 테이블 (Base 주소: MVBAL, VBAL_EL3)
- 시큐어 영역의 EL1 익셉션 벡터 테이블 (Base 주소: S.VBAL_EL1)
ARMv8 기준으로 SMC 명령어를 실행하면 Synchronous 익셉션이 발생하며 `VBAL_EL3 + offset` (아마 거의 EL1에서 실행할 테니 +0x400) 주소로 분기합니다. (기억 안 나시면 저번 포스트의 6.3.4절을 확인하세요)
8.5. 트러스트존은 어떻게 동작할까?
지금까지 설명만 들으면 마치 시큐어 월드의 시큐어OS와 시큐어 애플리케이션은 백그라운드로 실행되고 있는 무언가 같습니다. 실제로는 논-시큐어 애플리케이션과 시큐어월드가 어떻게 상호작용할까요?
- 트러스트존 시큐어 월드의 OS는 높은 보안을 만족하는 전용 RTOS를 이용합니다.
- 논-시큐어 월드에서 동작중인 커널(EL1)에서 SMC 명령어를 호출하거나 FIQ가 걸리면 그제야 시큐어 RTOS가 동작합니다.
- 그러니까 백그라운드로 상시 동작하는 게 아니라 논-시큐어 월드에 의존적으로 동작하는 거지요.
참고로 ARM에서는 트러스티드 펌웨어 (TF-A)를 오픈소스로 제공해 EL3 시큐어 모니터 위에서 동작하는 인터페이스들의 레퍼런스를 제공합니다. (링크) 교재에는 EL1에서 발생한 SMC 요청이 어떤 과정으로 처리되는지 어셈블리 코드를 빌드해 보여줍니다.
8.6. (★) 정리
지금까지 배운 내용을 정말 간단히 요약해 봅시다.
논-시큐어 영역의 커널(EL1)에서 SMC 명령어를 요청하면,
- Synchronous 익셉션 발생합니다
- VBAL_EL3 + offset (아마 +0x400) 주소로 분기합니다
- ESR_EL3 레지스터 하위 bit를 읽어 익셉션 클래스 확인합니다. (AArch64 - 0x17, AArch32 - 0x13)
- 적절한 익셉션 핸들러 (`smc_handler()`)로 분기합니다.
- 기존 논-시큐어 EL1 context를 SP_EL3를 움직여 EL3 스택에 백업한 뒤
시큐어 EL1 context를 복원해서 시큐어 영역으로 들어갈 준비를 마칩니다. - 시큐어 EL1, EL0에서 요청을 수행합니다.
- 시큐어 EL1에서 SMC 명령어를 실행합니다. 위의 1~4번 과정이 시큐어 월드 버전으로 한 번 더 발생합니다.
- 5번 과정이 반대로 일어납니다. 논-시큐어 EL1 context를 복구한 뒤 원래 실행흐름으로 복귀합니다.
9. 하이퍼바이저
9.1. 하이퍼바이저란?
하이퍼바이저(Hypervisor)는 하나의 시스템 위에서 2개 이상의 OS가 동작할 수 있는 아키텍처 또는 플랫폼을 말합니다.
정의만 들으면 거의 사용될 일이 없어 보이는 기능 같아도 많은 곳에서 이미 하이퍼바이저 시스템을 적용하고 있습니다.
- 클라우드: 사용자마다 일정 vCPU나 vRAM이 할당된 OS를 제공하는 구독형 클라우드 VPS 시스템이라던지,
- 차량 내 인포테인먼트 시스템: 보안성이 높은 RTOS 위에서 돌아가는 계기판 시스템, 범용 리눅스 위에서 동작하는 내비게이션 시스템
하이퍼바이저는 운용되는 형태에 따라 2가지로 나뉩니다.
- Type 1 (Stand-alone): 위 그림처럼 따로 실행돼 OS들을 관리하는 타입입니다.
- Type 2 (Hosted): 하이퍼바이저가 Host 메인 OS 위에서 실행되고, 그 위에서 또 다른 OS들이 실행됩니다. 리눅스/윈도 같은 OS의 기능을 활용해 하이퍼바이저를 실행할 수 있어서 많이 채택됩니다.
우리는 지금까지 각 익셉션레벨을 건너는 명령어들을 배웠습니다.
SVC 명령어로 EL0에서 EL1으로 진입해 커널자원을 요청할 수 있었고,
SMC 명령어로 EL1에서 EL3로 진입해 시큐어월드로 넘어갈 수 있었습니다.
같은 맥락으로 HVC 명령어로 EL1에서 EL2로 진입해 하이퍼바이저 자원에 접근할 수 있습니다.
- SVC, SMC 때와 똑같이 HVC 명령어를 실행하면 synchronous 익셉션이 발생하며
- 익셉션 벡터 테이블 = VBAL_EL2 + offset (아마 EL1에서 실행할 테니 +0x400) 주소로 분기한 뒤
- HVC에 대한 익셉션 핸들러로 분기할 것입니다.
※ 즉, 익셉션 및 트러스트존 때 다뤘던 이야기의 반복입니다!
9.2. HVC 명령어와 WFE/WFI 명령어
HVC 명령어를 올바르게 사용하기 위해서는 만족해야 하는 조건이 몇 가지 있습니다.
- (8.3절에서 배운) SCR_EL3 레지스터의 HCE bit가 `1`로 활성화 돼있어야 합니다.
- 하이퍼바이저 관련 컨트롤 레지스터 HCR_EL2의 HCD bit가 `0`으로 돼있어야 합니다.
- EL2 하이퍼바이저가 구현돼야 합니다. (SP_EL2 설정, VBAL_EL2 익셉션 테이블 구현)
- EL0에서 HVC를 call 하지 않도록 합시다.
EL2에 진입하는 명령어는 특이하게 한 개가 아닙니다.
특정 이벤트가 발생하면 저전력 mode에서 시스템을 깨어나게 하는 명령어 (WFE, WFI)입니다.
하이퍼바이저 컨트롤 레지스터 HCR_EL2에는 TWE, TWI bit가 있는데, `1`로 설정돼 있으면 시스템이 깨어날 때 EL2로 트랩됩니다.
EL2에 진입할 수 있는 위와 같이 HVC, WFE/WFI로 진입점이 2개 있는데, 익셉션 신드롬 레지스터 ESR_EL2 레지스터의 익셉션 클래스 필드를 확인하면 둘 중 무엇으로 진입했는지 구분할 수 있습니다.
9.3. HCR_EL2 레지스터
하이퍼바이저의 세부 동작을 설정하는 아주 중요한 레지스터 'HCR_EL2' 레지스터입니다.
- FMO/ IMO/ AMO - 각각 FIQ/ IRQ/ Synchronous 익셉션을 EL1이 아니라 EL2에 트랩 후 처리하도록 설정하는 필드입니다.
- TWI/ TWE - 바로 앞에서 배웠던 그 필드입니다. 각각 WFI/WFE 명령어가 EL2로 트랩 되도록 설정합니다.
- TSC - EL1에서 SMC 명령어를 실행하면 EL3가 아니라 EL2로 트랩되도록 설정합니다. 앞서 SCR_EL3에서 하이퍼바이저 관련 설정을 했던 것처럼, 여기서는 시큐어 모니터 관련 설정을 할 수 있습니다.
- HCD - HVC 명령어 활성화/비활성화를 설정합니다.
XEN 하이퍼바이저 코드 중 `inti_traps()` 함수의 일부입니다.
가장 밑 `WRITE_SYSREG()` 함수에서 HCR_EL2 레지스터를 설정하는 것을 확인할 수 있습니다.
FMO, IMO, AMO bit를 설정하고 있네요. EL1에서 익셉션이 발생하면 EL2로 트랩 될 것입니다.
(왜 hsr이라 쓰여있는지는 모르겠지만...) HCR_EL2 레지스터를 읽은 뒤 익셉션 클래스에 따라 서로 다른 동작을 하는 코드입니다. 익셉션 클래스를 봤는데 만일 WFI/WFE 명령어로 들어왔다면 첫 번째 case 문으로 들어갈 것입니다.
'Embedded' 카테고리의 다른 글
【공부/정리】 시스템 소프트웨어 개발을 위한 ARM 아키텍처 구조와 원리 ④ 메모리 모델, 캐시, MMU (0) | 2025.03.03 |
---|---|
【공부/정리】 시스템 소프트웨어 개발을 위한 ARM 아키텍처 구조와 원리 ② 익셉션과 익셉션레벨 (0) | 2025.02.28 |
【공부/정리】 시스템 소프트웨어 개발을 위한 ARM 아키텍처 구조와 원리 ① 레지스터와 명령어 (0) | 2025.02.26 |
Aarch64 Memory Management 요약 정리 (0) | 2025.02.25 |
[정리/요약] ARM Cortex-M 프로그래밍 (0) | 2025.02.11 |