IRQ / Time · Linux v6.6 · source + diagram note

early_irq_init

Linux v6.6의 sparse·비-sparse IRQ descriptor 준비와 init_IRQ() 이전의 기본 상태.

전체 흐름

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은 이 그림의 뒤 단계다.

그림 1. 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()로 진행한다.

그림 2. 빌드 설정에 따른 두 초기화 경로

CONFIG_SPARSE_IRQ=y

  1. default affinity 초기화
  2. arch_probe_nr_irqs()와 상한 확인
  3. initcnt회 alloc_desc → maple tree insert 시도
  4. arch_early_irq_init() 반환

CONFIG_SPARSE_IRQ=n

  1. 정적 irq_desc[NR_IRQS]
  2. default affinity 초기화
  3. 통계·마스크 할당 시도, 잠금·기본값 설정
  4. 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이나 호출자의 실패 중단을 주장할 수 없다.

분기저장소초기 작업확인할 한계
sparsesparse_irqs maple treeinitcnt회 할당·insert 시도nr_irqs와 같을 수도, 적을 수도 있음
비-sparse정적 irq_desc[NR_IRQS]배열 항목별 할당 시도·기본값 설정통계·마스크 할당 결과를 검사하지 않음
공통 끝architecture hookarch_early_irq_init() 반환hook 구현은 arch별 확인

구조체와 필드

저장소와 descriptor 내부 필드는 다른 층이다. sparse의 maple tree 또는 비-sparse의 정적 배열이 IRQ 번호에서 irq_desc를 찾는 바깥 구조이고, 각 descriptor에는 잠금·통계·irq_data와 기본 handler가 들어간다.

그림 3. IRQ 번호를 descriptor에 연결하는 저장소와 기본 필드
sparse: sparse_irqsmaple tree의 번호 → descriptor 관계
비-sparse: irq_desc[]정적 배열 인덱스 → descriptor
irq_desc 하나의 초기 상태
irq_data.irq = 논리 IRQ 번호irq_data.chip = no_irq_chiphandle_irq = handle_bad_irqdisabled·masked, depth=1

irqdesc.c:102–126, 132–137, 575–605. maple tree는 bitmap이 아니다. 표시된 chip/handler는 안전한 기본값이며 실제 하드웨어 구성 완료를 뜻하지 않는다.

요소초기화 시점의 역할구분할 것
sparse_irqssparse IRQ 번호에서 descriptor 검색·삽입정적 배열이나 bitmap으로 부르지 않음
irq_data.irqLinux 논리 IRQ 번호 기록hardware IRQ 번호와 동일하다고 단정하지 않음
irq_data.chip초기 no_irq_chip실제 irqchip 연결과 구분
handle_irq, depthhandle_bad_irq, depth 1실제 handler 설치나 enable과 구분

실행 단계와 상태 변화

여기서 확인할 수 있는 완료 상태는 descriptor 기본값이 준비되었다는 범위에 한정된다. sparse 선할당 중 실패 가능성과 architecture hook 결과의 소비 여부를 단계별로 따로 본다.

그림 4. sparse 선할당과 이후 호출의 경계
alloc_desc()통계·마스크·잠금·기본값을 가진 descriptor 반환 또는 NULL
irq_insert_desc()maple tree 저장; 앞 반환 포인터 검사 없음, 저장 실패는 경고
arch hook 반환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의 사용 준비
descriptor 기본값 준비 ≠ domain/chip 연결 ≠ 전역 IRQ enable

불변 조건과 놓치기 쉬운 분기

빌드 분기

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_IRQSsparse 로그는 상한 조정 전이라 실제 루프 횟수와 다를 수 있음
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 실패를 진단할 때, 이 빌드에서는 어떤 초기 로그와 경고를 관찰할 수 있는가?