ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] PM - Autosleep
    Linux/kernel 2023. 8. 7. 20:01

    글의 참고

    - https://lwn.net/Articles/479841/
    - https://lwn.net/Articles/388131/

    - https://github.com/torvalds/linux/commit/7483b4a4d9abf9dcf1ffe6e805ead2847ec3264e

    - https://lore.kernel.org/lkml/4F45D035.5000906@linux.vnet.ibm.com/

    - http://www.uwsg.indiana.edu/hypermail/linux/kernel/1202.0/02966.html

    - https://lishiwen4.github.io/linux-driver/PM-autosleep

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


    글의 전제

    - 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다. 나도 모르기 때문에 나중에 알아봐야 할 내용이라는 뜻이다.
    - 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다. 그러므로, 밑 줄 처친 글이 이해가 안간다면 링크를 따라서 관련 내용을 공부하자.


    글의 내용

    - Overview

    " `Opportunistic suspend`가 뭔말일까? 쉽게 `할일이 없으면 suspend로 들어간다`는 뜻이다. 그래서 안드로이드 기반의 기기에서 이 방식은 너무나도 효과적이었다고 한다. (이 방법이 임베디드 시장에 처음 나왔을 때는 PM에 대한 강력한 방법으로 칭송받았다. 왜? 이 방식은 2012년 쯤에 나왔다. 즉, 스마트폰이 엄청난 붐을 일으킬 때였다. 이와 동시에 휴대용 기기의 전류 소모에 대한 부분 또한 굉장히 문제가 많았던 시기다. `Opportunistic suspend`는 시스템 현재 작업중인 요소가 없을 경우, 시스템이 스스로 sleep으로 들어가게 하는 방식이다보니 휴대용 기기에서는 이 방식이 먹혔던 것 같다) 특히 엉망으로 작성된 유저 어플리케이션이 시스템이 배터리를 계속 소모하는 것을 막아주었다. 근데 여기서 `할일이 없으면 suspend`로 들어간다고 했는데,`할일이 없으면` 이라는 것을 어떤 기준으로 정해할지는 굉장히 어려운 부분이다. 안드로이드에서는 이 기준을 `wakelock/suspend blocker`(예전 이름은 `wake lock`) mechanism으로 정했다. suspend blockers를 이용하면 kernel과 privileged user-space code가 시스템의 suspend 진입을 막을 수 있었고, 무슨 짓을 해도 계속 시스템을 active한 상태로 동작시키게 만들 수 있었다.
     
    " 그런데, 구글에서 제안한 suspend blocker가 안드로이드 자체적으로는 자주 사용되었지만, 실제 리눅스 kernel mainline 에 포함되지 못했다(2010 ~ 2012). 그런데, 리눅스 커널에서는 이 당시에 `Opportunistic suspend` 관련한 별도의 파워 매니지먼트 프레임워크가존재하지 않았었다. 그래서 리눅스 커널에서 Opportunistic suspend 와 비슷한 대체재가 필요하게 됬다(왜냐면, 저 당시가 커널 2.6 버전일 때인데, 주로 리눅스 커널을 구성하는 핵심 기술들이 x86 기반의 디바이스들에 초점이 되어있었기 때문에, 휴대용쪽에는 약간 보수적인 반응들이 많았다).

     

    그래서 커널 2.6.36 에 `wakeup events` 라는 프레임워크가 합류한다. wakeup events 는 button press 같은 이벤트들을 트랙킹하고 시스템을 wake-up 시키거나 system 을 계속 active 상태로 remain 시키는 기능들을 포함하고 있었다. 그리고, 그 후에 2.6.37 에서 다시 `Wakeup sources` 라는 기술이 추가됬다. wakeup source 는 실제 wakeup event 를 발생시키는 `주체` 가 된다. 그런데, 이런 좋은 기술들을 두고도, 커널 2.6.XX 시절에는, 거의 사용하지 않았다.
     
    그래서 Rafael 형은 위의 2 개의 프레임워크(`wakeup source`, `wakup event`)들을 이용해서 이름만 바꾼 `autosleep` 이라는 개념을 만들었다(autosleep 과 opportunistic suspend 는 완전한 동일어로 보면 된다). Rafael의 패치에서 가장 첫 번째 주목할점은 /sys/power/autosleep이다. 이 파일에 `mem`을 쓰면, 시스템은 active wakeup sources 가 없을 경우, suspend로 진입한다. 다시 위의 파일에 `disk` 를 쓰면 시스템은 active wakeup sources 가 없으면 hibernation 모드로 진입한다(opportunistically hibernate). 여기서 핵심은 active wakeup sources 가 존재하지 않으면, system 이 자동으로 suspend mode 로 진입한다는 것에 그 의의가 있다.
     
    " 안드로이드 시스템은 `suspend blocker` 가 시스템을 suspend 로 진입하지 못하게 하는 time duration 을 기억하고 있다. 그래서 이 time duration 을 통해서 개발자들은 `내 배터리 상태가 왜 이 모양이지?`에 대한 답을 얻을 수 있다. 리눅스에서 `wakeup source` 를 활성화하면, 해당 wakeup source 의 active time duration 은 `/sys/ernel/debug/wakeup_source` 에서 `prevent_sleep_time` 을 의미한다. 즉, prevent_sleep_time 은 특정 `wakeup source(suspend blocker)`가 active 된 시간의 양을 의미한다. prevent_sleep_time 은 누가 시스템을 suspend 상태로 빠지게 못하는지에 대한 정보를 제공하기도 한다.
     
    " 그런데, wakeup sources 가 만들어진 초기에 문제가 하나있다. 그건 유저 레벨에서 시스템이 suspend 되는 것을 막는 방법을 제공하지 않는다는 것이었다. 그런데, 왜 시스템을 계속 active 시켜야 하는 중대한 일을 유저 레벨에서 하는 것일까? 사실, 디바이스 드라이버의 대부분 동작들이 수동적이다. 즉, 기본적인 기능들을 유저에게 export 시키고 나머지 응용 동작들은 모두 위쪽에 위임하는 구조를 취한다. 그래서 여기서도 wakeup sources 를 유저에게 알려 디테일한 파워 매니지먼트는 유저가 컨트롤하게 하려는 의도이다. 그래서 Rafael 형이 마지막 patch 에 이 기능을 추가해줬다. 즉, 유저 애플리케이션은 `/sys/power/wake_lock`에 뭔가를 쓰면 그 이름에 맞는 새로운 active wakeup source가 생성된다. 이 wakeup sources 는 `/sys/power/wake_lock`에 써지는 순간, 즉각적으로 동작하며 다음의 조건들로 인해 해제될 수 있다.

    1. wakeup source timeout expires - wakeup source 가 active 되는 time duration 을 설정할 수 있음 
    2. 동일한 이름이 /sys/power/wake_unlock에 쓸 때

     

     

    "참고로 /sys/power/wake_lock 에 새로 생성할 wakeup source 이름은 필수 인자고 옵션으로 timeout을 넣을 수 있다.

    echo "yohda 10000" > /sys/power/wake_lock

     

     

    " 예를 들어, 어떤 드라이버가 있다. 이 드라이버는 외부 버튼을 인식해서 누르고 있는 동안에는 시스템을 웨이크-업 시킨다고 치자. 이 때, 드라이버는 시스템을 웨이크-업 시키기 위해 이 버튼과 연관된 wakeup source를 active 시켜야 한다. 즉, 외부 버튼은 SW적으로 해당 드라이버가 초기화 되는 시점에 wakeup source로 등록이 되어있어야 한다는 것이다. 그래서 버튼이 누르면 드라이버가 인식해서 버튼과 매핑된 wakeup source를 active 시켜 시스템을 wakeup 시키는 것이다. 근데, 사실 드라이버가 시스템을 wakeup 시키기는 하지만, 이 wakeup을 유지시키는 일은 드라이버가 하지 않는다. 왜냐면, 버튼에 대한 실제 기능(서비스) 처리를 드라이버가 하지 않기 때문이다. 버튼이 눌린 것만 드라이버가 인식하고, 실제 버튼에 대한 기능 처리는 유저 애플리케이션이 하게 된다. 그래서 wakeup 유지는 유저 애플리케이션에 담당한다. wakeup을 유지시키는 기간은 유저 애플리케이션이 버튼을 모두 처리할 때 까지 계속된다. 유저 애플리케이션이 시스템 wakeup을 유지시키는 방법은 위에서 봤던, `sys/power/wake_lock`을 통해서 한다.

     

     

     

    - Autosleep

    • autosleep은 매우 단순하다. `시스템이 할일이 없으면, 저전력 상태로 전환한다`가 끝이다. 사용 시나리오에 따라 시스템을 어떤 저전력 상태로 보낼지 정할 수가 있다.
      • Freeze, Standby, Suspend-to-RAM, Suspend-to-DISK
    • 그렇면 시스템이 아무 것도 하지 않는다는 것을 어떻게 판단할까? wakeup events framework를 이용한다.
      • 시스템이 처리중인 일이 없으면, suspend 진입.
      • 새로 추가된 wakeup event가 없으면, suspend 진입.
      • 시스템이 suspending 중인데, 새로운 wakeup event가 발생하면 resume된다.
    • suspend/resume이 빈번하게 발생하면 동기화 문제가 발생할 수 있다. 그래서 동기화를 문제를 해결하는 것이 무엇보다 중요하다. 동기화 문제는 wakeup events frameowkr와 wakeup count 기능에 따라 달라진다.

     

     

    - Autosleep 구조

    • autosleep을 구현한 소스는 /kernel/pwer/autosleep.c 파일이다. auotsleep은 `wakeup count` 와 `suspend & hibernate` 를 기반으로 한다.

    출처 - http://www.wowotech.net/pm_subsystem/autosleep.html

     
     

    - Autosleep 코드 분석

    • autosleep 은 필수 기능이 아니므로, CONFG_PM_AUTOSLEEP에 의해 On/Off 될 수 있음.
    • autosleep 의 상태는 총 6개가 있다. `freeze`, `standby`, `mem`, `disk`, `off`, `error`. 
    • autosleep 의 상태를 read/write하는 함수는 다음과 같다. 2개의 함수 모두 kernel/power/autosleep.c 에 구현되어 있다.
      • get - pm_autosleep_state()
      • set - pm_autosleep_set_state()
    /* kernel/power/suspend.c - v6.2.2 */
    const char *pm_states[PM_SUSPEND_MAX];
    
    
    -----------------------------------------------------------------------------------------
    /* include/linux/suspend.h - v6.2.2 */
    typedef int __bitwise suspend_state_t;
    
    #define PM_SUSPEND_ON		((__force suspend_state_t) 0)
    #define PM_SUSPEND_TO_IDLE	((__force suspend_state_t) 1)
    #define PM_SUSPEND_STANDBY	((__force suspend_state_t) 2)
    #define PM_SUSPEND_MEM		((__force suspend_state_t) 3)
    #define PM_SUSPEND_MIN		PM_SUSPEND_TO_IDLE
    #define PM_SUSPEND_MAX		((__force suspend_state_t) 4)
    
    
    -----------------------------------------------------------------------------------------
    /* kernel/power/main.c - v6.2.2 */
    #ifdef CONFIG_PM_AUTOSLEEP
    static ssize_t autosleep_show(struct kobject *kobj,
    			      struct kobj_attribute *attr,
    			      char *buf)
    {
    	suspend_state_t state = pm_autosleep_state();
    
    	if (state == PM_SUSPEND_ON)
    		return sprintf(buf, "off\n");
    
    #ifdef CONFIG_SUSPEND
    	if (state < PM_SUSPEND_MAX)
    		return sprintf(buf, "%s\n", pm_states[state] ?
    					pm_states[state] : "error");
    #endif
    #ifdef CONFIG_HIBERNATION
    	return sprintf(buf, "disk\n");
    #else
    	return sprintf(buf, "error");
    #endif
    }
    
    static ssize_t autosleep_store(struct kobject *kobj,
    			       struct kobj_attribute *attr,
    			       const char *buf, size_t n)
    {
    	suspend_state_t state = decode_state(buf, n);
    	int error;
    
    	if (state == PM_SUSPEND_ON
    	    && strcmp(buf, "off") && strcmp(buf, "off\n"))
    		return -EINVAL;
    
    	if (state == PM_SUSPEND_MEM)
    		state = mem_sleep_current;
    
    	error = pm_autosleep_set_state(state);
    	return error ? error : n;
    }
    
    power_attr(autosleep);
    #endif /* CONFIG_PM_AUTOSLEEP */

     

     

    " `pm_autosleep_init()` 함수는 autosleep 시 사용되는 중요한 전역 변수 2 개를 초기화하는 함수다.

    1. autosleep_ws - autosleep 이 자기 자신을 wakeup source 로 등록한다. 이 말은 autosleep module 이 자체적으로 system suspend 를 막을 때 도 있다는 것을 의미한다. 그러나, 생각해보면 autosleep 은 system 이 suspend 로 진입하는 것을 막으면 안된다. 왜냐면, 실제적인 wakeup source 가 아니기 때문이다. 예를 들어, power-key, usb detection, timer interrupt 와 같은 external events 들은 system suspend 를 막는 실제 존재하는 events 들이다. 그러나, autosleep 은 실제하지 않는 wake-up event 다. 그렇다면, autosleep 을 왜 wakeup source 로서 등록할까? 자신의 설정을 바꿔야 하는 경우에 system 이 sleep 되어 있으면 설정을 변경할 수 없기 때문에, 설정을 바꿀 동안만 system 이 suspend 로 진입하는 것을 막는다. 이런 경우는 2 가지가 있다.

    1. autosleep 을 초기화할 때 - pm_autosleep_init()
    2. autosleep_state 를 변경할 때 - pm_autosleep_set_state()

    2. autosleep_wq - autosleep 은 `ordered workqueue`를 사용한다. `ordered workqueue` 는 흔히, max_active가 1이고 unbound_wq 라고 불린다. max_active는 wq에서 동일 시점에 CPU 마다 실행할 수 있는 최대 wokr items 개수를 명시한다. 예를 들어, max_active가 16이면, CPU 마다 동시에 총 16개의 work items을 실행할 수 있다. unbound wq는 특정 CPU에 종속되지 않은 wq를 의미한다. 일반적으로 wq라고 말하면, bound wq를 말하는데 이 wq는 CPU 마다 각각 존재한다. 그런데, unbound wq는 말 그대로 특정 CPU 바운딩되어 있지 않은 워크큐로 concurrency 문제를 거걱정할 필요가 없다. 모든 CPU가 공통으로 사용하고 싶은 wq가 있다면, 해당 wq는 unbound wq로 선언하면 된다. 결국 ordered wq의 특징은 전역적으로 한 시점에 하나의 work item만 실행시킬 수 있다는 특징을 갖는다.
    // kernel/power/autosleep.c - v6.5
    int __init pm_autosleep_init(void)
    {
    	autosleep_ws = wakeup_source_register(NULL, "autosleep");
    	if (!autosleep_ws)
    		return -ENOMEM;
    
    	autosleep_wq = alloc_ordered_workqueue("autosleep", 0);
    	if (autosleep_wq)
    		return 0;
    
    	wakeup_source_unregister(autosleep_ws);
    	return -ENOMEM;
    }

     

     

    " autosleep 에서 ordered workqueue를 사용하는 이유는 바로 뒤에서 알아본다.

     

    " `pm_autosleep_set_state()` 함수는 shell 에서 `echo ${POWER_STATE} > /sys/power/autosleep` 를 실행했을 때, 실제 autosleep 의 상태를 바꾸는 함수다. 이 때, autosleep_state 를 변경하기전에 `__pm_stay_awake()` 함수를 호출해서 system 을 suspend 로 진입하는 것을 막는다. 생각해보면 당연하다. sleep 정책이 바뀌었으니, 현재 진행중인 system suspend(이전 sleep 정책이 적용된) 또한 invalidated 되야 하는 것이다. 그리고, 안전하게 상태를 변경(`autosleep_state = state`) 하는 것을 알 수 있다.

    //include/linux/suspend.h - v6.5
    
    typedef int __bitwise suspend_state_t;
    
    #define PM_SUSPEND_ON		((__force suspend_state_t) 0)
    #define PM_SUSPEND_TO_IDLE	((__force suspend_state_t) 1)
    #define PM_SUSPEND_STANDBY	((__force suspend_state_t) 2)
    #define PM_SUSPEND_MEM		((__force suspend_state_t) 3)
    #define PM_SUSPEND_MIN		PM_SUSPEND_TO_IDLE
    #define PM_SUSPEND_MAX		((__force suspend_state_t) 4)
    // kernel/power/autosleep.c - v6.5
    int pm_autosleep_set_state(suspend_state_t state)
    {
    
    #ifndef CONFIG_HIBERNATION
    	if (state >= PM_SUSPEND_MAX)
    		return -EINVAL;
    #endif
    
    	__pm_stay_awake(autosleep_ws);
    
    	mutex_lock(&autosleep_lock);
    
    	autosleep_state = state;
    
    	__pm_relax(autosleep_ws);
    
    	if (state > PM_SUSPEND_ON) {
    		pm_wakep_autosleep_enabled(true);
    		queue_up_suspend_work();
    	} else {
    		pm_wakep_autosleep_enabled(false);
    	}
    
    	mutex_unlock(&autosleep_lock);
    	return 0;
    }

     

     

    " `PM_SUSPEND_ON` 은 시스템이 RUNNING 인 상태를 의미한다. 그리고, 위에 코드를 보면 알겠지만 PM_SUSPEND_ON 보다 큰 값들은 모두 SLEEP 관련 매크로 값들이다. 그래서 현재 autosleep 상태가 sleep 관련 상태라면, autosleep 을 활성화시키고 `queue_up_suspend_work` 함수를 통해 suspend work(try_to_suspend)가 실행시킨다.

     

    " autosleep 에서 ordered wq 를 사용하는 이유는 멀티 프로세서에서의 동시성 문제를 해결하기 위해서다. system suspend 는 system global 한 작업이다. CPU 코어마다 발생하는 이벤트가 아니라는 소리다. 사용자가 `/sys/power/autosleep` 에 `disk`, `mem`, `standby`를 동시에 입력했다고 치자. 만약, autosleep 에서 사용하는 wq 가 bound wq 였다면, CPU 마다 시스템 상태를 바꾸려고 하다가 패닉이 날 것이다. 만약, unbound wq 라도 max_active 가 2 이상의 값이라면 하나의 CPU 마다 2개의 work items 을 동시에 실행시킬 수 있기 때문에 이것 또한 문제가 된다. 결국, 이러한 조건을 만족시키는 wq 는 unbound wq 밖에 없다.

     

    " `pm_wakeup_autosleep_enabled()` 함수는 커널에 등록된 모든 `struct wakeup_source.autosleep_enabled` 를 설정한다. 그런데, `wakeup_source.autosleep_enabled` 필드가 필요한지는 의문이다. 왜냐면, autosleep 은 시스템의 전역적인 슬립과 관련이 있는 기술이기 때문에, 전역 변수 하나로 두어도 되지 않을까? patch 코드를 살펴보자. prevent_sleep_time 은 wakeup sources 마다 개별적으로 존재한다. 그리고, prevent_sleep_time 은 autosleep 이 활성화 되어 있을때만 의미가 있다. 즉, ws->autosleep_enabled 이 true 인 wakeup source 만 prevent_sleep_time 을 update 한다. 결국, ws->autosleep_enabled 는 그 자체로는 의미가 없고, prevent_sleep_time 를 사용하기 위해서 존재한다고 보는 것이 맞다.

    //drivers/base/power/wakeup.c - v6.5
    #ifdef CONFIG_PM_AUTOSLEEP
    /**
     * pm_wakep_autosleep_enabled - Modify autosleep_enabled for all wakeup sources.
     * @set: Whether to set or to clear the autosleep_enabled flags.
     */
    void pm_wakep_autosleep_enabled(bool set)
    {
    	struct wakeup_source *ws;
    	ktime_t now = ktime_get();
    	int srcuidx;
    
    	srcuidx = srcu_read_lock(&wakeup_srcu);
    	list_for_each_entry_rcu_locked(ws, &wakeup_sources, entry) {
    		spin_lock_irq(&ws->lock);
    		if (ws->autosleep_enabled != set) {
    			ws->autosleep_enabled = set;
    			if (ws->active) {
    				if (set)
    					ws->start_prevent_time = now;
    				else
    					update_prevent_sleep_time(ws, now);
    			}
    		}
    		spin_unlock_irq(&ws->lock);
    	}
    	srcu_read_unlock(&wakeup_srcu, srcuidx);
    }
    #endif /* CONFIG_PM_AUTOSLEEP */
    ...
    ...
    
    #ifdef CONFIG_PM_AUTOSLEEP
    static void update_prevent_sleep_time(struct wakeup_source *ws, ktime_t now)
    {
    	ktime_t delta = ktime_sub(now, ws->start_prevent_time);
    	ws->prevent_sleep_time = ktime_add(ws->prevent_sleep_time, delta);
    }
    #else
    static inline void update_prevent_sleep_time(struct wakeup_source *ws,
    					     ktime_t now) {}
    #endif

     

     

    " wakeup_source 의 `start_prevent_time`, `prevent_sleep_time` 은 autosleep 때문에 추가된 필드다(참고). `ws->start_prevent_time` 은 말 그대로 autosleep 이 전제되는 상황에서 wakeup source 가 시스템 suspend 를 막는 시작 시간을 저장하고 있다. `autosleep` 이 enabled 되는 시점에 특정 wakeup source 가 이미 activate 상태라면, 해당 wakeup source 때문에 시스템이 suspend 에 진입하지 못하는 것과 같다. 그러므로, `ws->start_prevent_time = now` 과 같이 현재 시간을 저장한다.

     

    `prevent_sleep_time` 은 autosleep 상황에서 wakeup source 가 activate 되어 시스템이 suspend 를 들어가지 못하도록 막은 시간의 양을 기록한다. wakeup source 가 activate 이고, autosleep 이 비활성화 되는 시점에 `prevent_sleep_time` 을 업데이트하는 이유는 `prevent_sleep_time` 이 autosleep 상황에서만 의미가 있기 때문이다. 즉, autosleep 이 비활성화되는 시점에 해당 wakeup source 의 `prevent_sleep_time` 도 의미가 없기 때문에 값을 `start_prevent_time` 부터 현재까지를 계산해서 저장한다.

     

    `update_prevent_sleep_time` 함수를 보면 알 수 있겠지만, 현재 시간(`autosleep이 비활성화 되는 시점`)에서 `start_prevent_time`을 빼고, 이전 `prevent_sleep_time`에 그 차이를 더한다.

     

    `queue_up_suspend_work` 함수는 위에서 잠깐 설명했지만, suspend_work(try_to_suspend) 를 실행시키는 역할을 한다.

    //kernel/power/autosleep.c - v6.5
    static struct workqueue_struct *autosleep_wq;
    ...
    
    static DECLARE_WORK(suspend_work, try_to_suspend);
    
    void queue_up_suspend_work(void)
    {
    	if (autosleep_state > PM_SUSPEND_ON)
    		queue_work(autosleep_wq, &suspend_work);
    }

     

     

    " try_to_suspend() 함수는 실제 시스템 suspend 를 trigger 하는 함수다. autosleep 에서 가장 핵심 함수라고 볼 수 있다. try_to_suspend() 함수에서 제일 먼저 볼 부분은 전체 구조다. 이 함수는 루프 방식으로 CPU 를 갉아먹는 while 문을 사용하지 않고, 마지막에 다시 워크큐에 삽입되면서 루프 형태를 만들었다.

     

    " try_to_suspend() 함수는 제일 먼저, `pm_get_wakeup_count()` 함수를 통해서 현재 처리 중인 wake-up event 가 있는지 여부를 확인한다. 처리 중인 wake-up event 가 있으면, `goto out` 으로 이동해서 다시 워크큐에서 삽입되서 루프를 돌게된다. 그런데, pm_get_wakeup_count() 함수의 2번째 인자가 true 면, 현재 처리 중인 wake-up event 가 마무리 될 때 까지 blocked 되게 된다. 즉, 처리가 끝날 때 까지 대기한다.

     

     

    " 만약, 처리중 인 wakeup event 가 없다면(pm_get_wakeup_count() 함수를 통과할 경우), `pm_save_wakeup_count()` 를 통해서 `saved_count` 를 업데이트해서 suspend 시에 wakeup count 의 기준값을 설정해주고, `events_check_enabled` 변수를 true 로 설정해서 suspending 중에 wakeup event가 발생할 경우, 해당 event 를 인지해서 suspending 을 abort 할 수 있도록 한다(자세한 내용은 이 글을 참고하자). 그런데, 만약 `pm_save_wakeup_count` 가 실패하면, 중간에 wakeup event가 발생했다는 의미다. 그때는 다시 워크큐를 통해 루프를 돈다. 

    // kernel/power/autosleep.c - v6.5
    static void try_to_suspend(struct work_struct *work)
    {
    	unsigned int initial_count, final_count;
    
    	if (!pm_get_wakeup_count(&initial_count, true))
    		goto out;
    
    	mutex_lock(&autosleep_lock);
    
    	if (!pm_save_wakeup_count(initial_count) ||
    		system_state != SYSTEM_RUNNING) {
    		mutex_unlock(&autosleep_lock);
    		goto out;
    	}
    
    	if (autosleep_state == PM_SUSPEND_ON) {
    		mutex_unlock(&autosleep_lock);
    		return;
    	}
    	if (autosleep_state >= PM_SUSPEND_MAX)
    		hibernate();
    	else
    		pm_suspend(autosleep_state);
    
    	mutex_unlock(&autosleep_lock);
    
    	if (!pm_get_wakeup_count(&final_count, false))
    		goto out;
    
    	/*
    	 * If the wakeup occurred for an unknown reason, wait to prevent the
    	 * system from trying to suspend and waking up in a tight loop.
    	 */
    	if (final_count == initial_count)
    		schedule_timeout_uninterruptible(HZ / 2);
    
     out:
    	queue_up_suspend_work();
    }

     

     

    " autosleep 통한 suspending 과정에서 혹은 suspended 이후에 wakeup event 가 발생하면, 다시 한 번 `pm_get_wakeup_count`를 통해 처리 중인 wake-up event 가 있는지 확인한다. 이 때는 suspend 를 들어갈 목적이 아니므로, block 설정을 하지 않는다. 마지막에, `final_count == initial_count` 가 참이면 이건 문제가 발생한 것이다. 왜냐면, system suspend 에서 wake-up 됬다는 것은 wake-up event 가 발생했다는 것을 의미한다. 그러므로, `if (!pm_get_wakeup_count(&final_count, false))` 이후에는 반드시 final_count > init_count 여야 한다. 그런데, system wake-up 됬는데, final_count == initial_count 라는 것은 wake-up 이 발생하지도 않았는데, system 이 wake-up 됬다는 것을 의미한다. 

     

    " 이 상황에서는 즉각적으로 다시 autosleep 을 재개할 수 없는 상황이다. 만약, 이 상황에서 곧 바로 autosleep 을 재개하면 어마 무시한 `sleep->wakeup->sleep->wakeup-> ...` 무한 루프 문제가 발생할 수 있다. 그래서, `HZ / 2 (0.5s)` 만큼 sleep 후에 다시 autosleep을 진행한다.

Designed by Tistory.