← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 09/21

IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC

device가 interrupt line이나 MSI를 올린 뒤 CPU가 interrupt ID를 읽고 irq_desc handler를 실행하고 EOI하는 전체 경로를 controller 관점에서 비교합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 187 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

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된다.

지연 시간 관점interrupt latency는 controller routing, priority masking, CPU interrupt-off 구간, irq_desc contention과 threaded handler wakeup으로 나뉜다. 최악값은 같은 priority의 폭주와 긴 hardirq-disabled section이 만든다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64GICv3 distributor, redistributor와 ICC system register interfacespurious INTID를 정상 IRQ로 처리하거나 level source를 clear하기 전에 deactivate하면 같은 interrupt가 연속 재진입한다.INTID, group/priority, redistributor affinity, pending/active bit, irq_desc depth와 device status를 같은 timestamp로 남긴다.
x86-64local APIC vector, IOAPIC redirection과 IDT dispatchvector를 재사용하기 전에 이전 CPU의 in-flight interrupt를 drain하지 않으면 다른 device irq_desc를 호출할 수 있다.APIC mode xAPIC/x2APIC, vector, destination, IOAPIC RTE, remote IRR, IRTE와 irq migration status를 본다.
RISC-VS-mode local interrupt와 external APLIC/PLIC/IMSIC domainSCAUSE code를 Linux hwirq로 잘못 변환하거나 PLIC complete를 빼면 해당 source가 막히거나 계속 pending 상태로 남는다.hart id, SCAUSE, SIE/SIP, PLIC claim ID 또는 IMSIC EIID, threshold와 irqdomain parent chain을 기록한다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

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을 기록한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractirqdomain이 hardware interrupt number를 Linux virtual IRQ에 매핑하고 irqchip callback이 mask, unmask, ack, eoi, affinity를 제공한다. flow handler는 edge/level 성격에 맞춰 irq_desc lock과 device action chain을 실행한다.
arm64GICv3 distributor, redistributor와 ICC system register interfaceIAR read 뒤 handler 실행, ICC_EOIR1_EL1과 필요 시 ICC_DIR_EL1 순서를 지켜 active state를 해제한다.
x86-64local APIC vector, IOAPIC redirection과 IDT dispatchAPIC EOI와 level-triggered IOAPIC remote IRR, interrupt remapping의 ordering이 affinity 이동 중 중복/손실을 막는다.
RISC-VS-mode local interrupt와 external APLIC/PLIC/IMSIC domainSIE bit와 controller threshold/enable, claim 뒤 device clear와 complete 순서를 맞춰야 한다.
lifetime boundaryirqdomain mapping과 irq_desc는 device가 IRQ를 요청한 동안 유지된다. hot-unplug은 affinity 이동, synchronize_irq와 pending interrupt drain 뒤 mapping을 해제해야 한다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비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-64state 준비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-Vstate 준비SIE bit와 controller threshold/enable, claim 뒤 device clear와 complete 순서를 맞춰야 한다.관찰: 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개 줄에 각각 설명을 붙였습니다.

L956 gic_pmr_mask_irqs();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L957 isb();

memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.

L958 irqnr = gic_read_iar();

CPU interface에서 가장 높은 우선순위의 pending INTID를 acknowledge하며 active 상태로 전환한다.

L959 gic_write_pmr(pmr);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L960(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L961 __gic_handle_nmi(irqnr, regs);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L962}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L963(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L964static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L965{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L966 if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs)))

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L967 __gic_handle_irq_from_irqsoff(regs);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L968 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L969 __gic_handle_irq_from_irqson(regs);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L970}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L971(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L972static void __init gic_dist_init(void)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L973{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L974 unsigned int i;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L975 u64 affinity;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L976 void __iomem *base = gic_data.dist_base;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L977 u32 val;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L978(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L979 /* Disable the distributor */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L980 writel_relaxed(0, base + GICD_CTLR);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L981 gic_dist_wait_for_rwp();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L982(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L983 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L984 * Configure SPIs as non-secure Group-1. This will only matter

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L985 * if the GIC only has a single security state. This will not

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L986 * do the right thing if the kernel is running in secure mode,

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L987 * but that's not the intended use case anyway.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L988 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L989 for (i = 32; i < GIC_LINE_NR; i += 32)

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L990 writel_relaxed(~0, base + GICD_IGROUPR + i / 8);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L991(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L992 /* Extended SPI range, not handled by the GICv2/GICv3 common code */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L993 for (i = 0; i < GIC_ESPI_NR; i += 32) {

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L994 writel_relaxed(~0U, base + GICD_ICENABLERnE + i / 8);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L995 writel_relaxed(~0U, base + GICD_ICACTIVERnE + i / 8);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L996 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L997(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L998 for (i = 0; i < GIC_ESPI_NR; i += 32)

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L999 writel_relaxed(~0U, base + GICD_IGROUPRnE + i / 8);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1000(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1001 for (i = 0; i < GIC_ESPI_NR; i += 16)

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L1002 writel_relaxed(0, base + GICD_ICFGRnE + i / 4);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1003(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1004 for (i = 0; i < GIC_ESPI_NR; i += 4)

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L1005 writel_relaxed(REPEAT_BYTE_U32(dist_prio_irq),

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1006 base + GICD_IPRIORITYRnE + i);

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1007(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1008 /* Now do the common stuff */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1009 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를 연결해 본다.

L1010(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1011 val = GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L1012 if (gic_data.rdists.gicd_typer2 & GICD_TYPER2_nASSGIcap) {

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1013 pr_info("Enabling SGIs without active state\n");

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1014 val |= GICD_CTLR_nASSGIreq;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L1015 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1016(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1017 /* Enable distributor with ARE, Group1, and wait for it to drain */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1018 writel_relaxed(val, base + GICD_CTLR);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1019 gic_dist_wait_for_rwp();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1020(blank)

빈 줄은 arm64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1021 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1022 * Set all global interrupts to the boot CPU only. ARE must be

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1023 * enabled.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1024 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1025 affinity = gic_cpu_to_affinity(smp_processor_id());

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1026 for (i = 32; i < GIC_LINE_NR; i++)

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L1027 gic_write_irouter(affinity, base + GICD_IROUTER + i * 8);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1028(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개 줄에 각각 설명을 붙였습니다.

L1347 if (apic_extnmi == APIC_EXTNMI_NONE)

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1348 value |= APIC_LVT_MASKED;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L1349 apic_write(APIC_LVT1, value);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1350}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1351(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1352static void __init apic_bsp_setup(bool upmode);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1353(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1354/* Init the interrupt delivery mode for the BSP */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1355void __init apic_intr_mode_init(void)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1356{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1357 bool upmode = IS_ENABLED(CONFIG_UP_LATE_INIT);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1358(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1359 switch (apic_intr_mode) {

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1360 case APIC_PIC:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1361 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를 연결해 본다.

L1362 return;

이 함수가 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1363 case APIC_VIRTUAL_WIRE:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1364 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를 연결해 본다.

L1365 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L1366 case APIC_VIRTUAL_WIRE_NO_CONFIG:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1367 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를 연결해 본다.

L1368 upmode = true;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L1369 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L1370 case APIC_SYMMETRIC_IO:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1371 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를 연결해 본다.

L1372 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L1373 case APIC_SYMMETRIC_IO_NO_ROUTING:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1374 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를 연결해 본다.

L1375 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L1376 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1377(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1378 x86_64_probe_apic();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1379(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1380 if (x86_platform.apic_post_init)

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1381 x86_platform.apic_post_init();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1382(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1383 apic_bsp_setup(upmode);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1384}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1385(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1386static void lapic_setup_esr(void)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1387{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1388 unsigned int oldvalue, value, maxlvt;

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1389(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1390 if (!lapic_is_integrated()) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1391 pr_info("No ESR for 82489DX.\n");

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1392 return;

이 함수가 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1393 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1394(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1395 if (apic->disable_esr) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1396 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1397 * Something untraceable is creating bad interrupts on

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1398 * secondary quads ... for the moment, just leave the

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1399 * ESR disabled - we can't do anything useful with the

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1400 * errors anyway - mbligh

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1401 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1402 pr_info("Leaving ESR disabled.\n");

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1403 return;

이 함수가 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1404 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1405(blank)

빈 줄은 x86-64 IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1406 maxlvt = lapic_get_maxlvt();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1407 if (maxlvt > 3) /* Due to the Pentium erratum 3AP. */

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1408 apic_write(APIC_ESR, 0);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1409 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개 줄에 각각 설명을 붙였습니다.

L21(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L22#include <asm/hwcap.h>

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L23(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L24static struct irq_domain *intc_domain;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L25static unsigned int riscv_intc_nr_irqs __ro_after_init = BITS_PER_LONG;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L26static unsigned int riscv_intc_custom_base __ro_after_init = BITS_PER_LONG;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L27static unsigned int riscv_intc_custom_nr_irqs __ro_after_init;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L28(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L29static void riscv_intc_irq(struct pt_regs *regs)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L30{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L31 unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L32(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L33 if (generic_handle_domain_irq(intc_domain, cause))

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L34 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를 연결해 본다.

L35}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L36(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L37static void riscv_intc_aia_irq(struct pt_regs *regs)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L38{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L39 unsigned long topi;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L40(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L41 while ((topi = csr_read(CSR_TOPI)))

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L42 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를 연결해 본다.

L43}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L44(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L45/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L46 * On RISC-V systems local interrupts are masked or unmasked by writing

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L47 * the SIE (Supervisor Interrupt Enable) CSR. As CSRs can only be written

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L48 * on the local hart, these functions can only be called on the hart that

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L49 * corresponds to the IRQ chip.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L50 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L51(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L52static void riscv_intc_irq_mask(struct irq_data *d)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L53{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L54 if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L55 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를 연결해 본다.

L56 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L57 csr_clear(CSR_IE, BIT(d->hwirq));

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L58}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L59(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L60static void riscv_intc_irq_unmask(struct irq_data *d)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L61{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L62 if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L63 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를 연결해 본다.

L64 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L65 csr_set(CSR_IE, BIT(d->hwirq));

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L66}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L67(blank)

빈 줄은 RISC-V IRQ controller: GICv3, local APIC/IOAPIC와 RISC-V INTC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L68static void andes_intc_irq_mask(struct irq_data *d)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L69{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L70 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L71 * Andes specific S-mode local interrupt causes (hwirq)

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

level-triggered device IRQ가 종료되는 조건

device status bit가 1인 동안 line을 계속 assert하는 network device를 가정한다.

  1. acceptcontroller가 pending을 active로 옮기고 CPU가 INTID/vector/claim ID를 읽는다.
  2. servicedriver가 device status를 읽고 RX descriptor를 처리한 뒤 MMIO write로 원인을 clear한다.
  3. orderingposted MMIO write가 device에 도달하기 전에 controller를 deactivate하면 line이 아직 높아 재pending될 수 있다.
  4. finish필요한 readback/barrier 뒤 EOI/complete를 수행하고 unmask한다.

결론EOI는 device 원인을 지우는 연산이 아니다. device 상태와 controller active state 두 축이 모두 끝나야 interrupt가 종료된다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 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된다.

02

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로 남긴다.

03

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를 본다.

04

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을 기록한다.

05

객체 수명과 소유권을 먼저 고정한다

irqdomain mapping과 irq_desc는 device가 IRQ를 요청한 동안 유지된다. hot-unplug은 affinity 이동, synchronize_irq와 pending interrupt drain 뒤 mapping을 해제해야 한다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

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

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64spurious INTID를 정상 IRQ로 처리하거나 level source를 clear하기 전에 deactivate하면 같은 interrupt가 연속 재진입한다.INTID, group/priority, redistributor affinity, pending/active bit, irq_desc depth와 device status를 같은 timestamp로 남긴다.
x86-64vector를 재사용하기 전에 이전 CPU의 in-flight interrupt를 drain하지 않으면 다른 device irq_desc를 호출할 수 있다.APIC mode xAPIC/x2APIC, vector, destination, IOAPIC RTE, remote IRR, IRTE와 irq migration status를 본다.
RISC-VSCAUSE code를 Linux hwirq로 잘못 변환하거나 PLIC complete를 빼면 해당 source가 막히거나 계속 pending 상태로 남는다.hart id, SCAUSE, SIE/SIP, PLIC claim ID 또는 IMSIC EIID, threshold와 irqdomain parent chain을 기록한다.

08 · LAB

재현과 계측 절차

  1. irqsoff tracer와 controller register dump를 결합해 interrupt assertion부터 handler 시작까지 최대 지연을 측정한다.
  2. affinity를 반복 이동하면서 MSI/vector/INTID와 irq_desc mapping이 언제 바뀌는지 추적한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.