← HardwareDUJINLABS.COM

Hardware 06 · Interrupt · Linux v6.18.37

RISC-V PLIC와 AIA: source에서 S-mode handler까지

장치 interrupt source가 PLIC gateway, pending, context enable, threshold, claim/complete를 거쳐 supervisor external interrupt로 전달되는 경로를 먼저 확인하고, APLIC·IMSIC 기반 AIA가 무엇을 바꾸는지 비교합니다.

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

01 · QUESTION

무엇을 증명할 것인가

SCAUSE의 external interrupt bit가 보이지 않을 때 장치 source, PLIC context, hart delegation 중 어디서 막혔는지 어떤 순서로 분리할 것인가?

장치 interrupt source가 PLIC gateway, pending, context enable, threshold, claim/complete를 거쳐 supervisor external interrupt로 전달되는 경로를 먼저 확인하고, APLIC·IMSIC 기반 AIA가 무엇을 바꾸는지 비교합니다. 이 글은 Linux 로그를 출발점으로 삼지 않습니다. 같은 사건이 보드, controller, firmware, kernel에서 어떤 이름과 번호로 바뀌는지 아래에서 위로 연결합니다.

02 · CONTRACT

입력과 완료 조건

구분확인할 내용
입력장치의 level/edge interrupt source, 해당 hart의 PLIC context 또는 AIA interrupt file, M-mode의 위임 설정
출력S-mode trap 진입 후 Linux가 hwirq를 claim하고 등록된 driver handler를 실행한 뒤 complete 또는 EOI 처리
소유권장치 원인은 peripheral driver, priority/enable/claim은 PLIC context, privilege delegation은 firmware, virq dispatch는 Linux IRQ core가 소유
완료 조건claim한 source ID를 같은 context의 complete register에 기록하고 장치 원인이 제거되어 gateway가 새 요청을 정상 수용함

03 · BOTTOM-UP

완전히 아래에서 시작하는 8단계

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

PLIC에 들어가는 source가 level인지 edge-latched인지 SoC manual에서 확인합니다. PLIC gateway는 source별 요청을 한 번에 하나만 전달하므로 장치 원인 제거와 complete 순서가 중요합니다.

START
02
2. Priority and pending

priority 0은 사실상 disabled이며 pending bit는 gateway가 요청을 수락했음을 뜻합니다. pending만 있고 context에 전달되지 않으면 priority와 threshold를 비교합니다.

CHECKPOINT
03
3. Context enable

PLIC context는 hart와 privilege mode의 조합입니다. 같은 hart라도 M-mode context와 S-mode context의 enable bitmap, threshold, claim register 주소가 다릅니다.

CHECKPOINT
04
4. Hart local interrupt

PLIC이 context line을 올리면 mip.SEIP가 set됩니다. mideleg, sie.SEIE, sstatus.SIE가 모두 허용되어야 S-mode trap으로 진입합니다.

CHECKPOINT
05
5. Claim and complete

claim read는 가장 높은 priority ID를 반환하고 해당 요청을 servicing 상태로 옮깁니다. handler가 장치 원인을 제거한 뒤 같은 ID를 complete에 써야 gateway가 다음 요청을 받습니다.

CHECKPOINT
06
6. Firmware description

Device Tree의 interrupt-controller, #interrupt-cells, interrupts-extended가 source와 hart context를 연결합니다. SBI 또는 M-mode firmware가 delegation과 hart startup을 준비해야 합니다.

CHECKPOINT
07
7. Linux domains

riscv-intc가 hart-local cause를 처리하고 chained PLIC handler가 claim ID를 PLIC irqdomain으로 넘깁니다. Linux virq는 source ID와 같다고 가정하면 안 됩니다.

CHECKPOINT
08
8. AIA path

AIA에서는 APLIC이 wired source를 direct delivery 또는 MSI로 변환하고 IMSIC이 hart별 interrupt file에서 message를 받습니다. PLIC의 claim/complete와 다른 pending/enable/EOI 모델을 별도로 검증합니다.

LINUX / RESULT
01

1. Device source

PLIC에 들어가는 source가 level인지 edge-latched인지 SoC manual에서 확인합니다. PLIC gateway는 source별 요청을 한 번에 하나만 전달하므로 장치 원인 제거와 complete 순서가 중요합니다.

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

02

2. Priority and pending

priority 0은 사실상 disabled이며 pending bit는 gateway가 요청을 수락했음을 뜻합니다. pending만 있고 context에 전달되지 않으면 priority와 threshold를 비교합니다.

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

03

3. Context enable

PLIC context는 hart와 privilege mode의 조합입니다. 같은 hart라도 M-mode context와 S-mode context의 enable bitmap, threshold, claim register 주소가 다릅니다.

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

04

4. Hart local interrupt

PLIC이 context line을 올리면 mip.SEIP가 set됩니다. mideleg, sie.SEIE, sstatus.SIE가 모두 허용되어야 S-mode trap으로 진입합니다.

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

05

5. Claim and complete

claim read는 가장 높은 priority ID를 반환하고 해당 요청을 servicing 상태로 옮깁니다. handler가 장치 원인을 제거한 뒤 같은 ID를 complete에 써야 gateway가 다음 요청을 받습니다.

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

06

6. Firmware description

Device Tree의 interrupt-controller, #interrupt-cells, interrupts-extended가 source와 hart context를 연결합니다. SBI 또는 M-mode firmware가 delegation과 hart startup을 준비해야 합니다.

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

07

7. Linux domains

riscv-intc가 hart-local cause를 처리하고 chained PLIC handler가 claim ID를 PLIC irqdomain으로 넘깁니다. Linux virq는 source ID와 같다고 가정하면 안 됩니다.

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

08

8. AIA path

AIA에서는 APLIC이 wired source를 direct delivery 또는 MSI로 변환하고 IMSIC이 hart별 interrupt file에서 message를 받습니다. PLIC의 claim/complete와 다른 pending/enable/EOI 모델을 별도로 검증합니다.

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

04 · DIAGRAMS

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

그림 2. Interrupt 실행 경로왼쪽에서 오른쪽으로 실제 소유권과 관찰 지점이 이동합니다.
01 Device source
02 PLIC gateway
03 pending+priority
04 context enable/threshold
05 SEIP
06 S-mode trap
07 claim
08 driver+complete
그림 3. firmware와 Linux 번호 공간의 연결왼쪽에서 오른쪽으로 실제 소유권과 관찰 지점이 이동합니다.
01 Device source
02 APLIC sourcecfg
03 MSI message
04 IMSIC interrupt file
05 SEIP
06 TOPEI/EOI
07 Linux handler
그림 4. 입력, 소유권, 완료 조건의 경계완료 조건을 충족하기 전에는 다음 계층이 해당 자원을 재사용하면 안 됩니다.
INPUT

장치의 level/edge interrupt source, 해당 hart의 PLIC context 또는 AIA interrupt file, M-mode의 위임 설정

OWNER

장치 원인은 peripheral driver, priority/enable/claim은 PLIC context, privilege delegation은 firmware, virq dispatch는 Linux IRQ core가 소유

OUTPUT

S-mode trap 진입 후 Linux가 hwirq를 claim하고 등록된 driver handler를 실행한 뒤 complete 또는 EOI 처리

DONE

claim한 source ID를 같은 context의 complete register에 기록하고 장치 원인이 제거되어 gateway가 새 요청을 정상 수용함

05 · DETAILED MANUAL

RISC-V trap, PLIC와 AIA를 단계별로 읽는 상세 매뉴얼

hart의 mip/mie와 trap entry, PLIC gateway·pending·enable·priority·threshold·claim/complete context를 먼저 읽고, AIA의 APLIC wired source와 IMSIC interrupt file/MSI delivery를 별도 구조로 비교합니다.

공개적으로 다시 확인할 수 있는 자료

공개되지 않은 vendor register나 integration별 offset을 추측해서 채우지 않았습니다. 아래 제조사 자료, architecture 문서와 Linux v6.18.37 원본에서 다시 확인할 수 있는 범위만 사용했습니다.

06 · REGISTERS

읽어야 할 상태와 해석

값 하나만 떼어 보지 말고 동일 사건의 enable, status, ownership, completion register를 한 줄에 기록합니다. read-to-clear나 write-one-to-clear 속성은 반드시 datasheet에서 먼저 확인합니다.

레지스터/상태소유 블록검증할 의미
SOURCE_PRIORITY[n]PLIC globalsource 우선순위, 0이면 전달 안 됨
PENDING[n]PLIC globalgateway가 접수한 source pending
ENABLE[context][n]PLIC context특정 hart/privilege로 전달 허용
THRESHOLD[context]PLIC contextpriority가 threshold보다 커야 전달
CLAIM/COMPLETE[context]PLIC context가장 높은 ID 수신과 처리 완료 통지
mideleg / sie / sip / sstatusRISC-V CSRSEIP 위임, enable, pending, global mask
APLIC_SOURCECFG / TARGETAIA APLICwired source mode와 hart/EIID target
IMSIC_EIDELIVERY / EITHRESHOLD / EIEAIA IMSIChart-local message delivery와 enable

07 · LINUX SOURCE

Linux v6.18.37 원본 코드와 줄별 해설

라인 번호를 고정해 외우는 대신 함수 선언을 기준으로 Linux v6.18.37 tree에서 다시 찾았습니다. 로컬 tree에 있는 파일은 실제 코드를 직접 싣고, 나머지는 원본 파일과 읽을 함수 좌표를 연결했습니다.

Linux v6.18.37 · 실제 원본 코드

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler

drivers/irqchip/irq-sifive-plic.c:375-435

375};
376 
377/*
378 * Handling an interrupt is a two-step process: first you claim the interrupt
379 * by reading the claim register, then you complete the interrupt by writing
380 * that source ID back to the same claim register.  This automatically enables
381 * and disables the interrupt, so there's nothing else to do.
382 */
383static void plic_handle_irq(struct irq_desc *desc)
384{
385	struct plic_handler *handler = this_cpu_ptr(&plic_handlers);
386	struct irq_chip *chip = irq_desc_get_chip(desc);
387	void __iomem *claim = handler->hart_base + CONTEXT_CLAIM;
388	irq_hw_number_t hwirq;
389 
390	WARN_ON_ONCE(!handler->present);
391 
392	chained_irq_enter(chip, desc);
393 
394	while ((hwirq = readl(claim))) {
395		int err = generic_handle_domain_irq(handler->priv->irqdomain,
396						    hwirq);
397		if (unlikely(err)) {
398			pr_warn_ratelimited("%pfwP: can't find mapping for hwirq %lu\n",
399					    handler->priv->fwnode, hwirq);
400		}
401	}
402 
403	chained_irq_exit(chip, desc);
404}
405 
406static void plic_set_threshold(struct plic_handler *handler, u32 threshold)
407{
408	/* priority must be > threshold to trigger an interrupt */
409	writel(threshold, handler->hart_base + CONTEXT_THRESHOLD);
410}
411 
412static int plic_dying_cpu(unsigned int cpu)
413{
414	if (plic_parent_irq)
415		disable_percpu_irq(plic_parent_irq);
416 
417	return 0;
418}
419 
420static int plic_starting_cpu(unsigned int cpu)
421{
422	struct plic_handler *handler = this_cpu_ptr(&plic_handlers);
423 
424	if (plic_parent_irq)
425		enable_percpu_irq(plic_parent_irq,
426				  irq_get_trigger_type(plic_parent_irq));
427	else
428		pr_warn("%pfwP: cpu%d: parent irq not available\n",
429			handler->priv->fwnode, cpu);
430	plic_set_threshold(handler, PLIC_ENABLE_THRESHOLD);
431 
432	return 0;
433}
434 
435static const struct of_device_id plic_match[] = {
L375 };

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L376 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L377 /*

원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L378 * Handling an interrupt is a two-step process: first you claim the interrupt

원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L379 * by reading the claim register, then you complete the interrupt by writing

원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L380 * that source ID back to the same claim register. This automatically enables

원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L381 * and disables the interrupt, so there's nothing else to do.

원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L382 */

원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L383 static void plic_handle_irq(struct irq_desc *desc)

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.

L384 {

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L385 struct plic_handler *handler = this_cpu_ptr(&plic_handlers);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L386 struct irq_chip *chip = irq_desc_get_chip(desc);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L387 void __iomem *claim = handler->hart_base + CONTEXT_CLAIM;

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L388 irq_hw_number_t hwirq;

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L389 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L390 WARN_ON_ONCE(!handler->present);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L391 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L392 chained_irq_enter(chip, desc);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L393 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L394 while ((hwirq = readl(claim))) {

여러 pending source 또는 resource를 순회하는 구간입니다. 한 번의 진입에서 처리 가능한 양과 종료 조건이 interrupt latency에 영향을 줍니다.

L395 int err = generic_handle_domain_irq(handler->priv->irqdomain,

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.

L396 hwirq);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L397 if (unlikely(err)) {

상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.

L398 pr_warn_ratelimited("%pfwP: can't find mapping for hwirq %lu\n",

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L399 handler->priv->fwnode, hwirq);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L400 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L401 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L402 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L403 chained_irq_exit(chip, desc);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L404 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L405 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L406 static void plic_set_threshold(struct plic_handler *handler, u32 threshold)

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.

L407 {

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L408 /* priority must be > threshold to trigger an interrupt */

원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L409 writel(threshold, handler->hart_base + CONTEXT_THRESHOLD);

hardware 또는 공유 메모리에 상태를 게시합니다. write가 완료되는 지점과 다음 read/doorbell 사이의 ordering 조건을 확인합니다.

L410 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L411 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L412 static int plic_dying_cpu(unsigned int cpu)

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.

L413 {

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L414 if (plic_parent_irq)

상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.

L415 disable_percpu_irq(plic_parent_irq);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L416 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L417 return 0;

호출자에게 상태를 반환합니다. 반환값만 보지 말고 그 전에 controller ownership이나 active 상태가 정리되었는지 확인합니다.

L418 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L419 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L420 static int plic_starting_cpu(unsigned int cpu)

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.

L421 {

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L422 struct plic_handler *handler = this_cpu_ptr(&plic_handlers);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L423 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L424 if (plic_parent_irq)

상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.

L425 enable_percpu_irq(plic_parent_irq,

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L426 irq_get_trigger_type(plic_parent_irq));

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L427 else

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L428 pr_warn("%pfwP: cpu%d: parent irq not available\n",

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L429 handler->priv->fwnode, cpu);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L430 plic_set_threshold(handler, PLIC_ENABLE_THRESHOLD);

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L431 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L432 return 0;

호출자에게 상태를 반환합니다. 반환값만 보지 말고 그 전에 controller ownership이나 active 상태가 정리되었는지 확인합니다.

L433 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L434 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L435 static const struct of_device_id plic_match[] = {

PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

Linux v6.18.37 · 실제 원본 코드

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로

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

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L22 #include <asm/hwcap.h>

이 구현이 사용하는 architecture helper, IRQ core 또는 controller 자료구조의 선언을 가져옵니다.

L23 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L24 static struct irq_domain *intc_domain;

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L25 static unsigned int riscv_intc_nr_irqs __ro_after_init = BITS_PER_LONG;

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L26 static unsigned int riscv_intc_custom_base __ro_after_init = BITS_PER_LONG;

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L27 static unsigned int riscv_intc_custom_nr_irqs __ro_after_init;

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L28 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L29 static void riscv_intc_irq(struct pt_regs *regs)

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.

L30 {

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

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

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L32 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L33 if (generic_handle_domain_irq(intc_domain, cause))

상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.

L34 pr_warn_ratelimited("Failed to handle interrupt (cause: %ld)\n", cause);

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L35 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L36 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L37 static void riscv_intc_aia_irq(struct pt_regs *regs)

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.

L38 {

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L39 unsigned long topi;

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L40 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

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

여러 pending source 또는 resource를 순회하는 구간입니다. 한 번의 진입에서 처리 가능한 양과 종료 조건이 interrupt latency에 영향을 줍니다.

L42 generic_handle_domain_irq(intc_domain, topi >> TOPI_IID_SHIFT);

hardware ID를 Linux IRQ core로 넘기는 경계입니다. 여기부터 hwirq, virq, irq_desc, irqaction의 번호 공간을 구분해 추적합니다.

L43 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L44 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L45 /*

원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

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

원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

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

원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

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

원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L49 * corresponds to the IRQ chip.

원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L50 */

원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

L51 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L52 static void riscv_intc_irq_mask(struct irq_data *d)

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.

L53 {

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

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

상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.

L55 csr_clear(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L56 else

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

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

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L58 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L59 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L60 static void riscv_intc_irq_unmask(struct irq_data *d)

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.

L61 {

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

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

상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.

L63 csr_set(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L64 else

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

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

scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.

L66 }

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L67 (blank)

논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.

L68 static void andes_intc_irq_mask(struct irq_data *d)

함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.

L69 {

제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.

L70 /*

원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

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

원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.

Linux v6.18.37 · 원본 좌표

drivers/irqchip/irq-riscv-imsic-platform.c

AIA IMSIC platform device와 MSI irqdomain을 구성하는 Linux 구현

읽을 함수
imsic_platform_probe()
읽을 함수
imsic_irq_domain_alloc()

Linux v6.18.37 원본 파일 열기 →

08 · VERIFICATION

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

  1. Source

    실행 장치 raw status와 interrupt output 조건을 읽고 가능하면 핀 또는 SoC trace를 측정합니다.

    통과 기준 source 원인이 실제로 set되고 mask를 통과해야 합니다.

  2. PLIC global

    실행 source ID의 priority와 pending bit를 사건 전후로 기록합니다.

    통과 기준 priority는 threshold보다 크고 assert 뒤 pending이 보여야 합니다.

  3. Context

    실행 실행 중인 hart/S-mode에 해당하는 enable bitmap, threshold, claim 주소를 확인합니다.

    통과 기준 다른 context를 읽는 실수를 배제하고 claim이 기대 ID를 반환해야 합니다.

  4. CSR

    실행 trap entry에서 scause, sepc, sstatus, sie, sip를 기록합니다.

    통과 기준 interrupt bit와 supervisor external cause가 일치하고 SEIP가 전달되어야 합니다.

  5. Completion

    실행 장치 clear와 complete write 전후로 pending과 재진입 횟수를 비교합니다.

    통과 기준 level source가 deassert되고 같은 ID가 이유 없이 즉시 재claim되지 않아야 합니다.

09 · FAILURE MATRIX

처음 끊긴 경계로 원인을 좁히기

관측 결과우선 의심할 경계다음 증거
장치 status 없음peripheral clock/reset/configuration 문제장치 raw status
status 있음, PLIC pending 없음source ID/배선/gateway mode 문제SoC interrupt map과 pending bit
pending 있음, SEIP 없음priority 0, enable/threshold, 잘못된 contextPLIC context register
SEIP 있음, S-mode trap 없음mideleg/sie/sstatus 또는 M-mode firmware 문제CSR dump와 firmware 설정
claim 반복 또는 정지장치 clear/complete 순서, 다른 context complete, AIA/PLIC 모델 혼동claim log와 device status

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