전체 흐름
start_kernel()은 early_irq_init() 뒤 init_IRQ()을 호출합니다. 이 시점의 일은 인터럽트 처리가 아니라 arm64 IRQ 진입 기반을 만드는 것입니다. 다음 런타임 경로는 다른 시간축입니다.
start_kernel()early_irq_init()init_IRQ(): IRQ stack·SCS·irqchip- timer/softirq 준비 이후
local_irq_enable()
init_IRQ() 안에서는 hardirq/softirq handler를 실행하지 않습니다.
- GICv3가 인터럽트를 전달
gic_handle_irq()가 IRQ 번호를 해석·전달- generic IRQ/driver handler, 필요하면 softirq
이 경로는 GICv3 사용 시스템의 예시이며 부팅 함수의 내부 순서가 아닙니다.
두 시간축을 분리했습니다. init_IRQ()에 대한 설명은 왼쪽의 부팅 준비 경로를 따릅니다.
arm64 init_IRQ() 본문, irqchip_init()의 DT/ACPI 분기, GICv3 등록 예시를 읽습니다.
실제 인터럽트 entry, flow handler, softirq, device driver probe는 이 함수의 반환 뒤에도 각각 다른 경로에서 동작합니다.
소스 코드 위치
| 파일 | 이 글에서 확인할 지점 |
|---|---|
| init/main.c | early_irq_init() → init_IRQ()와 그보다 뒤의 local_irq_enable() |
| arch/arm64/kernel/irq.c | IRQ stack/SCS 준비, irqchip_init() 호출, 선택적 PMR/PSR 정합성 설정 |
| drivers/irqchip/irqchip.c | of_irq_init()와 acpi_probe_device_table() 호출 |
| drivers/irqchip/irq-gic-v3.c | GICv3 DT 선언, domain 생성, root IRQ handler 등록의 한 구현 예시 |
GICv3는 가능한 irqchip의 한 가지입니다. DT 일치 또는 ACPI 설정이 다르면 초기화 callback과 준비 결과도 달라집니다. 아래 GIC 세부 흐름을 모든 arm64 보드의 공통 실행 경로로 일반화하지 않습니다.
대표 코드
init_IRQ() — arm64 원문
void __init init_IRQ(void)
{
init_irq_stacks();
init_irq_scs();
irqchip_init();
if (system_uses_irq_prio_masking()) {
/*
* Now that we have a stack for our IRQ handler, set
* the PMR/PSR pair to a consistent state.
*/
WARN_ON(read_sysreg(daif) & PSR_A_BIT);
local_daif_restore(DAIF_PROCCTX_NOIRQ);
}
}읽는 법: stack, SCS, irqchip 순서가 명시되어 있습니다. system_uses_irq_prio_masking()이 참일 때만 DAIF 확인과 local_daif_restore(DAIF_PROCCTX_NOIRQ)를 수행합니다. 이 호출은 start_kernel() 뒤쪽의 local_irq_enable()과 같은 사건이 아닙니다.
irqchip_init() — 호출 대상 원문
void __init irqchip_init(void)
{
of_irq_init(__irqchip_of_table);
acpi_probe_device_table(irqchip);
}읽는 법: 이전 페이지에서 이 제목 아래 init_IRQ() 본문을 다시 실은 중복 발췌를 바로잡았습니다. CONFIG_IRQCHIP이 꺼진 빌드에서는 header의 빈 inline 함수가 호출됩니다. 실제 irqchip 구성과 실패 처리는 선택된 DT/ACPI callback에서 살펴야 합니다.
함수별 분석
init_IRQ() 본문은 세 호출과 하나의 조건부 정합성 설정으로 구성됩니다. irqchip_init() 아래에서 선택된 컨트롤러 callback이 실행되는 부분은 별도 계층입니다.
화살표는 arch/arm64/kernel/irq.c의 호출 순서입니다. stack/SCS의 실제 할당 방식은 빌드 설정과 커널 버전에 좌우됩니다.
| 지점 | 소스에서 확인되는 동작 | 해석의 경계 |
|---|---|---|
init_irq_stacks() | CONFIG_VMAP_STACK이면 possible CPU별 VMAP IRQ stack을 할당하고, 아니면 정적 per-CPU IRQ stack 주소를 연결한다. | 인터럽트를 여기서 바로 처리하는 동작이 아님 |
init_irq_scs() | scs_is_enabled()일 때 possible CPU별 shadow call stack을 cpu_to_node(cpu) 기준으로 할당한다. | SCS가 꺼져 있으면 바로 반환 |
irqchip_init() | DT 매칭 callback을 부모 컨트롤러부터 초기화하고 ACPI irqchip table을 probe | 선택한 irqchip/firmware 경로에 따라 세부 결과가 다름 |
| priority masking 조건 | WARN_ON(DAIF & PSR_A_BIT) 뒤 DAIF_PROCCTX_NOIRQ 복원 | 일반적인 local_irq_enable() 호출과 동일시하지 않음 |
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init)는 DT callback 연결입니다. GIC 초기화는 distributor/redistributor 매핑을 검증하고 gic_init_bases()에서 domain을 생성하며 set_handle_irq(gic_handle_irq)를 호출합니다. 이것을 일반 장치 driver의 probe 시점이나 런타임 IRQ handler 호출과 혼동하면 안 됩니다.
구조체와 연결점
GICv3 예시에서는 irq domain과 루트 handler가 같은 초기화 callback에서 준비되지만, 둘은 순서대로 변환되는 한 객체가 아닙니다. IRQ stack/SCS는 CPU별 진입 문맥의 별도 자원입니다.
irq_stack_ptr와 선택적 irq_shadow_call_stack_ptr · arm64 irq.cirq_domain_create_tree() · hwirq→Linux IRQ 매핑 기반set_handle_irq(gic_handle_irq) · handle_arch_irq 등록세 자원은 서로 다른 역할입니다. gic_handle_irq()의 실제 호출은 나중의 인터럽트 진입 경로에 속합니다.
| 대상 | 준비 위치 | 이후 역할 |
|---|---|---|
irq_stack_ptr | arm64 irq.c의 init_irq_stacks() | CPU별 IRQ stack 주소 |
irq_shadow_call_stack_ptr | arm64 irq.c의 init_irq_scs() | SCS 사용 시 IRQ 경로의 shadow call stack |
gic_data.domain | GICv3 gic_init_bases() | GIC 하드웨어 IRQ와 Linux IRQ의 연결 |
handle_arch_irq | arm64 irq.c의 set_handle_irq() | default handler에서 선택한 root handler로 교체 |
실행 단계와 상태 변화
초기화 완료와 인터럽트 활성화는 다른 경계입니다. 특히 GIC callback 안에서 domain과 handler를 준비했다고 해서 init_IRQ()가 곧바로 runtime hardirq/softirq를 실행했다는 뜻은 아닙니다.
init_IRQ()가 스스로 local IRQ를 켜는 단계라는 표현을 제거했습니다. 실제 경계는 init/main.c의 뒤쪽입니다.
| 단계 | 확인할 증거 | 주의할 예외 |
|---|---|---|
| arm64 stack 준비 | irq_stack_ptr의 possible CPU별 설정 | v6.6의 VMAP_STACK 분기와 v6.18.37 할당 방식이 다름 |
| irqchip 등록 | DT/ACPI에서 실제 선택된 callback과 초기화 로그 | irqchip 종류, firmware, CONFIG에 따라 결과가 다름 |
| GICv3 예시 | irq_domain_create_tree(), set_handle_irq() 호출 | 성공·실패는 GIC callback과 자원 정리 코드를 확인 |
| local IRQ 허용 | start_kernel()의 local_irq_enable() | 앞의 init_IRQ()와 다른 시점 |
불변 조건과 실패 사례
arm64의 handle_arch_irq는 처음에 default_handle_irq를 가리키며, 이 기본 함수는 IRQ 수신 시 panic합니다. 실제 컨트롤러 handler의 등록을 확인해야 합니다.
set_handle_irq()는 기본 handler가 이미 바뀐 상태면 -EBUSY를 반환합니다. GICv3 예시에서는 이 호출의 반환값을 이 지점에서 검사하지 않으므로 단순 호출 사실만으로 성공을 단정하지 않습니다.
SCS, priority masking, GICv3 여부, DT/ACPI 선택은 설정과 하드웨어에 좌우됩니다. 모든 보드에 네 경로가 동일하게 적용된다는 가정은 피합니다.
init_IRQ() 자체에는 오류 반환값이나 정리 label이 없습니다. GICv3 map·domain 준비 실패는 gic_of_init()/gic_init_bases()의 반환·정리 경로에서 확인합니다.
계측과 검증
부팅 준비와 런타임 IRQ 활동을 서로 다른 증거로 읽습니다. 이 목록은 읽기 전용 확인 예시이며 특정 장치에서 실행한 결과가 아닙니다.
| 관찰 | 확인 가능한 것 | 확인할 수 없는 것 |
|---|---|---|
부팅 로그의 Root IRQ handler | set_handle_irq() 성공 시 출력된 등록 handler | 모든 IRQ의 정상 처리나 softirq 완료 |
| GICv3 초기화 오류 로그 | 선택한 GIC callback에서 보고한 map·설정 문제 | 다른 irqchip의 초기화 상태 |
/proc/interrupts | 부팅 이후 Linux IRQ 카운터 | init_IRQ() 내부 단계의 직접 trace |
런타임 irq:irq_handler_entry/exit | 활성화된 IRQ의 handler 실행 | 부팅 시 init_IRQ() 호출 자체 |
dmesg | grep -E 'Root IRQ handler|GIC|irqchip'
cat /proc/interrupts로그는 ring buffer 보존과 출력 설정에 좌우됩니다. 이전의 전역 function_graph·set_ftrace_filter 변경 예시는 이 함수의 부팅 단계를 증명하지 못하고 다른 추적 작업을 건드릴 수 있어 제거했습니다.
추가 확인 사항
- 대상 보드가 DT와 ACPI 중 어느 경로로 irqchip callback을 선택하는가?
- v6.6에서
CONFIG_VMAP_STACK과 SCS는 켜져 있는가? v6.18.37의 CPU별 할당은 어떤 NUMA 노드를 사용하는가? - GICv3 callback이 선택된다면 distributor/redistributor 매핑과 domain 생성은 어느 단계에서 실패할 수 있는가?
set_handle_irq()의 등록 결과와 그보다 뒤의local_irq_enable()경계를 어떤 근거로 구별할 수 있는가?