← Hardware

Hardware 07 · Interrupt · Linux v6.18.37

x86 APIC·IOAPIC·MSI: vector에서 Linux IRQ까지

legacy PIC에서 Local APIC, IOAPIC, MSI/MSI-X, x2APIC, interrupt remapping으로 이어지는 x86 interrupt 구조를 한 장치 사건의 주소·데이터·vector 변화로 연결합니다.

검증 깊이
board → Linux 8 layers
그림
4 boundary diagrams
원본 코드
2 excerpts · 116 lines
기준
Linux v6.18.37

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단계

그림 1. 아래에서 Linux까지 올라오는 8개 검증 경계위 계층의 로그보다 아래 계층의 물리 증거를 먼저 확정합니다.
01
1. Legacy baseline

8259A PIC의 IRQ0~15, cascade, edge/level 한계를 출발점으로 잡습니다. 최신 시스템에서도 early boot와 호환 경로를 이해해야 APIC 전환 로그를 해석할 수 있습니다.

START
02
2. Local APIC

각 logical CPU의 APIC ID, SVR, TPR, IRR, ISR, TMR, EOI, LVT를 확인합니다. xAPIC MMIO와 x2APIC MSR 접근 방식은 다르지만 vector와 priority 모델은 이어집니다.

CHECKPOINT
03
3. IOAPIC

핀 기반 장치 interrupt는 redirection table의 vector, delivery mode, polarity, trigger, mask, destination으로 변환됩니다. ACPI MADT interrupt source override가 legacy IRQ와 GSI 관계를 바꿀 수 있습니다.

CHECKPOINT
04
4. IDT and vector

x86 IDT의 32~255 vector 중 Linux가 device IRQ용 vector를 CPU별로 할당합니다. Linux IRQ 번호, GSI, IOAPIC pin, APIC vector는 서로 다른 번호 공간입니다.

CHECKPOINT
05
5. Entry path

CPU가 IDT gate를 따라 entry stub으로 들어오면 pt_regs를 만들고 common_interrupt가 vector를 Linux irqdomain/irq_desc로 변환합니다. handler 전후 Local APIC EOI 시점도 chip flow handler에 달려 있습니다.

CHECKPOINT
06
6. MSI/MSI-X

PCIe 장치는 물리 핀 대신 APIC 대상 주소와 vector data를 Memory Write TLP로 보냅니다. MSI-X table entry마다 address/data/mask가 있어 queue별 affinity를 구성할 수 있습니다.

CHECKPOINT
07
7. Interrupt remapping

IOMMU의 interrupt remapping이 켜지면 장치 message는 직접 APIC 목적지를 담기보다 IRTE index/subhandle을 참조합니다. 보안과 CPU 수 확장성이 좋아지지만 검증 경계가 하나 늘어납니다.

CHECKPOINT
08
8. Linux runtime

ACPI/MADT, APIC mode 선택, irqdomain, vector allocation, request_irq, affinity 변경까지 연결합니다. `/proc/interrupts`만 보지 말고 장치 status와 APIC vector trace를 함께 대응시킵니다.

LINUX / RESULT
01

1. Legacy baseline

8259A PIC의 IRQ0~15, cascade, edge/level 한계를 출발점으로 잡습니다. 최신 시스템에서도 early boot와 호환 경로를 이해해야 APIC 전환 로그를 해석할 수 있습니다.

이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 2. Local APIC로 이동합니다.

02

2. Local APIC

각 logical CPU의 APIC ID, SVR, TPR, IRR, ISR, TMR, EOI, LVT를 확인합니다. xAPIC MMIO와 x2APIC MSR 접근 방식은 다르지만 vector와 priority 모델은 이어집니다.

이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 3. IOAPIC로 이동합니다.

03

3. IOAPIC

핀 기반 장치 interrupt는 redirection table의 vector, delivery mode, polarity, trigger, mask, destination으로 변환됩니다. ACPI MADT interrupt source override가 legacy IRQ와 GSI 관계를 바꿀 수 있습니다.

이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 4. IDT and vector로 이동합니다.

04

4. IDT and vector

x86 IDT의 32~255 vector 중 Linux가 device IRQ용 vector를 CPU별로 할당합니다. Linux IRQ 번호, GSI, IOAPIC pin, APIC vector는 서로 다른 번호 공간입니다.

이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 5. Entry path로 이동합니다.

05

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로 이동합니다.

06

6. MSI/MSI-X

PCIe 장치는 물리 핀 대신 APIC 대상 주소와 vector data를 Memory Write TLP로 보냅니다. MSI-X table entry마다 address/data/mask가 있어 queue별 affinity를 구성할 수 있습니다.

이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 7. Interrupt remapping으로 이동합니다.

07

7. Interrupt remapping

IOMMU의 interrupt remapping이 켜지면 장치 message는 직접 APIC 목적지를 담기보다 IRTE index/subhandle을 참조합니다. 보안과 CPU 수 확장성이 좋아지지만 검증 경계가 하나 늘어납니다.

이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 8. Linux runtime으로 이동합니다.

08

8. Linux runtime

ACPI/MADT, APIC mode 선택, irqdomain, vector allocation, request_irq, affinity 변경까지 연결합니다. `/proc/interrupts`만 보지 말고 장치 status와 APIC vector trace를 함께 대응시킵니다.

이 단계의 통과 증거를 저장한 뒤에만 최종 사용자 결과를 판정합니다.

04 · DIAGRAMS

주소와 상태가 이동하는 모습

그림 2. Interrupt 실행 경로왼쪽부터 신호나 데이터를 처리하는 순서를 나타냅니다. 각 단계에서 확인할 항목을 함께 표시했습니다.
01 Device pin
02 GSI/IOAPIC pin
03 redirection entry
04 Local APIC IRR
05 IDT vector
06 common_interrupt
07 irq_desc
08 handler+EOI
그림 3. firmware와 Linux 번호 공간의 연결왼쪽부터 신호나 데이터를 처리하는 순서를 나타냅니다. 각 단계에서 확인할 항목을 함께 표시했습니다.
01 PCIe queue
02 MSI-X table address/data
03 Memory Write TLP
04 IRTE optional
05 Local APIC vector
06 Linux handler
그림 4. 입력, 소유권, 완료 조건의 경계완료 조건을 충족하기 전에는 다음 계층이 해당 자원을 재사용하면 안 됩니다.
INPUT

legacy IRQ pin 또는 MSI address/data write, 활성 Local APIC, 유효한 IDT gate와 Linux vector allocation

OWNER

장치 원인은 driver, IOAPIC/MSI route는 irqdomain과 PCI/MSI core, vector는 x86 vector allocator, in-service/EOI는 Local APIC이 관리

OUTPUT

대상 logical CPU가 vector를 수신해 common_interrupt로 진입하고 Linux irq_desc와 device handler를 실행함

DONE

장치 원인이 제거되고 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 원본으로 확인할 수 있는 내용만 다뤘습니다.

06 · REGISTERS

읽어야 할 상태와 해석

같은 동작에 대한 활성화 상태, 처리 상태, 자원 소유권, 완료 레지스터 값을 함께 기록합니다. 읽으면 지워지는 비트나 1을 써야 지워지는 비트가 있는지는 먼저 데이터시트에서 확인합니다.

레지스터/상태소유 블록검증할 의미
IA32_APIC_BASECPU MSRLocal APIC enable, x2APIC mode, base
SVR / TPR / PPRLocal APICsoftware enable과 현재 priority mask
IRR / ISR / TMR / EOILocal APICpending, servicing, trigger mode, completion
ICR / LVTLocal APICIPI와 timer/error/local interrupt routing
IOAPIC RTE[n]IOAPICvector, polarity, trigger, mask, destination
IDTR / IDT gateCPUvector별 entry address와 gate attributes
MSI/MSI-X address/dataPCI functiondestination/APIC message와 vector
IRTEVT-d/AMD IOMMUsource 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

arch/x86/kernel/irq.c:310-356

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()

Linux v6.18.37 원본 파일 열기 →

08 · VERIFICATION

실제 장비에서 확인하는 순서

  1. Firmware topology

    실행 ACPI MADT에서 Local APIC/x2APIC, IOAPIC, interrupt source override 항목을 확인합니다.

    통과 기준 OS가 선택한 APIC ID, GSI base, override가 firmware table과 일치해야 합니다.

  2. Route

    실행 핀 방식이면 IOAPIC RTE, MSI면 PCI capability와 MSI-X table의 mask/address/data를 기록합니다.

    통과 기준 unmask된 entry가 Linux affinity와 같은 목적지/vector를 가리켜야 합니다.

  3. APIC state

    실행 debugger 또는 kernel trace로 해당 vector의 IRR→ISR 이동과 EOI 후 clear를 확인합니다.

    통과 기준 발생 전후 vector 한 개의 상태 변화가 설명 가능해야 합니다.

  4. Linux mapping

    실행 `/proc/interrupts`, `/proc/irq/N/smp_affinity_list`, irq vector trace를 함께 수집합니다.

    통과 기준 Linux IRQ와 실제 처리 CPU/vector의 관계가 affinity 변경 후에도 일관되어야 합니다.

  5. 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

공유 IRQ: 내 장치의 상태부터 확인합니다

Linux v6.18.37의 __handle_irq_event_percpu()는 등록된 irqaction들을 순회하며 반환값을 모읍니다. 한 handler가 IRQ_HANDLED를 반환했다고 그 자리에서 다른 handler 호출을 끝내는 구조가 아닙니다. 각 드라이버는 자신의 장치 status를 확인하고 자기 요청이 아니면 IRQ_NONE, 처리했다면 IRQ_HANDLED, 등록된 thread handler를 깨워야 하면 IRQ_WAKE_THREAD를 구분합니다.

공유 요청에서는 dev_id가 NULL이면 안 됩니다. 이 값은 장치별 상태를 찾고 등록을 해제할 대상을 구분하는 데 사용합니다. 장치 원인 비트 해제와 interrupt controller의 EOI는 같은 작업이 아니며, mask·ack·EOI의 순서를 모든 트리거 방식과 controller에 동일하게 적용하지 않습니다. 단순히 인터럽트가 공유된다는 이유만으로 장애나 성능 저하를 단정하지 않습니다.

고정 원문: v6.18.37 handle.c · request_threaded_irq의 공유 IRQ 계약. 장치 status를 함께 읽는 진단 관점이며 이 글에서 실제 IRQ 부하·VM 격리를 시험한 것은 아닙니다.

핵심은 마지막 오류 메시지가 아니라 처음 기대값과 달라진 checkpoint입니다. 그보다 아래의 통과 증거와 그 지점의 실패 증거를 한 묶음으로 남겨야 수정의 효과도 검증할 수 있습니다.