ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] interrupt - IRQ number
    Linux/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://stackoverflow.com/questions/65093611/why-does-the-linux-kernel-not-stop-at-the-first-handler-for-a-shared-irq-that-re

    - 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 들을 관리한다. 

     

     

Designed by Tistory.