전체 흐름
start_kernel()은 early_irq_init()으로 일반 IRQ descriptor의 기본 상태를 준비한 다음 init_IRQ()을 호출한다. IRQ를 전역적으로 다시 허용하는 local_irq_enable()은 다른 시간·타이머 초기화 뒤에 나온다. 이 순서가 descriptor 준비와 하드웨어 IRQ 연결을 구분하는 기준이다.
CONFIG_SPARSE_IRQ는 initcnt 기준 descriptor를 maple tree에 넣으려 한다. 비-sparse 경로는 정적 irq_desc[NR_IRQS] 배열을 초기화한다.
새 descriptor는 no_irq_chip, handle_bad_irq, disabled·masked 상태로 시작한다. 실제 chip/domain 연결과 IRQ enable은 이 그림의 뒤 단계다.
start_kernel() 안의 IRQ 준비 순서early_irq_init()generic descriptor 기본 상태 준비init_IRQ()architecture IRQ 초기화 호출tick_init, timer, timekeeping 등local_irq_enable()이후 전역 IRQ 허용init/main.c:969–995. 순서도는 호출 위치만 나타낸다. 특정 보드의 irqchip·domain 구성이 ②에서 어떻게 이루어지는지는 아키텍처 소스를 별도로 확인해야 한다.
소스 코드 위치
Linux v6.6에서 함께 읽을 파일
init/main.c — start_kernel 호출 순서
kernel/irq/irqdesc.c — 두 CONFIG 분기, descriptor 기본값과 할당
kernel/softirq.c — weak arch_probe_nr_irqs / arch_early_irq_init 기본 구현
아래 코드는 사이트에 보관된 v6.6 irqdesc.c의 지정 연속 행이다. 호출 순서는 보관된 init/main.c에서 확인했다. 파일 전체가 공식 태그와 바이트 단위로 같은지는 irqdesc.c에 대해 확인되지 않았으므로, 버전 비교에는 공식 소스도 함께 연다.
공식 v6.6 태그: irqdesc.c · init/main.c · softirq.c
대표 코드
두 early_irq_init() 구현은 같은 빌드에서 순서대로 실행되지 않는다. CONFIG_SPARSE_IRQ 조건의 서로 다른 분기이며, 아래 발췌도 분리해서 읽어야 한다.
CONFIG_SPARSE_IRQ: initcnt 기준 선할당
kernel/irq/irqdesc.c, 545–571행int __init early_irq_init(void)
{
int i, initcnt, node = first_online_node;
struct irq_desc *desc;
init_irq_default_affinity();
/* Let arch update nr_irqs and return the nr of preallocated irqs */
initcnt = arch_probe_nr_irqs();
printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n",
NR_IRQS, nr_irqs, initcnt);
if (WARN_ON(nr_irqs > MAX_SPARSE_IRQS))
nr_irqs = MAX_SPARSE_IRQS;
if (WARN_ON(initcnt > MAX_SPARSE_IRQS))
initcnt = MAX_SPARSE_IRQS;
if (initcnt > nr_irqs)
nr_irqs = initcnt;
for (i = 0; i < initcnt; i++) {
desc = alloc_desc(i, node, 0, NULL, NULL);
irq_insert_desc(i, desc);
}
return arch_early_irq_init();
}읽는 법: arch_probe_nr_irqs()의 initcnt와 nr_irqs를 상한에 맞춘 뒤 0부터 initcnt-1까지만 alloc_desc()와 irq_insert_desc()를 호출한다. 선할당 수는 initcnt 기준이며 nr_irqs 전체와 같을 수도, 그보다 적을 수도 있다.
!CONFIG_SPARSE_IRQ: 정적 배열 초기화
kernel/irq/irqdesc.c, 573–606행#else /* !CONFIG_SPARSE_IRQ */
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
int __init early_irq_init(void)
{
int count, i, node = first_online_node;
struct irq_desc *desc;
init_irq_default_affinity();
printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);
desc = irq_desc;
count = ARRAY_SIZE(irq_desc);
for (i = 0; i < count; i++) {
desc[i].kstat_irqs = alloc_percpu(unsigned int);
alloc_masks(&desc[i], node);
raw_spin_lock_init(&desc[i].lock);
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
mutex_init(&desc[i].request_mutex);
init_waitqueue_head(&desc[i].wait_for_threads);
desc_set_defaults(i, &desc[i], node, NULL, NULL);
irq_resend_init(desc);
}
return arch_early_irq_init();
}읽는 법: irq_desc[NR_IRQS] 정적 배열의 각 항목에서 통계·마스크 할당을 시도하고 잠금·기본값을 설정한다. 두 할당 결과는 이 루프에서 검사하지 않는다. sparse 쪽 alloc_desc() 루프와 합쳐 그리면 안 된다.
sparse 경로의 alloc_desc()
kernel/irq/irqdesc.c, 412–447행static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,
const struct cpumask *affinity,
struct module *owner)
{
struct irq_desc *desc;
desc = kzalloc_node(sizeof(*desc), GFP_KERNEL, node);
if (!desc)
return NULL;
/* allocate based on nr_cpu_ids */
desc->kstat_irqs = alloc_percpu(unsigned int);
if (!desc->kstat_irqs)
goto err_desc;
if (alloc_masks(desc, node))
goto err_kstat;
raw_spin_lock_init(&desc->lock);
lockdep_set_class(&desc->lock, &irq_desc_lock_class);
mutex_init(&desc->request_mutex);
init_rcu_head(&desc->rcu);
init_waitqueue_head(&desc->wait_for_threads);
desc_set_defaults(irq, desc, node, affinity, owner);
irqd_set(&desc->irq_data, flags);
kobject_init(&desc->kobj, &irq_kobj_type);
irq_resend_init(desc);
return desc;
err_kstat:
free_percpu(desc->kstat_irqs);
err_desc:
kfree(desc);
return NULL;
}읽는 법: descriptor와 per-CPU 통계·마스크 확보가 실패하면 이 함수는 이미 얻은 자원을 정리하고 NULL을 반환할 수 있다. 이 발췌 자체가 선할당 루프의 성공을 보장하지 않는다.
desc_set_defaults()의 비활성 기본값
kernel/irq/irqdesc.c, 110–118행 desc->irq_data.common = &desc->irq_common_data;
desc->irq_data.irq = irq;
desc->irq_data.chip = &no_irq_chip;
desc->irq_data.chip_data = NULL;
irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
irqd_set(&desc->irq_data, IRQD_IRQ_MASKED);
desc->handle_irq = handle_bad_irq;
desc->depth = 1;읽는 법: IRQ 번호를 기록하지만 chip은 no_irq_chip, flow handler는 handle_bad_irq, 상태는 disabled·masked, depth는 1이다. 이 단계만으로 domain이나 장치 IRQ가 동작하지 않는다.
함수별 분석
선택된 빌드 설정에 따라 descriptor를 마련하는 방식이 달라진다. 두 분기 모두 끝에서 arch_early_irq_init()의 결과를 반환하지만, start_kernel()은 그 반환값을 검사하지 않고 다음 init_IRQ()로 진행한다.
CONFIG_SPARSE_IRQ=y
- default affinity 초기화
arch_probe_nr_irqs()와 상한 확인initcnt회alloc_desc→ maple tree insert 시도arch_early_irq_init()반환
CONFIG_SPARSE_IRQ=n
- 정적
irq_desc[NR_IRQS] - default affinity 초기화
- 통계·마스크 할당 시도, 잠금·기본값 설정
arch_early_irq_init()반환
irqdesc.c:545–571, 573–606. 아키텍처 hook의 실제 구현은 빌드 대상마다 다를 수 있다. 이 그림은 generic 코드에서 확인되는 경로만 그린다.
1. 수량과 저장소
sparse는 arch_probe_nr_irqs()가 준 초기 개수 initcnt와 nr_irqs를 조정한다. descriptor 인덱스는 sparse_irqs maple tree가 관리한다. 비-sparse는 NR_IRQS 크기의 정적 배열을 순회한다.
2. 기본 descriptor
sparse의 alloc_desc()는 통계·마스크·잠금 등을 만든 뒤 desc_set_defaults()를 호출한다. 비-sparse도 배열의 각 항목에 같은 기본값을 설정한다. 기본 chip과 handler는 실제 장치용 값이 아니다.
3. 실패와 다음 호출
alloc_desc()는 NULL을 반환할 수 있지만 sparse 선할당 루프는 반환 포인터를 검사하지 않고 insert에 넘긴다. irq_insert_desc()는 maple tree 저장 실패를 경고한다. 이 코드만으로 성공적인 rollback이나 호출자의 실패 중단을 주장할 수 없다.
| 분기 | 저장소 | 초기 작업 | 확인할 한계 |
|---|---|---|---|
| sparse | sparse_irqs maple tree | initcnt회 할당·insert 시도 | nr_irqs와 같을 수도, 적을 수도 있음 |
| 비-sparse | 정적 irq_desc[NR_IRQS] | 배열 항목별 할당 시도·기본값 설정 | 통계·마스크 할당 결과를 검사하지 않음 |
| 공통 끝 | architecture hook | arch_early_irq_init() 반환 | hook 구현은 arch별 확인 |
구조체와 필드
저장소와 descriptor 내부 필드는 다른 층이다. sparse의 maple tree 또는 비-sparse의 정적 배열이 IRQ 번호에서 irq_desc를 찾는 바깥 구조이고, 각 descriptor에는 잠금·통계·irq_data와 기본 handler가 들어간다.
sparse_irqsmaple tree의 번호 → descriptor 관계irq_desc[]정적 배열 인덱스 → descriptorirq_desc 하나의 초기 상태irq_data.irq = 논리 IRQ 번호irq_data.chip = no_irq_chiphandle_irq = handle_bad_irqdisabled·masked, depth=1irqdesc.c:102–126, 132–137, 575–605. maple tree는 bitmap이 아니다. 표시된 chip/handler는 안전한 기본값이며 실제 하드웨어 구성 완료를 뜻하지 않는다.
| 요소 | 초기화 시점의 역할 | 구분할 것 |
|---|---|---|
sparse_irqs | sparse IRQ 번호에서 descriptor 검색·삽입 | 정적 배열이나 bitmap으로 부르지 않음 |
irq_data.irq | Linux 논리 IRQ 번호 기록 | hardware IRQ 번호와 동일하다고 단정하지 않음 |
irq_data.chip | 초기 no_irq_chip | 실제 irqchip 연결과 구분 |
handle_irq, depth | handle_bad_irq, depth 1 | 실제 handler 설치나 enable과 구분 |
실행 단계와 상태 변화
여기서 확인할 수 있는 완료 상태는 descriptor 기본값이 준비되었다는 범위에 한정된다. sparse 선할당 중 실패 가능성과 architecture hook 결과의 소비 여부를 단계별로 따로 본다.
alloc_desc()통계·마스크·잠금·기본값을 가진 descriptor 반환 또는 NULLirq_insert_desc()maple tree 저장; 앞 반환 포인터 검사 없음, 저장 실패는 경고arch_early_irq_init()의 int를 그대로 반환start_kernel()반환값 검사 없이 init_IRQ() 호출irqdesc.c:156–160, 412–446, 545–570; init/main.c:971–972. 이 그림은 코드의 검사 유무를 나타내며 특정 실패가 실제 보드에서 어떻게 나타나는지까지 예측하지 않는다.
| 지점 | 소스에서 확인되는 변화 | 이 단계에서 아직 말할 수 없는 것 |
|---|---|---|
desc_set_defaults | 논리 번호, 비활성 chip·handler·mask 기본값 | domain/hwirq 연결 완료 |
| sparse 선할당 | initcnt 반복에서 할당·insert 시도 | 매 반복의 성공 보장 |
| arch hook 반환 | generic 함수의 반환값 형성 | start_kernel의 오류 처리 |
init_IRQ() 이후 | 다음 architecture 초기화 단계 진입 | 모든 장치 IRQ의 사용 준비 |
불변 조건과 놓치기 쉬운 분기
CONFIG_SPARSE_IRQ의 maple tree 선할당과 정적 배열 초기화는 선택 관계다. 둘을 하나의 런타임 흐름으로 연결하지 않는다.
no_irq_chip, handle_bad_irq, disabled·masked는 준비 중인 descriptor의 기본값이다. 장치용 handler가 등록된 상태가 아니다.
alloc_desc 내부의 자원 정리와 바깥 선할당 루프의 반환 포인터 검사 유무를 분리한다. 전체 초기화가 자동 rollback된다고 말하지 않는다.
early_irq_init() 다음 init_IRQ()가 오고, 여러 초기화 뒤 local_irq_enable()이 온다. 호출 순서만으로 아키텍처 hook의 상세 효과를 추정하지 않는다.
계측과 검증
부팅 초기에 어느 단계까지 실행됐는지 볼 때는 소스의 호출 순서와 실제 로그의 관찰 범위를 구분한다. 일반 IRQ handler tracepoint나 부팅 후 /proc/interrupts는 descriptor 선할당의 각 반복을 직접 기록하지 않는다.
| 관찰 자료 | 확인할 수 있는 것 | 한계 |
|---|---|---|
NR_IRQS: 부팅 로그 | sparse에서는 당시 nr_irqs와 initcnt 값; 비-sparse에서는 NR_IRQS | sparse 로그는 상한 조정 전이라 실제 루프 횟수와 다를 수 있음 |
WARN_ON 로그 | 상한 초과 또는 maple tree 저장 경고 여부 | 경고가 없다는 사실만으로 전체 성공 증명 불가 |
/proc/interrupts | 부팅 후 설치된 IRQ의 카운트 | early_irq_init 내부 단계 추적 아님 |
| 추가 계측 | 필요하면 해당 빌드에서 함수 추적 가능 시점을 확인 | ftrace:early_irq_init라는 기본 event를 가정하지 않음 |
dmesg | grep 'NR_IRQS:'
cat /proc/interrupts로그 버퍼 보존, 접근 권한, 커널 설정에 따라 두 명령의 결과는 달라질 수 있다.
추가 확인 사항
- 대상 보드의
CONFIG_SPARSE_IRQ값과arch_probe_nr_irqs()override는 무엇인가? initcnt와nr_irqs가 다를 때, 이 시점에 실제 descriptor가 있는 번호는 어디까지인가?- 해당 아키텍처의
init_IRQ()와 IRQ domain/irqchip 설정은 어느 후속 함수에서 이루어지는가? - 선할당이나 maple tree insert 실패를 진단할 때, 이 빌드에서는 어떤 초기 로그와 경고를 관찰할 수 있는가?