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까지 연결합니다.
공개적으로 다시 확인할 수 있는 자료
공개되지 않은 vendor register나 integration별 offset을 추측해서 채우지 않았습니다. 아래 제조사 자료, architecture 문서와 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
읽어야 할 상태와 해석
값 하나만 떼어 보지 말고 동일 사건의 enable, status, ownership, completion register를 한 줄에 기록합니다. read-to-clear나 write-one-to-clear 속성은 반드시 datasheet에서 먼저 확인합니다.
| 레지스터/상태 | 소유 블록 | 검증할 의미 |
|---|---|---|
| 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);if (!lapic_is_integrated()) /* 82489DX */
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
value |= APIC_LVT_LEVEL_TRIGGER;
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
if (apic_extnmi == APIC_EXTNMI_NONE)
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
value |= APIC_LVT_MASKED;
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
apic_write(APIC_LVT1, value);
hardware 또는 공유 메모리에 상태를 게시합니다. write가 완료되는 지점과 다음 read/doorbell 사이의 ordering 조건을 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static void __init apic_bsp_setup(bool upmode);
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 입력 계약을 만족하는지 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
/* Init the interrupt delivery mode for the BSP */
원본 주석입니다. 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
void __init apic_intr_mode_init(void)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
bool upmode = IS_ENABLED(CONFIG_UP_LATE_INIT);
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 입력 계약을 만족하는지 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
switch (apic_intr_mode) {
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
case APIC_PIC:
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
pr_info("APIC: Keep in PIC mode(8259)\n");
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
return;
호출자에게 상태를 반환합니다. 반환값만 보지 말고 그 전에 controller ownership이나 active 상태가 정리되었는지 확인합니다.
case APIC_VIRTUAL_WIRE:
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
pr_info("APIC: Switch to virtual wire mode setup\n");
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
break;
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
case APIC_VIRTUAL_WIRE_NO_CONFIG:
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
pr_info("APIC: Switch to virtual wire mode setup with no configuration\n");
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
upmode = true;
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
break;
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
case APIC_SYMMETRIC_IO:
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
pr_info("APIC: Switch to symmetric I/O mode setup\n");
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
break;
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
case APIC_SYMMETRIC_IO_NO_ROUTING:
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
pr_info("APIC: Switch to symmetric I/O mode setup in no SMP routine\n");
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
break;
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
x86_64_probe_apic();
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
if (x86_platform.apic_post_init)
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
x86_platform.apic_post_init();
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
apic_bsp_setup(upmode);
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static void lapic_setup_esr(void)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
unsigned int oldvalue, value, maxlvt;
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
if (!lapic_is_integrated()) {
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
pr_info("No ESR for 82489DX.\n");
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
return;
호출자에게 상태를 반환합니다. 반환값만 보지 말고 그 전에 controller ownership이나 active 상태가 정리되었는지 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
if (apic->disable_esr) {
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
/*
원본 주석입니다. 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* Something untraceable is creating bad interrupts on
원본 주석입니다. 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* secondary quads ... for the moment, just leave the
원본 주석입니다. 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* ESR disabled - we can't do anything useful with the
원본 주석입니다. 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* errors anyway - mbligh
원본 주석입니다. 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
*/
원본 주석입니다. 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
pr_info("Leaving ESR disabled.\n");
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
return;
호출자에게 상태를 반환합니다. 반환값만 보지 말고 그 전에 controller ownership이나 active 상태가 정리되었는지 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
maxlvt = lapic_get_maxlvt();
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
if (maxlvt > 3) /* Due to the Pentium erratum 3AP. */
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
apic_write(APIC_ESR, 0);
hardware 또는 공유 메모리에 상태를 게시합니다. write가 완료되는 지점과 다음 read/doorbell 사이의 ordering 조건을 확인합니다.
oldvalue = apic_read(APIC_ESR);
공유 상태나 MMIO를 읽습니다. 이 값은 단순 변수라기보다 hardware가 갱신한 시점과 read ordering을 포함해 해석해야 합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
/* enables sending errors */
원본 주석입니다. 부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
value = ERROR_APIC_VECTOR;
부팅 중 PIC, virtual wire, symmetric I/O APIC interrupt mode를 선택하고 초기화하는 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
apic_write(APIC_LVTERR, value);
hardware 또는 공유 메모리에 상태를 게시합니다. write가 완료되는 지점과 다음 read/doorbell 사이의 ordering 조건을 확인합니다.
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{handle_irq(desc, regs);
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
return true;
호출자에게 상태를 반환합니다. 반환값만 보지 말고 그 전에 controller ownership이나 active 상태가 정리되었는지 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
/*
원본 주석입니다. IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* common_interrupt() handles all normal device IRQ's (the special SMP
원본 주석입니다. IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* cross-CPU interrupts have their own entry points).
원본 주석입니다. IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
*/
원본 주석입니다. IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
DEFINE_IDTENTRY_IRQ(common_interrupt)
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
struct pt_regs *old_regs = set_irq_regs(regs);
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
/* entry code tells RCU that we're not quiescent. Check it. */
원본 주석입니다. IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
if (unlikely(!call_irq_handler(vector, regs)))
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
apic_eoi();
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
set_irq_regs(old_regs);
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
#ifdef CONFIG_X86_LOCAL_APIC
빌드 설정에 따라 포함되는 경로입니다. 실행 중 분기가 아니므로 실제 커널의 .config와 전처리 결과를 함께 봐야 합니다.
/* Function pointer for generic interrupt vector handling */
원본 주석입니다. IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
void (*x86_platform_ipi_callback)(void) = NULL;
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 입력 계약을 만족하는지 확인합니다.
/*
원본 주석입니다. IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* Handler for X86_PLATFORM_IPI_VECTOR.
원본 주석입니다. IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
*/
원본 주석입니다. IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
DEFINE_IDTENTRY_SYSVEC(sysvec_x86_platform_ipi)
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
struct pt_regs *old_regs = set_irq_regs(regs);
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
apic_eoi();
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
trace_x86_platform_ipi_entry(X86_PLATFORM_IPI_VECTOR);
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
inc_irq_stat(x86_platform_ipis);
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
if (x86_platform_ipi_callback)
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
x86_platform_ipi_callback();
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
trace_x86_platform_ipi_exit(X86_PLATFORM_IPI_VECTOR);
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
set_irq_regs(old_regs);
IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
#endif
빌드 설정에 따라 포함되는 경로입니다. 실행 중 분기가 아니므로 실제 커널의 .config와 전처리 결과를 함께 봐야 합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
#if IS_ENABLED(CONFIG_KVM)
빌드 설정에 따라 포함되는 경로입니다. 실행 중 분기가 아니므로 실제 커널의 .config와 전처리 결과를 함께 봐야 합니다.
static void dummy_handler(void) {}
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 입력 계약을 만족하는지 확인합니다.
static void (*kvm_posted_intr_wakeup_handler)(void) = dummy_handler;
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 입력 계약을 만족하는지 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
void kvm_set_posted_intr_wakeup_handler(void (*handler)(void))
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 IDT device vector 진입을 Linux IRQ dispatch로 연결하는 공통 entry의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
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입니다. 그보다 아래의 통과 증거와 그 지점의 실패 증거를 한 묶음으로 남겨야 수정의 효과도 검증할 수 있습니다.