01 · QUESTION
무엇을 증명할 것인가
장치가 interrupt를 만들었는데 CPU vector가 오지 않을 때 IOAPIC redirection entry, MSI message, Local APIC IRR/ISR, IDT 중 어느 경계를 먼저 확인해야 하는가?
legacy PIC에서 Local APIC, IOAPIC, MSI/MSI-X, x2APIC, interrupt remapping으로 이어지는 x86 interrupt 구조를 한 장치 사건의 주소·데이터·vector 변화로 연결합니다. 이 글은 Linux 로그를 출발점으로 삼지 않습니다. 같은 사건이 보드, controller, firmware, kernel에서 어떤 이름과 번호로 바뀌는지 아래에서 위로 연결합니다.
02 · CONTRACT
입력과 완료 조건
| 구분 | 확인할 내용 |
|---|---|
| 입력 | legacy IRQ pin 또는 MSI address/data write, 활성 Local APIC, 유효한 IDT gate와 Linux vector allocation |
| 출력 | 대상 logical CPU가 vector를 수신해 common_interrupt로 진입하고 Linux irq_desc와 device handler를 실행함 |
| 소유권 | 장치 원인은 driver, IOAPIC/MSI route는 irqdomain과 PCI/MSI core, vector는 x86 vector allocator, in-service/EOI는 Local APIC이 관리 |
| 완료 조건 | 장치 원인이 제거되고 Local APIC EOI 뒤 ISR bit가 내려가며 vector와 Linux IRQ affinity가 의도한 CPU에서 안정적으로 처리됨 |
03 · BOTTOM-UP
가장 아래 계층부터 확인하는 8단계
8259A PIC의 IRQ0~15, cascade, edge/level 한계를 출발점으로 잡습니다. 최신 시스템에서도 early boot와 호환 경로를 이해해야 APIC 전환 로그를 해석할 수 있습니다.
각 logical CPU의 APIC ID, SVR, TPR, IRR, ISR, TMR, EOI, LVT를 확인합니다. xAPIC MMIO와 x2APIC MSR 접근 방식은 다르지만 vector와 priority 모델은 이어집니다.
핀 기반 장치 interrupt는 redirection table의 vector, delivery mode, polarity, trigger, mask, destination으로 변환됩니다. ACPI MADT interrupt source override가 legacy IRQ와 GSI 관계를 바꿀 수 있습니다.
x86 IDT의 32~255 vector 중 Linux가 device IRQ용 vector를 CPU별로 할당합니다. Linux IRQ 번호, GSI, IOAPIC pin, APIC vector는 서로 다른 번호 공간입니다.
CPU가 IDT gate를 따라 entry stub으로 들어오면 pt_regs를 만들고 common_interrupt가 vector를 Linux irqdomain/irq_desc로 변환합니다. handler 전후 Local APIC EOI 시점도 chip flow handler에 달려 있습니다.
PCIe 장치는 물리 핀 대신 APIC 대상 주소와 vector data를 Memory Write TLP로 보냅니다. MSI-X table entry마다 address/data/mask가 있어 queue별 affinity를 구성할 수 있습니다.
IOMMU의 interrupt remapping이 켜지면 장치 message는 직접 APIC 목적지를 담기보다 IRTE index/subhandle을 참조합니다. 보안과 CPU 수 확장성이 좋아지지만 검증 경계가 하나 늘어납니다.
ACPI/MADT, APIC mode 선택, irqdomain, vector allocation, request_irq, affinity 변경까지 연결합니다. `/proc/interrupts`만 보지 말고 장치 status와 APIC vector trace를 함께 대응시킵니다.
1. Legacy baseline
8259A PIC의 IRQ0~15, cascade, edge/level 한계를 출발점으로 잡습니다. 최신 시스템에서도 early boot와 호환 경로를 이해해야 APIC 전환 로그를 해석할 수 있습니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 2. Local APIC로 이동합니다.
2. Local APIC
각 logical CPU의 APIC ID, SVR, TPR, IRR, ISR, TMR, EOI, LVT를 확인합니다. xAPIC MMIO와 x2APIC MSR 접근 방식은 다르지만 vector와 priority 모델은 이어집니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 3. IOAPIC로 이동합니다.
3. IOAPIC
핀 기반 장치 interrupt는 redirection table의 vector, delivery mode, polarity, trigger, mask, destination으로 변환됩니다. ACPI MADT interrupt source override가 legacy IRQ와 GSI 관계를 바꿀 수 있습니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 4. IDT and vector로 이동합니다.
4. IDT and vector
x86 IDT의 32~255 vector 중 Linux가 device IRQ용 vector를 CPU별로 할당합니다. Linux IRQ 번호, GSI, IOAPIC pin, APIC vector는 서로 다른 번호 공간입니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 5. Entry path로 이동합니다.
5. Entry path
CPU가 IDT gate를 따라 entry stub으로 들어오면 pt_regs를 만들고 common_interrupt가 vector를 Linux irqdomain/irq_desc로 변환합니다. handler 전후 Local APIC EOI 시점도 chip flow handler에 달려 있습니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 6. MSI/MSI-X로 이동합니다.
6. MSI/MSI-X
PCIe 장치는 물리 핀 대신 APIC 대상 주소와 vector data를 Memory Write TLP로 보냅니다. MSI-X table entry마다 address/data/mask가 있어 queue별 affinity를 구성할 수 있습니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 7. Interrupt remapping으로 이동합니다.
7. Interrupt remapping
IOMMU의 interrupt remapping이 켜지면 장치 message는 직접 APIC 목적지를 담기보다 IRTE index/subhandle을 참조합니다. 보안과 CPU 수 확장성이 좋아지지만 검증 경계가 하나 늘어납니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 8. Linux runtime으로 이동합니다.
8. Linux runtime
ACPI/MADT, APIC mode 선택, irqdomain, vector allocation, request_irq, affinity 변경까지 연결합니다. `/proc/interrupts`만 보지 말고 장치 status와 APIC vector trace를 함께 대응시킵니다.
이 단계의 통과 증거를 저장한 뒤에만 최종 사용자 결과를 판정합니다.
04 · DIAGRAMS
주소와 상태가 이동하는 모습
legacy IRQ pin 또는 MSI address/data write, 활성 Local APIC, 유효한 IDT gate와 Linux vector allocation
장치 원인은 driver, IOAPIC/MSI route는 irqdomain과 PCI/MSI core, vector는 x86 vector allocator, in-service/EOI는 Local APIC이 관리
대상 logical CPU가 vector를 수신해 common_interrupt로 진입하고 Linux irq_desc와 device handler를 실행함
장치 원인이 제거되고 Local APIC EOI 뒤 ISR bit가 내려가며 vector와 Linux IRQ affinity가 의도한 CPU에서 안정적으로 처리됨
05 · DETAILED MANUAL
x86 PIC, APIC, IOAPIC와 MSI를 번호 공간별로 읽는 상세 매뉴얼
legacy IRQ/PIC, ACPI GSI와 IOAPIC input, Linux virq, Local APIC vector, MSI message address/data를 분리합니다. MADT topology에서 시작해 IRR/ISR/TMR, IDT entry, vector allocation, interrupt remapping과 EOI까지 연결합니다.
공개적으로 다시 확인할 수 있는 자료
공개되지 않은 제조사 레지스터나 제품별 오프셋은 추측하지 않았습니다. 아래의 제조사 자료, 아키텍처 문서, Linux v6.18.37 원본으로 확인할 수 있는 내용만 다뤘습니다.
Intel 64 and IA-32 SDM
Local APIC, x2APIC, interrupt/exception과 virtualization
Linux APIC code
APIC mode 선택과 초기화
Linux IOAPIC code
GSI와 redirection entry 관리
Linux vector allocator
CPU별 vector 할당과 이동
06 · REGISTERS
읽어야 할 상태와 해석
같은 동작에 대한 활성화 상태, 처리 상태, 자원 소유권, 완료 레지스터 값을 함께 기록합니다. 읽으면 지워지는 비트나 1을 써야 지워지는 비트가 있는지는 먼저 데이터시트에서 확인합니다.
| 레지스터/상태 | 소유 블록 | 검증할 의미 |
|---|---|---|
| IA32_APIC_BASE | CPU MSR | Local APIC enable, x2APIC mode, base |
| SVR / TPR / PPR | Local APIC | software enable과 현재 priority mask |
| IRR / ISR / TMR / EOI | Local APIC | pending, servicing, trigger mode, completion |
| ICR / LVT | Local APIC | IPI와 timer/error/local interrupt routing |
| IOAPIC RTE[n] | IOAPIC | vector, polarity, trigger, mask, destination |
| IDTR / IDT gate | CPU | vector별 entry address와 gate attributes |
| MSI/MSI-X address/data | PCI function | destination/APIC message와 vector |
| IRTE | VT-d/AMD IOMMU | source validation, destination, vector remap |
07 · LINUX SOURCE
Linux v6.18.37 원본 코드와 줄별 해설
라인 번호를 고정해 외우는 대신 함수 선언을 기준으로 Linux v6.18.37 tree에서 다시 찾았습니다. 로컬 tree에 있는 파일은 실제 코드를 직접 싣고, 나머지는 원본 파일과 읽을 함수 좌표를 연결했습니다.
Linux v6.18.37 · 실제 원본 코드
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로
arch/x86/kernel/apic/apic.c:1345-1413
1345 if (!lapic_is_integrated()) /* 82489DX */
1346 value |= APIC_LVT_LEVEL_TRIGGER;
1347 if (apic_extnmi == APIC_EXTNMI_NONE)
1348 value |= APIC_LVT_MASKED;
1349 apic_write(APIC_LVT1, value);
1350}
1351
1352static void __init apic_bsp_setup(bool upmode);
1353
1354/* Init the interrupt delivery mode for the BSP */
1355void __init apic_intr_mode_init(void)
1356{
1357 bool upmode = IS_ENABLED(CONFIG_UP_LATE_INIT);
1358
1359 switch (apic_intr_mode) {
1360 case APIC_PIC:
1361 pr_info("APIC: Keep in PIC mode(8259)\n");
1362 return;
1363 case APIC_VIRTUAL_WIRE:
1364 pr_info("APIC: Switch to virtual wire mode setup\n");
1365 break;
1366 case APIC_VIRTUAL_WIRE_NO_CONFIG:
1367 pr_info("APIC: Switch to virtual wire mode setup with no configuration\n");
1368 upmode = true;
1369 break;
1370 case APIC_SYMMETRIC_IO:
1371 pr_info("APIC: Switch to symmetric I/O mode setup\n");
1372 break;
1373 case APIC_SYMMETRIC_IO_NO_ROUTING:
1374 pr_info("APIC: Switch to symmetric I/O mode setup in no SMP routine\n");
1375 break;
1376 }
1377
1378 x86_64_probe_apic();
1379
1380 if (x86_platform.apic_post_init)
1381 x86_platform.apic_post_init();
1382
1383 apic_bsp_setup(upmode);
1384}
1385
1386static void lapic_setup_esr(void)
1387{
1388 unsigned int oldvalue, value, maxlvt;
1389
1390 if (!lapic_is_integrated()) {
1391 pr_info("No ESR for 82489DX.\n");
1392 return;
1393 }
1394
1395 if (apic->disable_esr) {
1396 /*
1397 * Something untraceable is creating bad interrupts on
1398 * secondary quads ... for the moment, just leave the
1399 * ESR disabled - we can't do anything useful with the
1400 * errors anyway - mbligh
1401 */
1402 pr_info("Leaving ESR disabled.\n");
1403 return;
1404 }
1405
1406 maxlvt = lapic_get_maxlvt();
1407 if (maxlvt > 3) /* Due to the Pentium erratum 3AP. */
1408 apic_write(APIC_ESR, 0);
1409 oldvalue = apic_read(APIC_ESR);
1410
1411 /* enables sending errors */
1412 value = ERROR_APIC_VECTOR;
1413 apic_write(APIC_LVTERR, value);Linux v6.18.37 · 실제 원본 코드
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry
310 handle_irq(desc, regs);
311 return true;
312}
313
314/*
315 * common_interrupt() handles all normal device IRQ's (the special SMP
316 * cross-CPU interrupts have their own entry points).
317 */
318DEFINE_IDTENTRY_IRQ(common_interrupt)
319{
320 struct pt_regs *old_regs = set_irq_regs(regs);
321
322 /* entry code tells RCU that we're not quiescent. Check it. */
323 RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");
324
325 if (unlikely(!call_irq_handler(vector, regs)))
326 apic_eoi();
327
328 set_irq_regs(old_regs);
329}
330
331#ifdef CONFIG_X86_LOCAL_APIC
332/* Function pointer for generic interrupt vector handling */
333void (*x86_platform_ipi_callback)(void) = NULL;
334/*
335 * Handler for X86_PLATFORM_IPI_VECTOR.
336 */
337DEFINE_IDTENTRY_SYSVEC(sysvec_x86_platform_ipi)
338{
339 struct pt_regs *old_regs = set_irq_regs(regs);
340
341 apic_eoi();
342 trace_x86_platform_ipi_entry(X86_PLATFORM_IPI_VECTOR);
343 inc_irq_stat(x86_platform_ipis);
344 if (x86_platform_ipi_callback)
345 x86_platform_ipi_callback();
346 trace_x86_platform_ipi_exit(X86_PLATFORM_IPI_VECTOR);
347 set_irq_regs(old_regs);
348}
349#endif
350
351#if IS_ENABLED(CONFIG_KVM)
352static void dummy_handler(void) {}
353static void (*kvm_posted_intr_wakeup_handler)(void) = dummy_handler;
354
355void kvm_set_posted_intr_wakeup_handler(void (*handler)(void))
356{Linux v6.18.37 · 원본 좌표
arch/x86/kernel/apic/vector.c
Linux IRQ에 CPU별 APIC vector를 할당하고 이동시키는 구현
- 읽을 함수
x86_vector_alloc_irqs()- 읽을 함수
assign_irq_vector()- 읽을 함수
apic_ack_edge()
08 · VERIFICATION
실제 장비에서 확인하는 순서
- Firmware topology
실행 ACPI MADT에서 Local APIC/x2APIC, IOAPIC, interrupt source override 항목을 확인합니다.
통과 기준 OS가 선택한 APIC ID, GSI base, override가 firmware table과 일치해야 합니다.
- Route
실행 핀 방식이면 IOAPIC RTE, MSI면 PCI capability와 MSI-X table의 mask/address/data를 기록합니다.
통과 기준 unmask된 entry가 Linux affinity와 같은 목적지/vector를 가리켜야 합니다.
- APIC state
실행 debugger 또는 kernel trace로 해당 vector의 IRR→ISR 이동과 EOI 후 clear를 확인합니다.
통과 기준 발생 전후 vector 한 개의 상태 변화가 설명 가능해야 합니다.
- Linux mapping
실행 `/proc/interrupts`, `/proc/irq/N/smp_affinity_list`, irq vector trace를 함께 수집합니다.
통과 기준 Linux IRQ와 실제 처리 CPU/vector의 관계가 affinity 변경 후에도 일관되어야 합니다.
- Load and migration
실행 queue별 traffic을 만들며 CPU hotplug와 affinity 이동을 반복합니다.
통과 기준 old vector가 quiesce된 뒤 새 CPU vector로 손실·중복 없이 이동해야 합니다.
09 · FAILURE MATRIX
처음 끊긴 경계로 원인을 좁히기
| 관측 결과 | 우선 의심할 경계 | 다음 증거 |
|---|---|---|
| 장치 status 없음 | 장치 자체 동작 또는 interrupt enable 문제 | device status/mask |
| status 있음, IOAPIC/MSI 미전달 | 잘못된 GSI, masked RTE/table, MSI capability 설정 오류 | RTE 또는 MSI-X entry |
| message 전달, APIC IRR 없음 | destination APIC ID, interrupt remap/IRTE 오류 | IOMMU fault와 APIC state |
| IRR 있음, entry 없음 | TPR/PPR priority, IF mask, IDT/vector 설정 문제 | RFLAGS.IF, TPR, IDT gate |
| entry 있음, handler/완료 오류 | vector→virq mapping, shared IRQ, EOI 또는 장치 clear 문제 | common_interrupt trace와 ISR |
핵심은 마지막 오류 메시지가 아니라 처음 기대값과 달라진 checkpoint입니다. 그보다 아래의 통과 증거와 그 지점의 실패 증거를 한 묶음으로 남겨야 수정의 효과도 검증할 수 있습니다.