-
[리눅스 커널] interrupt - IRQ numberLinux/kernel 2023. 12. 27. 01:45
글의 참고
- https://docs.kernel.org/core-api/genericirq.html
- https://docs.kernel.org/core-api/irq/concepts.html
- https://www.kernel.org/doc/html/next/core-api/irq/irq-domain.html
- http://www.wowotech.net/irq_subsystem/interrupt_descriptor.html
- https://lwn.net/Articles/380931/
- https://linux-kernel-labs.github.io/refs/heads/master/lectures/interrupts.html
- https://0xax.gitbooks.io/linux-insides/content/Interrupts/linux-interrupts-7.html
글의 전제
- 밑줄로 작성된 글은 강조 표시를 의미한다.
- 그림 출처는 항시 그림 아래에 표시했다.
- v3.17 기준으로 작성.
글의 내용
- Overview
" 이 글은 interrupt handling process 를 다루면서, 이와 연관된 2 가지 개념을 설명하려고 한다.
1. IRQ number
2. interrupt descriptor" 먼저 리눅스 커널의 interrupt handling process 는 다음과 같다.
Figure 1 - interrupt handling process" 리눅스 커널에서 각 peripheral 의 IRQ 는 `struct irq_desc` 구조체로 표현된다(흔히, interrupt descriptor 라고 불린다). 그리고, 리눅스 커널에서 모든 interrupt descriptors(모든 IRQs) 를 저장하고 관리하는 자료 구조를 `interrupt descritor DB` 라고 부른다(위에서 레드 점선 박스). 예를 들어, interrupt 가 발생했을 때 과정을 한 번 살펴보자.
1. 먼저 발생한 interrupt 의 hwirq 를 알아내야 한다.
2. irq domain 을 참고해서 발생한 interrupt 의 hwirq 를 IRQ number 로 translate 한다.
3. 그리고, translated 된 IRQ number 를 통해서 대응하는 interrupt descriptor 를 얻어낸다.
4. 얻어낸 interrupt descriptor 를 통해서 high-level interrupt handler 를 호출한다." high-level interrupt handler 는 크게 2 가지 작업을 수행한다.
1. interrupt flow 를 control 하기 위해서 interrupt descriptor 의 irq chip driver callbacks 함수들을 호출한다(이 때, irq chip driver 에서 등록한 mask, ack 등의 callbacks 들이 호출된다).
2. interrupt descritor(struct irq_desc) 의 action list(struct irq_desc.action) 에서 `driver irq handler(struct irq_desc.action.handler)` 를 호출한다. 그러나, specific handler 의 호출 여부는 interrupt descriptor 의 현재 상태가 어떤 지에 따라 호출되지 않을 수 있다.
그리고, high-level irq handler 와 driver irq handler는 반드시 구분할 필요가 있다. high-level irq handler 는 이름 보았을 때, 가장 상위 계층에서 호출하는 interrupt handler 라고 생각하기 쉽지만, interrupt subsystem 에서는 그렇지 않다. 여기서 high-level 은 `underlying` 으로 해석하는게 좀 더 이해하기 쉬울 것이다. 그리고, driver irq handler 는 device driver engineer 측에서 등록한 irq handler 를 의미한다. 즉, interrupt 가 발생하면, SoC 제조사 혹은 프로세서 제조사에서 만든 irq chip driver 가 먼저 high-level irq handler 를 호출해서 hardware 관련해서 여러 가지 작업을 처리한다. 그리고, driver engineer 가 등록한 irq handler 가 호출된다.- Interrupt enable / disable [참고1]
" interrupt 를 enable / disable 하는 방법은 크게 2 가지가 있다.
1. enable / disable local CPU interrupt - Uni-proceesor 시스템에서는 CPU interrupts 를 disable 하면, 시스템에 존재하는 모든 interrupts 가 disable 되는 것과 같았고, preempted 또한 절대 불가능 했다(interrupt 가 disabled 되면, timer interrupt 또한 disabled 되기 때문에, processor 가 하나인 경우에는 선점 당할 수 있는 방법이 없다. 즉, 무적의 processor 가 되는 것이다). 그러나, SMP 에서는 얘기가 다르다. SMP 에서 `disable local CPU interrupts` 는 해당 local CPU 의 모든 interrupts 를 disabled 하겠다는 뜻이다.
2. enable / disable hardware interrupt on interrupt controller - 특정 IRQ number 에 대응하는 interrupt controller 의 hardware interrupt 를 disabled 할 수 있다." 리눅스 커널 v2.6.35 전까지는 peripheral 의 interrupt handler 를 작성할 때, 2 가지 방법으로 작성할 수 있었다.
1. fast handler - interrupt handler 를 등록할 때, IRQF_DISABLED 플래그를 전달한다. 이걸 통해 fast handler 는 interrupt handling 과정에서 interrupt disabled 이 보장된다. 왜냐면, 이름에서도 알 수 있다시피, fast handler 는 빨리 실행되어야 하기 때문에 interrupt 를 disabled 한다.
2. slow handler - 모든 peripheral interrupt handler 가 빨리(fast) 실행될 필요는 없다. 이 말은 interrupt handling process 가 꾀 오래 걸릴 수 도 있다는 것을 의미한다. 이렇게 처리가 오래 걸리는 경우에 driver irq handler 가 interrupt handling 처리를 완료할 때 까지, interrupt 를 disabled 하면, system performance 에 큰 악영향을 줄 수 있다. 왜냐면, interrupt 가 disabled 되는 시간이 너무 길어지기 때문이다. 그래서, slow handler 같은 경우는 high-level handler 에서 specific handler 로 interupt handling 이 넘어가려는 시점에, IRQF_DISABLED 플래그가 설정되어 있는지 여부를 검사해서 interrupt 를 enabled 할지를 결정하게 된다(IRQF_DISABLED 플래그가 설정 되어있으면, interrupt 를 계속 disabled 상태로 남겨둠).
//kernel/irq/handle.c - v2.6.31 /** * handle_IRQ_event - irq action chain handler * @irq: the interrupt number * @action: the interrupt action chain for this irq * * Handles the action chain of an irq event */ irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) { irqreturn_t ret, retval = IRQ_NONE; unsigned int status = 0; if (!(action->flags & IRQF_DISABLED)) local_irq_enable_in_hardirq(); .... };
만약, slow handler 에서 IRQF_DISABLED 플래그가 CLEAR 되어 있으면, interrupt 를 re-enabled 하게 된다. 그러나, software 와 hardware 기술이 발전함에 따라 이전에 정의한 몇 가지 가정들이 이제 더 이상은 들어맞지 않게 되었다.
1. hardware 발전에 따라, CPU 의 물리적인 처리 속도가 점점 빨라지고 있다. 즉, 이 말은 slow handler 또한 빠르게 작업을 처리하 수 있다는 뜻이 된다.
2. software 발전에 따라, 리눅스 커널에서 이전보다 훨씬 더 진보적이면서 기술적인 `bottom-half` 메커니즘을 지원한다.
v3.14 리눅스 커널에서는 IRQF_DISABLED 플래그는 deprecated 되었다. 그런데 ,한 번 생각해보자. 왜, slow handler 가 필요했을까? 예외없이 모든 interrupt handler 들은 최대한 빨리 interrupt 를 처리한 뒤, 이전 프로세스(인터럽트에게 선점 당한 프로세스) 가 실행하고 있던 루틴으로 돌아가야 하는거 아닌가? 게다가, slow handler 가 동작할 때는 interrupt 가 발생할 수 있기 때문에, nesting 이 발생할 수 있고, nesting 은 kernel stack overflow 를 발생시킬 여지가 충분하다. 그러므로, 상대적으로 newer kernel 에서는 interrupt specific handler 가 실행 중일 때는 CPU interrupts 들은 disabled 된다.- IRQ number
" 사실, CPU 입장에서는 interrupt controllers 들이 형성하는 구조(irq domain)가 아무리 복잡하더라도, 문제가 되지 않는다. CPU 는 interrupt 를 발생시킨 놈한테만 관심이 있다. 즉, external peripherals 에서 interrupt 가 발생했을 때, GPIO controller 를 거쳐, interrupt controller 를 거쳐, CPU 에게 전달된다. 이 때, CPU 는 interrupt 가 나한테 어떻게 해서 왔냐에는 관심이 없다. 지금 나한테 온 interrupt 를 누가 발생시켰는지만 관심이 있다. interrupt 의 원인이 어떤 peripheral 때문인지를 알게되면, CPU 는 해당 peripheral 이 등록한 irq handler 를 호출하게 된다.
" 좀 더, 정확히 표현하면 CPU 가 관심이 없는게 아니라, 커널의 interrupt handling module 이 interrupt controllers 들이 어떤 구조로 irq domains 을 이루는지에 관심이 없다. 커널의 interrupt handling module 은 어떤 peripheral 에서 발생한 interrupt 인지 확인하고, 등록된 irq handler 만 호출하면 그만이기 때문이다(power management module 같은 경우는 반드시 interrupt controllers 들이 형성하는 구조(부모-자식, 공급자-수요자) 에 대해 올바르게 인식하고 있어야 한다. 사실, power management module 은 커널에 등록되는 모든 devices 들이 형성하는 구조(부모-자식, 공급자-수요자) 에 대해 알고 있어야 한다. 왜냐면, system suspend & resume process 가 top-down & bottom-up 방식으로 진행되기 때문이다.).
" 아래에서 볼 수 있다시피, 리눅스 커널에서 모든 external interupts 들은 `irq_desc` 배열에 의해서 관리된다. 이 배열의 각 요소는 하나의 external interrupt 를 의미하며, 하나의 external interrupt 는 하나의 struct irq_desc 구조체에 mapping 된다. NR_IRQS 는 현재 시스템에서 정적으로 할당할 수 있는 IRQ number 개수라고 보면된다. 자신의 시스템에 맞게 NR_IRQS 값은 수정이 가능할 듯 싶다.
//kernel/irq/irqdesc.c - v3.17 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), } };
" 그런데, 위와 같이 statically 하게 자료 구조를 만드는게 효율적일까? 예를 들어, list 자료 구조를 사용하면 안될까? 가능할 것 같다. 그러나, performance 가 좋지 않을 것이다. 왜냐면, 리스트는 특정 위치를 한 번에 찾기가 어렵기 때문이다. 그에 비해 정적 배열(static table) 은 검색, 추가, 삭제 등 대부분의 동작들에서 굉장히 빠른 performance 를 보여주지만, 낭비되는 공간이 많다는 것이 문제다. 위와 같은 상황도 마찬가지다. 아무리 시스템을 열심히 분석해서 NR_IRQS 를 최적화 하더라도, IRQ number 가 동적으로 더 추가되면 어떻게 될까? 정적 배열은 이걸 타개할 방법이 없다. 그래서, 일부 시스템에서는 NR_IRQS 를 엄청 크게 설정하지만, 그 만큼 낭비되는 공간 또한 늘어나게 된다.
" 이럴 경우, static table 은 적합하지 않다. 대신에, radix tree 를 통해서 interrupt descriptors 를 관리할 수 있다. radix tree 를 통해 interrupt descriptors 를 관리할 경우, hwirq 를 통해서 interrupt descriptor 를 get 할 수 있다(즉, hwirq 를 index 로 사용). radix tree 를 사용하면, interrupt descriptors 들은 동적 할당을 통해 생성된다. 참고로, interrupt descriptors 를 관리하는 용도로 radix tree 를 사용할 경우, `CONFIG_SPARSE_IRQ` 컨피그를 반드시 설정해야 한다. `Overview` 섹션에서 설명했던 interrupt descriptor DB 는 static table(정적 배열) 을 기반으로 설명했다. CONFIG_SPARSE_IRQ 컨피그가 설정되면, 시스템은 radix tree 를 기반으로 interrupt descriptor DB 를 구축한다. 그러나, interrupt descriptors 가 저장되는 자료 구조만 바뀔 뿐이지 실제 커널의 interrupt flow 는 완전히 동일하기 때문에, block diagram 을 볼 때 참고하길 바란다.
- Data structures about interrupt
1. Interrupt descriptor
" 리눅스 커널에서 struct irq_desc 구조체는 하나의 peripheral interrupt 를 나타내는데 사용된다.
// include/linux/irqdesc.h - v3.17 /** * struct irq_desc - interrupt descriptor * @irq_data: per irq and chip data passed down to chip functions * @kstat_irqs: irq stats per cpu * @handle_irq: highlevel irq-events handler * @preflow_handler: handler called before the flow handler (currently used by sparc) * @action: the irq action chain * @status: status information * @core_internal_state__do_not_mess_with_it: core internal status information * @depth: disable-depth, for nested irq_disable() calls * @wake_depth: enable depth, for multiple irq_set_irq_wake() callers * @irq_count: stats field to detect stalled irqs * @last_unhandled: aging timer for unhandled count * @irqs_unhandled: stats field for spurious unhandled interrupts * @threads_handled: stats field for deferred spurious detection of threaded handlers * @threads_handled_last: comparator field for deferred spurious detection of theraded handlers * @lock: locking for SMP * @affinity_hint: hint to user space for preferred irq affinity * @affinity_notify: context for notification of affinity changes * @pending_mask: pending rebalanced interrupts * @threads_oneshot: bitfield to handle shared oneshot threads * @threads_active: number of irqaction threads currently running * @wait_for_threads: wait queue for sync_irq to wait for threaded handlers * @dir: /proc/irq/ procfs entry * @name: flow handler name for /proc/interrupts output */ struct irq_desc { struct irq_data irq_data; unsigned int __percpu *kstat_irqs; irq_flow_handler_t handle_irq; #ifdef CONFIG_IRQ_PREFLOW_FASTEOI irq_preflow_handler_t preflow_handler; #endif struct irqaction *action; /* IRQ action list */ unsigned int status_use_accessors; unsigned int core_internal_state__do_not_mess_with_it; unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int irq_count; /* For detecting broken IRQs */ unsigned long last_unhandled; /* Aging timer for unhandled count */ unsigned int irqs_unhandled; atomic_t threads_handled; int threads_handled_last; raw_spinlock_t lock; struct cpumask *percpu_enabled; #ifdef CONFIG_SMP const struct cpumask *affinity_hint; struct irq_affinity_notify *affinity_notify; #ifdef CONFIG_GENERIC_PENDING_IRQ cpumask_var_t pending_mask; #endif #endif unsigned long threads_oneshot; atomic_t threads_active; wait_queue_head_t wait_for_threads; #ifdef CONFIG_PROC_FS struct proc_dir_entry *dir; #endif int parent_irq; struct module *owner; const char *name; } ____cacheline_internodealigned_in_smp;
- kstat_irqs : per-CPU interrupt status 를 파악하기 위한 변수다. `cat /proc/stat` 을 해보면, 6 번째 컬럼에 현재 servicing 중인 per-CPU interrupts 가 몇 개 있는지 확인할 수 있다. 내 컴퓨터에서 확인한 결과, `cat /proc/stat` 을 입력한 시점에는 모든 CPUs 에서 servicing 중인 per-CPU interrupt 가 없었다.
$ cat /proc/stat cpu 1677063 1860 391204 23420306 9014 0 136822 0 0 0 cpu0 210728 19 49082 6375893 1438 0 72003 0 0 0 cpu1 210584 410 47688 2437737 740 0 33655 0 0 0 cpu2 212938 163 47362 2436531 638 0 12272 0 0 0 cpu3 214693 536 47536 2433162 2375 0 6510 0 0 0 cpu4 206254 193 50714 2433011 1037 0 3608 0 0 0 cpu5 206667 197 47932 2434241 1039 0 4861 0 0 0 cpu6 207836 165 52310 2435265 964 0 3051 0 0 0 cpu7 207359 174 48577 2434463 780 0 860 0 0 0
- handle_irq : 이 필드는 high-level irq handler 를 의미한다. `Overview` 섹션에서 interrupt subsystem 에서 사용하는 high-level 에 대한 의미를 알아봤다. high-level irq handler 에서 처리할 interrupt 관련 작업은 굉장히 다양하다. 이 때, 처리할 작업을 구분해주는 2 가지 요소가 있다. 즉, high-level irq handler 는 아래 2 가지 요소를 기준으로 하는 작업들이 달라진다. 그리고, high-level irq handler 는 irq chip driver 코드이다 보니, 주로 hardware level 의 interrupt controller 와 관련이 있다)
1. 어떤 interrupt controller 를 사용하냐에 따라 high-level irq handler 가 달라진다.
2. 어떤 trigger-mode 를 사용하느냐에 따라 high-level irq handler 가 달라진다.
리눅스 커널에서 high-level irq handler 의 종류는 다양하다. 아래 작성된 high-level irq handler 말고도 더 많은 핸들러가 존재한다[참고1].
1. handle_level_irq - level-triggerd interrupt handler
2. handle_edge_irq - edge-triggerd interrupt handler
3. handle_simple_irq - simple type interrupt hander(irq chip driver 는 호출하지 않고, driver irq handler 만 호출)
4. handle_fasteoi_irq - EOI(End-Of-Interrupt) 를 처리하는 interrupt handler
- action : 만약, 다수의 peripherals 들이 하나의 interrupt request line 에 연결되어 있을 경우(공유하고 있을 경우), 연결되어 있는 peripherals 개수만큼 struct irqaction(driver irq handler) 구조체들이 서로 연결되서(struct irqaction.next 를 사용) linked list 구조를 형성한다. 만약, interrupt request line 에 하나의 peripheral 만 연결되어 있을 경우, linked list(다수의 struct irqaction 이 연결된 구조) 는 하나의 struct irqaction 만 존재한다[참고1].
// include/linux/interrupt.h - v3.17 /** * struct irqaction - per interrupt action descriptor * @handler: interrupt handler function * .... * @next: pointer to the next irqaction for shared interrupts * @irq: interrupt number * .... */ struct irqaction { irq_handler_t handler; .... struct irqaction *next; .... unsigned int irq; .... } ____cacheline_internodealigned_in_smp;
- depth : critical section 에서 shared data 를 보호하기 위해 특정 IRQ 를 disable 할 수 있다. 이 외에도 특정 IRQ 만 disable / enable 해야 하는 경우는 많을 것이다. 이 때, IRQ disable / enable 은 nested 되기 때문에, 얼마나 깊게(depth) enable 및 disable 되었는지를 알 수 있어야 한다. 이 필드는 참고로, `disable-depth` 를 의미한다. 즉, `irq_disable()` 함수가 호출될 때마다, 이 필드는 증가한다.
- wake_depth : system power managemnt 에서 사용하는 wakeup_source 와 관련있는 필드다. `irq_set_irq_wake()` 함수를 통해서 특정 IRQ 가 suspend state 에 있는 system 을 wake-up 할 수 있도록 설정할 수 있다. 물론, 반대도 가능하다. 이 필드도 `depth` 필드와 유사하다. 그러나, 차이가 있다. 이 필드는 `enable-depth` 다. irq_set_irq_wake(irq, 1) 함수가 호출되면, 이 값은 증가한다. 반대로, irq_set_irq_wake(irq, 0) 으로 호출되면, 감소한다.
- irq_count, last_unhandled, irqs_unhandled : 이 필드들은 소위 말하는 `broken IRQ` 를 handling 하기 위해서 사용된다.
- percpu_enabled : 이 필드는 해당 interrupt 가 per-CPU type 의 interrupt 인지를 나타낸다. 여기서 interrupt 관련 약간의 사전 지식이 필요하다. processor architecture 관점에서 interrupt 는 크게 2 개로 나눠 볼 수 있다.
1. global IRQ - 특정 global IRQ 가 disabled 되면, 해당 IRQ 는 모든 CPUs 에서 disabled 된다.
2. per-CPU IRQ - 특정 per-CPU IRQ 가 disabled 되면, 해당 IRQ 는 해당 CPU 에서만 disabled 된다. 즉, 다른 CPUs 들에서는 여전히 해당 IRQ 는 enabled 상태인 것이다.
per-CPU IRQ 같은 경우, 대표적으로 timer interrupt 가 있다.- Initialization of interrupt descriptors
" interrupt descriptors 를 관리하는 자료 구조를 초기화는 `early_irq_init()` 함수를 통해 진행된다. 그런데, 이 함수는 CONFIG_SPARSE_IRQ 컨피그 여부에 따라 static table or radix tree 를 초기화한다. 즉, 2 개를 동시에 사용할 수 는 없다.
1. When using static table[참고1]
" CONFIG_SPARSE_IRQ 컨피그가 설정되지 않을 경우, interrupt descriptors DB 로 static table 을 사용하게 된다.
//kernel/irq/irqdesc.c - v3.17 #ifdef CONFIG_SPARSE_IRQ .... #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); // irq_desc(static table) 을 모두 초기화 해야 하므로, 먼저 개수를 추출해낸다. for (i = 0; i < count; i++) { desc[i].kstat_irqs = alloc_percpu(unsigned int); alloc_masks(&desc[i], GFP_KERNEL, node); // irq descriptor affinity 를 위해서 cpumask 할당 raw_spin_lock_init(&desc[i].lock); lockdep_set_class(&desc[i].lock, &irq_desc_lock_class); desc_set_defaults(i, &desc[i], node, NULL); } return arch_early_irq_init(); } .... #endif /* !CONFIG_SPARSE_IRQ */
2. When using radix tree
" CONFIG_SPASE_IRQ 컨피그가 설정되어 있을 경우 자동으로 static table 이 아닌 radix tree 가 사용된다.
// kernel/irq/irqdesc.c - v3.17 static DECLARE_BITMAP(allocated_irqs, IRQ_BITMAP_BITS); #ifdef CONFIG_SPARSE_IRQ static RADIX_TREE(irq_desc_tree, GFP_KERNEL); .... int __init early_irq_init(void) { .... /* Let arch update nr_irqs and return the nr of preallocated irqs */ initcnt = arch_probe_nr_irqs(); // --- 1 .... if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS)) // --- 1 nr_irqs = IRQ_BITMAP_BITS; if (WARN_ON(initcnt > IRQ_BITMAP_BITS)) // --- 1 initcnt = IRQ_BITMAP_BITS; if (initcnt > nr_irqs) // --- 1 nr_irqs = initcnt; for (i = 0; i < initcnt; i++) { // --- 2 desc = alloc_desc(i, node, NULL); set_bit(i, allocated_irqs); irq_insert_desc(i, desc); } .... }
(1) radix tree 는 run-time 에 요청이 있을 때만 interrupt descriptors 를 생성한다. 그러나, radix tree 도 interrupt descriptor DB 를 초기화 시점에 미리 interrupt descriptors 를 생성해 놓을 수 있다. 이 때, 미리 생성할 interrupt descriptors 개수를 지정해야 하는데, 2 개의 변수가 사용된다.
1. nr_iqs - 이 값은 초기화 값이 `NR_IRQS` 다. 즉, static table 과 동일한 개수의 interrupt descriptors 를 미리 생성한다. 그러나, `irq_expand_nr_irqs()` 함수를 통해서 값은 언제든 변경이 가능하다.
// kernel/irq/irqdesc.c - v3.17 int nr_irqs = NR_IRQS; EXPORT_SYMBOL_GPL(nr_irqs); .... static int irq_expand_nr_irqs(unsigned int nr) { if (nr > IRQ_BITMAP_BITS) return -ENOMEM; nr_irqs = nr; return 0; }
2. initcnt - 이름 그대로 `초기화 개수` 를 의미한다. 그런데, ARMv7 같은 경우 nr_irqs 와 initcnt 를 구분하는 것이 의미가 없다. 왜냐면, 아래에서 볼 수 있다시피, arch_probe_nr_irqs() 함수에서 nr_irqs 를 직접 수정하고, 이 함수의 반환값(nr_irqs) 이 initcnt 에 저장되기 때문이다. 즉, arch_probe_nr_irqs() 함수가 호출되면, 무조건 nr_irqs == initcnt 라고 볼 수 있다. 그러나, 이 케이스는 ARMv7 에 한정된다. 다른 프로세서에서 이렇게 할 것이란 보장이 없다. 즉, 일반적으로, initcnt 와 nr_irqs 는 다른 값이다.
// arch/arm/kernel/irq.c - v3.17 #ifdef CONFIG_SPARSE_IRQ int __init arch_probe_nr_irqs(void) { nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS; return nr_irqs; } #endif
" 최종적으로, initcnt 와 nr_irqs 중에 더 큰 숫자로 pre-allocate 를 진행한다. 그러나, radix tree 를 사용할 때도 동적으로 할당될 수 있는 최대 interrupt descriptors 개수는 제한되어 있다. 이 값은 IRQ_BITMAP_BITS 에 저장되어 있다.
// kernel/irq/internals.h - v3.17 #ifdef CONFIG_SPARSE_IRQ # define IRQ_BITMAP_BITS (NR_IRQS + 8196) #else # define IRQ_BITMAP_BITS NR_IRQS #endif
(2) radix tree 를 사용해서 interrupt descriptor DB 를 사용할 경우, `irq_alloc_descs()` 및 `irq_free_descs()` 함수를 이용해서 동적으로 interrupt descriptors 를 할당 및 해제 할 수 있다. 앞에 함수들은 다수의 interrupt descriptors 들을 동적으로 할당한다. 만약, interrupt descriptor 한 개만 할당하고 싶다면, `alloc_desc()` 함수를 사용하면 된다(이 때, alloc_desc() 함수는 interrupt descriptor 생성 및 초기화를 모두 수행한다. alloc_desc() 함수가 interrupt descriptor 를 초기화 과정은 static table 를 사용할 때, early_irq_init() 함수에서 interrut descriptors 를 초기화할 때와 동일하다). 그리고, radix tree 를 사용하기 때문에 bitmap(allocated_irqs) 을 사용해서 interrupt descriptors 들을 관리한다.
'Linux > kernel' 카테고리의 다른 글
[리눅스 커널] Interrupt - Driver interrupt handler (0) 2023.12.29 [리눅스 커널] Interrupt - High-level flow irq handler (1) 2023.12.27 [리눅스 커널] Interrupt - GICv2 part 2 (0) 2023.12.24 [리눅스 커널] DMA - cache consistency (0) 2023.12.06 [리눅스 커널] pinctrl - overview (1) 2023.12.04