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단계
PLIC에 들어가는 source가 level인지 edge-latched인지 SoC manual에서 확인합니다. PLIC gateway는 source별 요청을 한 번에 하나만 전달하므로 장치 원인 제거와 complete 순서가 중요합니다.
priority 0은 사실상 disabled이며 pending bit는 gateway가 요청을 수락했음을 뜻합니다. pending만 있고 context에 전달되지 않으면 priority와 threshold를 비교합니다.
PLIC context는 hart와 privilege mode의 조합입니다. 같은 hart라도 M-mode context와 S-mode context의 enable bitmap, threshold, claim register 주소가 다릅니다.
PLIC이 context line을 올리면 mip.SEIP가 set됩니다. mideleg, sie.SEIE, sstatus.SIE가 모두 허용되어야 S-mode trap으로 진입합니다.
claim read는 가장 높은 priority ID를 반환하고 해당 요청을 servicing 상태로 옮깁니다. handler가 장치 원인을 제거한 뒤 같은 ID를 complete에 써야 gateway가 다음 요청을 받습니다.
Device Tree의 interrupt-controller, #interrupt-cells, interrupts-extended가 source와 hart context를 연결합니다. SBI 또는 M-mode firmware가 delegation과 hart startup을 준비해야 합니다.
riscv-intc가 hart-local cause를 처리하고 chained PLIC handler가 claim ID를 PLIC irqdomain으로 넘깁니다. Linux virq는 source ID와 같다고 가정하면 안 됩니다.
AIA에서는 APLIC이 wired source를 direct delivery 또는 MSI로 변환하고 IMSIC이 hart별 interrupt file에서 message를 받습니다. PLIC의 claim/complete와 다른 pending/enable/EOI 모델을 별도로 검증합니다.
1. Device source
PLIC에 들어가는 source가 level인지 edge-latched인지 SoC manual에서 확인합니다. PLIC gateway는 source별 요청을 한 번에 하나만 전달하므로 장치 원인 제거와 complete 순서가 중요합니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 2. Priority and pending로 이동합니다.
2. Priority and pending
priority 0은 사실상 disabled이며 pending bit는 gateway가 요청을 수락했음을 뜻합니다. pending만 있고 context에 전달되지 않으면 priority와 threshold를 비교합니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 3. Context enable로 이동합니다.
3. Context enable
PLIC context는 hart와 privilege mode의 조합입니다. 같은 hart라도 M-mode context와 S-mode context의 enable bitmap, threshold, claim register 주소가 다릅니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 4. Hart local interrupt로 이동합니다.
4. Hart local interrupt
PLIC이 context line을 올리면 mip.SEIP가 set됩니다. mideleg, sie.SEIE, sstatus.SIE가 모두 허용되어야 S-mode trap으로 진입합니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 5. Claim and complete로 이동합니다.
5. Claim and complete
claim read는 가장 높은 priority ID를 반환하고 해당 요청을 servicing 상태로 옮깁니다. handler가 장치 원인을 제거한 뒤 같은 ID를 complete에 써야 gateway가 다음 요청을 받습니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 6. Firmware description로 이동합니다.
6. Firmware description
Device Tree의 interrupt-controller, #interrupt-cells, interrupts-extended가 source와 hart context를 연결합니다. SBI 또는 M-mode firmware가 delegation과 hart startup을 준비해야 합니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 7. Linux domains로 이동합니다.
7. Linux domains
riscv-intc가 hart-local cause를 처리하고 chained PLIC handler가 claim ID를 PLIC irqdomain으로 넘깁니다. Linux virq는 source ID와 같다고 가정하면 안 됩니다.
이 단계의 통과 증거를 저장한 뒤에만 다음 단계인 8. AIA path로 이동합니다.
8. AIA path
AIA에서는 APLIC이 wired source를 direct delivery 또는 MSI로 변환하고 IMSIC이 hart별 interrupt file에서 message를 받습니다. PLIC의 claim/complete와 다른 pending/enable/EOI 모델을 별도로 검증합니다.
이 단계의 통과 증거를 저장한 뒤에만 최종 사용자 결과를 판정합니다.
04 · DIAGRAMS
주소와 상태가 이동하는 모습
장치의 level/edge interrupt source, 해당 hart의 PLIC context 또는 AIA interrupt file, M-mode의 위임 설정
장치 원인은 peripheral driver, priority/enable/claim은 PLIC context, privilege delegation은 firmware, virq dispatch는 Linux IRQ core가 소유
S-mode trap 진입 후 Linux가 hwirq를 claim하고 등록된 driver handler를 실행한 뒤 complete 또는 EOI 처리
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 원본에서 다시 확인할 수 있는 범위만 사용했습니다.
RISC-V PLIC specification
gateway, priority, target context와 claim/complete
RISC-V AIA specification
APLIC, IMSIC, interrupt files와 virtualization
Linux SiFive PLIC driver
PLIC domain, enable, claim/complete와 chained handler
Linux RISC-V INTC
local interrupt cause와 root domain
06 · REGISTERS
읽어야 할 상태와 해석
값 하나만 떼어 보지 말고 동일 사건의 enable, status, ownership, completion register를 한 줄에 기록합니다. read-to-clear나 write-one-to-clear 속성은 반드시 datasheet에서 먼저 확인합니다.
| 레지스터/상태 | 소유 블록 | 검증할 의미 |
|---|---|---|
| SOURCE_PRIORITY[n] | PLIC global | source 우선순위, 0이면 전달 안 됨 |
| PENDING[n] | PLIC global | gateway가 접수한 source pending |
| ENABLE[context][n] | PLIC context | 특정 hart/privilege로 전달 허용 |
| THRESHOLD[context] | PLIC context | priority가 threshold보다 커야 전달 |
| CLAIM/COMPLETE[context] | PLIC context | 가장 높은 ID 수신과 처리 완료 통지 |
| mideleg / sie / sip / sstatus | RISC-V CSR | SEIP 위임, enable, pending, global mask |
| APLIC_SOURCECFG / TARGET | AIA APLIC | wired source mode와 hart/EIID target |
| IMSIC_EIDELIVERY / EITHRESHOLD / EIE | AIA IMSIC | hart-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[] = {};
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
/*
원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* Handling an interrupt is a two-step process: first you claim the interrupt
원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* by reading the claim register, then you complete the interrupt by writing
원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* that source ID back to the same claim register. This automatically enables
원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* and disables the interrupt, so there's nothing else to do.
원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
*/
원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
static void plic_handle_irq(struct irq_desc *desc)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
struct plic_handler *handler = this_cpu_ptr(&plic_handlers);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
struct irq_chip *chip = irq_desc_get_chip(desc);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
void __iomem *claim = handler->hart_base + CONTEXT_CLAIM;
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
irq_hw_number_t hwirq;
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
WARN_ON_ONCE(!handler->present);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
chained_irq_enter(chip, desc);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
while ((hwirq = readl(claim))) {
여러 pending source 또는 resource를 순회하는 구간입니다. 한 번의 진입에서 처리 가능한 양과 종료 조건이 interrupt latency에 영향을 줍니다.
int err = generic_handle_domain_irq(handler->priv->irqdomain,
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.
hwirq);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
if (unlikely(err)) {
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
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와 연결해 확인합니다.
handler->priv->fwnode, hwirq);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
chained_irq_exit(chip, desc);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static void plic_set_threshold(struct plic_handler *handler, u32 threshold)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
/* priority must be > threshold to trigger an interrupt */
원본 주석입니다. PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
writel(threshold, handler->hart_base + CONTEXT_THRESHOLD);
hardware 또는 공유 메모리에 상태를 게시합니다. write가 완료되는 지점과 다음 read/doorbell 사이의 ordering 조건을 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static int plic_dying_cpu(unsigned int cpu)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
if (plic_parent_irq)
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
disable_percpu_irq(plic_parent_irq);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
return 0;
호출자에게 상태를 반환합니다. 반환값만 보지 말고 그 전에 controller ownership이나 active 상태가 정리되었는지 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static int plic_starting_cpu(unsigned int cpu)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
struct plic_handler *handler = this_cpu_ptr(&plic_handlers);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
if (plic_parent_irq)
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
enable_percpu_irq(plic_parent_irq,
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
irq_get_trigger_type(plic_parent_irq));
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
else
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
pr_warn("%pfwP: cpu%d: parent irq not available\n",
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
handler->priv->fwnode, cpu);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
plic_set_threshold(handler, PLIC_ENABLE_THRESHOLD);
PLIC context에서 source를 claim하고 irqdomain을 거쳐 처리한 뒤 complete하는 chained handler의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
return 0;
호출자에게 상태를 반환합니다. 반환값만 보지 말고 그 전에 controller ownership이나 active 상태가 정리되었는지 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
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)(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
#include <asm/hwcap.h>
이 구현이 사용하는 architecture helper, IRQ core 또는 controller 자료구조의 선언을 가져옵니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static struct irq_domain *intc_domain;
scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
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와 연결해 확인합니다.
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와 연결해 확인합니다.
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와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static void riscv_intc_irq(struct pt_regs *regs)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG;
scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
if (generic_handle_domain_irq(intc_domain, cause))
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
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와 연결해 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static void riscv_intc_aia_irq(struct pt_regs *regs)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
unsigned long topi;
scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
while ((topi = csr_read(CSR_TOPI)))
여러 pending source 또는 resource를 순회하는 구간입니다. 한 번의 진입에서 처리 가능한 양과 종료 조건이 interrupt latency에 영향을 줍니다.
generic_handle_domain_irq(intc_domain, topi >> TOPI_IID_SHIFT);
hardware ID를 Linux IRQ core로 넘기는 경계입니다. 여기부터 hwirq, virq, irq_desc, irqaction의 번호 공간을 구분해 추적합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
/*
원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* On RISC-V systems local interrupts are masked or unmasked by writing
원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* the SIE (Supervisor Interrupt Enable) CSR. As CSRs can only be written
원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* on the local hart, these functions can only be called on the hart that
원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* corresponds to the IRQ chip.
원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
*/
원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static void riscv_intc_irq_mask(struct irq_data *d)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
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와 연결해 확인합니다.
else
scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
csr_clear(CSR_IE, BIT(d->hwirq));
scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static void riscv_intc_irq_unmask(struct irq_data *d)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)
상태나 ID를 분류하는 분기입니다. 이 조건의 참·거짓은 장치 register, firmware 기술 또는 CPU mode 중 하나에서 결정됩니다.
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와 연결해 확인합니다.
else
scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
csr_set(CSR_IE, BIT(d->hwirq));
scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 한 단계입니다. 이 줄이 바꾸는 software 상태를 대응하는 controller register나 trace checkpoint와 연결해 확인합니다.
}
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
(blank)
논리 구간을 나누는 빈 줄입니다. 바로 위에서 준비한 상태와 다음 제어 흐름을 분리해 읽습니다.
static void andes_intc_irq_mask(struct irq_data *d)
함수 경계입니다. 호출자가 넘긴 번호 공간과 현재 CPU/controller context가 scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로의 입력 계약을 만족하는지 확인합니다.
{
제어 블록의 범위를 표시합니다. 이 괄호 안에서만 유효한 lock, CPU context, resource lifetime을 함께 표시해 둡니다.
/*
원본 주석입니다. scause의 hart-local interrupt cause를 Linux IRQ core에 전달하는 RISC-V 최상위 경로에서 뒤따르는 코드가 전제하는 하드웨어 조건이나 예외를 먼저 확인합니다.
* 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()
08 · VERIFICATION
실제 장비에서 확인하는 순서
- Source
실행 장치 raw status와 interrupt output 조건을 읽고 가능하면 핀 또는 SoC trace를 측정합니다.
통과 기준 source 원인이 실제로 set되고 mask를 통과해야 합니다.
- PLIC global
실행 source ID의 priority와 pending bit를 사건 전후로 기록합니다.
통과 기준 priority는 threshold보다 크고 assert 뒤 pending이 보여야 합니다.
- Context
실행 실행 중인 hart/S-mode에 해당하는 enable bitmap, threshold, claim 주소를 확인합니다.
통과 기준 다른 context를 읽는 실수를 배제하고 claim이 기대 ID를 반환해야 합니다.
- CSR
실행 trap entry에서 scause, sepc, sstatus, sie, sip를 기록합니다.
통과 기준 interrupt bit와 supervisor external cause가 일치하고 SEIP가 전달되어야 합니다.
- 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, 잘못된 context | PLIC 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입니다. 그보다 아래의 통과 증거와 그 지점의 실패 증거를 한 묶음으로 남겨야 수정의 효과도 검증할 수 있습니다.