Linux v6.18.37 · AArch64 GICv3 · Non-secure Group 1 · 일반 IRQ 예

GIC 인터럽트의 번호와 처리 완료를 따라가 봅니다

SGI 대상 CPU를 지정하는 비트, DT·INTID·Linux IRQ 번호, acknowledge와 처리 완료를 실제 값의 예로 연결합니다.

같은 SGI라도 GICv2와 GICv3의 전송 방식이 다릅니다

SGI는 Software Generated Interrupt로, 소프트웨어가 다른 CPU에 알림을 보낼 때 사용하는 인터럽트입니다. “SGI를 발생시키려면 GICD_SGIR에 쓴다”는 설명을 GICv3의 일반적인 system register 경로에 그대로 적용하면 안 됩니다. GICv2와 호환 동작의 설명인지, GICv3 affinity routing을 사용하는 설명인지 먼저 구분해야 합니다.

경로Linux v6.18.37에서 보이는 전송 동작구분할 점
GICv2 경로irq-gic.c가 Distributor의 GIC_DIST_SOFTINT 위치에 MMIO로 값을 기록합니다.GICD_SGIR에 해당하는 방식입니다. 장치 주소로 쓰는 MMIO 동작입니다.
이 글의 GICv3 경로irq-gic-v3.c의 gic_send_sgi가 값을 조립하고 gic_write_sgi1r로 ICC_SGI1R_EL1에 기록합니다.MSR로 system register에 쓰는 동작입니다. 임의의 MMIO 주소를 x0에 넣는 과정이 아닙니다.

이 글은 GICv3의 system register interface와 affinity routing이 준비된 Non-secure Group 1 환경을 설명합니다. Non-secure는 Linux가 실행되는 보안 상태이며, Group 1은 GIC의 인터럽트 그룹입니다. EL0 사용자 프로그램이 아래 MSR을 직접 실행할 수 있다는 뜻은 아닙니다. 접근 권한과 상위 EL의 설정이 맞아야 합니다. GICv2 IPI 전송 GICv3 gic_send_sgi gic_write_sgi1r

대상 CPU와 SGI 번호를 값 하나에 담아 봅니다

설명용으로 Linux 논리 CPU 5의 affinity가 0.0.2.2라고 정하겠습니다. 네 숫자는 Aff3.Aff2.Aff1.Aff0이며 실제 장치의 CPU 배치는 firmware와 커널의 CPU mapping으로 확인해야 합니다. CPU 번호 5를 그대로 TargetList의 5번 비트에 넣지 않습니다. 여기서는 SGI INTID 3을 그 CPU 한 곳으로 보냅니다.

ICC_SGI1R_EL1의 필드이 예의 값담고 있는 뜻
Aff3 / Aff2 / Aff10 / 0 / 2대상이 속한 상위 affinity입니다. 이 이름들이 모든 SoC에서 같은 물리 cluster 구조를 뜻하는 것은 아닙니다.
RS, RangeSelector0TargetList가 Aff0의 0~15 구간을 나타내는 예입니다. 더 큰 구간은 Range Selector Support, RSS의 지원과 RS 값을 확인해야 합니다.
TargetList0x0004 = 1 << 2Aff0=2인 대상을 선택합니다. 0x0002를 넣으면 Aff0=1을 고르게 됩니다.
INTID3대상에 보낼 SGI 번호입니다. Linux 전역 IRQ 번호나 함수의 주소가 아닙니다.
IRM, Interrupt Routing Mode0지정한 affinity와 TargetList를 사용합니다. 이 예는 자신을 제외한 전체 대상에게 보내는 broadcast 설정이 아닙니다.
/* 위의 affinity와 SGI 번호로 만든 설명용 값입니다. */
value = (3ULL << 24) | (2ULL << 16) | (1ULL << 2);
/* value = 0x0000000003020004 */

ULL은 C에서 unsigned long long 정수 상수를 뜻하고, |는 비트별 OR 연산입니다. 24와 16은 이 register에서 각각 SGI 번호와 Aff1 필드가 시작하는 비트 위치입니다. 1 << 2는 CPU 수를 4로 정하는 계산이 아니라 2번 비트 하나를 켜는 계산입니다. Linux는 gic_cpu_to_affinity에서 논리 CPU를 변환한 뒤, 같은 전송에 묶을 수 있는 대상들을 gic_compute_target_list로 모읍니다. TargetList가 16비트라는 이유로 시스템 전체 CPU 수가 16개 이하라는 결론을 내리면 안 됩니다. SGI1R 필드의 비트 위치 CPU mapping·TargetList 계산

명령마다 x0와 알림 상태가 어떻게 바뀝니까?

아래는 위 숫자를 명령으로 준비하는 예입니다. 초기 x0는 앞선 코드가 남긴 값이며 무엇인지 몰라도 첫 MOVZ 뒤의 값은 정해집니다. 전송 전에 공유 자료가 이미 준비되어 있고, 대상 CPU와 SGI 설정이 유효하다고 가정합니다. 실제 Linux에서 임의의 SGI 번호를 골라 실행할 예제가 아니라 값과 순서를 읽기 위한 코드입니다.

movz x0, #0x0004
movk x0, #0x0302, lsl #16
dsb ishst
msr ICC_SGI1R_EL1, x0
isb
SGI 전송 예: register 준비와 대상의 처리는 별개입니다

1. MOVZ로 낮은 비트를 준비합니다

실행 전 x0실행 후 x0이 명령이 하는 일
앞선 코드가 남긴 64비트 값입니다.0x0000000000000004하위 16비트에 4를 넣고 나머지 비트는 0으로 만듭니다. 기존 x0 값 전체가 대체됩니다.

메모리 주소 4에 데이터를 쓰는 명령이 아닙니다. 일반 register x0 안의 숫자만 바뀝니다.

2. MOVK로 SGI 번호와 Aff1을 채웁니다

실행 전 x0실행 후 x0보존하는 부분
0x00000000000000040x0000000003020004bits 31:16에 0x0302를 넣고 그 밖의 비트는 유지합니다. 하위 TargetList 값 4도 남습니다.

lsl #16은 넣을 16비트 조각의 위치입니다. x0 값 전체를 왼쪽으로 16비트 이동시키는 명령이 아닙니다.

3. DSB로 앞선 저장과 알림의 순서를 맞춥니다

x0는 0x0000000003020004로 유지됩니다. DSB는 여기서 앞선 Normal memory 저장과 뒤의 SGI 전송 사이에 필요한 완료·순서 조건을 만듭니다. v6.18.37의 gic_ipi_send_mask도 전송 전에 dsb(ishst)를 사용합니다. ishst는 Inner Shareable 범위의 저장에 대한 조건입니다. 대상 CPU의 handler가 이미 실행되었다는 뜻은 아닙니다.

4. MSR로 SGI 발생을 요청합니다

x0변하는 대상아직 알 수 없는 것
0x0000000003020004를 유지합니다.ICC_SGI1R_EL1에 전송할 값이 기록됩니다.대상 CPU가 예외에 진입했는지, handler를 끝냈는지는 이 명령만으로 확인할 수 없습니다.

x0의 값을 system register에 쓰는 방향입니다. 대상 CPU의 x0에 이 값을 복사하지 않습니다. SGI는 이 64비트 값을 일반 메시지 payload로 전달하는 기능도 아닙니다.

5. ISB 뒤에도 완료 응답이 필요할 수 있습니다

x0는 여전히 같은 값입니다. Linux의 전송 경로는 마지막에 ISB를 두어 SGI register 쓰기가 실행되도록 합니다. 이후 대상이 일을 마쳤는지 알아야 한다면 별도의 완료 상태나 동기화 규약을 사용해야 합니다. SGI를 보낸 횟수와 대상이 작업을 완료한 횟수를 같다고 가정하지 않습니다.

단계는 보내는 CPU의 명령 실행 순서입니다. register 값은 예제이며 실기 실행 기록이 아닙니다. 보내는 CPU → 대상 CPU는 인터럽트 알림 방향이고, x0의 전달이나 대상 작업의 완료를 뜻하지 않습니다.

온라인 CPU에 보내는 SGI와 PSCI CPU_ON으로 보조 CPU의 시작 주소를 넘기는 과정은 다릅니다. CPU가 아직 커널 초기화를 마치지 않았다면 SGI 값만 맞춘다고 정상 IPI handler가 생기지 않습니다. CPU 시작·idle 복귀·task wakeup의 차이는 기존 CPU 시작 글에서 이어서 보실 수 있습니다.

DT의 6, GIC의 38, Linux IRQ 52가 같은 사건일 수 있습니다

장치가 GIC에 직접 연결된 SPI를 사용하고, interrupt-parent가 해당 GIC node인 예를 보겠습니다. 숫자 52는 설명을 위해 가정한 Linux 할당 결과이며 실제 부팅에서는 달라질 수 있습니다.

interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
표현값의 의미언제 해석합니까?
GIC_SPI일반 SPI를 나타내는 DT 상수 0입니다.DTS 전처리에서 정의를 치환하고 DTB의 cell 값으로 저장합니다.
두 번째 cell의 6SPI 범위 안의 상대 번호입니다.Linux의 GIC domain translate가 32를 더하므로 INTID는 38입니다.
IRQ_TYPE_LEVEL_HIGH높은 level이 유지되는 trigger를 나타내는 상수 4입니다.binding과 driver가 전기적 trigger 설정에 반영합니다. 소스가 실제로 그 신호를 내는지도 맞아야 합니다.
GIC INTID / 이 domain의 hwirq38IAR에서 받아 domain에 전달하는 하드웨어 번호입니다.
Linux IRQ, virq예에서는 52Linux IRQ core가 관리하는 번호입니다. request_irq와 /proc/interrupts에서 보는 번호를 INTID 38과 혼동하지 않습니다.

GIC_SPI와 IRQ_TYPE_LEVEL_HIGH는 CPU가 런타임에 읽는 문자열 이름이 아닙니다. 필요한 DT header가 포함된 DTS에서 전처리되는 상수입니다. 런타임의 Linux가 DTB cell을 읽어 번호와 trigger를 해석하는 단계와 나누어 봅니다. PPI는 이 경로에서 16을 더하며, ACPI GSI에는 같은 DT offset 규칙을 기계적으로 적용하지 않습니다. GICv3 DT cell 규칙 DT의 GIC 종류 상수 trigger 상수 gic_irq_domain_translate·gic_irq_domain_map

INTID 범위분류적용 조건
0~15SGI대상 PE의 소프트웨어 인터럽트입니다.
16~31PPI특정 PE에 속하는 인터럽트입니다. 타이머가 대표적인 예입니다.
32~1019SPI일반 공유 주변장치 범위입니다. 구현이 모든 번호를 제공한다는 뜻은 아닙니다.
1020~1023특수 번호일반 장치 IRQ로 dispatch하지 않습니다. 1023은 acknowledge할 인터럽트가 없는 상황을 나타낼 수 있습니다.
1056~1119Extended PPI해당 확장을 지원하는 구현에서 사용합니다.
4096~5119Extended SPI해당 확장을 지원하는 구현에서 사용합니다.
8192 이상LPI지원과 ID 폭·할당 범위를 확인합니다. 무한한 번호 공간이 아닙니다.

따라서 예전 표의 “1024~8191은 모두 reserved”라는 설명을 현재 지원 범위 전체에 적용하면 안 됩니다. v6.18.37의 __get_intid_range와 DT binding은 Extended PPI/SPI를 따로 구분합니다. 반대로 표에 범위가 있다고 모든 장치가 그 확장을 갖는 것도 아닙니다. 현재 driver의 INTID 범위 판정

IRQ 진입, acknowledge, 원인 제거, EOI는 다릅니다

CPU가 IRQ 예외에 진입한 것만으로 어떤 INTID인지 읽은 것은 아닙니다. GIC의 IAR, Interrupt Acknowledge Register를 읽어 번호를 얻으면서 acknowledge합니다. 일반적인 SPI에서는 이때 active 상태가 생깁니다. 디버거에서 IAR을 임의로 읽으면 상태를 바꿀 수 있으므로 단순 조회용 register처럼 취급하면 안 됩니다.

동작바뀌는 상태자동으로 해결되지 않는 것
ICC_IAR1_EL1 읽기처리할 Group 1 INTID를 받고 acknowledge합니다.장치의 status 원인을 제거하지 않습니다.
장치의 원인 제거예를 들어 장치가 유지하던 level interrupt 원인을 처리합니다.GIC의 active 해제·priority drop과 같은 동작은 아닙니다.
ICC_EOIR1_EL1 쓰기EOImode에 따라 priority drop만 하거나 deactivate도 함께 합니다.장치의 status register를 대신 지워 주지 않습니다.
ICC_DIR_EL1 쓰기분리 모드에서 해당 active interrupt를 deactivate합니다.소스가 다시 asserted되어 있으면 재전달되는 상황까지 막지는 않습니다.

EOI는 End Of Interrupt의 약어입니다. EOImode=0은 EOIR에서 priority drop과 deactivate를 함께 하고, EOImode=1은 분리합니다. priority drop은 CPU interface의 running priority를 내려놓는 동작이며 장치의 프로그램 작업을 끝내는 명령이 아닙니다. 또한 SPI의 shared는 모든 CPU가 한 사건을 동시에 처리한다는 뜻이 아닙니다. 지정한 한 PE 또는 지원되는 1-of-N 정책으로 선택된 한 PE가 대상이 됩니다. DAI 0492B · SPI routing·acknowledge·EOImode

Linux v6.18.37의 분리 모드: 일반 SPI 한 건의 처리
  1. IAR에서 INTID 38을 받습니다.

    유효한 일반 IRQ를 가정합니다. 실제 driver는 1020~1023의 특수 번호를 먼저 제외합니다.

    INTID를 acknowledge한 뒤 priority를 처리합니다.

  2. gic_complete_ack에서 EOIR을 기록합니다.

    분리 모드에서는 여기서 priority drop을 먼저 합니다. 아직 장치 handler가 실행되기 전입니다.

    GIC domain에서 Linux IRQ를 찾아 dispatch합니다.

  3. Linux IRQ 52의 장치 handler가 처리합니다.

    52는 이 예의 할당 결과입니다. 장치 상태를 읽고 필요한 원인 제거를 장치 규약에 맞춰 수행합니다.

    generic IRQ 흐름의 완료 처리로 이어집니다.

  4. irq_eoi callback에서 deactivate합니다.

    일반적인 경우 gic_eoimode1_eoi_irq가 DIR을 씁니다. guest로 전달한 IRQ나 erratum 처리 등은 별도 조건이 있습니다.

화살표는 이 버전의 일반 SPI 처리 순서입니다. 분리 모드에서는 EOIR이 handler보다 앞에 올 수 있습니다. 장치 원인 제거와 GIC 상태 변경을 각각 표시했으며 모든 IRQ 종류·예외 경로가 이 순서라는 뜻은 아닙니다.

이 구분 때문에 “EOI는 언제나 handler 맨 뒤의 한 번짜리 완료 작업”이라고 설명하면 실제 Linux 흐름을 놓칩니다. GIC가 priority를 내려놓아도 CPU의 interrupt mask와 다른 실행 조건까지 모두 풀린 것은 아닙니다. 또 level source가 남아 있으면 deactivate 이후에도 pending 상태가 남거나 다시 전달될 수 있습니다. __gic_handle_irq·gic_complete_ack·gic_eoimode1_eoi_irq IAR·DIR 접근

물리 LPI는 일반 SPI처럼 active와 active+pending 상태를 사용하지 않습니다. Linux의 분리 모드 callback도 LPI에는 DIR을 통한 deactivate를 하지 않습니다. SGI 역시 후속 기능으로 active 상태를 쓰지 않는 구성이 있으므로 “모든 인터럽트는 네 상태를 거친다”는 일반화는 피해야 합니다. 이 버전의 gic_dist_init은 지원되는 경우 nASSGI 기능을 활성화합니다. nASSGI는 SGI의 active 상태를 사용하지 않는 기능입니다. LPI deactivate 제외·nASSGI 설정 DAI 0492B · LPI 상태의 예외

MSI의 EventID와 LPI INTID도 별개의 번호입니다

ITS, Interrupt Translation Service를 사용하는 물리 LPI 예를 보겠습니다. 장치의 DeviceID는 0x120, 전송한 EventID는 5, 미리 설정한 변환 결과는 LPI INTID 8197이라고 정했습니다. 여기서 8197은 예제의 매핑 값입니다. 8192+EventID라는 고정 계산 규칙을 뜻하지 않습니다.

ITS가 사용하는 번호를 한 요청에서 구분합니다
  1. MSI: ITS doorbell 주소에 EventID 5를 씁니다.

    DeviceID 0x120은 시스템의 장치 식별 경로로 함께 구별됩니다. 일반적으로 두 값을 MSI data 하나에 합쳐 넣는 설명으로 읽지 않습니다.

    ITS가 DeviceID에 해당하는 변환 표를 선택합니다.

  2. 해당 장치의 ITT에서 EventID 5를 찾습니다.

    ITT는 Interrupt Translation Table입니다. 이 예의 entry가 INTID 8197과 collection을 연결합니다.

    미리 설정한 번호와 target 관계를 적용합니다.

  3. collection이 가리키는 Redistributor로 전달합니다.

    collection ID는 CPU의 Linux 논리 번호와 같은 개념이 아닙니다. 연결된 대상 정보를 확인합니다.

    대상의 LPI pending 상태와 전달 조건에 반영됩니다.

  4. CPU에서 LPI INTID 8197을 acknowledge합니다.

    Linux의 IRQ domain과 MSI domain이 자신의 IRQ 객체로 연결합니다. EventID 5나 INTID 8197이 그대로 Linux IRQ 번호라고 가정하지 않습니다.

화살표는 인터럽트 식별자 변환과 전달 방향입니다. buffer 데이터가 이 표들을 통과해 복사되는 그림이 아닙니다. 번호는 임의의 설정 예이며 ITS를 사용하는 구성을 가정합니다.

Linux의 its_irq_compose_msi_msg는 EventID를 MSI data에 넣습니다. 별도로 its_build_mapti_cmd는 DeviceID, EventID, physical INTID, collection을 명령에 담습니다. MSI를 보내는 동작과 그 전에 대응 관계를 설정하는 MAPTI 명령은 다릅니다. MAPTI는 ITS의 Interrupt Translation Table에 사용할 대응을 설정하는 명령입니다. MSI 메시지 구성·MAPTI 인코딩

LPI가 항상 ITS를 거쳐야 한다고 일반화하지는 않습니다. Arm의 이 입문 규격은 ITS 지원을 선택 사항으로 설명하며, Redistributor에 직접 보내는 구성도 다룹니다. 여기서는 Linux의 ITS 경로를 예로 들었습니다. LPI 지원 여부와 선택한 전달 경로는 실제 구현에서 확인해야 합니다. DAI 0492B · 6절의 선택적 ITS 지원

게스트의 인터럽트 번호는 호스트 번호와 분리합니다

가상 머신에는 자신의 GIC와 CPU topology가 있습니다. 게스트가 쓰는 affinity는 게스트 vCPU를 식별합니다. 같은 숫자의 호스트 물리 CPU를 직접 선택하는 값으로 읽으면 안 됩니다. 이 절은 일반적인 GICv3 VGIC 소프트웨어 SGI 처리 경로를 다루며, GICv4 계열의 직접 전달 최적화는 별도로 봅니다.

게스트가 다른 vCPU에 SGI를 보내는 경로
  1. 게스트가 ICC_SGI1R_EL1에 씁니다.

    SGI 번호와 게스트의 대상 affinity를 담습니다.

    이 예의 trap 처리에서 KVM으로 전달합니다.

  2. KVM의 vgic_v3_dispatch_sgi가 값을 해석합니다.

    SGI 번호·TargetList·affinity를 읽어 해당 VM의 vCPU를 찾습니다.

    대상 vCPU의 virtual interrupt를 queue에 반영합니다.

  3. 실행할 vCPU의 interrupt 상태를 준비합니다.

    일반 LR 경로에서는 가상 INTID, priority와 pending·active 상태 등을 List Register 표현에 담습니다.

    게스트 실행과 가상 CPU interface에 반영합니다.

  4. 게스트가 자신의 가상 INTID를 처리합니다.

    게스트의 Linux IRQ mapping과 handler가 동작합니다. 호스트의 동일 번호 IRQ handler를 직접 호출한 것이 아닙니다.

화살표는 선택한 KVM 경로의 제어·상태 전달입니다. vCPU가 즉시 물리 CPU에서 실행된다는 보장이나 매 단계마다 QEMU로 반환한다는 뜻은 아닙니다.

LR은 List Register의 약어입니다. 여기서 어셈블리의 link register x30을 뜻하지 않습니다. vgic_v3_populate_lr는 irq->intid와 상태로 값을 구성합니다. 물리 IRQ와 연결하는 hardware-backed 경로에서는 hwintid도 별도 필드로 사용합니다. 가상 INTID와 물리 INTID가 다른 값으로 관리된다는 점을 코드에서도 확인할 수 있습니다. 가상 SGI 대상 선택 LR 구성·상태 회수

게스트 장치의 전달 방식확인할 연결단정하지 않을 부분
virtio-mmiofirmware가 기술한 platform IRQ를 Linux가 받아 등록합니다.virtio라는 이름만으로 MSI나 LPI라고 판단하지 않습니다. GIC에 SPI로 연결된 구성도 있습니다.
virtio-pci의 MSI-X가상 PCI의 MSI-X 설정과 guest의 interrupt controller·ITS 구성을 봅니다.모든 virtio transport가 이 방식을 사용하는 것은 아닙니다.
가상 ITSKVM의 선택적 VGIC ITS 장치를 구성합니다.가상 ITS가 있다는 사실만으로 host에 물리 ITS가 반드시 있다고 결론 내리지 않습니다.

v6.18.37의 virtio_mmio.c는 platform_get_irq로 얻은 번호를 request_irq에 넘깁니다. virtio_pci_common.c에는 별도의 MSI-X 경로가 있습니다. VGIC API 문서는 사용자 공간 장치가 VM의 interrupt controller로 주입하도록 정하며, 가상 ITS 지원은 별도 선택 사항입니다. virtio-mmio IRQ 등록 virtio-pci MSI-X VGIC의 VM 내 역할 가상 ITS의 지원 조건

irqfd는 eventfd의 알림을 KVM의 guest interrupt 주입에 연결하는 인터페이스입니다. eventfd는 알림을 주고받는 Linux 파일 descriptor이며, irqfd의 gsi는 KVM에 설정한 routing entry를 찾는 값입니다. arm64에서 해당 entry가 irqchip 경로이면 pin+32가 가상 SPI INTID이고, MSI 경로이면 메시지와 DeviceID를 가상 ITS가 LPI로 번역합니다. 따라서 irqfd를 썼다는 사실만으로 SPI인지 LPI인지 정해지지 않습니다. 예를 들어 gsi 12의 entry가 irqchip pin 6에 연결되어 있다면 가상 SPI는 38입니다. 12·6·38은 역할이 서로 다른 예제 값입니다. KVM_IRQFD의 arm64 routing 규칙

로그의 숫자를 연결해서 읽는 순서

로그·현상함께 확인할 값
SGI를 썼는데 대상에서 처리되지 않습니다.논리 CPU→affinity 대응, SGI INTID, TargetList·RS, 대상의 group enable·priority·CPU 상태를 나누어 확인합니다.
DT에는 6인데 /proc/interrupts에는 52입니다.DT 종류·상대 번호 → GIC INTID 38 → 할당된 Linux IRQ 52의 관계가 맞는지 봅니다. 예제 숫자를 실제 할당 결과로 고정하지 않습니다.
EOIR을 썼는데 active가 남습니다.EOImode와 이후 deactivate 경로를 확인합니다. guest로 전달한 IRQ인지도 봅니다.
IRQ가 반복해서 들어옵니다.장치의 level 원인이 해제되었는지와 GIC 상태를 구분합니다. pending bit만 지우면 해결된다고 가정하지 않습니다.
MSI data 5를 찾았는데 handler는 다른 번호입니다.DeviceID·EventID → LPI INTID → Linux IRQ mapping을 연결합니다.
guest INTID와 host IRQ가 다릅니다.guest의 VGIC 번호 공간과 host의 물리 IRQ·Linux IRQ 공간을 별도로 적습니다.

기존 GIC 상세 글은 Distributor·Redistributor·CPU interface와 Linux dispatch를 나누어 설명합니다. 이 글은 그 구조에 숫자를 대입하고, 한 줄의 SGI 명령과 acknowledge·EOI가 바꾸는 상태를 보충한 설명입니다. 예제는 실제 장치에 실행하지 않았으며, 커널 경로는 Linux v6.18.37 소스와 대조했습니다.