ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] PM - Wakeup interrupt
    Linux/kernel 2023. 9. 5. 00:49

    글의 참고

    - https://blog.csdn.net/weixin_41155581/article/details/121276733

    - http://www.wowotech.net/pm_subsystem/491.html

    - https://lkml.org/lkml/2014/8/11/291

    - http://www.wowotech.net/pm_subsystem/suspend-irq.html

    - https://www.kernel.org/doc/Documentation/power/suspend-and-interrupts.rst

    - http://www.wowotech.net/irq_subsystem/irq_handle_procedure.html

    - https://blog.csdn.net/hello_yj/article/details/125054121

    - https://zhuanlan.zhihu.com/p/90074320?utm_id=0

    - Exynos4412 datasheet


    글의 전제

    - 밑줄로 작성된 글은 강조 표시를 의미한다.

    - 그림 출처는 항시 그림 아래에 표시했다.


    글의 내용

    - Wakeup interrupt 

    " 이 글에서는 `system suspend` 상태에서 인터럽트를 통해 시스템이 wakeup 되는지에 대해 알아본다. 참조하는 글이 `미디어텍` SoC를 사용하기 때문에 플로우 차트 그림들이 모두 미티어택(접두사 `mtk`)인 것만 주의하자. 

     

     

    - Wake irq in suspend noirq 

    " `system suspend` 과정에서 언제 인터럽트를 disable 할까?

    static int suspend_enter(suspend_state_t state, bool *wakeup)
    {
    	....
    
    	error = dpm_suspend_late(PMSG_SUSPEND); // devices late suspend 단계
    
    	error = platform_suspend_prepare_late(state); // platform-dependent late suspend 단계
    
    	// irq는 아래의 코드들을 통해서 disabled 된다.
    
    	error = dpm_suspend_noirq(PMSG_SUSPEND); // devices noirq suspend 단계
    
    	error = platform_suspend_prepare_noirq(state); // platform-dependent noirq suspend 단계
    	....
    }

     

    " 각 디바이스의 `noirq suspend` 콜백 함수를 호출하기 전에, `dpm_suspend_noirq` 함수안에 내부적으로 호출하는 `suspend_device_irqs` 함수를 통해서 모든 디바이스의 인터럽트를 disabled 한다.

     

    " 일반적으로 `late suspend` 단계를 마치고 나면, 일반적으로 디바이스들은 더 이상 인터럽트를 발생시키지 못한다. 그런데, 인터럽트를 공유하는 상황에서는 이게 문제가된다. 대게 리눅스는 IRQ를 비활성화 한다기 보다는 인터럽트 핸들러를 비활성화 하는 방법을 택한다. 왜냐면, 얼마나 인터럽트가 발생했는지에 대한 트래킹을 하기 위해서다. 인터럽트를 공유할 경우, 다른 인터럽트 핸들러에서 자신이 처리할 인터럽트가 아니라며, `IRQF_NONE`을 리턴할 경우 이미 suspended 상태인 인터럽트 핸들러가 호출될 수도 있다. 즉, 이미 suspended 된 디바이스가 해당 인터럽트를 처리하게 된다는 것이다. 이러한 문제를 방지하기 위해 `IRQF_NO_SUSPEND`와 `IRQF_SHARED`는 같이 사용하지 못하도록 한다.

    // drivers/base/power/main.c - v6.5
    /**
     * dpm_suspend_noirq - Execute "noirq suspend" callbacks for all devices.
     * @state: PM transition of the system being carried out.
     *
     * Prevent device drivers' interrupt handlers from being called and invoke
     * "noirq" suspend callbacks for all non-sysdev devices.
     */
    int dpm_suspend_noirq(pm_message_t state)
    {
    	int ret;
    
    	device_wakeup_arm_wake_irqs();
    	suspend_device_irqs();
    
    	ret = dpm_noirq_suspend_devices(state);
    	if (ret)
    		dpm_resume_noirq(resume_event(state));
    
    	return ret;
    }

     

    " `device_wake_arm_wake_irqs` 함수를 통해서 시스템 suspending 과정에서 자동으로 시스템에 존재하는 모든 wakeup source의 wakeup irq가 enable 된다. `dev_pm_arm_wake_irq` 함수를 보면 알 수 있겠지만, `wakeup sources`에 `wake irq`만 등록을 해놓으면 시스템 suspending 시에 알아서 모든 wake irq를 `enable_irq_wake` 함수를 통해서 SoC에 등록해준다.

    // drivers/base/power/wakeup.c - v6.5
    ....
    
    static LIST_HEAD(wakeup_sources);
    ....
    
    /**
     * device_wakeup_arm_wake_irqs -
     *
     * Iterates over the list of device wakeirqs to arm them.
     */
    void device_wakeup_arm_wake_irqs(void)
    {
    	struct wakeup_source *ws;
    	int srcuidx;
    
    	list_for_each_entry_rcu_locked(ws, &wakeup_sources, entry)
    		dev_pm_arm_wake_irq(ws->wakeirq);
    }
    ....
    
    // drivers/base/power/wakeirq.c - v6.5
    /**
     * dev_pm_arm_wake_irq - Arm device wake-up
     * @wirq: Device wake-up interrupt
     *
     * Sets up the wake-up event conditionally based on the
     * device_may_wake().
     */
    void dev_pm_arm_wake_irq(struct wake_irq *wirq)
    {
    	if (!wirq)
    		return;
    
    	if (device_may_wakeup(wirq->dev)) {
    		if (wirq->status & WAKE_IRQ_DEDICATED_ALLOCATED &&
    		    !(wirq->status & WAKE_IRQ_DEDICATED_ENABLED))
    			enable_irq(wirq->irq);
    
    		enable_irq_wake(wirq->irq);
    	}
    }

     

     

    " 그렇면, `wakeup source`에 어떻게 `wake irq`를 연결시킬까? 아래의 코드를 보면 `wake irq`를 2군데 연결된다.

    1. dev->power.wakeirq  = wakeirq 
    2. ws->wakeirq = wakeirq

     

     

    " 시스템 suspend / resume 과정에서 사용하는 값은 `ws->wakeirq` 변수를 사용한다.

    // drivers/base/power/power.h - v6.5
    struct wake_irq {
    	struct device *dev;
    	unsigned int status;
    	int irq;
    	const char *name;
    };
    // drivers/base/power/wakeup.c - v6.5
    /**
     * device_wakeup_attach_irq - Attach a wakeirq to a wakeup source
     * @dev: Device to handle
     * @wakeirq: Device specific wakeirq entry
     *
     * Attach a device wakeirq to the wakeup source so the device
     * wake IRQ can be configured automatically for suspend and
     * resume.
     *
     * Call under the device's power.lock lock.
     */
    void device_wakeup_attach_irq(struct device *dev,
    			     struct wake_irq *wakeirq)
    {
    	struct wakeup_source *ws;
    
    	ws = dev->power.wakeup;
    	
        ....
        
    	ws->wakeirq = wakeirq;
    }
    // drivers/base/power/wakeirq.c - v6.5
    /**
     * dev_pm_attach_wake_irq - Attach device interrupt as a wake IRQ
     * @dev: Device entry
     * @wirq: Wake irq specific data
     *
     * Internal function to attach a dedicated wake-up interrupt as a wake IRQ.
     */
    static int dev_pm_attach_wake_irq(struct device *dev, struct wake_irq *wirq)
    {
    	unsigned long flags;
    
    	....
    
    	dev->power.wakeirq = wirq;
    	device_wakeup_attach_irq(dev, wirq);
    	
        ....
    
    	return 0;
    }
    // drivers/base/power/wakeirq.c - v6.5
    /**
     * dev_pm_set_wake_irq - Attach device IO interrupt as wake IRQ
     * @dev: Device entry
     * @irq: Device IO interrupt
     *
     * Attach a device IO interrupt as a wake IRQ. The wake IRQ gets
     * automatically configured for wake-up from suspend  based
     * on the device specific sysfs wakeup entry. Typically called
     * during driver probe after calling device_init_wakeup().
     */
    int dev_pm_set_wake_irq(struct device *dev, int irq)
    {
    	struct wake_irq *wirq;
    	int err;
    	....
        
    	wirq = kzalloc(sizeof(*wirq), GFP_KERNEL);
    	if (!wirq)
    		return -ENOMEM;
    
    	wirq->dev = dev;
    	wirq->irq = irq;
    
    	err = dev_pm_attach_wake_irq(dev, wirq);
        ....
    	
    	return err;
    }

     

     

     

    - IRQF_NO_SUSPEND

    : 이 플래그가 설정되면, 해당 IRQ는 시스템 suspend / resume 모든 단계에서 계속 enable 상태를 유지한다. 기본적으로, `suspend_noirq`, `non-boot CPU's`단계는 모든 인터럽트 핸들러를 비활성화된 상태다. 그러나, `IRQF_NO_SUSPEND` 플래그가 설정된 인터럽트 핸들러는 이 단계에서도 동작을 한다. 대게, 타이머 인터럽트 혹은 IPI는 이 플래그가 설정된다. 그러나, 이 플래그가 suspend 단계에서 인터럽트를 계속 활성화시킬 수 는 있지만, 이미 suspended 상태인 시스템을 wakeup 시키는 능력을 가지고 있지는 않다. 이런 경우에는 `enable_irq_wakeup` 함수를 통해 인터럽트를 설정하면 해당 인터럽트를 통해서 suspended 시스템을 깨울 수 있다. 절대, `IRQF_NO_SUSPEND`와 `enable_irq_wakeup` 를 헷갈리면 안된다!

    : 이 플래그를 `IRQF_SHARED` 플래그와 함께 사용하면, 아주 골치가 아픈 상황이 될 수 있다. 왜냐면, 해당 IRQ에 등록된 모든 인터럽트 핸들러가 NOIRQ 상태에서도 트리거되기 때문이다(이미, suspended된 인터럽트 핸들러도 호출됨). 그래서, 기본적으로 `IRQF_NO_SUSPEND`와 `IRQF_SHARED`는 같이 사용되지 않는 것이 좋다.

     

     

     

    - enable_irq_wake vs disable_irq_wake

    " SoC가 완전히 절전 모드에 들어갔을 경우, 벤더사에서 지정한 일부 인터럽트들을(External Wake-up Interrupts) 통해서만 SoC 를 wakeup 시킬 수 있다. 대개, 이 인터럽트들은 시스템이 일반적인 working state 에서는 일반적인 I/O 인터럽트로 동작한다. 그런데, 시스템이 절전 모드 상태가 되면 해당 인터럽트들은 시스템을 wakeup 시킬 수 있는 능력을 갖게된다. 어떻게 이럴 수 있을까? Exynos4412 를 가져왔다. 그런데, 재미있는 건 `Exynos4412 - Interrupt controller` 파트에서는 wake-up 에 대한 내용을 찾을 수 없었다. 그렇다면, 어디서 해당 내용을 찾을 수 있을까? 총 2군데가 있다.

    1. `Exynos4412 - Power Management Unit` 파트에서 `8.5.4.1 External Interrupts` 에 다음과 같은 내용이 있다.

    8.5.4.1 External Interrupts
    External interrupts are the common wake-up source of LPA, DEEP-STOP, and SLEEP modes. The logic for external interrupt configuration such as polarity, edge/level sensitivity, and masking resides in the GPIO. You can modify the external interrupts through GPIO register setting before entering power-down modes. The external interrupt handling logic holds the information until user clears the information.

    " 위 내용에서 볼 수 있다시피, system wake-up 을 `GIC` 가 아닌, `GPIO controller` 를 통해서 한다.


    2. `Exynos4412 - General Purpose Input/Output (GPIO) Control` 파트에 아래와 같은 그림을 볼 수 있다.

    Exynos4412 datasheet

    " exynos 4412 의 GPIO module 은 2개로 나눠진다.

    1. alive-part : sleep mode 에서도 전원이 공급된다.
    2. off-part : sleep mode 에서 전원 공급이 안된다.

     

     

    " 위에 exynos4412 GPIO controller 를 바탕으로 인터럽트 종류는 크게 2 개가 존재한다고 볼 수 있다.

    1. off-part interrupt : 시스템이 working state 일 경우에는 해당 인터럽트 컨트롤러를 통해서 인터럽트가 처리된다. 이 인터럽트 컨트롤러가 turn on 되어있으면, sleep state 인터럽트 컨트롤러는 turn off 되어있다.

    - alive-part interrupt : 시스템이 sleep state가 되면, working state 인터럽트 컨트롤러가 turn off 된다. 이 시점에 sleep state 인터럽트 컨트롤러가 turn on 되면서, wakeup interrupt 들을 처리할 수 있게 된다. 만약, 시스템이 다시 working state가 되면, sleep state 인터럽트 컨트롤러는 turn off가 되고, 일반적인 I/O 인터럽트를 처리하기 위해서 working state 인터럽트 컨트롤러가 다시 turn on 된다.

     

     

    " `enable_irq_wake` 함수는 전달받은 인터럽트 라인을 working state 인터럽트 컨트롤러가 아닌, sleep state 인터럽트 컨트롤러쪽으로 연결시킨다. `disable_irq_wake`는 반대의 작업을 한다. 즉, `xxxxxx_irq_wake` 함수는 인자로 전달된 인터럽트 라인의 wakeup 기능을 직접적으로 enable / disable 한다기 보다는, 상황에 맞는 인터럽트 컨트롤러에게 라우팅 시킨다고 보는게 맞다.

     

     

    " `enabled_irq_wake` 함수로 전달된 인터럽트는 시스템 절전 모드 상태에서도 비활성화되지 않는다. 그리고, 해당 인터럽트 플래그로 `IRQD_WAKEUP_ARMED` 플래그가 SET 된다. 일반적인 인터럽트는 `suspend_device_irq` 함수에서 `IRQS_SUSPENDED` 플래그가 SET 되고, 시스템이 절전 모드에 있을 때 같이 비활성화된다. 시스템 절전 모드에서 비활성화 되어있던 일반적인 인터럽트들은 시스템이 resume 과정에서 `resume_device_irqs` 함수에서 다시 활성화된다.

    // kernel/irq/internals.h - v6.5
     * IRQS_PENDING			- irq is pending and replayed later
     * IRQS_SUSPENDED		- irq is suspended
    // include/linux/irq.h - v6.5
    /*
     * Bit masks for irq_common_data.state_use_accessors
     ....
     * IRQD_WAKEUP_STATE		- Interrupt is configured for wakeup
     ....
     * IRQD_WAKEUP_ARMED		- Wakeup mode armed
     ....
     * IRQD_IRQ_ENABLED_ON_SUSPEND	- Interrupt is enabled on suspend by irq pm if
     *				  irqchip have flag IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND set.
     ....
    */
    
    ....
    #define __irqd_to_state(d) ACCESS_PRIVATE((d)->common, state_use_accessors)
    
    ....
    
    static inline bool irqd_is_wakeup_set(struct irq_data *d)
    {
    	return __irqd_to_state(d) & IRQD_WAKEUP_STATE;
    }
    ....
    // kernel/irq/pm.c - v6.5
    static bool suspend_device_irq(struct irq_desc *desc)
    {
    	....
    	
            if (!desc->action || irq_desc_is_chained(desc) ||
    	    desc->no_suspend_depth) // --- 1
    		return false;
        
    	if (irqd_is_wakeup_set(irqd)) { // --- 2
    		irqd_set(irqd, IRQD_WAKEUP_ARMED); // --- 2
    
    		....
    		/*
    		 * We return true here to force the caller to issue
    		 * synchronize_irq(). We need to make sure that the
    		 * IRQD_WAKEUP_ARMED is visible before we return from
    		 * suspend_device_irqs().
    		 */
    		return true;
    	}
    
    	desc->istate |= IRQS_SUSPENDED; // --- 3
    	__disable_irq(desc);
    
    	....
    }
    1. desc->no_suspend_depth 가 양수라면, 해당 인터럽트를 IRQF_NO_SUSPEND 플래그가 설정됬다는 것을 의미한다. 그러므로, 인터럽트를 비활성화하지 않는다.

    2. enable_irq_wake() 함수를 통해서 전달된 인터럽트는 IRQD_WAKEUP_STATE 가 설정된다. 그런데, 이 플래그는 단지 wake-up 이 기능을 가지고 있다는 상태를 나타낸다. 즉, wake-up interrupt 는 단지 기능을 가지고 있다라는 측면과 wake-up 기능을 enable 하는 것으로 나눠볼 수 있다.

    1. 단순 wake-up 기능을 가지고 있다의 의미 - IRQD_WAKEUP_STATE
    2. 실제 wake-up 기능을 활성화 - IRQD_WAKEUP_ARMED  [참고1 참고2 참고3]

    3. IRQD_WAKEUP_STATE 플래그는 enable_irq_wake() 함수를 통해서만 설정된다. 즉, enable_irq_wake() 함수를 통해서 wake-up interrupt 로 설정되지 않은 인터럽트를 IRQD_SUSPENDED 플래그가 설정된다. 이 플래그는 system suspend 시에 interrupt disabled 되는 것을 의미한다.

     

     

    " suspend_device_irqs() 함수는 IRQF_NO_SUSPEND 플래그가 설정된 인터럽트를 제외한 모든 인터럽트를 disable 한다(suspend_device_irq() 함수에서 `desc->no_suspend_depth` 를 통해 인터럽트 비활성화를 막는다). 

    // kernel/irq/pm.c - v6.5
    /**
     * suspend_device_irqs - disable all currently enabled interrupt lines
     *
     * During system-wide suspend or hibernation device drivers need to be
     * prevented from receiving interrupts and this function is provided
     * for this purpose.
     *
     * So we disable all interrupts and mark them IRQS_SUSPENDED except
     * for those which are unused, those which are marked as not
     * suspendable via an interrupt request with the flag IRQF_NO_SUSPEND
     * set and those which are marked as active wakeup sources.
     *
     * The active wakeup sources are handled by the flow handler entry
     * code which checks for the IRQD_WAKEUP_ARMED flag, suspends the
     * interrupt and notifies the pm core about the wakeup.
     */
    void suspend_device_irqs(void)
    {
    	struct irq_desc *desc;
    	int irq;
    
    	for_each_irq_desc(irq, desc) {
    		unsigned long flags;
    		bool sync;
    
    		if (irq_settings_is_nested_thread(desc))
    			continue;
    		raw_spin_lock_irqsave(&desc->lock, flags);
    		sync = suspend_device_irq(desc);
    		raw_spin_unlock_irqrestore(&desc->lock, flags);
    
    		if (sync)
    			synchronize_irq(irq);
    	}
    }

     

     

    " `irqd_is_wakeup_set` 함수는 인자로 전달된 IRQ 에 `IRQD_WAKEUP_STATE` 플래그가 SET 되어있는지 검사한다. 이 플래그는 해당 IRQ가 시스템을 WAKEUP 시킬 기능이 있다는 것을 의미한다(`enable_irq_wake` 함수에서 SET 된다). 그리고, 인터럽트가 WAKEUP 기능이 있는 놈이라면, `IRQD_WAKEUP_ARMED`를 설정한다. 근데, `IRQD_WAKEUP_STATE`와 `IRQD_WAKEUP_ARMED`의 차이는 뭘까?

     

     

    " 만약, `enable_irq_wake` 함수를 통해 설정된 인터럽트가 발생하면, 해당 인터럽트는 `IRQS_PENDING | IRQS_SUSPENDED` 플래그가 설정되고 해당 인터럽트는 비활성화된다(`irq_pm_check_wakeup` 함수에서 해당 내용 수행). 해당 인터럽트를 비활성하는 이유는 wakeup 기능으로만 사용되는 핀이기 때문에 시스템이 working state 가 되면, 굳이 필요가 없기 때문이다.

     

     

     

    " `IRQS_SUSPENDED` 플래그는 왜 설정할까? `IRQS_SUSPENDED` 플래그는 system suspended 상태에서 인터럽트가 발생할 수 없음을 의미한다. `irq_pm_check_wakeup` 함수가 호출되는 시점은 아직 시스템이 suspend 상태이다. 나중에 `resume_device_irqs` 함수에서 CLEAR 된다. `IRQS_PENDING`은 왜 설정할까? 인터럽트 핸들러에 의해 아직 처리가 되지 않았기 때문이다. 즉, 아직 서비스가 되지 않았으므로, PENDING 상태로 남겨논다.

    // include/linux/interrupt.h - v6.5
    ....
    
    static inline int enable_irq_wake(unsigned int irq)
    {
    	return irq_set_irq_wake(irq, 1);
    }
    
    static inline int disable_irq_wake(unsigned int irq)
    {
    	return irq_set_irq_wake(irq, 0);
    }
    ....
    // kernel/irq/manage.c - v6.5
    /**
     *	irq_set_irq_wake - control irq power management wakeup
     *	@irq:	interrupt to control
     *	@on:	enable/disable power management wakeup
     *
     *	Enable/disable power management wakeup mode, which is
     *	disabled by default.  Enables and disables must match,
     *	just as they match for non-wakeup mode support.
     *
     *	Wakeup mode lets this IRQ wake the system from sleep
     *	states like "suspend to RAM".
     *
     *	Note: irq enable/disable state is completely orthogonal
     *	to the enable/disable state of irq wake. An irq can be
     *	disabled with disable_irq() and still wake the system as
     *	long as the irq has wake enabled. If this does not hold,
     *	then the underlying irq chip and the related driver need
     *	to be investigated.
     */
    int irq_set_irq_wake(unsigned int irq, unsigned int on)
    {
    	....
    	if (on) {
    		if (desc->wake_depth++ == 0) {
    			ret = set_irq_wake_real(irq, on);
    			....
    				irqd_set(&desc->irq_data, IRQD_WAKEUP_STATE);
    		}
    	} else {
    		if (desc->wake_depth == 0) {
    			WARN(1, "Unbalanced IRQ %d wake disable\n", irq);
    		} else if (--desc->wake_depth == 0) {
    			ret = set_irq_wake_real(irq, on);
    			....
    				irqd_clear(&desc->irq_data, IRQD_WAKEUP_STATE);
    		}
    	}
    	....
    }

     

     

    " enable_irq_wake() 함수는 위에서 볼 수 있다시피, 2가지 작업을 수행한다.

    1. driver-specific 한 함수를 호출해서(`struct irq_chip->irq_set_wake`), 물리적으로 해당 인터럽트 라인을 wake-up interrupt 로 설정한다.

    2. 소프트웨어적으로 해당 인터럽트에 IRQD_WAKEUP_STATE 플래그를 설정하여, system suspend 시에 wake-up 기능을 장착(IRQD_WAKEUP_ARMED)할 수 있도록 한다.

     

     

    " `suspend_device_irqs` 함수가 실행되면, 일반적인 인터럽트들은 마스크된다. suspending 도중에 wakeup 인터럽트가 발생해서 시스템을 깨웠는데, 정작 인터럽트 핸들러는 실행되지 않는 경우가 있다. 나는 분명히 SoC 에서 지원하는 하드웨어 라인을 `enable_irq_wake` 함수를 통해서 등록했는데, 인터럽트 핸들러가 호출이 되지 않았다. 왜 그럴까? 인터럽트 핸들러를 등록할 때, `IRQF_NO_SUSPEND` 플래그를 같이 설정하지 않아서 그렇다. 요약하면, 다음과 같다.

    1. enable_irq_wake / disable_irq_wake 함수는 하드웨어적으로 wake-up interrupt 활성 및 비활성한다. 인터럽트 핸들러와는 관련이 없다.

    2. IRQF_NO_SUSPEND 플래그는 물리적인 인터럽트 라인과는 관련없이 인터럽트 핸들러를 suspend / resume 과정 내내 활성화시켜 준다.

     

     

     

     

    - Determining whether or not Interrupt handled  

    " 아래 그림은 `handle_level_irq` 함수에서 시작해서 인터럽트 핸들러를 실행할 지 여부를 판단하는 순서도를 보여준다. 참고로, 아래 그림은 레벨 트리거를 전제로 했기 때문에, `handle_level_irq` 함수로 다이어그램을 만들었다. 만약, 엣지 트리거라면 `handle_edge_irq` 함수를 사용한다.


    https://blog.csdn.net/hello_yj/article/details/125054121

     

     

    " `irq_may_run` 함수는 현재 발생한 인터럽트에 등록된 핸들러를 실행해도 되는지를 여부를 판단한다. 만약, false 를 반환하면 핸들러를 실행하지 못한다. 핸들러가 실행되는 조건은 2가지가 있다.

    1. 현재 시점을 기준으로 해당 인터럽트의 hardirq 핸들러가 실행 중이지 않아여야 한다. IRQ thread는 신경쓰지 않는다.

    2. 현재 발생한 인터럽트가 시스템을 wakeup 시킨 irq 가 아니여야 한다. 즉, 현재 시스템이 wakeup 된 원인이 해당 인터럽트이면 안된다(`IQRD_WAKEUP_ARMED` 판단). 

     

     

    " `irq_pm_check_wakeup` 함수는 현재 발생한 인터럽트 때문에 시스템이 wake-up 됬는지를 검사하고, 적당한 세팅후에 시스템을 wakeup 시킨다(`pm_system_irq_wakeup`).

    // kernel/irq/pm.c - v6.5
    bool irq_pm_check_wakeup(struct irq_desc *desc)
    {
    	if (irqd_is_wakeup_armed(&desc->irq_data)) { // --- 1
    		irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); // --- 2
    		desc->istate |= IRQS_SUSPENDED | IRQS_PENDING; // --- 2
    		desc->depth++;
    		irq_disable(desc); // --- 2
    		pm_system_irq_wakeup(irq_desc_get_irq(desc)); // --- 3
    		return true;
    	}
    	return false;
    }
    1. 현재 발생한 인터럽트 때문에 시스템이 wake-up 됬는지를 체크한다.

    2. 여기서 중요한 건, `IRQS_PENDING` 플래그다. 지금 이 시점에서는 인터럽트를 처리하기 보다는 system resume 이 더 중요하다. 그렇기 때문에, 지금 당장은 처리하지 않고 나중으로 미룬다. 당연히, 인터럽트를 처리하지 못했기 때문에, 해당 인터럽트를 enable 시켜서는 안된다. 왜? 아직 해당 인터럽트를 처리하지도 못했는데, 동일한 인터럽트를 또 발생 시킬 수 는 없기 때문이다[참고1].

    3. `pm_system_irq_wakeup()` 함수는 wake-up interrupt 를 저장한 뒤, 시스템을 wake-up 시키는 함수다. 주의할 점은 wake-up interrupt 가 연속적으로 발생할 수 있기 때문에, 최대 2개 까지 저장할 수 가 있다. 만약, 연속적으로 3 개의 wake-up interrupt 가 발생할 경우에는 irq_number 를 0 으로 설정해서 시스템을 wake-up 시키지 않는다. 그렇다면, wakeup_irq[0] 과 wakeup_irq[1] 은 언제 clear 될까? 만약, 이 값을 clear 하지 않을 경우, wake-up interrupt 가 발생하더라도 `pm_system_wakeup()` 함수가 호출되지 않게 된다. wakeup_irq 배열은 `pm_wakeup_clear()` 함수를 통해서 clear 된다[참고1 참고2].
    // drivers/base/power/wakeup.c - v6.5
    void pm_system_wakeup(void)
    {
    	atomic_inc(&pm_abort_suspend);
    	s2idle_wake();
    }​
    // drivers/base/power/wakeup.c - v6.5
    void pm_system_irq_wakeup(unsigned int irq_number)
    {
    	unsigned long flags;
    
    	raw_spin_lock_irqsave(&wakeup_irq_lock, flags);
    
    	if (wakeup_irq[0] == 0)
    		wakeup_irq[0] = irq_number;
    	else if (wakeup_irq[1] == 0)
    		wakeup_irq[1] = irq_number;
    	else
    		irq_number = 0;
    
    	pm_pr_dbg("Triggering wakeup from IRQ %d\n", irq_number);
    
    	raw_spin_unlock_irqrestore(&wakeup_irq_lock, flags);
    
    	if (irq_number)
    		pm_system_wakeup();
    }

     

     

    " `irq_may_run` 및 `irq_pm_check_wakeup` 함수 모두 system suspend 시에 호출되는 함수들이 아닌, resume 과정에서 호출되는 함수들이다. `irq_may_run` 함수 같은 경우는 2 가지 일을 한다고 볼 수 있다. wakeup interrupt 과정에서만 호출되는 함수가 아니다. 시스템이 working state 일 때도, 인터럽트가 발생하면 이 함수를 통해 핸들러를 실행시킬지 말지를 결정한다.

    // kernel/irq/chip.c - v6.5
    static bool irq_may_run(struct irq_desc *desc)
    {
    	unsigned int mask = IRQD_IRQ_INPROGRESS | IRQD_WAKEUP_ARMED;
    
    	/*
    	 * If the interrupt is not in progress and is not an armed
    	 * wakeup interrupt, proceed.
    	 */
    	if (!irqd_has_set(&desc->irq_data, mask)) // --- 1
    		return true;
    
    	/*
    	 * If the interrupt is an armed wakeup source, mark it pending
    	 * and suspended, disable it and notify the pm core about the
    	 * event.
    	 */
    	if (irq_pm_check_wakeup(desc)) // --- 2
    		return false;
    
    	/*
    	 * Handle a potential concurrent poll on a different core.
    	 */
    	return irq_check_poll(desc);
    }
    1. 다른 CPU`s 들에서 이미 인터럽트를 처리 중 아니거나(!IRQD_IRQ_INPROGRESS), 시스템을 wake-up 시킨 interrupt 아니라면(!IRQD_WAKEUP_ARMED), true 를 반환하여 인터럽트 핸들러를 실행할 수 있음을 알린다.

    2. 만약, 현재 발생한 인터럽트 때문에 시스템이 wake-up 됬다면, 해당 인터럽트 핸들러 실행을 resume flow 시점으로 미룬다(만약, irq_pm_check_wakeup() 함수에서 true 를 반환하면). 지금이 resume flow 시점이 아닌가? 이에 대한 대답은 `
    Resume when suspended through interrupt` 섹션에서 알아본다.

     

     

     

     

    - Resume when suspended through interrupt

    " 시스템이 절전 모드 상태일 때, 어떻게 인터럽트를 통해서 시스템을 wakeup 시킬까? resume 중에는 언제 인터럽트가 enable 될까? `freeze_seconday_cpus` 함수를 통해 non-boot CPU`s 를 모둗 down 시키고, `arch_suspend_enable_irqs` 함수안에 구현되어 있는 `local_irq_disable` 함수를 통해서 boot CPU 는 인터럽트가 비활성화 된다. 그리고, PSCI 스펙에 따라 system suspend 를 진행하는 CPU 를 제외한 모든 CPU`s 들은 CPU_OFF 되고, 코드를 실행중 인 CPU 만 CPU_SUSPEND 상태가 된다(코드를 실행중 인 CPU 는 `SYSTEM_SUSPEND` 커맨드를 SCP(Power controller) 에게 전달한다. 이 때, CPU_SUSPEND 커맨드와 동일한 효과가 있다).  

     

    " 이 상황에서 이제 어떻게 해야 할까? 현재 상태는 non-boot CPU`s 들은 모두 power-down 이며, 유일하게 boot CPU 만이 dormant 상태로 되어있다. 즉, boot CPU 는 아직 power-down 된 것은 아니기 때문에, wake-up interrupt 를 받을 수 는 있다. system suspend 상태에서 Power controller 가 wake-up interrupt 를 받게되면, hardware level 의 suspended state 는 종료하고, system resume 을 준비한다.


    https://www.elecfans.com/d/1920109.html

     

     

    " wake-up interrupt 가 발생하면, 인터럽트 처리는 resume flow 로 미루고  `suspend_ops->enter` 함수 내부에서 멈췄던 지점부터 다시 시작된다. 그 이후에 첫 번째로 호출되는 함수가 `syscore_resume` 함수다. 그리고 나서 조금있다 `arch_suspend_enable_irqs` 함수가 호출된다. 이 시점에 `local_irq_enable` 함수가 호출되면서 boot CPU 의 인터럽트가 활성화된다. 이 시점에 인터럽트 컨트롤러는 자신의 `pending register` 를 확인해서 SET 되어 있는 친구를 CPU 에게 전달한다. 그렇다면, `arch_suspend_enable_irqs()` 함수가 호출된 후에는 실제 wake-up interrupt 처리 루틴이 시작될까? 아니다.

     

     

     

    " 위에서 봤겠지만, wake-up interrupt 는 이 시점에 처리되지 않는다. handle_level_irq() 함수안에 irq_may_run() 함수를 통해 interrupt handler 의 실행 여부를 결정하는데, wake-up interrupt 는 여기서 false 가 반환되서 처리가 뒤로 미뤄진다. 그렇면, 도대체 이 놈의 처리는 언제쯤 되는것이냐? 뒤에서 다루겠지만, `check_irq_resend()` 함수에서 처리된다. 

     

     

    " 아래 그림은 미디어텍(`mtk`는 미디어텍을 나타낸다)에서 wake-up interrupt 를 통해서 system resume 가 처리되는 과정을 보여준다. 여기서 인터럽트 루틴이 2개로 나눠진다는 것에 주의해야 한다.

    1. wake-up interrupt 처리를 resume flow 로 미루는 과정 - (1), (2)
    2. wake-up interrupt 를 실제로 처리하는 과정 - (4)

    https://blog.csdn.net/hello_yj/article/details/125054121

     

     

    " 위의 그림은 `resume flow` 와 `irq flow`를 합친 것이다. 위쪽은 `resume flow`를 의미하고, 아래쪽은 `irq flow`를 나타낸다. 인터럽트를 통한 시스템 웨이크업 프로세스는 아래와 같이 요약할 수 있다.

    1" 시스템 절전 모드 상태에서 `wakeup interrupt`를 받게 되면, `hardware-level sleep state`를 종료하고 `system-level wakeup` 프로세스를 수행한다. 그리고 `arch_suspend_enable_irq` 함수가 호출되는 시점에 boot CPU의 인터럽트가 다시 활성화된다.

    2" 인터럽트를 활성화 하자마자 즉각적으로 인터럽트 컨트롤러에게 펜딩 웨이크업 인터럽트를 받게되고, CPU는 시스템 웨이크업 프로세스보다는 인터럽트 웨이크업 프로세스를 진행하게 된다.

    3" 그러나, `웨이크-업 인터럽트 프로세스` 에서는 기본적으로 인터럽트를 처리하지 않는다. 그러므로, `handle_level(edge)_irq` 함수에서 웨이크-업 인터럽트 인지로 결론이 나면, `인터럽트 웨이크-업 프로세스`는 종료하고, `시스템 웨이크-업 프로세스`를 계속 진행해나간다. 왜 `웨이크-업 인터럽트 프로세스` 에서 인터럽트를 처리하지 않을까? 내 추측이지만, 아마 이 시점에는 boot CPU 하나만 동작하고 있기 때문에, `resume latency`가 길어지는 것에 대한 방지를 위해서이지 않을까 싶다.

    4" 시스템 웨이크-업 프로세스를 진행하는 과정에서 `resume_irq` 함수가 호출된다. 이 함수에서는 `웨이크-업 인터럽트 프로세스`에서 실행하지 못했던 웨이크-업 인터럽트 핸들러를 실행할 지 여부를 판단한다. 해당 여부를 판단하는 함수가 `check_irq_send` 다.

     

     

    " 그렇다면, `check_irq_send` 함수는 어떻게 pending wake-up interrupt 다시 처리할까?


    https://blog.csdn.net/hello_yj/article/details/125054121

     

     

    " `resume_device_irqs` 함수를 통해서 모든 인터럽트 라인들을 다시 활성화가 된다. 그리고, `resume_irq` 함수를 보면, `irq_pm_check_wakeup` 함수에서 설정했던 인터럽트 플래그들이 CLEAR 되는 것을 확인할 수 있다.

    // kernel/irq/pm.c - v6.5
    static void resume_irq(struct irq_desc *desc)
    {
    	struct irq_data *irqd = &desc->irq_data;
    
    	irqd_clear(irqd, IRQD_WAKEUP_ARMED);
    
    	....
    
    	if (desc->istate & IRQS_SUSPENDED)
    		goto resume;
    
    	/* Force resume the interrupt? */
    	if (!desc->force_resume_depth)
    		return;
    
    	/* Pretend that it got disabled ! */
    	desc->depth++;
    	irq_state_set_disabled(desc);
    	irq_state_set_masked(desc);
    resume:
    	desc->istate &= ~IRQS_SUSPENDED;
    	__enable_irq(desc);
    }
    // kernel/irq/pm.c - v6.5
    static void resume_irqs(bool want_early)
    {
    	....
    	for_each_irq_desc(irq, desc) {
    		....
    		resume_irq(desc);
    		....
    	}
    }
    // kernel/irq/pm.c - v6.5
    /**
     * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs()
     *
     * Enable all non-%IRQF_EARLY_RESUME interrupt lines previously
     * disabled by suspend_device_irqs() that have the IRQS_SUSPENDED flag
     * set as well as those with %IRQF_FORCE_RESUME.
     */
    void resume_device_irqs(void)
    {
    	resume_irqs(false);
    }

     

     

    " `check_irq_resend()` 함수는 전달받은 인터럽트에 `IRQS_PENDING` 플래그가 설정되어 있는 경우에만 CPU 에게 인터럽트를 re-trigger 한다. 그리고, 이미 해당 인터럽트에 `IRQS_REPLAY` 플래그가 SET 되어있으면 재전송을 하지않는다. 왜냐면,  `IRQS_REPLAY` 플래그가 설정된 인터럽트는 re-trigger 중임을 의미하기 때문이다. 여기서 re-trigger 은 peripherals 들이 interrupt controller 에게 re-trigger 하는 것이 아니라, interrupt controller 가 CPU 에게 re-trigger 한다는 뜻이다. 

    // kernel/irq/resend.c - v6.5
    /*
     * IRQ resend
     *
     * Is called with interrupts disabled and desc->lock held.
     */
    int check_irq_resend(struct irq_desc *desc, bool inject)
    {
    	int err = 0;
        
    	....
    	....
        
    	if (desc->istate & IRQS_REPLAY)
    		return -EBUSY;
    
    	if (!(desc->istate & IRQS_PENDING) && !inject)
    		return 0;
    
    	desc->istate &= ~IRQS_PENDING;
    
    	if (!try_retrigger(desc))
    		err = irq_sw_resend(desc);
    
    	/* If the retrigger was successful, mark it with the REPLAY bit */
    	if (!err)
    		desc->istate |= IRQS_REPLAY;
    	return err;
    }

     

     

    " try_retrigger() 함수는 하드웨어 레벨에서 interrupt controller 가 re-trigger 기능을 지원하는지를 판단한다. 만약, 하드웨어 레벨에서 지원한다면, interrupt controller 에서 re-trigger 를 실행한다(`desc->irq_data.chip->irq_retrigger(&desc->irq_data)`). 만약, 하드웨어 레벨에서 지원하지 않을 경우, `0` 을 반화해서 software 적으로 re-trigger 를 실행하도록 한다.

    // kernel/irq/resend.c - v6.5
    static int try_retrigger(struct irq_desc *desc)
    {
    	if (desc->irq_data.chip->irq_retrigger)
    		return desc->irq_data.chip->irq_retrigger(&desc->irq_data);
    
    #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
    	return irq_chip_retrigger_hierarchy(&desc->irq_data);
    #else
    	return 0;
    #endif
    }

     

     

    " 실제 인터럽트 재전송을 하는 함수는 `irq_sw_resend()` 함수다. irq_sw_resend() 함수에서 처리하는 작업은 크게 2 가지다.

    1. re-trigger 되야 하는 interrupt 는 `irq_resend_list` 에 저장.
    2. tasklet 을 사용해서 irq_resend_list 에 저장한 re-trigger interrupt 를 처리하도록 설정. 

     

     

    " 위에서 tasklet 은 `tasklet_schedule(&resend_tasklet)` 함수를 호출해서 `resend_tasklet` 에 등록된 `resend_irqs` 핸들러가  호출된다. `resend_tasklet` 은 추후에 스케줄링을 통해서 실행된다(hardirq 인터럽트 핸들러는 인터럽트 컨텍스트에서 실행되어야 하기 때문에 태스크릿을 통해 실행한다. 이걸 워크큐로 실행하면 큰일난다)

    // kernel/irq/resend.c - v6.5
    /* hlist_head to handle software resend of interrupts: */
    static HLIST_HEAD(irq_resend_list);
    ....
    
    /* Tasklet to handle resend: */
    static DECLARE_TASKLET(resend_tasklet, resend_irqs);
    
    static int irq_sw_resend(struct irq_desc *desc)
    {
    	....
    	/* Add to resend_list and activate the softirq: */
    	if (hlist_unhashed(&desc->resend_node))
    		hlist_add_head(&desc->resend_node, &irq_resend_list);
    	raw_spin_unlock(&irq_resend_lock);
    	....
        tasklet_schedule(&resend_tasklet);
        
        return 0;
    }

     

     

    " `resend_irqs()` 함수에서는 irq_resend_list 를 순회하면서 리스트에 존재하는 모든 re-trigger irq 의 high-level irq flow handler 를 호출한다. 즉, interrupt controller handler 를 호출한다(`desc->handle_irq` 는 드라이버 개발자가 작성한 핸들러가 아닌, CPU 및 SoC 제조사에서 작성한 interrupt controller 핸들러다. 이 함수에서 내부적으로 드라이버 개발자가 작성한 `action->handle` 를 호출한다. 자세한 내용은 이 글을 참고하자).

    // kernel/irq/resend.c - v6.5
    /*
     * Run software resends of IRQ's
     */
    static void resend_irqs(struct tasklet_struct *unused)
    {
    	struct irq_desc *desc;
    
    	....
    	while (!hlist_empty(&irq_resend_list)) {
    		desc = hlist_entry(irq_resend_list.first, struct irq_desc,
    				   resend_node);
    		hlist_del_init(&desc->resend_node);
    		....
    		desc->handle_irq(desc);
    		....
    	}
    	....
    }

     

     

     

     

    - Suspend in IRQ handler working 

     

    " IRQ 인터럽트 핸들러가 실행 중에 `system suspend`가 발생하면 어떻게 될까? 결론부터 말하면, 일단 인터럽트 처리가 완료될 때 까지 기다려준다. 이 코드는 `suspend_device_irqs` 함수안에 `synchronize_irq` 함수에서 이루어진다.

    // kernel/irq/manage.c - v6.5
    /**
     *	synchronize_irq - wait for pending IRQ handlers (on other CPUs)
     *	@irq: interrupt number to wait for
     *
     *	This function waits for any pending IRQ handlers for this interrupt
     *	to complete before returning. If you use this function while
     *	holding a resource the IRQ handler may need you will deadlock.
     *
     *	Can only be called from preemptible code as it might sleep when
     *	an interrupt thread is associated to @irq.
     *
     *	It optionally makes sure (when the irq chip supports that method)
     *	that the interrupt is not pending in any CPU and waiting for
     *	service.
     */
    void synchronize_irq(unsigned int irq)
    {
    	struct irq_desc *desc = irq_to_desc(irq);
    
    	if (desc) {
    		__synchronize_hardirq(desc, true);
    		/*
    		 * We made sure that no hardirq handler is
    		 * running. Now verify that no threaded handlers are
    		 * active.
    		 */
    		wait_event(desc->wait_for_threads,
    			   !atomic_read(&desc->threads_active));
    	}
    }
    EXPORT_SYMBOL(synchronize_irq);

     

     

    " `__synchronize_hardirq` 함수는 현재 hardirq가 실행 중이라면, 처리가 끝날 때 까지 기다려준다. 그리고, `threaded handler`는 `wait_event` 함수를 통해서 `threads_active` 카운트가 0 이 될 때까지 계속 대기한다. 즉, 아직 남은 `threaded handler`가 있으므로, 대기하는 것이다. (`wait_event` 함수는 기본적으로 두 번째 인자가 `true`가 될 때까지 프로세스를 `TASK_UNINTERRUPTIBLE` 상태로 만들어서 대기시킨다. 두 번째 인자가 `true`가 되는것은 앞에 `desc->wait_for_threads` 워크가 일정 시간을 주기로 워크큐에 들어가서 두 번째 인자가 true가 됬는지를 검사한다)

     

    " `irqd_irq_inprogress` 함수는 해당 irq에 `IRQD_IRQ_INPROGRESS`가 설정되어 있는지 여부를 알려준다. 이 플래그가 설정되어 있으면, 현재 `hardirq`가 처리 중임을 의미한다. `IRQD_IRQ_INPROGRESS` 플래그는 `handle_irq_event` 함수가 실행될 때, 설정된다.

    // kernel/irq/manage.c - v6.5
    static void __synchronize_hardirq(struct irq_desc *desc, bool sync_chip)
    {
    	struct irq_data *irqd = irq_desc_get_irq_data(desc);
    	bool inprogress;
    
    	do {
    		unsigned long flags;
    
    		/*
    		 * Wait until we're out of the critical section.  This might
    		 * give the wrong answer due to the lack of memory barriers.
    		 */
    		while (irqd_irq_inprogress(&desc->irq_data))
    			cpu_relax();
    
    		/* Ok, that indicated we're done: double-check carefully. */
    		raw_spin_lock_irqsave(&desc->lock, flags);
    		inprogress = irqd_irq_inprogress(&desc->irq_data);
    
    		/*
    		 * If requested and supported, check at the chip whether it
    		 * is in flight at the hardware level, i.e. already pending
    		 * in a CPU and waiting for service and acknowledge.
    		 */
    		if (!inprogress && sync_chip) {
    			/*
    			 * Ignore the return code. inprogress is only updated
    			 * when the chip supports it.
    			 */
    			__irq_get_irqchip_state(irqd, IRQCHIP_STATE_ACTIVE,
    						&inprogress);
    		}
    		raw_spin_unlock_irqrestore(&desc->lock, flags);
    
    		/* Oops, that failed? */
    	} while (inprogress);
    }

     

     

    " IRQ thread는 어떻게 깨우는 것일까? `__handle_irq_event_percpu` 함수안에 `__irq_wake_thread` 함수를 통해서 실행된다. `__handle_irq_event_percpu` 함수와 `__irq_wake_thread` 함수는 다음과 같다.

    // kernel/irq/handle.c - v6.5
    irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc)
    {
    	....
    	for_each_action_of_desc(desc, action) {
    		....
    		res = action->handler(irq, action->dev_id);
    
    		if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
    			      irq, action->handler))
    			local_irq_disable();
    
    		switch (res) {
    		case IRQ_WAKE_THREAD:
    			....
    			__irq_wake_thread(desc, action);
    			break;
    
    		default:
    			break;
    		}
    		....
    	}
    
    	return retval;
    }
    // kernel/irq/handle.c - v6.5
    void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
    {
    	if (action->thread->flags & PF_EXITING)
    		return;
    
    	/*
    	 * Wake up the handler thread for this action. If the
    	 * RUNTHREAD bit is already set, nothing to do.
    	 */
    	if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags))
    		return;
    
    	desc->threads_oneshot |= action->thread_mask;
    
    	/*
    	 * We increment the threads_active counter in case we wake up
    	 * the irq thread. The irq thread decrements the counter when
    	 * it returns from the handler or in the exit path and wakes
    	 * up waiters which are stuck in synchronize_irq() when the
    	 * active count becomes zero. synchronize_irq() is serialized
    	 * against this code (hard irq handler) via IRQS_INPROGRESS
    	 * like the finalize_oneshot() code. See comment above.
    	 */
    	atomic_inc(&desc->threads_active);
    
    	wake_up_process(action->thread);
    }

     

    " `__handle_irq_event_percpu` 함수에서 hardirq는 `action->handler`와 매핑되며, IRQ thread는 `__irq_wake_thread`와 매핑된다. hardirq에서 `IRQ_WAKE_THREAD`를 반환해야 `IRQ thread`를 wakeup 시킬 수 있다. 그리고, IRQ thread가 실행되는 환경은 로컬 CPU의 인터럽트가 모두 비활성된 (`local_irq_disable`)에서 실행된다.

     

    " `__irq_wake_thread` 함수는 먼저 IRQ thread가 `IRQTF_RUNTHREAD` 플래그가 SET 되어있는지 부터 확인한다. 이 플래그가 SET 되어있다는 것은 이미 IRQ thread가 실행 중임을 의미한다. 만약, `true`라면 wakeup 시킬 필요가 없으므로 바로 함수를 종료한다. IRQ thread가 실행중이 아니라면, `threads_active`를 증가시킨다. 이 값은 위에서도 말했지만, 현재 해당 irq 에서 실행되고 있는 IRQ thread 개수를 나타낸다. `wake_up_process` 함수는 인자로 전달된 프로세스를 `TASK_RUNNING` 상태로 변경한 뒤, 런큐에 집어넣는다.

     

    " 근데, IRQ thread의 정체는 뭘까? IRQ thread는 `setup_irq_thread` 함수를 통해서 생성된다. IRQ thread의 이름은 아래와 같은 형식을 띈다.

    1. irq / {IRQ 번호-IRQ 이름}
    2. irq / {IRQ 번호-s-IRQ 이름}

     

    " `get_task_struct`는 인자로 전달된 태스크의 `reference count`를 증가시킨다. 즉, `IRQ thread`로 사용될 수 있도록 새로 생성한 스레드를 제거 대상이 되지 않도록 한다. 이 함수의 반대는 `put_task_struct` 함수가 있다.

    // kernel/irq/manage.c - v6.5
    static int
    setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
    {
    	struct task_struct *t;
    
    	if (!secondary) {
    		t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
    				   new->name);
    	} else {
    		t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
    				   new->name);
    	}
    
    	if (IS_ERR(t))
    		return PTR_ERR(t);
    
    	/*
    	 * We keep the reference to the task struct even if
    	 * the thread dies to avoid that the interrupt code
    	 * references an already freed task_struct.
    	 */
    	new->thread = get_task_struct(t);
    	/*
    	 * Tell the thread to set its affinity. This is
    	 * important for shared interrupt handlers as we do
    	 * not invoke setup_affinity() for the secondary
    	 * handlers as everything is already set up. Even for
    	 * interrupts marked with IRQF_NO_BALANCE this is
    	 * correct as we want the thread to move to the cpu(s)
    	 * on which the requesting code placed the interrupt.
    	 */
    	set_bit(IRQTF_AFFINITY, &new->thread_flags);
    	return 0;
    }

     

     

    " IRQ thread가 수행하는 기본적인 필터 함수는 `irq_thread`와 `irq_thread_fn` 다. 드라이버 개발자가 등록한 IRQ thread는  `action->thread_fn` 함수다. `threads_active` 값은 IRQ thread가 매번 끝날 때 마다, 1씩 차감한다(`wake_threads_waitq`). atomic_dec_and_test 함수는 `arg = arg - 1` 로 동작한다. 그리고, 만약, 1을 뺀 결과값이 0 이면, true 를 리턴한다. 그렇지 않다면, false 를 리턴한다.

    // kernel/irq/manage.c - v6.5
    ....
    
    /*
     * Interrupts explicitly requested as threaded interrupts want to be
     * preemptible - many of them need to sleep and wait for slow busses to
     * complete.
     */
    static irqreturn_t irq_thread_fn(struct irq_desc *desc,
    		struct irqaction *action)
    {
    	irqreturn_t ret;
    
    	ret = action->thread_fn(action->irq, action->dev_id);
    	if (ret == IRQ_HANDLED)
    		atomic_inc(&desc->threads_handled);
    
    	irq_finalize_oneshot(desc, action);
    	return ret;
    }
    
    static void wake_threads_waitq(struct irq_desc *desc)
    {
    	if (atomic_dec_and_test(&desc->threads_active))
    		wake_up(&desc->wait_for_threads);
    }
    ....
    
    /*
     * Interrupt handler thread
     */
    static int irq_thread(void *data)
    {
    	struct callback_head on_exit_work;
    	struct irqaction *action = data;
    	struct irq_desc *desc = irq_to_desc(action->irq);
    	irqreturn_t (*handler_fn)(struct irq_desc *desc,
    			struct irqaction *action);
    
    	....
    
    	if (force_irqthreads() && test_bit(IRQTF_FORCED_THREAD,
    					   &action->thread_flags))
    		handler_fn = irq_forced_thread_fn;
    	else
    		handler_fn = irq_thread_fn;
    
    	....
    
    	while (!irq_wait_for_interrupt(action)) {
    		irqreturn_t action_ret;
    
    		....
    
    		action_ret = handler_fn(desc, action);
    		if (action_ret == IRQ_WAKE_THREAD)
    			irq_wake_secondary(desc, action);
    
    		wake_threads_waitq(desc);
    	}
    
    	....
    	return 0;
    }

     

    " 하나 궁금한 것이 있다. 시스템이 suspend 과정중에 인터럽트가 발생했다. 그런데, 해당 인터럽트의 IRQ thread에서 msleep(1000)을 하면 어떻게 될까? workqueue 와 비교해서 다른 점이 있을까? 다른 점이 있다. hardirq 및 IRQ thread는 시스템이 suspend로 들어가려고 할 때, `synchronize_irq` 함수가 호출되면서 IRQ 처리가 다 끝날때까지 기다려준다. 그래서, IRQ thread에서 `msleep` 함수를 호출하더라도 시스템이 suspend 되지 않는다(워크큐 같은 경우는 시스템 기다려주지 않는다).

     

     

    - IRQD_NO_SUSPEND vs xxxxxx_irq_wake

    " 일반적으로, `IRQD_NO_SUSPEND`와 `enable_irq_wake` 함수를 동시에 사용하지는 않는다. 기본적으로 `IRQD_NO_SUSPEND` 플래그는 인터럽트 핸들러에 초점을 둔 메커니즘이고, `enable_irq_wake` 함수는 인터럽트 라인 자체에 초점을 둔 방식이다. 그리고, `IRQD_NO_SUSPEND` 플래그를 설정하면, `suspend_device_irqs` 함수 호출 이후에도 인터럽트 핸들러가 호출된다. 그러나, `enable_irq_wake` 함수를 통해 등록된 인터럽트 핸들러는 `suspend_device_irqs` 함수 호출 이후에는 비활성화된다(이부분은 정확히 소스 코드로 파악해보는 것이 가장 좋다. 소스상에서 하나의 인터럽트 라인에 2개를 같이 사용하면 꼬이는 부분이 있다). `enable_irq_wake` 함수는 인터럽트 라인을 통해 시스템을 wakeup 시키는 것이기 때문에, 인터럽트 핸들러에는 관심이없다.

    First of all, if the IRQ is not shared, the rules for handling IRQF_NO_SUSPEND interrupts (interrupt handlers are invoked after suspend_device_irqs()) are directly at odds with the rules for handling system wakeup interrupts (interrupt handlers are not invoked after suspend_device_irqs()).

    Second, both enable_irq_wake() and IRQF_NO_SUSPEND apply to entire IRQs and not to individual interrupt handlers, so sharing an IRQ between a system wakeup interrupt source and an IRQF_NO_SUSPEND interrupt source does not generally make sense.

    - 참고 : https://www.kernel.org/doc/Documentation/power/suspend-and-interrupts.txt
Designed by Tistory.