01 · QUESTION
무엇을 확인할 것인가
hardware pending bit가 Linux IRQ number와 irq_desc로 변환되고, level interrupt가 다시 들어오지 않도록 mask와 EOI를 어떤 순서로 다루는가?
irqdomain이 hardware interrupt number를 Linux virtual IRQ에 매핑하고 irqchip callback이 mask, unmask, ack, eoi, affinity를 제공한다. flow handler는 edge/level 성격에 맞춰 irq_desc lock과 device action chain을 실행한다.
controller acknowledge로 active state를 얻는 구간, generic_handle_domain_irq가 driver handler를 호출하는 구간, EOI/deactivate 구간을 분리한다. level source는 device status를 먼저 지우지 않으면 즉시 재assert된다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | GICv3 distributor, redistributor와 ICC system register interface | spurious INTID를 정상 IRQ로 처리하거나 level source를 clear하기 전에 deactivate하면 같은 interrupt가 연속 재진입한다. | INTID, group/priority, redistributor affinity, pending/active bit, irq_desc depth와 device status를 같은 timestamp로 남긴다. |
| x86-64 | local APIC vector, IOAPIC redirection과 IDT dispatch | vector를 재사용하기 전에 이전 CPU의 in-flight interrupt를 drain하지 않으면 다른 device irq_desc를 호출할 수 있다. | APIC mode xAPIC/x2APIC, vector, destination, IOAPIC RTE, remote IRR, IRTE와 irq migration status를 본다. |
| RISC-V | S-mode local interrupt와 external APLIC/PLIC/IMSIC domain | SCAUSE code를 Linux hwirq로 잘못 변환하거나 PLIC complete를 빼면 해당 source가 막히거나 계속 pending 상태로 남는다. | hart id, SCAUSE, SIE/SIP, PLIC claim ID 또는 IMSIC EIID, threshold와 irqdomain parent chain을 기록한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- GICv3 distributor, redistributor와 ICC system register interface
- state
- CPU는 ICC_IAR1_EL1에서 INTID를 acknowledge하고 irqdomain을 통해 irq_desc로 보낸다. SPI는 distributor, PPI/SGI는 redistributor의 per-CPU 상태를 사용하며 EOImode에 따라 priority drop과 deactivate가 나뉜다.
- checkpoint
- INTID, group/priority, redistributor affinity, pending/active bit, irq_desc depth와 device status를 같은 timestamp로 남긴다.
x86-64
- mechanism
- local APIC vector, IOAPIC redirection과 IDT dispatch
- state
- IOAPIC pin 또는 MSI message가 destination APIC ID와 vector를 선택한다. local APIC가 vector를 accept하면 IDT stub이 vector별 handler로 들어가고 vector_irq per-CPU mapping이 irq_desc를 찾는다.
- checkpoint
- APIC mode xAPIC/x2APIC, vector, destination, IOAPIC RTE, remote IRR, IRTE와 irq migration status를 본다.
RISC-V
- mechanism
- S-mode local interrupt와 external APLIC/PLIC/IMSIC domain
- state
riscv_intc는 SCAUSE에 들어온 supervisor software/timer/external cause를 per-hart domain으로 전달한다. external device interrupt는 PLIC claim/complete 또는 APLIC/IMSIC MSI interrupt-file을 거쳐 별도 irqdomain에서 해석된다.- checkpoint
- hart id, SCAUSE, SIE/SIP, PLIC claim ID 또는 IMSIC EIID, threshold와 irqdomain parent chain을 기록한다.
04 · SOURCE
Linux 6.18.37 원본 코드와 줄별 설명
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.
arm64 · Linux 6.18.37
GICv3 distributor, redistributor와 ICC system register interface
CPU는 ICC_IAR1_EL1에서 INTID를 acknowledge하고 irqdomain을 통해 irq_desc로 보낸다. SPI는 distributor, PPI/SGI는 redistributor의 per-CPU 상태를 사용하며 EOImode에 따라 priority drop과 deactivate가 나뉜다.
원본 코드: drivers/irqchip/irq-gic-v3.c:956-1028
956 gic_pmr_mask_irqs();
957 isb();
958 irqnr = gic_read_iar();
959 gic_write_pmr(pmr);
960
961 __gic_handle_nmi(irqnr, regs);
962}
963
964static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
965{
966 if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs)))
967 __gic_handle_irq_from_irqsoff(regs);
968 else
969 __gic_handle_irq_from_irqson(regs);
970}
971
972static void __init gic_dist_init(void)
973{
974 unsigned int i;
975 u64 affinity;
976 void __iomem *base = gic_data.dist_base;
977 u32 val;
978
979 /* Disable the distributor */
980 writel_relaxed(0, base + GICD_CTLR);
981 gic_dist_wait_for_rwp();
982
983 /*
984 * Configure SPIs as non-secure Group-1. This will only matter
985 * if the GIC only has a single security state. This will not
986 * do the right thing if the kernel is running in secure mode,
987 * but that's not the intended use case anyway.
988 */
989 for (i = 32; i < GIC_LINE_NR; i += 32)
990 writel_relaxed(~0, base + GICD_IGROUPR + i / 8);
991
992 /* Extended SPI range, not handled by the GICv2/GICv3 common code */
993 for (i = 0; i < GIC_ESPI_NR; i += 32) {
994 writel_relaxed(~0U, base + GICD_ICENABLERnE + i / 8);
995 writel_relaxed(~0U, base + GICD_ICACTIVERnE + i / 8);
996 }
997
998 for (i = 0; i < GIC_ESPI_NR; i += 32)
999 writel_relaxed(~0U, base + GICD_IGROUPRnE + i / 8);
1000
1001 for (i = 0; i < GIC_ESPI_NR; i += 16)
1002 writel_relaxed(0, base + GICD_ICFGRnE + i / 4);
1003
1004 for (i = 0; i < GIC_ESPI_NR; i += 4)
1005 writel_relaxed(REPEAT_BYTE_U32(dist_prio_irq),
1006 base + GICD_IPRIORITYRnE + i);
1007
1008 /* Now do the common stuff */
1009 gic_dist_config(base, GIC_LINE_NR, dist_prio_irq);
1010
1011 val = GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1;
1012 if (gic_data.rdists.gicd_typer2 & GICD_TYPER2_nASSGIcap) {
1013 pr_info("Enabling SGIs without active state\n");
1014 val |= GICD_CTLR_nASSGIreq;
1015 }
1016
1017 /* Enable distributor with ARE, Group1, and wait for it to drain */
1018 writel_relaxed(val, base + GICD_CTLR);
1019 gic_dist_wait_for_rwp();
1020
1021 /*
1022 * Set all global interrupts to the boot CPU only. ARE must be
1023 * enabled.
1024 */
1025 affinity = gic_cpu_to_affinity(smp_processor_id());
1026 for (i = 32; i < GIC_LINE_NR; i++)
1027 gic_write_irouter(affinity, base + GICD_IROUTER + i * 8);
1028 라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 73개 줄에 각각 설명을 붙였습니다.
gic_pmr_mask_irqs();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
isb();memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.
irqnr = gic_read_iar();CPU interface에서 가장 높은 우선순위의 pending INTID를 acknowledge하며 active 상태로 전환한다.
gic_write_pmr(pmr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
__gic_handle_nmi(irqnr, regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs)))이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
__gic_handle_irq_from_irqsoff(regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
__gic_handle_irq_from_irqson(regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void __init gic_dist_init(void)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned int i;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
u64 affinity;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
void __iomem *base = gic_data.dist_base;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
u32 val;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Disable the distributor */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
writel_relaxed(0, base + GICD_CTLR);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
gic_dist_wait_for_rwp();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Configure SPIs as non-secure Group-1. This will only matterLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* if the GIC only has a single security state. This will notLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* do the right thing if the kernel is running in secure mode,Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* but that's not the intended use case anyway.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
for (i = 32; i < GIC_LINE_NR; i += 32)range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
writel_relaxed(~0, base + GICD_IGROUPR + i / 8);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Extended SPI range, not handled by the GICv2/GICv3 common code */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
for (i = 0; i < GIC_ESPI_NR; i += 32) {range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
writel_relaxed(~0U, base + GICD_ICENABLERnE + i / 8);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
writel_relaxed(~0U, base + GICD_ICACTIVERnE + i / 8);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
for (i = 0; i < GIC_ESPI_NR; i += 32)range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
writel_relaxed(~0U, base + GICD_IGROUPRnE + i / 8);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
for (i = 0; i < GIC_ESPI_NR; i += 16)range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
writel_relaxed(0, base + GICD_ICFGRnE + i / 4);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
for (i = 0; i < GIC_ESPI_NR; i += 4)range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
writel_relaxed(REPEAT_BYTE_U32(dist_prio_irq),이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
base + GICD_IPRIORITYRnE + i);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Now do the common stuff */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
gic_dist_config(base, GIC_LINE_NR, dist_prio_irq);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
val = GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (gic_data.rdists.gicd_typer2 & GICD_TYPER2_nASSGIcap) {이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
pr_info("Enabling SGIs without active state\n");helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
val |= GICD_CTLR_nASSGIreq;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Enable distributor with ARE, Group1, and wait for it to drain */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
writel_relaxed(val, base + GICD_CTLR);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
gic_dist_wait_for_rwp();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Set all global interrupts to the boot CPU only. ARE must beLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* enabled.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
affinity = gic_cpu_to_affinity(smp_processor_id());helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
for (i = 32; i < GIC_LINE_NR; i++)range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
gic_write_irouter(affinity, base + GICD_IROUTER + i * 8);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
x86-64 · Linux 6.18.37
local APIC vector, IOAPIC redirection과 IDT dispatch
IOAPIC pin 또는 MSI message가 destination APIC ID와 vector를 선택한다. local APIC가 vector를 accept하면 IDT stub이 vector별 handler로 들어가고 vector_irq per-CPU mapping이 irq_desc를 찾는다.
원본 코드: arch/x86/kernel/apic/apic.c:1347-1409
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);라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 63개 줄에 각각 설명을 붙였습니다.
if (apic_extnmi == APIC_EXTNMI_NONE)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
value |= APIC_LVT_MASKED;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
apic_write(APIC_LVT1, value);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void __init apic_bsp_setup(bool upmode);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Init the interrupt delivery mode for the BSP */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
void __init apic_intr_mode_init(void)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
bool upmode = IS_ENABLED(CONFIG_UP_LATE_INIT);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
switch (apic_intr_mode) {hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case APIC_PIC:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
pr_info("APIC: Keep in PIC mode(8259)\n");helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return;이 함수가 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
case APIC_VIRTUAL_WIRE:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
pr_info("APIC: Switch to virtual wire mode setup\n");helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case APIC_VIRTUAL_WIRE_NO_CONFIG:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
pr_info("APIC: Switch to virtual wire mode setup with no configuration\n");helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
upmode = true;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case APIC_SYMMETRIC_IO:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
pr_info("APIC: Switch to symmetric I/O mode setup\n");helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case APIC_SYMMETRIC_IO_NO_ROUTING:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
pr_info("APIC: Switch to symmetric I/O mode setup in no SMP routine\n");helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
x86_64_probe_apic();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (x86_platform.apic_post_init)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
x86_platform.apic_post_init();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
apic_bsp_setup(upmode);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void lapic_setup_esr(void)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned int oldvalue, value, maxlvt;이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!lapic_is_integrated()) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
pr_info("No ESR for 82489DX.\n");helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return;이 함수가 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (apic->disable_esr) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Something untraceable is creating bad interrupts onLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* secondary quads ... for the moment, just leave theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* ESR disabled - we can't do anything useful with theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* errors anyway - mblighLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
pr_info("Leaving ESR disabled.\n");helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return;이 함수가 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
maxlvt = lapic_get_maxlvt();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (maxlvt > 3) /* Due to the Pentium erratum 3AP. */이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
apic_write(APIC_ESR, 0);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
oldvalue = apic_read(APIC_ESR);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
RISC-V · Linux 6.18.37
S-mode local interrupt와 external APLIC/PLIC/IMSIC domain
riscv_intc는 SCAUSE에 들어온 supervisor software/timer/external cause를 per-hart domain으로 전달한다. external device interrupt는 PLIC claim/complete 또는 APLIC/IMSIC MSI interrupt-file을 거쳐 별도 irqdomain에서 해석된다.
원본 코드: drivers/irqchip/irq-riscv-intc.c:21-71
21
22#include <asm/hwcap.h>
23
24static struct irq_domain *intc_domain;
25static unsigned int riscv_intc_nr_irqs __ro_after_init = BITS_PER_LONG;
26static unsigned int riscv_intc_custom_base __ro_after_init = BITS_PER_LONG;
27static unsigned int riscv_intc_custom_nr_irqs __ro_after_init;
28
29static void riscv_intc_irq(struct pt_regs *regs)
30{
31 unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG;
32
33 if (generic_handle_domain_irq(intc_domain, cause))
34 pr_warn_ratelimited("Failed to handle interrupt (cause: %ld)\n", cause);
35}
36
37static void riscv_intc_aia_irq(struct pt_regs *regs)
38{
39 unsigned long topi;
40
41 while ((topi = csr_read(CSR_TOPI)))
42 generic_handle_domain_irq(intc_domain, topi >> TOPI_IID_SHIFT);
43}
44
45/*
46 * On RISC-V systems local interrupts are masked or unmasked by writing
47 * the SIE (Supervisor Interrupt Enable) CSR. As CSRs can only be written
48 * on the local hart, these functions can only be called on the hart that
49 * corresponds to the IRQ chip.
50 */
51
52static void riscv_intc_irq_mask(struct irq_data *d)
53{
54 if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)
55 csr_clear(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));
56 else
57 csr_clear(CSR_IE, BIT(d->hwirq));
58}
59
60static void riscv_intc_irq_unmask(struct irq_data *d)
61{
62 if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)
63 csr_set(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));
64 else
65 csr_set(CSR_IE, BIT(d->hwirq));
66}
67
68static void andes_intc_irq_mask(struct irq_data *d)
69{
70 /*
71 * Andes specific S-mode local interrupt causes (hwirq)라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 51개 줄에 각각 설명을 붙였습니다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#include <asm/hwcap.h>compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static struct irq_domain *intc_domain;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
static unsigned int riscv_intc_nr_irqs __ro_after_init = BITS_PER_LONG;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
static unsigned int riscv_intc_custom_base __ro_after_init = BITS_PER_LONG;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
static unsigned int riscv_intc_custom_nr_irqs __ro_after_init;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void riscv_intc_irq(struct pt_regs *regs)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (generic_handle_domain_irq(intc_domain, cause))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
pr_warn_ratelimited("Failed to handle interrupt (cause: %ld)\n", cause);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void riscv_intc_aia_irq(struct pt_regs *regs)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned long topi;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
while ((topi = csr_read(CSR_TOPI)))range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
generic_handle_domain_irq(intc_domain, topi >> TOPI_IID_SHIFT);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* On RISC-V systems local interrupts are masked or unmasked by writingLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* the SIE (Supervisor Interrupt Enable) CSR. As CSRs can only be writtenLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* on the local hart, these functions can only be called on the hart thatLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* corresponds to the IRQ chip.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void riscv_intc_irq_mask(struct irq_data *d)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
csr_clear(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
csr_clear(CSR_IE, BIT(d->hwirq));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void riscv_intc_irq_unmask(struct irq_data *d)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
csr_set(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
csr_set(CSR_IE, BIT(d->hwirq));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void andes_intc_irq_mask(struct irq_data *d)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Andes specific S-mode local interrupt causes (hwirq)Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
05 · WORKED EXAMPLE
숫자로 검산하기
level-triggered device IRQ가 종료되는 조건
device status bit가 1인 동안 line을 계속 assert하는 network device를 가정한다.
- acceptcontroller가 pending을 active로 옮기고 CPU가 INTID/vector/claim ID를 읽는다.
- servicedriver가 device status를 읽고 RX descriptor를 처리한 뒤 MMIO write로 원인을 clear한다.
- orderingposted MMIO write가 device에 도달하기 전에 controller를 deactivate하면 line이 아직 높아 재pending될 수 있다.
- finish필요한 readback/barrier 뒤 EOI/complete를 수행하고 unmask한다.
결론EOI는 device 원인을 지우는 연산이 아니다. device 상태와 controller active state 두 축이 모두 끝나야 interrupt가 종료된다.
06 · DEEP DIVE
경계별 상세 분석
공통 kernel core와 architecture hook의 경계
irqdomain이 hardware interrupt number를 Linux virtual IRQ에 매핑하고 irqchip callback이 mask, unmask, ack, eoi, affinity를 제공한다. flow handler는 edge/level 성격에 맞춰 irq_desc lock과 device action chain을 실행한다.
controller acknowledge로 active state를 얻는 구간, generic_handle_domain_irq가 driver handler를 호출하는 구간, EOI/deactivate 구간을 분리한다. level source는 device status를 먼저 지우지 않으면 즉시 재assert된다.
arm64: GICv3 distributor, redistributor와 ICC system register interface
CPU는 ICC_IAR1_EL1에서 INTID를 acknowledge하고 irqdomain을 통해 irq_desc로 보낸다. SPI는 distributor, PPI/SGI는 redistributor의 per-CPU 상태를 사용하며 EOImode에 따라 priority drop과 deactivate가 나뉜다.
IAR read 뒤 handler 실행, ICC_EOIR1_EL1과 필요 시 ICC_DIR_EL1 순서를 지켜 active state를 해제한다. 디버깅할 때는 INTID, group/priority, redistributor affinity, pending/active bit, irq_desc depth와 device status를 같은 timestamp로 남긴다.
x86-64: local APIC vector, IOAPIC redirection과 IDT dispatch
IOAPIC pin 또는 MSI message가 destination APIC ID와 vector를 선택한다. local APIC가 vector를 accept하면 IDT stub이 vector별 handler로 들어가고 vector_irq per-CPU mapping이 irq_desc를 찾는다.
APIC EOI와 level-triggered IOAPIC remote IRR, interrupt remapping의 ordering이 affinity 이동 중 중복/손실을 막는다. 디버깅할 때는 APIC mode xAPIC/x2APIC, vector, destination, IOAPIC RTE, remote IRR, IRTE와 irq migration status를 본다.
RISC-V: S-mode local interrupt와 external APLIC/PLIC/IMSIC domain
riscv_intc는 SCAUSE에 들어온 supervisor software/timer/external cause를 per-hart domain으로 전달한다. external device interrupt는 PLIC claim/complete 또는 APLIC/IMSIC MSI interrupt-file을 거쳐 별도 irqdomain에서 해석된다.
SIE bit와 controller threshold/enable, claim 뒤 device clear와 complete 순서를 맞춰야 한다. 디버깅할 때는 hart id, SCAUSE, SIE/SIP, PLIC claim ID 또는 IMSIC EIID, threshold와 irqdomain parent chain을 기록한다.
객체 수명과 소유권을 먼저 고정한다
irqdomain mapping과 irq_desc는 device가 IRQ를 요청한 동안 유지된다. hot-unplug은 affinity 이동, synchronize_irq와 pending interrupt drain 뒤 mapping을 해제해야 한다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
latency upper bound는 hardware instruction 하나가 아니다
interrupt latency는 controller routing, priority masking, CPU interrupt-off 구간, irq_desc contention과 threaded handler wakeup으로 나뉜다. 최악값은 같은 priority의 폭주와 긴 hardirq-disabled section이 만든다.
평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | spurious INTID를 정상 IRQ로 처리하거나 level source를 clear하기 전에 deactivate하면 같은 interrupt가 연속 재진입한다. | INTID, group/priority, redistributor affinity, pending/active bit, irq_desc depth와 device status를 같은 timestamp로 남긴다. |
| x86-64 | vector를 재사용하기 전에 이전 CPU의 in-flight interrupt를 drain하지 않으면 다른 device irq_desc를 호출할 수 있다. | APIC mode xAPIC/x2APIC, vector, destination, IOAPIC RTE, remote IRR, IRTE와 irq migration status를 본다. |
| RISC-V | SCAUSE code를 Linux hwirq로 잘못 변환하거나 PLIC complete를 빼면 해당 source가 막히거나 계속 pending 상태로 남는다. | hart id, SCAUSE, SIE/SIP, PLIC claim ID 또는 IMSIC EIID, threshold와 irqdomain parent chain을 기록한다. |
08 · LAB
재현과 계측 절차
- irqsoff tracer와 controller register dump를 결합해 interrupt assertion부터 handler 시작까지 최대 지연을 측정한다.
- affinity를 반복 이동하면서 MSI/vector/INTID와 irq_desc mapping이 언제 바뀌는지 추적한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64drivers/irqchip/irq-gic-v3.c:956-1028
- x86-64arch/x86/kernel/apic/apic.c:1347-1409
- RISC-Vdrivers/irqchip/irq-riscv-intc.c:21-71
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.