ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] PM - Wakeup events framework
    Linux/kernel 2023. 8. 3. 02:14

    글의 참고

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

    - https://lishiwen4.github.io/linux-driver/PM-wakeup-source

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

    - https://lwn.net/Articles/416690/


    글의 전제

    - 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다. 나도 모르기 때문에 나중에 알아봐야 할 내용이라는 뜻이다.

    - 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다. 그러므로, 밑 줄 처친 글이 이해가 안간다면 링크를 따라서 관련 내용을 공부하자.


    글의 내용

    1. 소개

    - 리눅스 시스템이 `system-wide suspend` 상태가 되었을 때, 어떻게 wakeup 시킬까? 이 글을 읽고나면 질문에 대한 부분이 많이 해소될 것이라 생각된다.

     

     

    2. wakeup events framework 목적

    : 시스템이 suspend 상태일 때, wakeup events를 통해 깨울 수 있다. 구체적인 wakeup events의 예를 들자면 버튼 누름, 충전기 삽입 등일 수 있습니다. 그런데 만약 suspend 과정중에 wakeup events가 발생한다면? 정답은 `시스템을 wakeup 시킨다` 이다. 사실 시스템이 suspend 과정에 있다는 것은 실제 시스템이 suspended 상태가 된 것을 의미하지는 않는다. 결국 suspend 과정에서의 wakeup은 suspended 된 시스템을 wakeup하는 것이 아니라, suspend 중인 과정을 종료시켜 버리는 것을 의미한다. 

     

    : suspend 과정에서, process freeze(사용자 프로그램 멈추게 해버림), device suspend(커널의 디바이스들이 멈춤) 등의 동작을 수행한다. 그런데 이러한 동작들로 인해 커널이나 사용자 공간 프로그램이 wakeup events를 제때 획득하지 못하는 경우가 있다. 결국 suspend 과정에서 wakeup event가 발생했는데도, 시스템은 wakeup을 제대로 인식하지 못해 suspended 상태가 되어 버리는 것이다. 이러한 문제 때문에, wakeup events framework가 등장했다. wakeup events framework의 목적은 다음과 같다.

    system suspend와 system wakeup events 간의 동기화 문제를 해결한다.

    : 사실 kernel framework의 내부 구조를 공부하다 보면 알겠지만, 대부분의 동기화 관련 코드들이 많다. 즉, wakeup events framework 뿐만 아니라, 많은 kernel framework가 동기화 관련 문제를 해결하려고 등장했다는 것이다.

     

    3. wakeup events framework 기능

    - 자세히 검토해 보면, 위에서 말한 동기화 문제는 두 가지 경우로 나뉜다.

     

    1) 커널 공간의 동기화

    wakeup events가 발생하면 보통 device driver에게 interrupt의 형태로 알려준다. driver는 wakeup events를 처리하고, 이 과정에서 시스템은 suspend를 할 수 없다.

    동기화 문제는 interrupt가 켜져 있을 때만 존재합니다. 인터럽트가 꺼져 있으면 wakeup events가 생성되지 않고 동기화 문제가 발생할 이유가 없다(여기서, 말하는 `동기화`는 `suspend`와 `wakeup` 사이에 동기화를 말한다).

     

    2) 사용자 공간 동기화

    일반적으로 driver는 wakeup events를 처리한 후 유저 애플리케이션에게 처리를 넘긴다. 처리 과정에서 `시스템 suspend`는 허용되지 않는다. 이것은 두 가지 상황으로 나눌 수 있습니다.

    1 사용자 공간 프로그램이 전혀 scheduled 되지 않는 경우. 즉, 유저 레벨 프로세스가 스케줄링 되지 않으니 해당 wakeup event를 처리해야 할 프로세스가 존재하지 않고, 유저 스페이스 올라온 wakeup event 처리할 수 없는 상황이 된다.

    2)사용자 공간 프로그램이 스케줄링되었고, wakeup event를 처리하는 경우. 이때, 사용자 프로세스가 wakeup event를 처리하고 있는 상황에서는 시스템은 suspend 될 수 없다(wakeup event를 모두 처리한 후, suspend 작업을 종료하기로 함).

     

    : 따라서 wakeup events framework에는 3가지 기능이 포함됩니다.

    kernel space 동기화 문제 해결(커널 프레임워크)

    user space 동기화 문제를 해결하는 시나리오 1(wakeup count 기능)

    user space 동기화 문제를 해결하는 시나리오 2 (wake lock 기능).

    : 커널 프레임워크은 디바이스 드라이버 개발자들이 커널 스레드를 생성하는 API를 사용하지 않은 이상 크게 걱정할 필요가 없다. `wakeup count`와 `wake lock`은 `wakeup event framework`을 기반으로 동작하면서, suspend와 wakeup 사이의 동기화 문제를 해결해준다. 

     

     

    4. wakeup events framework architecture

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

    - 위 그림에서 빨간색 테두리의 block은 wakeup events 관련 block입니다.

     

    - 1) wakeup events framework core는 drivers/base/power/wakeup.c 파일에 구현되어 있다. 아래의 기능들을 제공한다.

    1) wakeup source와 wakeup event 개념을 추상화한다.
    2) device driver에 wakeup source의 register, enable 등의 인터페이스를 제공한다.
    3) device driver에 wakeup event의 report, stop 등의 인터페이스를 제공한다.
    4) 상위 계층인 PM core(wakeup count, autosleep, suspend, hibernate 등의 모듈 포함)에게 wakeup event의 쿼리 인터페이스를 제공하여, suspend가 가능한지, 진행 중인 suspend를 종료해야 하는지 여부를 알려준다.

    2) wakeup events framework sysfs - device의 wakeup 정보를 sysfs의 형태로 사용자 공간에 제공한다. 사용자 공간 프로그램특정 디바이스의 wakeup 정보를 query 및 configuration 할 수 있다. 해당 기능은 drivers/base/power/sysfs.c에서 구현되어 있다.

     

    3) wake lock/unlock - 초기 Android의 wakeup lock 메커니즘과의 호환성을 위해 남겨진 기술이다. wakeup events framework의 기능을 확장하여 사용자 공간 프로그램이 wakeup events를 report/stop 할 수 있도록 합니다. 즉, 이 기술은 사용자 공간의 모든 프로그램이 시스템을 suspended 상태로 갈지 여부를 결정할 수 있도록 합니다.

     

    4) wakeup count - wakeup events framework를 기반으로 사용자의 공간 동기화 문제를 해결합니다.

    5) autosleep - 활동이 없을 때(즉, 일정 기간 동안 wakeup event가 생성되지 않음) 시스템이 자동으로 suspended 상태로 전환되도록 한다.

     

    Linux kernel의 관점에서, power는 시스템의 핵심 자원이며 사용자 프로그램의 임의 접근에 허용해선 된다(wake lock 메커니즘은 이 원칙에 위배됨). 런타임 시, 전원 관리 프로세스에서 시스템이 suspended 상태에 진입하는 시점은 사용자 공간 프로그램에 의해 결정되면 안된다. sys/power/main.c 파일에 보면 커널이 유저 레벨에게 권한을 주지 않겠다는 의지를 나름 내보이고 있다.

     

    : wake_lock이나 기능은 유저에게 시스템 전원 관리를 넘기는 좋지 못한 기능이다. 그래서 커널에서는 해당 기능들을 FEATURE 처리를 통해 기본적으로 사용하지 못하게 하지만, 원하면 쓸 수 있게 해주고 있다. 이 2개 기능가는 다르게 wakeup_count는 기본적으로 활성화가 되어 있다.

    1: static struct attribute * g[] = {
       2:         &state_attr.attr,
       3: #ifdef CONFIG_PM_TRACE
       4:         &pm_trace_attr.attr,
       5:         &pm_trace_dev_match_attr.attr,
       6: #endif
       7: #ifdef CONFIG_PM_SLEEP
       8:         &pm_async_attr.attr,
       9:         &wakeup_count_attr.attr,
      10: #ifdef CONFIG_PM_AUTOSLEEP
      11:         &autosleep_attr.attr,
      12: #endif
      13: #ifdef CONFIG_PM_WAKELOCKS
      14:         &wake_lock_attr.attr,
      15:         &wake_unlock_attr.attr,
      16: #endif
      17: #ifdef CONFIG_PM_DEBUG
      18:         &pm_test_attr.attr,
      19: #endif
      20: #ifdef CONFIG_PM_SLEEP_DEBUG
      21:         &pm_print_times_attr.attr,
      22: #endif
      23: #endif
      24: #ifdef CONFIG_FREEZER
      25:         &pm_freeze_timeout_attr.attr,
      26: #endif
      27:         NULL,
      28: };

    : 시스템이 wakeup 되는 시나리오의 외부 이벤트를 통해서만 되는 것이 가장 이상적이다. 즉, 유저 애플리케이션에서 `/sys/power/wake_lock`을 건드리는 것은 좋지 못하다.  

     

    5. 코드 분석

    5.1) wakeup source 및 wakeup event

     

    - kernel에서 시스템을 웨이크업 할 수 있는 것은 디바이스(struct device) 뿐이지만, 모든 디바이스가 웨이크업 기능을 가지고 있는 것은 아니며, 그 웨이크업 기능을 가지고 있는 디바이스를 wakeup source라고 한다. struct device 구조를 보면 struct dev_pm_info 유형의 power 변수가 있다는 것을 알 수 있다.

    1: struct device {
       2:         ...
       3:         struct dev_pm_info      power;
       4:         ...
       5: };

    - strcut device 에는 wakeup event에 대한 정보를 저장하는 power 변수가 있습니다. 다음으로 struct dev_pm_info 데이터 구조를 살펴보겠습니다.

     

    1: struct dev_pm_info {
       2:         ...
       3:         unsigned int            can_wakeup:1;
       4:         ...
       5: #ifdef CONFIG_PM_SLEEP
       6:         ...
       7:         struct wakeup_source    *wakeup;
       8:         ...
       9: #else
      10:         unsigned int            should_wakeup:1;
      11: #endif
      12: };

    - `can_wakeup`은 해당 디바이스가 wakeup 기능이 있는지 여부를 식별합니다. wakeup 기능이 있는 디바이스는 sysfs에 power 디렉토리를 갖는다. 해당 폴더에는 디바이스의 wakeup 관련 정보들을 들어있다. 그리고 그 wakeup 관련 정보들은 위의 `struct wakeup_source` 구조체가 제공해주는 정보들을 의미한다. 그렇면, strcut wakeup_source는 어떻게 구성되어 있을까?

     

    : 위에서 sysfs에 power 디렉토리를 갖는다고 했는데, 저 폴더는 절대 `/sys/power`는 아니다. 확인해보면 알 수 있겠지만 `/sys/power`는 PM core에서 생성한 시스템 전역으로 쓰이는 power subsystem 디렉토리다. 위의 구조체는 시스템 전역적인 부분이 아닌 개별 디바이스 측면에서 바라봐야 하기 때문에 `/sys/devices/**/power` 폴더로 보는 것이 옳다.

    //include/linux/pm_wakeup.h - v6.5
    /**
     * struct wakeup_source - Representation of wakeup sources
     *
     * @name: Name of the wakeup source
     * @id: Wakeup source id
     * @entry: Wakeup source list entry
     * @lock: Wakeup source lock
     * @wakeirq: Optional device specific wakeirq
     * @timer: Wakeup timer list
     * @timer_expires: Wakeup timer expiration
     * @total_time: Total time this wakeup source has been active.
     * @max_time: Maximum time this wakeup source has been continuously active.
     * @last_time: Monotonic clock when the wakeup source's was touched last time.
     * @prevent_sleep_time: Total time this source has been preventing autosleep.
     * @event_count: Number of signaled wakeup events.
     * @active_count: Number of times the wakeup source was activated.
     * @relax_count: Number of times the wakeup source was deactivated.
     * @expire_count: Number of times the wakeup source's timeout has expired.
     * @wakeup_count: Number of times the wakeup source might abort suspend.
     * @dev: Struct device for sysfs statistics about the wakeup source.
     * @active: Status of the wakeup source.
     * @autosleep_enabled: Autosleep is active, so update @prevent_sleep_time.
     */
    struct wakeup_source {
    	const char 		*name;
    	int			id;
    	struct list_head	entry;
    	spinlock_t		lock;
    	struct wake_irq		*wakeirq;
    	struct timer_list	timer;
    	unsigned long		timer_expires;
    	ktime_t total_time;
    	ktime_t max_time;
    	ktime_t last_time;
    	ktime_t start_prevent_time;
    	ktime_t prevent_sleep_time;
    	unsigned long		event_count;
    	unsigned long		active_count;
    	unsigned long		relax_count;
    	unsigned long		expire_count;
    	unsigned long		wakeup_count;
    	struct device		*dev;
    	bool			active:1;
    	bool			autosleep_enabled:1;
    };

    - 따라서 wakeup source는 시스템을 wakeup 시킬 수 있는 디바이스를 의미하며, 이를 wakeup source 라고도 한다.

     

    name - wakeup source의 이름이다. 일반적으로 device name을 많이 사용한다.

    entery - 모든 wakeup source는 `wakeup sources` 라는 전역 리스트로 관리된다. 리눅스에서 `리스트` 자료 구조는 유연성을 위해서 각 자료 구조에 선언하게 된다. 즉, 개별 wakeup source를 커널에서 관리하는 전역 리스트인 `wakeup sources`에 연결하려면, `struct list_head`가 선언되어 있어야 한다.

    timer, timer_expires, wakeup source는 wakeup event를 생성하여 wakeup source activate라고 하며, wakeup event가 처리된 후(더 이상 시스템이 active를 유지할 필요가 없음), deactivate라고 합니다.activate와 deactivate의 동작은 driver가 직접 설정하거나 activate 시 timeout 시간을 지정해 놓으면 wakeup events framework에 의해 자동으로 deactivate 상태로 설정된다.여기서 timer와 expires 시간은 이 기능을 구현하기 위한 것입니다.

    total_time, 이 wakeup source가 activate 상태에 있는 총 시간(해당 디바이스의 혼잡도와 전력 소비 수준을 나타낼 수 있음)

    max_time - 이 wakeup source가 activate 상태로 지속되는 최대 시간(길수록 불합리함)

    last_time - 이 wakeup source의 마지막 active 시작 시간;

    start_prevent_time -  wakeup source가 autosleep을 차단하기 시작하는 시점입니다.

    prevent_sleep_time,  wakeup source가 현재 시스템을 autosleep으로 들어가지 못하도록 시간적으로 얼마동안 막고 있었는지.

    event_count - wakeup source가 발생시킨 전체 wakeup event 개수.

    active_count - wakeup source activate 횟수

    relax_count - wakeup source deactivate 횟수

    expire_count - wakeup source timeout 발생한 횟수

    wakeup_count - wakeup source가 suspend 과정을 abort 한 횟수

    active - wakeup source의 activate 상태;

    autosleep_enabled - 해당 wakeup source의 autosleep이 활성화 되어 있는지를 나타냄.

    - wakeup source는 wakeup 기능을 가진 디바이스를 나타내며, 이 디바이스는 시스템을 wakeup 할 수 있는 event를 생성하며 이를 wakeup event 라고 합니다. wakeup source가 wakeup event를 생성할 때, wakeup source를 activate 상태로 전환해야 하고 wakeup event 처리가 완료되면 deactivate 상태로 전환해야 합니다.

     

    - 근데 혹시 wakeup_source 구조체에서 event_count, active_count 및 wakeup_count가 좀 애매하지 않나? 이 놈들을 좀 더 깊게 이해해보자.

    event_count - wakeup source에서 발생하는 wakeup event 개수.

    active_count - active_count는 해당 wakeup event가 시스템을 실제로 wakeup 시켰을 때, 증가된다. 일반적으로는 wakeup source는 wakeup event 발생 시 activate 상태로 전환해야 한다.  하지만 이미 activate 상태라면? 이럴 경우는 전환할 필요가 없다. 따라서 active_count는 event_count보다 작을 수 있다. 즉, 이와 같은 상황은 이전 wakeup event가 처리되지 않았을 때, 하나가 더 생성될 가능성이 높다. 결국 active_count는 wakeup source가 나타내는 디바이스의 혼잡도를 반영한다.

    wakeup_count - wakeup source가 suspend 과정에서 wakeup event를 생성하면 suspend 과정을 종료하게 된다. 결국 wakeup_count는 wakeup source가 suspend 과정을 종료한 횟수를 기록한다(시스템이 항상 suspend에 실패하는 것을 발견하면, 각 wakeup source의 이 변수를 체크하면 누가 문제인지 알 수 있습니다).

    5.2) counters

    - drivers\base\power\wakeup.c에는 wakeup events framework의 근간을 이루는 몇 가지 중요한 카운터가 있습니다.

     

    5.2.1) registered wakeup events 및 saved_count [ 참고1 ]

    :  `registered wakeup events`는 이미 처리된 wakeup events를 의미한다. 그리고 `saved count`는 시스템이 suspend 상태에 진입하기 직전에 `registered wakeup events`를 `saved count`에 쓴다. 그리고 suspend의 각 단계에서 `saved count`는 현재 wakeup event 개수와 비교되면서 suspend를 abort 시킬지에 대한 기준이 된다. 아래 코드는 커널 초기 코드다. 현재 버전(v6.5)에서는 `event_count`와 `events_in_progress`가 하나로 합쳐져서 `combined_event_count`로 바뀌었다.

    //drivers/base/power/wakeup.c - v2.6.37.4
    /*
     * If set, the suspend/hibernate code will abort transitions to a sleep state
     * if wakeup events are registered during or immediately before the transition.
     */
    bool events_check_enabled;
    /* The counter of registered wakeup events. */
    static atomic_t event_count = ATOMIC_INIT(0);
    /* A preserved old value of event_count. */
    static unsigned int saved_count;
    /* The counter of wakeup events being processed. */
    static atomic_t events_in_progress = ATOMIC_INIT(0);
    ...

    : 이 카운터는 사용자 공간 동기화 문제를 해결하는 데 매우 유용합니다. 왜냐면, 일반적으로(사용자 프로그램이 능동적으로 suspend 하든 autosleep 이든) suspend은 특수한 프로세스(또는 스레드)에 의해 트리거되기 때문입니다. 이 프로세스는 시스템이 suspend 조건을 충족됬다고 판단하면 시스템을 suspend로 보내버린다. 그리고 counter 값(saved_count)을 기록합니다. 시스템은 위에서 언급한 프로세스가 counter 값을 바꿨다는 것을 감지하고, 새로운 wakeup event가 발생시킨다. 결국 suspend를 종료된다.

     

    5.2.2) wakeup events in progress

    - `wakeup events in prograss`는 처리 중인 event 개수를 기록합니다. 즉, 아직 미완료 wakeup event를 의미합니다. `wakeup events in progress` 가 처리가 완료되면, `registered wakeup events`가 된다. 

     

    - wakeup source가 wakeup event를 생성할 때, wakeup events framework에서 제공하는 인터페이스를 통해 wakeup source를 activate 상태로 설정한다. 해당 wakeup event가 처리되면 deactivate 상태로 설정된다. activate에서 deactivate까지의 구간은 해당 event가 처리되고 있음을 나타냅니다. 아직 이 시점까지는 wakeup evevnts in progress 값이 아직 증가해 있는 상황이다.

     

    - 시스템에 처리 중인 wakeup event가 있는 경우, suspend가 허용되지 않습니다. 만약, suspending 에서 wakeup event가 발생하면 suspend 절차를 종료한다. 즉, wakeup event는 suspend보다 우선순위가 훨씬 높다.

     

    - registered wakeup events는 언제 증가할까? 답은 `wakeup events in progress 값이 감소했을 때` 이다. 왜? 하나의 event를 완벽하게 처리했기 때문에 registered wakeup events를 증가시킬 수 있다. 커널은 registered wakeup events와 wakeup events in progress를 하나의 32비트 정수로 합쳐서,  atomic operation을 통해서 2개의 값을 함께 업데이트합니다.

    /drivers/base/power/wakeup.c - v6.2.2
    /*
     * Combined counters of registered wakeup events and wakeup events in progress.
     * They need to be modified together atomically, so it's better to use one
     * atomic variable to hold them both.
     */
    static atomic_t combined_event_count = ATOMIC_INIT(0);
    
    #define IN_PROGRESS_BITS	(sizeof(int) * 4)
    #define MAX_IN_PROGRESS		((1 << IN_PROGRESS_BITS) - 1)
    
    static void split_counters(unsigned int *cnt, unsigned int *inpr)
    {
    	unsigned int comb = atomic_read(&combined_event_count);
    
    	*cnt = (comb >> IN_PROGRESS_BITS);
    	*inpr = comb & MAX_IN_PROGRESS;
    }
    
    /* A preserved old value of the events counter. */
    static unsigned int saved_count;

    - 위의 `split_counters` 함수 내부를 보면 알 수 있겠지만, 이 함수는 `cnt`에는 현재 처리가 완료된 wakeup events, `inpr`에는 현재 처리 중인 wakeup event 개수를 반환한다(`inpr` = `in process`). 위의 식을 한 번 이해해 보자. combined_event_count를 32비트(4바이트)로 보면, 아래와 같이 상위 비트와 하위 비트로 나눌 수 있다.

    1) 상위 16비트 - registered wakeup events
    2) 하위 16비트 - wakeup events in progress

    - 16비트면 총, 6만 5천개 정도의 wakeup events를 기록할 수 있으니, 하나의 변수에 2개의 wakeup events를 저장하더라도 충분한 사이즈다. IN_PROGRESS_BITS = 0x10(int가 32비트라고 가정), MAX_IN_PROGRESS = 0xFFFF가 된다.

     

    - `wakeup events in progress` 가 발생 시, `combined_event_count` 변수에 1씩 추가한다. 그리고 wakeup events in progress가 끝나면, combined_event_count에 MAX_IN_PROGRESS를 더한다.

    1) wakeup events in progress 가 발생 -> `combined_event_count++` -> combined_event_count = 0x1

    2) wakeup events in progress 가 끝 -> `combined_event_count + MAX_IN_PROGRESS` -> combined_event_count = 0x10000

     

    - 위에서 MAX_IN_PROGRESS를 더 했을 때를, combined_event_count의 상위 16비트가 어떻게 변하고 하위 16비트가 어떻게 변하는 지를 유념해서 보자. wakeup events in progress 가 여러번 발생하면? 아래의 경우를 보자.

    1) wakeup events in progress-A가 발생 -> combined_event_count++ -> combined_event_count = 0x1

    1) wakeup events in progress-B 가 발생 -> combined_event_count++ -> combined_event_count = 0x2

    2) wakeup events in progress-B 가 끝 -> combined_event_count+MAX_IN_PROGRESS -> combined_event_count = 0x00010001

    2) wakeup events in progress-A 가 끝 -> combined_event_count+MAX_IN_PROGRESS -> combined_event_count = 0x00020000

     

    - 결국 wakeup events in progress가 발생했으면, 언젠가는 반드시 끝이 있기 때문에 wakeup events in progress가 감소한 만큼 registered wakeup events는 늘어난다.

     

    5.3) wakeup events framework 기능

    - 디바이스 드라이버가 외부 인터럽트를 받으면, wakeup event framework 에서 제공하는 API(`pm_stay_awake`, `pm_relax` 등)을 통해서 시스템을 wakeup 시킨다. wakeup event framework 에서 제공하는 API는 적용 대상에 따라 다음과 같이 두 가지 타입으로 구분할 수 있습니다

     

    출처 -&nbsp;http://www.wowotech.net/pm_subsystem/wakeup_events_framework.html

    http://www.wowotech.net/pm_subsystem/wakeup_events_framework.html

     

    1) 적용 대상이 wakeup source이며, device driver를 작성할 때 일반적으로 직접 사용하지 않는다. 아래 함수들의 매개 변수를 보자. devie가 아닌, wakeup_source를 대상으로 동작하는 함수.

    1: /* include/linux/pm_wakeup.h */
       2: extern void __pm_stay_awake(struct wakeup_source *ws);
       3: extern void __pm_relax(struct wakeup_source *ws);
       4: extern void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec);

    - __pm_stay_awake : ws(wakeup_source)가 wakeup_event 를 생성하고 처리 중임을 PM 코어에 알립니다. 즉, 시스템이 suspend 되는 것을 허용하지 않음(stay awake).

     

    - __pm_relax - ws에 처리 중인 wakeup event 가 없음을 PM 코어에 알리고, 시스템 suspend(relax)를 허용한다.

     

    - __pm_wakeup_event - 위에 두 개 인터페이스의 기능을 합쳐놓은 함수로 PM core에게 ws가 wakeup event를 발생시켰음을 알리며, msec 밀리초 이내에 처리가 종료된다(처리가 종료되면 wakeup events framework가 자동으로 relax).

     

    - 주의 4 : __pm_stay_awake와 __pm_relax는 쌍으로 호출되어야 한다.

    - 주의 5 : 위의 3개의 인터페이스는 모두 인터럽트 컨텍스트에서 호출할 수 있다.

     

    2) 적용 대상이 device이며, device를 통해 동작하는 인터페이스. 아래 함수들의 매개 변수를 보자. wakeup_source가 아닌 devie를 대상으로 동작하는 함수.

    1: /* include/linux/pm_wakeup.h */
       2: extern int device_wakeup_enable(struct device *dev);
       3: extern int device_wakeup_disable(struct device *dev);
       4: extern void device_set_wakeup_capable(struct device *dev, bool capable);
       5: extern int device_init_wakeup(struct device *dev, bool val);
       6: extern int device_set_wakeup_enable(struct device *dev, bool enable);
       7: extern void pm_stay_awake(struct device *dev);
       8: extern void pm_relax(struct device *dev);
       9: extern void pm_wakeup_event(struct device *dev, unsigned int msec);

    - device_set_wakeup_capable : 인자로 전달된 dev의 `dev->can_wakeup` flag(enable 또는 disable, 5.1절 참고)를 설정하고, 해당 장치의 sysfs 관련 power 파일을 추가하거나 제거합니다(`/sys/devices/*/power`).

     

    - device_wakeup_enable/device_wakeup_discable/device_set_wakeup_enable : can_wakeup이 활성화된 디바이스에 대해 wakeup 기능을 enable/disable 한다. 즉, 인자로 전달된 디바이스에 `wakeup source`를 등록 및 해제하는 함수들이다. strcut wakeup_source 구조체와 관련된 동작을 컨트롤하는 함수들이다. 

     

    - device_init_wakeup : 인자로 전달된 dev의 `dev->can_wakeup` flag를 설정하고 enable이면 device_wakeup_enable을 동시에 호출하여 wakeup 기능을 사용할 수 있도록 한다.

     

    - pm_stay_awake, pm_relax, 및 pm_wakeup_event는 내부적으로 위에서 언급했던 wakeup_source로 동작하는 인터페이스(__pm_xxxx())를 호출한다. 구체적인 내용은 아래에서 설명한다.  

     

     

    5.3.1) device_set_wakeup_capable

    : 인터페이스는 drivers/base/power/wakeup.c에 있으며 코드는 다음과 같다.

    // drivers/base/power/wakeup.c - v6.5
    /**
     * device_set_wakeup_capable - Set/reset device wakeup capability flag.
     * @dev: Device to handle.
     * @capable: Whether or not @dev is capable of waking up the system from sleep.
     *
     * If @capable is set, set the @dev's power.can_wakeup flag and add its
     * wakeup-related attributes to sysfs.  Otherwise, unset the @dev's
     * power.can_wakeup flag and remove its wakeup-related attributes from sysfs.
     *
     * This function may sleep and it can't be called from any context where
     * sleeping is not allowed.
     */
    void device_set_wakeup_capable(struct device *dev, bool capable)
    {
    	if (!!dev->power.can_wakeup == !!capable)
    		return;
    
    	dev->power.can_wakeup = capable;
    	if (device_is_registered(dev) && !list_empty(&dev->power.entry)) {
    		if (capable) {
    			int ret = wakeup_sysfs_add(dev);
    
    			if (ret)
    				dev_info(dev, "Wakeup sysfs attributes not added\n");
    		} else {
    			wakeup_sysfs_remove(dev);
    		}
    	}
    }
    EXPORT_SYMBOL_GPL(device_set_wakeup_capable);

    : 이 인터페이스의 구현은 매우 간단하다. 주로 sysfs의 add/remove와 can_wakeup 플래그의 설정 두 부분으로 이루어져 있다. can_wakeup 플래그가 설정되면 디바이스의 sysfs 디렉터리에 power 폴더를 추가하고, 해당 속성 파일을 등록하기 위해 wakeup_sysfs_add를 호출한다. can_wakeup 플래그가 지워지면 sysfs 제거를 수행합니다. 이 함수에서는 `wakeup source`를 생성 및 제거하는 코드는 없다는 것이다.

     

    - wakeup_sysfs_add/wakeup_sysfs_remove는 drivers/base/power/sysfs.c에 있으며 wakeup events framework의 경우 다음과 같은 attribute 파일을 포함합니다.

    1: static struct attribute *wakeup_attrs[] = {
       2: #ifdef CONFIG_PM_SLEEP
       3:         &dev_attr_wakeup.attr,
       4:         &dev_attr_wakeup_count.attr,
       5:         &dev_attr_wakeup_active_count.attr,
       6:         &dev_attr_wakeup_abort_count.attr,
       7:         &dev_attr_wakeup_expire_count.attr,
       8:         &dev_attr_wakeup_active.attr,
       9:         &dev_attr_wakeup_total_time_ms.attr,
      10:         &dev_attr_wakeup_max_time_ms.attr,
      11:         &dev_attr_wakeup_last_time_ms.attr,
      12: #ifdef CONFIG_PM_AUTOSLEEP
      13:         &dev_attr_wakeup_prevent_sleep_time_ms.attr,
      14: #endif
      15: #endif
      16:         NULL,
      17: };
      18: static struct attribute_group pm_wakeup_attr_group = {
      19:         .name   = power_group_name,
      20:         .attrs  = wakeup_attrs,
      21: };

    - 속성들에 내용은 다음과 같다.

     

    1) wakeup 속성

    - wakeup 속성을 read 할 경우, 해당 디바이스의 wakeup 기능의 여부를 반환한다. 'enabled' 또는 'disabled' 문자열을 반환한다.

    - wakeup 속성을 write 할 경우, 해당 디바이스의  wakeup 기능을 eanble/disable 할 수 있다. 어떤 문자열("enabled" 또는 "disabled")에 write 하냐에 따라 내부적으로 device_set_wakeup_enable 인터페이스를 호출하여 상태 전환을 수행한다.

     

    - 디바이스가 시스템을 wakeup 할 수 있는지 여부는 아래의 2가지 요소에 따라 다르다.

    1) 디바이스의 can_wakeup 플래그가 enabled 여부.
    2) struct wakeup_source 포인터가 등록되어 있는지 여부.

    - 여기서 헷갈리게 이름이 비슷한 2가지 함수가 존재한다. 그 함수들은 can wakeup과 may wakeup이다.

     

    1: /*
       2:  * Changes to device_may_wakeup take effect on the next pm state change.
       3:  */
       4:  
       5: static inline bool device_can_wakeup(struct device *dev)
       6: {
       7:         return dev->power.can_wakeup;
       8: }
       9:  
      10: static inline bool device_may_wakeup(struct device *dev)
      11: {
      12:         return dev->power.can_wakeup && !!dev->power.wakeup;
      13: }

    - `device_can_wakeup(dev->power.can_wakeup)` 함수는 단순히 이 디바이스가 시스템을 웨이크-업 시킬 수 있는지에 대한 여부만 알려준다. `device_may_wakeup` 함수는 디바이스가 시스템을 웨이크-업을 시킬려고 할 때, 사용되는 함수다. 이 함수는 디바이스가 `power.wakeup` 객체가 등록되어 있는지 까지 판단을 한다. 디바이스에 `wakeup source(power.wakeup)`가 존재할 경우, 시스템을 웨이크-업 시킬 수 있는 `wakeup event`를 만들 수 있다. 즉, 실제 시스템을 웨이크-업 시키기 위해서는 결국 wakeup source가 필요하다는 뜻이다. 요약하면, 다음과 같다.

    device_can_wakeup" 해당 디바이스가 시스템을 웨이크-업을 시킬 수 있는지 여부

    device_may_wakeup" 해당 디바이스가 시스템을 웨이크-업을 시킬 수 있는지 여부 + 해당 디바이스에 시스템을 웨이크-업 시킬 웨이크-업 소스가 등록되어 있는지 여부

     

    : 아래 `at91_rtc_suspend` 함수를 보면, `device_may_wakeup` 함수를 통해 해당 디바이스에 연결된 `ws`가 있는지를 먼저 확인한다. 그리고, 추측이지만 at91 디바이스가 LPM 으로 진입하기 직전에 `wakeup interrupt`를 enable 할 것이다. 즉, 시스템이 절전 모드에서 자신이 설정한 인터럽트를 통해서 wakeup 하도록 하는 것이다.

    /linux/drivers/usb/host/isp116x-hcd.c -v6.2.2
    ...
    if (board->remote_wakeup_enable) {
    	if (!device_can_wakeup(hcd->self.controller))
    		device_init_wakeup(hcd->self.controller, 1);
    		val |= RH_HS_DRWE;
    }
    ...
    
    -----------------------------------------------------------------------------------
    /drivers/rtc/rtc-at91rm9200.c - v6.2.2
    #ifdef CONFIG_PM_SLEEP
    
    /* AT91RM9200 RTC Power management control */
    
    static int at91_rtc_suspend(struct device *dev)
    {
    	...
    	if (at91_rtc_imr) {
    		if (device_may_wakeup(dev)) {
    			...
    		} 
    	}
    	...
    }
    
    static int at91_rtc_resume(struct device *dev)
    {
    	...
        if (device_may_wakeup(dev)) {
    		...
    	}
    	...
    }
    #endif
    
    ...
    ...
    
    /* cpu init code should really have flagged this device as
    * being wake-capable; if it didn't, do that here.
    */
    if (!device_can_wakeup(&pdev->dev))
    	device_init_wakeup(&pdev->dev, 1);
    ...

    : `probe` 함수에서는 주로 마지막 부분에 `device_can_wakeup` 함수의 반환값을 통해 해당 디바이스의 `ws`를 생성할지 말지를 결정한다. `device_can_wakeup` 함수가 `0`을 반환하면, 해당 디바이스에 연결된 `ws`가 없다는 뜻이다. 즉, 이럴 때는 `ws`를 새로 만들어서 디바이스에 연결하면 된다. 그런데, `device_can_wakeup`이 `1`을 반환하면 상황이 조금 애매해진다. 왜냐면, `dev->power.can_wakeup` 만으로는 디바이스에 `ws`가 존재하는지를 알 수 없기 때문이다. 그래서, 항상 `!device_can_wakeup` 형태로만 사용되고 있다.

     

    3) wakeup_active_count 속성

    - 읽기 전용, dev->power.wakeup->active_count 값을 얻습니다.

    4) wakeup_abort_count 속성

    - 읽기 전용, dev->power.wakeup->wakeup_count 값을 얻습니다.

    5) wakeup_expire_count 속성

    - 읽기 전용, dev->power.wakeup->expire_count 값을 얻습니다.

    6) wakeup_active 속성

    - 읽기 전용, dev->power.wakeup->active 값을 얻습니다.

    7) wakeup_total_time_ms 속성

    - 읽기 전용, dev->power.wakeup->total_time 값을 얻고 단위는 ms입니다.

    8) wakeup_max_time_ms 속성

    - 읽기 전용, dev->power.wakeup->max_time 값을 구하고 단위는 ms입니다.

    9) wakeup_last_time_ms 속성

    - 읽기 전용, dev->power.wakeup->last_time 값을 구하고 단위는 ms입니다.

    10) wakeup_prevent_sleep_time_ms 속성

    - 읽기 전용, dev->power.wakeup->prevent_sleep_time 값을 얻고 단위는 ms입니다.

     

     

    5.3.2) device_wakeup_enable / device_wakeup_disable / device_set_wakeup_enable

    : 위의 3개의 함수들은 구조가 모두 비슷하다. 먼저 각 함수들의 기능은 다음과 같다.

    device_wakeup_enable" 인자로 전달된 디바이스에 wakeup source를 만들어서(register) 연결(attach)시킨다.

    device_wakeup_disable" 인자로 전달된 디바이스에 wakeup source를 연결 해제(detach)하고 제거(unregister)한다.

    device_set_wakeup_enable" 인자로 전달된 디바이스에 두 번째의 인자의 값이 true 혹은 false 이냐에 따라, wakeup source를 생성 및 제거한다.

    : 위의 3개 함수 모두 내용이 간단하다. 여기서는 `device_wakeup_enable` 함수만 간략하게 알아보자.

    //drivers/base/power/wakeup.c - v6.5
    **
     * device_wakeup_enable - Enable given device to be a wakeup source.
     * @dev: Device to handle.
     *
     * Create a wakeup source object, register it and attach it to @dev.
     */
    int device_wakeup_enable(struct device *dev)
    {
    	struct wakeup_source *ws;
    	int ret;
    
    	if (!dev || !dev->power.can_wakeup)
    		return -EINVAL;
    
    	if (pm_suspend_target_state != PM_SUSPEND_ON)
    		dev_dbg(dev, "Suspicious %s() during system transition!\n", __func__);
    
    	ws = wakeup_source_register(dev, dev_name(dev));
    	if (!ws)
    		return -ENOMEM;
    
    	ret = device_wakeup_attach(dev, ws);
    	if (ret)
    		wakeup_source_unregister(ws);
    
    	return ret;
    }
    EXPORT_SYMBOL_GPL(device_wakeup_enable);
    
    ...
    ...
    
    /**
     * device_wakeup_disable - Do not regard a device as a wakeup source any more.
     * @dev: Device to handle.
     *
     * Detach the @dev's wakeup source object from it, unregister this wakeup source
     * object and destroy it.
     */
    int device_wakeup_disable(struct device *dev)
    {
    	struct wakeup_source *ws;
    
    	if (!dev || !dev->power.can_wakeup)
    		return -EINVAL;
    
    	ws = device_wakeup_detach(dev);
    	wakeup_source_unregister(ws);
    	return 0;
    }
    EXPORT_SYMBOL_GPL(device_wakeup_disable);
    
    ...
    ...
    
    /**
     * device_set_wakeup_enable - Enable or disable a device to wake up the system.
     * @dev: Device to handle.
     * @enable: enable/disable flag
     */
    int device_set_wakeup_enable(struct device *dev, bool enable)
    {
    	return enable ? device_wakeup_enable(dev) : device_wakeup_disable(dev);
    }
    EXPORT_SYMBOL_GPL(device_set_wakeup_enable);

    1) 인자로 전달된 디바이스가 NULL이거나, 해당 device의 dev->can_wakeup이 enabled 되어 있지 않은 경우, -EINVAL을 리턴하고 함수를 종료한다. 즉, 해당 디바이스가 power 관련된 디바이스가 아니면, 이 함수는 에러를 반환한다.

     

    2) wakeup_source_register()를 호출하고, device name을 파라미터로 사용해서 wakeup source를 새로 생성하고 register 한다.

     

    3) device_wakeup_attach()를 호출해서 새로 생성된 wakeup source를 dev->power.wakeup 포인터에 저장한다.

     

    - wakeup_source_register() 함수의 구현도 비교적 간단하다. wakeup_source_create(), wakeup_source_prepare(), wakeup_source_add() 및 기타 함수들를 차례로 호출하여 `struct wakeup_source` 변수를 할당하는 데 필요한 메모리 공간, 내부 변수 초기화, `wakeup_sources`라는 global linked list에 새로운 wakeup source를 추가하는 등의 작업을 수행한다.

     

    : device_wakeup_attach() 함수는 먼저 전달받은 디바이스의 `dev->power.wakeup`가 이미 존재하면, 즉 이전에 이미 wakeup 소스가 있고, 다시 활성화할 수 없으며 오류가 반환됩니다. 만약, 디바이스에 연결된 wakeup source가 없다면, 두 번째 인자로 전달받은 wakeup source와 연결한다.

    /drivers/base/power/wakeup.c -v6.2.2
    /**
     * device_wakeup_attach - Attach a wakeup source object to a device object.
     * @dev: Device to handle.
     * @ws: Wakeup source object to attach to @dev.
     *
     * This causes @dev to be treated as a wakeup device.
     */
    static int device_wakeup_attach(struct device *dev, struct wakeup_source *ws)
    {
    	spin_lock_irq(&dev->power.lock);
    	if (dev->power.wakeup) {
    		spin_unlock_irq(&dev->power.lock);
    		return -EEXIST;
    	}
    	dev->power.wakeup = ws;
    	if (dev->power.wakeirq)
    		device_wakeup_attach_irq(dev, dev->power.wakeirq);
    	spin_unlock_irq(&dev->power.lock);
    	return 0;
    }

     

     

    5.3.3) pm_stay_awake

    : device가 wakeup event를 처리하고 있을 때, `pm_stay_awake` 함수는 현재 wakeup event가 발생해서 처리해야 하므로, PM core에게 wakeup event를 알려서 이벤트를 처리할 때 동안은 시스템이 suspend 상태로 진입하지 못하도록 막는다.

    /drivers/base/power/wakeup.c - v6.2.2
    /**
     * pm_stay_awake - Notify the PM core that a wakeup event is being processed.
     * @dev: Device the wakeup event is related to.
     *
     * Notify the PM core of a wakeup event (signaled by @dev) by calling
     * __pm_stay_awake for the @dev's wakeup source object.
     *
     * Call this function after detecting of a wakeup event if pm_relax() is going
     * to be called directly after processing the event (and possibly passing it to
     * user space for further processing).
     */
    void pm_stay_awake(struct device *dev)
    {
    	unsigned long flags;
    
    	if (!dev)
    		return;
    
    	spin_lock_irqsave(&dev->power.lock, flags);
    	__pm_stay_awake(dev->power.wakeup);
    	spin_unlock_irqrestore(&dev->power.lock, flags);
    }
    EXPORT_SYMBOL_GPL(pm_stay_awake);
    
    /**
     * __pm_stay_awake - Notify the PM core of a wakeup event.
     * @ws: Wakeup source object associated with the source of the event.
     *
     * It is safe to call this function from interrupt context.
     */
    void __pm_stay_awake(struct wakeup_source *ws)
    {
    	unsigned long flags;
    
    	if (!ws)
    		return;
    
    	spin_lock_irqsave(&ws->lock, flags);
    
    	wakeup_source_report_event(ws, false);
    	del_timer(&ws->timer);
    	ws->timer_expires = 0;
    
    	spin_unlock_irqrestore(&ws->lock, flags);
    }
    EXPORT_SYMBOL_GPL(__pm_stay_awake);

    : `__pm_stay_awake` 함수를 보면, `del_timer(&ws->timer)`와 `ws->timer_expires = 0`을 통해서 디폴트로 설정된 타이머를 종료시킨다. `ws->timer`에 설정된 콜백 함수는 특정 시간동안만 `ws`를 active 시켜 시스템을 awake 시키고, 그 후에는 `ws`를 de-active 시키는 함수다. 즉, 타이머를 제거한다는 것은 특정 조건이 발동하지 않은 이상 계속 `ws`를 activa 시켜 시스템을 awake 시키겠다는 것이다[참고1]. `pm_stay_awake` 함수와 짝을 이루는 함수가 `pm_relax` 함수다. 즉, 다시 시스템을 절전 모드로 보내고 싶다면, `pm_relax` 함수를 호출해야 한다.

     

    : `wakeup_source_report_event` 함수는 코드양에 비해 기능이 많은 함수다.

    /drivers/base/power/wakeup.c -v6.2.2
    /**
     * wakeup_source_report_event - Report wakeup event using the given source.
     * @ws: Wakeup source to report the event for.
     * @hard: If set, abort suspends in progress and wake up from suspend-to-idle.
     */
    static void wakeup_source_report_event(struct wakeup_source *ws, bool hard)
    {
    	ws->event_count++;
    	/* This is racy, but the counter is approximate anyway. */
    	if (events_check_enabled)
    		ws->wakeup_count++;
    
    	if (!ws->active)
    		wakeup_source_activate(ws);
    
    	if (hard)
    		pm_system_wakeup();
    }

    1) 일단, 자신이 발생시킨 `wakeup event`가 있으니 PM Core에게 전달될 것이기 때문에, `event_count`를 증가시킨다.

     

    2) `events_check_enabled`가 true면, suspending 중에 wakeup event를 검사하겠다는 뜻이다. 즉, 처리중 인 wakeup event가 아직은 없다는 것을 의미한다. 만약, false면 `wakeup event in progress`가 존재한다는 뜻이다. 그러므로, `ws->wakeup_count`를 증가시키지 않는다(`wakeup_count`는 자신 때문에 시스템이 wakeup 됬을 경우에 증가).

     

    3) `wakeup_source_activate` 함수가 실제 PM 코어에게 wakeup event가 발생했다는 것을 알리는 계기가 된다.

     

    3) wakeup source가 activate 되지 않은 경우, wakeup_source_activate() 함수를 호출하고 activate한다.

    /**
     * wakeup_source_activate - Mark given wakeup source as active.
     * @ws: Wakeup source to handle.
     *
     * Update the @ws' statistics and, if @ws has just been activated, notify the PM
     * core of the event by incrementing the counter of the wakeup events being
     * processed.
     */
    static void wakeup_source_activate(struct wakeup_source *ws)
    {
    	unsigned int cec;
    
    	if (WARN_ONCE(wakeup_source_not_registered(ws),
    			"unregistered wakeup source\n"))
    		return;
    
    	ws->active = true;
    	ws->active_count++;
    	ws->last_time = ktime_get();
    	if (ws->autosleep_enabled)
    		ws->start_prevent_time = ws->last_time;
    
    	/* Increment the counter of events in progress. */
    	cec = atomic_inc_return(&combined_event_count);
    
    	trace_wakeup_source_activate(ws->name, cec);
    }

    1) ws->active 플래그를 set하고, active_count에 1을 추가하고, 커널에서 기록하는 현재 시간을 last_time에 업데이트한다.

     

    2) autosleep이 enabled되면 start_prevent_time을 업데이트한다. 이 시점부터 wakeup source가 시스템이 autosleep 되려는 것을 막기 시작한다.

     

    3) `wakeup events in progress` 에 1을 더한다(`atomic_inc_return`). 이 카운트를 증가시키는 것은 시스템에서 처리 중인 wakeup event의 수가 0이 아니라는 것을 의미하고, 이 말은 결국 시스템이 suspend 할 수 없다는 것을 의미한다(`atomic_add_return`은 `wakeup events in progress`를 1 증가시키고, `registered wakeup event`를 1 감소시킨다. 그러나, `atomic_inc_return`은 `registered wakeup event`를 그대로 두면서, `wakeup events in process`를 1 증가시킨다).

     

    - pm_stay_awake() 실행이 완료되면 시스템이 적어도 하나의 wakeup event를 처리 중인 것을 의미하므로, 시스템은 suspend를 할 수 없습니다. wakeup event의 처리가 완료된 후에는 어떻게 해야 될까? driver는 pm_relax()를 호출하여 PM 코어에 알려야 한다.

     

     

    5.3.4) pm_relax

    - pm_relax와 pm_stay_awake는 쌍으로 나타나야 한다. 즉, 이 말은 wakeup event 처리가 종료 되면 PM core에 통지해야 한다는 말이다. 그 이후 PM core는 해당 wakeup source를 deactivate 하게 될 것이다. 아래

    /drivers/base/power/wakeup.c -v6.2.2
    /**
     * pm_relax - Notify the PM core that processing of a wakeup event has ended.
     * @dev: Device that signaled the event.
     *
     * Execute __pm_relax() for the @dev's wakeup source object.
     */
    void pm_relax(struct device *dev)
    {
    	unsigned long flags;
    
    	if (!dev)
    		return;
    
    	spin_lock_irqsave(&dev->power.lock, flags);
    	__pm_relax(dev->power.wakeup);
    	spin_unlock_irqrestore(&dev->power.lock, flags);
    }
    EXPORT_SYMBOL_GPL(pm_relax);
    
    ------------------------------------------------------------
    /drivers/base/power/wakeup.c -v6.2.2
    /**
     * __pm_relax - Notify the PM core that processing of a wakeup event has ended.
     * @ws: Wakeup source object associated with the source of the event.
     *
     * Call this function for wakeup events whose processing started with calling
     * __pm_stay_awake().
     *
     * It is safe to call it from interrupt context.
     */
    void __pm_relax(struct wakeup_source *ws)
    {
    	unsigned long flags;
    
    	if (!ws)
    		return;
    
    	spin_lock_irqsave(&ws->lock, flags);
    	if (ws->active)
    		wakeup_source_deactivate(ws);
    	spin_unlock_irqrestore(&ws->lock, flags);
    }
    EXPORT_SYMBOL_GPL(__pm_relax);

    - pm_relax() 함수 또한, 내부적으로 __pm_relax() 함수를 호출한다. 결국 최종적으로는 struct wakeup_source를 가지고 처리한다. 결국 wakeup events framework의 핵심 기능은 wakeup source에 대한 activate / deactivate 처리에 있다.

     

    /drivers/base/power/wakeup.c - v6.2.2
    /**
     * wakeup_source_deactivate - Mark given wakeup source as inactive.
     * @ws: Wakeup source to handle.
     *
     * Update the @ws' statistics and notify the PM core that the wakeup source has
     * become inactive by decrementing the counter of wakeup events being processed
     * and incrementing the counter of registered wakeup events.
     */
    static void wakeup_source_deactivate(struct wakeup_source *ws)
    {
    	unsigned int cnt, inpr, cec;
    	ktime_t duration;
    	ktime_t now;
    
    	ws->relax_count++;
    	/*
    	 * __pm_relax() may be called directly or from a timer function.
    	 * If it is called directly right after the timer function has been
    	 * started, but before the timer function calls __pm_relax(), it is
    	 * possible that __pm_stay_awake() will be called in the meantime and
    	 * will set ws->active.  Then, ws->active may be cleared immediately
    	 * by the __pm_relax() called from the timer function, but in such a
    	 * case ws->relax_count will be different from ws->active_count.
    	 */
    	if (ws->relax_count != ws->active_count) {
    		ws->relax_count--;
    		return;
    	}
    
    	ws->active = false;
    
    	now = ktime_get();
    	duration = ktime_sub(now, ws->last_time);
    	ws->total_time = ktime_add(ws->total_time, duration);
    	if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))
    		ws->max_time = duration;
    
    	ws->last_time = now;
    	del_timer(&ws->timer);
    	ws->timer_expires = 0;
    
    	if (ws->autosleep_enabled)
    		update_prevent_sleep_time(ws, now);
    
    	/*
    	 * Increment the counter of registered wakeup events and decrement the
    	 * counter of wakeup events in progress simultaneously.
    	 */
    	cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
    	trace_wakeup_source_deactivate(ws->name, cec);
    
    	split_counters(&cnt, &inpr);
    	if (!inpr && waitqueue_active(&wakeup_count_wait_queue))
    		wake_up(&wakeup_count_wait_queue);
    }

    1) relax_count에 1을 더한다(relax_count와 active_count가 동일하지 않으면 반복 호출이 있으며 종료됩니다). 그리고, `ws->relax_count`와 `ws->active_count`는 서로 쌍을 이루기 때문에 값이 동일하지 않으면, 문제가 발생한 것이다.

    2) 전달받은 wakeup source가 이제 deactive 이므로, active 플래그를 clear한다.

    3) total_time, max_time, lasst_time 등의 변수를 업데이트한다.

    4) autosleep이 가능하다면 관련 변수를 업데이트한다(후술).

    5) `atomic_add_return` 함수는 wakeup events in progress에서 1을 빼고, registered wakeup events에서 1을 더한다.

    6) wakeup count 관련 처리는 wakeup count 관련 글을 참고하자.

     

     

     

    5.3.5) pm_wakeup_event

    : pm_wakeup_event() 함수는  pm_stay_awake()와 pm_relax()의 짬뽕된 버전으로, event를 보고할 때 timeout 시간을 지정하고 timeout 후 자동으로 relax가 처리되며 일반적으로 언제 완료되는지 모르는 시나리오을 처리하는 데 사용됩니다. 이 인터페이스는 비교적 간단하므로 일일이 설명하지 않겠습니다.

    /include/linux/pm_wakeup.h - v6.2.2
    static inline void pm_wakeup_event(struct device *dev, unsigned int msec)
    {
    	return pm_wakeup_dev_event(dev, msec, false);
    }
    
    ---------------------------------------------------------------
    /drivers/base/power/wakeup.c - v6.2.2
    /**
     * pm_wakeup_dev_event - Notify the PM core of a wakeup event.
     * @dev: Device the wakeup event is related to.
     * @msec: Anticipated event processing time (in milliseconds).
     * @hard: If set, abort suspends in progress and wake up from suspend-to-idle.
     *
     * Call pm_wakeup_ws_event() for the @dev's wakeup source object.
     */
    void pm_wakeup_dev_event(struct device *dev, unsigned int msec, bool hard)
    {
    	unsigned long flags;
    
    	if (!dev)
    		return;
    
    	spin_lock_irqsave(&dev->power.lock, flags);
    	pm_wakeup_ws_event(dev->power.wakeup, msec, hard);
    	spin_unlock_irqrestore(&dev->power.lock, flags);
    }
    EXPORT_SYMBOL_GPL(pm_wakeup_dev_event);
    
    ---------------------------------------------------------------
    /drivers/base/power/wakeup.c - v6.2.2
    /**
     * pm_wakeup_ws_event - Notify the PM core of a wakeup event.
     * @ws: Wakeup source object associated with the event source.
     * @msec: Anticipated event processing time (in milliseconds).
     * @hard: If set, abort suspends in progress and wake up from suspend-to-idle.
     *
     * Notify the PM core of a wakeup event whose source is @ws that will take
     * approximately @msec milliseconds to be processed by the kernel.  If @ws is
     * not active, activate it.  If @msec is nonzero, set up the @ws' timer to
     * execute pm_wakeup_timer_fn() in future.
     *
     * It is safe to call this function from interrupt context.
     */
    void pm_wakeup_ws_event(struct wakeup_source *ws, unsigned int msec, bool hard)
    {
    	unsigned long flags;
    	unsigned long expires;
    
    	if (!ws)
    		return;
    
    	spin_lock_irqsave(&ws->lock, flags);
    
    	wakeup_source_report_event(ws, hard);
    
    	if (!msec) {
    		wakeup_source_deactivate(ws);
    		goto unlock;
    	}
    
    	expires = jiffies + msecs_to_jiffies(msec);
    	if (!expires)
    		expires = 1;
    
    	if (!ws->timer_expires || time_after(expires, ws->timer_expires)) {
    		mod_timer(&ws->timer, expires);
    		ws->timer_expires = expires;
    	}
    
     unlock:
    	spin_unlock_irqrestore(&ws->lock, flags);
    }
    EXPORT_SYMBOL_GPL(pm_wakeup_ws_event);

    : `pm_wakeup_event`함수는 본질적으로 timeout을 사용하는 구조를 택하기 때문에 전달된 `msec`가 0일 경우, 즉각적으로 wakeup source를 deactivate 하는 것을 볼 수 있다. msec가 0이 아닐 경우, msec 시간만큼 해당 wakeup source를 activate한다. 그리고 msec가 만료되면, 해당 wakeup source를 deactivate 한다.

     

     

    5.3.6) pm_wakeup_pending

    - drivers에서 생성된 wakeup events는 최종적으로 PM core에 보고된다. PM core는 wakeup events를 보고받으면, `suspending`을 종료할지 여부를 결정합니다. 이 때, 새로운 웨이크-업 이벤트가 발생해서 `suspending`을 종료해야 하는지 여부를 판별하는 함수가 `pm_wakeup_pending` 함수다. 이 함수는 suspend 단계에서 굉장히 자주 호출된다. 왜? wakeup event가 언제 발생할 지 알 수가 없기 때문이다. 그리고, 멀티 프로세서라면 wakeup event가 발생하는 프로세서와 suspend를 처리하는 프로세서가 다를 수 있기 때문에, 마냥 `웨이크-업 인터럽트는 코드 흐름이 이동하지 않는 건가?` 라고 착각하면 안된다.

    /drivers/base/power/wakeup.c - v6.2.2
    /**
     * pm_wakeup_pending - Check if power transition in progress should be aborted.
     *
     * Compare the current number of registered wakeup events with its preserved
     * value from the past and return true if new wakeup events have been registered
     * since the old value was stored.  Also return true if the current number of
     * wakeup events being processed is different from zero.
     */
    bool pm_wakeup_pending(void)
    {
    	unsigned long flags;
    	bool ret = false;
    
    	raw_spin_lock_irqsave(&events_lock, flags);
    	if (events_check_enabled) {
    		unsigned int cnt, inpr;
    
    		split_counters(&cnt, &inpr);
    		ret = (cnt != saved_count || inpr > 0);
    		events_check_enabled = !ret;
    	}
    	raw_spin_unlock_irqrestore(&events_lock, flags);
    
    	if (ret) {
    		pm_pr_dbg("Wakeup pending, aborting suspend\n");
    		pm_print_active_wakeup_sources();
    	}
    
    	return ret || atomic_read(&pm_abort_suspend) > 0;
    }
    EXPORT_SYMBOL_GPL(pm_wakeup_pending);

    - 이 함수의 자세한 내용은 이 글을 참고하자.

Designed by Tistory.