ARM 아키텍처에서 예외가 발생하면 어셈블리 단계를 거쳐 최종적으로 asm_do_IRQ() 함수로 진입한다. 이 시점까지 어셈블리 코드는 인터럽트 번호(irq)를 계산하여 C 함수에 전달한다. 이 문서에서는 Linux 인터럽트 시스템의 핵심 자료구조와 C 언어 단계의 처리 흐름을 살펴본다.
핵심 자료구조
커널은 모든 인터럽트를 일관된 번호 체계로 관리하며, irq_desc[NR_IRQS] 배열을 통해 각 인터럽트 소스를 기술한다. 각 배열 요소는 하드웨어 접근 방법, 상태 래그, 그리고 사용자 등록 콜백 함수들의 연결 정보를 담고 있다.
irq_desc 구조체
include/linux/irq.h에 정의된 irq_desc는 다음과 같다:
struct irq_desc {
irq_flow_handler_t handle_irq;
struct irq_chip *chip;
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action;
unsigned int status;
unsigned int depth;
unsigned int wake_depth;
unsigned int irq_count;
unsigned int irqs_unhandled;
spinlock_t lock;
const char *name;
} ____cacheline_internodealigned_in_smp;
각 멤버의 역할은 다음과 같다:
handle_irq: 해당 인터럽트의 상위 분배 함수chip: 하드웨어 제어를 위한 메서드 집합action: 드라이버가 등록한 실제 처리 함수들의 연결 리스트
irq_chip 구조체
하드웨어 레벨의 조작을 추상화한 구조체다:
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);
void (*ack)(unsigned int irq);
void (*mask)(unsigned int irq);
void (*mask_ack)(unsigned int irq);
void (*unmask)(unsigned int irq);
const char *typename;
};
irqaction 구조체
드라이버 관점의 인터트 핸들러 등록 정보를 담는다:
struct irqaction {
irq_handler_t handler;
unsigned long flags;
cpumask_t mask;
const char *name;
void *dev_id;
struct irqaction *next;
int irq;
struct proc_dir_entry *dir;
};
공유 인터럽트를 지원하기 위해 next 포인터로 동일 번호의 여러 들러를 연결한다.
시스템 초기화 흐름
플랫폼 독립 초기화
init_IRQ()는 arch/arm/kernel/irq.c에서 인터럽트 프레임워크의 기본 상태를 설정한다:
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
init_arch_irq();
}
init_arch_irq는 함수 포인터로, 대상 SoC에 맞는 초기화 루틴을 가리킨다.
SoC 특화 초기화 (S3C2440 예시)
s3c24xx_init_irq()에서는 각 인터럽트에 대해 chip과 handle_irq를 연결한다:
void __init s3c24xx_init_irq(void)
{
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irqdbf("registering irq %d (ext int)\n", irqno);
set_irq_chip(irqno, &s3c_irq_eint0t4);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
}
외부 인터럽트 0~3에 대해 하드웨어 조작 함수와 엣지 트리거 핸들러를 설정한다.
static struct irq_chip s3c_irq_eint0t4 = {
.name = "s3c-ext0",
.ack = s3c_irq_ack,
.mask = s3c_irq_mask,
.unmask = s3c_irq_unmask,
.set_wake = s3c_irq_wake,
.set_type = s3c_irqext_type,
};
드라이버의 인터럽트 등록
request_irq()는 kernel/irq/manage.c에 있으며, 내부적으로 irqaction을 할당하고 setup_irq()를 호출한다:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
struct irqaction *act = kmalloc(sizeof(*act), GFP_ATOMIC);
act->handler = handler;
act->flags = irqflags;
act->name = devname;
act->dev_id = dev_id;
act->next = NULL;
return setup_irq(irq, act);
}
setup_irq()의 내부 동작
int setup_irq(unsigned int irq, struct irqaction *fresh)
{
struct irq_desc *entry = irq_desc + irq;
struct irqaction **pos = &entry->action;
struct irqaction *cur = *pos;
int is_shared = 0;
if (cur) {
if (!((cur->flags & fresh->flags) & IRQF_SHARED) ||
((cur->flags ^ fresh->flags) & IRQF_TRIGGER_MASK))
return -EBUSY;
while (cur->next) {
pos = &cur->next;
cur = *pos;
}
is_shared = 1;
}
*pos = fresh;
if (!is_shared) {
irq_chip_set_defaults(entry->chip);
if (fresh->flags & IRQF_TRIGGER_MASK) {
if (entry->chip && entry->chip->set_type)
entry->chip->set_type(irq,
fresh->flags & IRQF_TRIGGER_MASK);
}
if (!(entry->status & IRQ_NOAUTOEN)) {
entry->depth = 0;
entry->status &= ~IRQ_DISABLED;
if (entry->chip->startup)
entry->chip->startup(irq);
else
entry->chip->enable(irq);
}
}
fresh->irq = irq;
return 0;
}
핵심 로직은: 공유 가능 여부 검증 → 리스트 연결 → 트리거 방식 설정 → 인터럽트 활성화 순이다.
인터럽트 해제
free_irq()는 dev_id로 특정 핸들러를 식별하여 제거한다:
void free_irq(unsigned int irq, void *dev_id)
{
struct irq_desc *entry = irq_desc + irq;
struct irqaction **prev = &entry->action;
struct irqaction *iter;
for (iter = *prev; iter; prev = &iter->next, iter = *prev) {
if (iter->dev_id != dev_id)
continue;
*prev = iter->next;
if (!entry->action) {
entry->status |= IRQ_DISABLED;
if (entry->chip->shutdown)
entry->chip->shutdown(irq);
else
entry->chip->disable(irq);
}
kfree(iter);
return;
}
}
마지막 핸들러가 제거되면 하드웨어 인터럽트를 완전히 비활성화한다.
런타임 처리 경로
최상위 진입점
asm_do_IRQ()는 어블리에서 직접 호출되며, 인터럽트 컨텍스트의 시작을 알린다:
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *saved = set_irq_regs(regs);
struct irq_desc *desc = irq_desc + irq;
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter();
desc_handle_irq(irq, desc);
irq_finish(irq);
irq_exit();
set_irq_regs(saved);
}
desc_handle_irq()는 단순히 desc->handle_irq(irq, desc)를 호출하는 인라인 함수다.
엣지 트리거 처리 예시
EINT0의 경우 handle_edge_irq()가 호출된다:
void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
kstat_cpu(smp_processor_id()).irqs[irq]++;
desc->chip->ack(irq);
desc->status |= IRQ_INPROGRESS;
handle_IRQ_event(irq, desc->action);
}
ack()로 하드웨어 플래그를 클리어하고, handle_IRQ_event()로 등록된 모든 핸들러를 순회 실행한다:
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *chain)
{
irqreturn_t cumulative = IRQ_NONE;
int ret;
do {
ret = chain->handler(irq, chain->dev_id);
if (ret == IRQ_HANDLED)
cumulative |= chain->flags;
chain = chain->next;
} while (chain);
return cumulative;
}
계층형 인터럽트: 서브 IRQ 사례
S3C2440의 EINT5는 EINT4~7 그룹에 속하는 2차 인터럽트다. 상위 그룹의 핸들러가 먼저 실행된 후 실제 소스를 파악한다.
상위 분배 핸들러 등록
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
실제 분배 로직
static void s3c_irq_demux_extint4t7(unsigned int parent_irq,
struct irq_desc *parent_desc)
{
unsigned long pending = __raw_readl(S3C24XX_EINTPEND);
unsigned long masked = __raw_readl(S3C24XX_EINTMASK);
unsigned long active;
int sub;
active = pending & ~masked;
active &= 0xff;
while (active) {
sub = __ffs(active);
active &= ~(1 << sub);
int real_irq = IRQ_EINT4 + sub - 4;
desc_handle_irq(real_irq, irq_desc + real_irq);
}
}
이 패턴에서는 INTOFFSET으로 얻은 번호가 그룹 번호이며, 실제 핀 번호는 펜딩 레지스터를 어 재계산한다. 이후 desc_handle_irq()를 재귀적으로 호출하여 최종 핸들러로 분기한다.
처리 흐름 정리
| 단계 | 함수/동작 | 설명 |
|---|---|---|
| 1 | CPU 예외 벡터 | 어셈블리에서 vector_irq로 진입 |
| 2 | asm_do_IRQ() | C 단계 진입, 컨텍스트 설정 |
| 3 | desc->handle_irq() | 유형별 1차 분배 (엣지/레벨/체인) |
| 4 | desc->chip->ack() | 하드웨어 인터럽트 클리어 |
| 5 | handle_IRQ_event() | 드라이버 핸들러 순회 실행 |
| 6 | 사용자 콜백 | 실제 디바이스 처리 로직 |
이 구조 덕분에 Linux는 단일 IRQ 번호 공간에서부터 복잡한 계층형 인터럽트 컨트롤러까지 일관된 추상화로 대응할 수 있다.