[Linux kernel] Runtime PM framework for I/O devices

이 문서의 저작권은 GPL을 따릅니다(This document is released under the GPL license).

번역: 양정석(dasomoli@gmailREMOVETHIS.com)

I/O 디바이스를 위한 런타임 전력 관리 프레임워크

(C) 2009-2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
(C) 2010 Alan Stern <stern@rowland.harvard.edu>
1. 도입
전력 관리 코어 레벨에 다음과 같은 방법으로 I/O 디바이스를 위한 런타임 전력 관리
(런타임 PM)의 지원을 제공합니다:
* 버스 타입과 디바이스 드라이버 내의 전력 관리 큐 pm_wq 는 그들의 PM에 관련된
  작업 아이템을 넣을 수 있습니다. 런타임 PM에 관련된 모든 작업 아이템의 큐 삽입
  에 pm_wq를 사용하는 것을 강력하게 권장합니다. 왜냐하면 이 것은 시스템 전체적인
  전력 상태 전이(RAM으로 suspend, 하이버네이션과 시스템 슬립 상태에서의 깨어남)를
  동기화하는 것을 허용하기 때문입니다. pm_wq 는 include/linux/pm_runtime.h 에
  선언되어 있고, kernel/power/main.c 에 정의되어 있습니다.
  
* ‘struct device’ 의 (include/linux/pm.h 에 선언된 ‘struct dev_pm_info’ 타입의)
  ‘power’ 멤버 안의 수많은 런타임 PM 필드는 다른 것과의 런타임 PM 동작의 동기화에
  사용할 수 있습니다.
  
* (include/linux/pm.h 에 선언된) 세 개의 디바이스 런타임 콜백
* drivers/base/power/runtime.c 안에 정의된 헬퍼 함수 집합은 PM Core에 의해서
  그들간의 동기화가 되는 방법 같은 것 내에서 런타임 PM 동작이 수행되는데
  사용할 수 있습니다. 버스 타입과 디바이스 드라이버는 이 함수들을 사용할만
  합니다.
‘struct dev_pm_ops’ 안에 있는 런타임 PM 콜백, ‘struct dev_pm_info’ 안의
디바이스 런타임 PM 필드들, 그리고 런타임 PM을 위해 제공되는 헬퍼 함수들은
아래에 설명되어 있습니다.
2. 디바이스 런타임 PM 콜백
‘struct dev_pm_ops’ 안에 정의된 세 개의 디바이스 런타임 PM 콜백이 있습니다;
struct dev_pm_ops {
        …
        int (*runtime_suspend)(struct device *dev);
        int (*runtime_resume)(struct device *dev);
        int (*runtime_idle)(struct device *dev);
        …
};
->runtime_suspend(), ->runtime_resume() 그리고 ->runtime_idle() 콜백은 다음 중
하나일 디바이스의 서브 시스템을 위해 PM 코어에 의해서 실행됩니다:
  1. 그 디바이스의 PM 도메인 객체인 dev->pm_domain 가 있다면, 그 디바이스의
     PM 도메인
  2. dev->type 과 dev->type->pm 이 둘 다 있다면, 그 디바이스의 디바이스 타입
  3. dev->class 과 dev->class->pm 이 둘 다 있다면, 그 디바이스의 디바이스 클래스
     
  4. dev->bus 과 dev->bus->pm 이 둘다 있다면, 그 디바이스의 버스 타입
위의 규칙에 의해서 선택된 서브 시스템이 관련 콜백을 제공하지 않으면, PM 코어는
(있다면) dev->driver->pm 에 담긴 해당 드라이버 콜백을 직접 호출할 것입니다.
PM 코어는 위에 있는 순서에 따라 사용할 콜백을 검사합니다. 그래서 콜백의 우선 순위
순서는 높은 것에서 낮은 순으로 PM 도메인, 디바이스 타입, 클래스 그리고 버스 타입
입니다. 더불어 높은 우선 순위의 것은 항상 낮은 우선 순위의 것보다 우선합니다.
PM 도메인, 버스 타입, 디바이스 타입과 클래스 콜백은 서스시스템 레벨 콜백처럼
참조됩니다.
기본적으로 그 콜백들은 항상 인터럽트가 활성화된 상태의 프로세스 컨텍스트에서
실행됩니다. 그러나, pm_runtime_irq_safe() 헬퍼 함수는 PM 코에에게 주어진
디바이스를 위해 인터럽트 비활성화 상태에서 원자적 컨텍스트 안에서
->runtime_suspend(), ->runtime_resume(), and ->runtime_idle() 콜백을 실행하는
것이 안전하다는 것을 알려주는데 사용할 수 있습니다. 이것은 질의 안에서의 콜백
루틴은 블러킹되거나 슬립할 수 없다는 것을 내포할 뿐만 아니라, 4절 끝에 나열된
싱크로너스 헬퍼 함수가 그 디바이스를 위해 인터럽트 핸들러 안에서 또는 일반적인
원자적 컨텍스트 안에서 사용될 수 있다는 것을 의미합니다.
서브 시스템 레벨 suspend 콜백이 있다면, 그 디바이스의 적절한 suspend 처리는
해당 콜백이 “전부” “책임집니다”. 그러나 그 디바이스 드라이버 자체의
->runtime_suspend() 콜백 실행은 포함할 필요 없습니다(PM 코어의 관점에서
그 서브 시스템 레벨의 suspend 콜백이 그 디바이스 처리를 위해 무엇을 해야 하는 지
아는 한, 디바이스 드라이버 안에 ->runtime_suspend() 콜백을 구현하는 것은
필요 없습니다).
  * 주어진 드라이버의 서브 시스템 레벨 suspend 콜백(또는 직접 호출되는
    그 드라이버의 suspend 콜백)이 성공적으로 완료되면, PM 코어는 그 디바이스가
    저전력 상태로 들어간 것을 의미할 필요는 없는, Suspend 된 것처럼 봅니다. 그러나
    이것은 그 디바이스가 적절한 resume 콜백이 실행되기 전까지 데이타를 처리하지도
    않고, (여러) CPU, RAM과 통신하지 않을 것을 의미한다고 가정합니다. suspend 콜백의
    성공적인 실행 후의 디바이스의 런타임 PM 상태는 ‘suspended’ 입니다.
  * suspend 콜백이 -EBUSY 나 -EAGAIN을 반환하면, 그 디바이스의 런타임 PM 상태는
    그 디바이스가 나중에 완전히 “수행되어야만 한다”는 것을 의미하는 ‘active’로
    남아 있습니다.
  * suspend 콜백이 -EBUSY나 -EAGAIN 외의 에러 코드를 반환하면, PM 코어는 이 것을
    치명적 에러로 보고 그 상태가 직접 ‘active’나 ‘suspended’ 로 설정(PM 코어는
    이런 목적의 특별한 헬퍼 함수를 제공합니다)되기 전까지 4절에 설명된 헬퍼
    함수의 실행을 거부할 것입니다.

특히, 드라이버가 적절한 동작을 위해 원격 웨이크업 능력을 요구하고, (즉, 하드웨어
메카니즘이 디바이스가 PCI PME처럼 그 파워 상태의 변경 요청을 허용한다면)
device_run_wake() 가 그 디바이스를 위해 ‘false’를 반환한다면, ->runtime_suspend() 는
-EBUSY를 반환해야 합니다. 다른 말로 하자면, device_run_wake() 가 ‘true’를 반환하고,
그 디바이스가 suspend 콜백의 실행 동안 저전력 상태에 놓여져 있다면, 원격 웨이크업이
디바이스를 위해 커져 있을 것으로 기대됩니다. 일반적으로 원격 웨이크업은 런타임에
저전력 상태로 들어가는 모든 입력 장치에 켜져 있어야 합니다.

서브 시스템 레벨 resume 콜백이 있다면, 그 디바이스의 적절한 resume 처리는
해당 콜백이 “전부” “책임집니다”. 그러나 그 디바이스 드라이버 자체의
->runtime_resume() 콜백 실행은 포함할 필요 없습니다(PM 코어의 관점에서
그 서브 시스템 레벨의 resume 콜백이 그 디바이스 처리를 위해 무엇을 해야 하는지
아는 한 디바이스 드라이버 안에 ->runtime_resume() 콜백을 구현하는 것은
필요 없습니다).
  * 서브 시스템 레벨의 resume 콜백(또는 직접 호출되는 그 드라이버의 resume 콜백)
    이 성공적으로 완료되면, PM 코어는 그 디바이스가 필요한 I/O 동작을 “완료할 수
    있어야만” 한다는 것을 의미하는, 완전히 동작하는 것으로 봅니다. 그 디바이스의
    런타임 PM 상태는 그럼 ‘active’ 입니다.
  * resume 콜백이 에러 코드를 반환하면, PM 코어는 이것을 치명적 에러로 보고
    그 상태가 ‘active’ 나  ‘suspended’로 (PM 코어에 의해 제공되는 특별한 헬퍼
    함수의 방법으로)직접 설정되기 전까지 4절에서 설명된 헬퍼 함수의 실행을
    거부할 것입니다.
  
idle 콜백(서브 시스템 레벨의 또는 드라이버의 idle 콜백이 있다면)은 PM 코어에
의해서 그 디바이스가 그 디바이스의 사용 카운터와 그 디바이스의 ‘active’인 자식의
카운터, 두 개의 카운터에 의해 PM 코어에 의해 표시되는 idle 상태가 될 때마다
실행됩니다.
  * PM 코어에 의해 제공되는 헬퍼 함수를 사용하는 이들 카운터들 중 하나가 감소되어
    0과 같아지면, 다른 카운터가 검사됩니다. 만약 그 카운터도 0이라면 PM 코어는
    그 디바이스의 idle 콜백을 그 인자와 함께 실행합니다.
idle 콜백에 의해 수행되는 동작은 질의 안에서 완전히 서브 시스템(또는 드라이버)
에 완전히 의존적입니다. 그러나 그 디바이스가 suspend 될 수 있는지(예를 들면,
그 디바이스의 suspend 진입을 위한 모든 필요한 조건이 만족되었는지)를 검사하는 것
과 그런 경우에 그 디바이스에 대한 suspend 요청을 큐에 쌓는 것을 권장합니다.
이 콜백에 의해 반환되는 값은 PM 코어에서 무시됩니다.
4절에서 설명된 PM 코어에 의해 제공되는 헬퍼 함수들은 한 디바이스를 위한
런타임 PM 콜백에 대해서 다음 제약 사항을 만족함을 보장합니다.
(1) ->runtime_suspend() 나 ->runtime_resume() 가 ->runtime_idle()와 동시에
    실행될 수 있다는 것을 제외(->runtime_idle()이 같은 디바이스에 대해서 어떤
    다른 콜백이 실행되는 동안 실행되지 않는다 하더라도)하면, 그 콜백들은
    서로 동시에 실행될 수 없습니다(실제로 ->runtime_suspend() 와 동시에
    ->runtime_resume() 또는 같은 디바이스의 다른 인스턴스에서
    ->runtime_suspend() 가 실행되는 것은 금지됩니다).
(2) ->runtime_idle() 과 ->runtime_suspend() 는 ‘active’ 디바이스만 실행될 수
    있습니다(즉, PM 코어는 런타임 PM 상태가 ‘active’인 디바이스만 ->runtime_idle()
    이나 ->runtime_suspend()를 실행할 것입니다).
(3) ->runtime_idle() 과 ->runtime_suspend() 는 그 사용 카운터가 0이 되고, “또한”
    ‘active’ 자식의 수가 0이 되거나 ‘power.ignore_children’ 플래그가 셋팅된
    디바이스만 실행될 수 있습니다.
(4) ->runtime_resume() 은 ‘suspended’ 디바이스만 실행될 수 있습니다(실제로
    PM 코어는 런타임 PM 상태가 ‘suspended’ 인 디바이스만 ->runtime_resume() 을
    실행할 것입니다.
추가적으로, PM 코어에 의해 제공되는 헬퍼 함수들은 다음 규칙을 따릅니다.
  * ->runtime_suspend() 가 막 실행되려고 하거나 실행이 보류된 요청이 있으면,
    ->runtime_idle() 은 같은 디바이스에서 실행되지 않을 것입니다.
  * ->runtime_suspend() 의 실행을 스케줄링하는 것이나 실행에 대한 요청은 같은
    디바이스의 ->runtime_idle() 을 실행하기 위한 다른 보류된 요청을 취소할 것입니다.
  * ->runtime_resume() 이 막 실행되려고 하거나 실행이 보류된 요청이 있으면,
    같은 디바이스의 다른 콜백은 실행되지 않을 것입니다.
  * ->runtime_resume() 실행의 요청은 스케줄링된 autosuspend를 제외하면,
    같은 디바이스의 다른 콜백을 실행하기 위한 보류되거나 스케줄된 요청을 취소할
    것입니다.
3. 런타임 PM 디바이스 필드
다음 디바이스 런타임 PM 필드들은 include/linux/pm.h 안에 정의된
‘struct dev_pm_info’ 안에 있습니다.
  struct timer_list suspend_timer;
    – 스케줄링된 (지연된) suspend 와 autosuspend 요청을 위해 사용되는 타이머
  unsigned long timer_expires;
    – jiffies로 된 타이머 만료 시간 (0이 아니라면, 그 타이머는 실행 중이고
      그 시간에 만료되는 것이거나, 그 타이머가 실행 중이 아닌 겁니다)
  struct work_struct work;
    – 요청을 큐에 쌓는데 사용되는 워크 구조체(실제로, pm_wq 안의 워크 아이템)
  wait_queue_head_t wait_queue;
    – 어떤 헬퍼 함수가 다른 것이 완료되기를 기다리는 것이 필요할 때 사용되는
      대기 큐
  spinlock_t lock;
    – 동기화에 사용되는 락
  atomic_t usage_count;
    – 그 디바이스의 사용 카운터
  atomic_t child_count;
    – 그 디바이스의 ‘active’ 자식의 수
  unsigned int ignore_children;
    – 셋팅되면, child_count 의 값이 무시됩니다(그러나 여전히 업데이트는 됩니다)
  unsigned int disable_depth;
    – 헬퍼 함수들을 비활성화하는데 사용됩니다(그것들은 이 값이 0일 때 일반적으로
      실행됩니다); 초기값은 1(실제로 런타임 PM은 초기에 모든 디바이스에 비활성화되어
      있습니다)
  unsigned int runtime_error;
    – 셋팅되면, 치명적 에러가 있는 것(2절에서 설명한 것처럼 콜백 중 하나가
      에러 코드를 반환한 것)입니다. 그래서 헬퍼 함수들은 이 플래그가 클리어될
      때까지 동작하지 않습니다; 이것은 실패한 콜백에 의해 반환된 에러 코드입니다.
  unsigned int idle_notification;
    – 셋팅되면, ->runtime_idle()이 실행 중입니다.
  unsigned int request_pending;
    – 셋팅되면, 보류된 요청(실제로 pm_wq 안에 워크 아이템이 담긴 것)이 있는 것입니다.
  enum rpm_request request;
    – 그 보류 요청의 타입(request_pending 이 셋팅되어 있을 때 유효)
  unsigned int deferred_resume;
    – ->runtime_resume() 이 ->runtime_suspend() 가 그 디바이스에서 실행되는 중에
      막 실행되려고 하고, 그것이 suspend가 완료되기를 기다리는 것-“suspend되자 마자
      resume을 시작함”을 의미-이 아니면 셋팅하세요.
  unsigned int run_wake;
    – 디바이스가 런타임 웨이크업 이벤트를 생성할 수 있으면 셋팅
  enum rpm_status runtime_status;
    – 디바이스의 런타임 PM 상태; 이 필드의 초기값은 RPM_SUSPENDED로, 각 디바이스는
      PM 코어에 의해 초기에 하드웨어 상태와 상관없이 ‘suspended’ 로 인식된다는
      뜻입니다.
  unsigned int runtime_auto;
    – 셋팅되면, 유저 공간이 디바이스 드라이버를 /sys/devices/…/power/control
      인터페이스를 통해 런타임에 디바이스의 파워를 관리할 수 있음을 나타냅니다;
      pm_rumtime_allow()와 rpm_runtime_forbid() 헬퍼 함수의 도움으로만 바뀝니다.
  unsigned int no_callbacks;
    – 디바이스가 런타임 PM 콜백을 사용하지 않음을 나타냅니다(8절 참조);
      pm_runtime_no_callbacks() 헬퍼 함수에 의해서만 바뀝니다.
  unsigned int irq_safe;
    – ->runtime_suspend() 와 ->runtime_resume() 콜백이 스핀락을 잡고 인터럽트가
      비활성화된 상태에서 호출됨을 나타냅니다.
  unsigned int use_autosuspend;
    – 디바이스의 드라이버가 지연된 autosuspend를 지원함을 나타냅니다(9절 참조);
      pm_runtime{_dont}_use_autosuspend() 헬퍼 함수에 의해서만 바뀝니다.
  unsigned int timer_autosuspends;
    – PM 코어가 보통의 suspend보다는 타이머가 만료되었을 때 autosuspend를 수행하는 것을
      시도하여야만 함을 나타냅니다.
  int autosuspend_delay;
    – autosuspend에 사용되는 지연 시간(밀리초)
  unsigned long last_busy;
    – 이 디바이스를 위해 pm_runtime_mark_last_busy() 헬퍼 함수가
      마지막으로 불린 시간(jiffies 값); autosuspend를 위해 비활성화 시간을 계산하는데
      사용됨
위 필드 모두는 ‘struct device’의 ‘power’ 멤버의 멤버들입니다.
4. 런타임 PM 디바이스 헬퍼 함수
다음의 런타임 PM 헬퍼 함수들은 drivers/base/power/runtime.c와 include/linux/pm_runtime.h에
정의되어 있습니다:
  void pm_runtime_init(struct device *dev);
    – ‘struct dev_pm_info’ 안의 그 디바이스의 런타임 PM 필드를 초기화
  void pm_runtime_remove(struct device *dev);
    – 디바이스의 런타임 PM이 디바이스 계층에서 그 디바이스가 제거된 후에 비활성화될 것임을 확인
  int pm_runtime_idle(struct device *dev);
    – 그 디바이스의 서브 시스템 레벨 idle 콜백을 실행; 성공 시에는 0을, 실패 시에는
      에러 코드를 반환합니다. -EINPROGRESS 는 ->runtime_idle()이 이미 실행 중임을 나타냅니다.
  int pm_runtime_suspend(struct device *dev);
    – 그 디바이스의 서브 시스템 레벨 suspend 콜백을 실행: 성공 시에는 0을, 그 디바이스의
      런타임 PM 상태가 이미 ‘suspended’ 일 때는 1을, 실패 시에는 에러 코드를 반환합니다.
      -EAGAIN 이나 -EBUSY는 나중에 그 디바이스의 suspend 시도가 안전할 것임을
      나타냅니다. -EACCES 는 ‘power.disable_depth’가 0이 아님을 나타냅니다.
  int pm_runtime_autosuspend(struct device *dev);
    – pm_runtime_suspend()와 autosuspend delay가 사용됨을 제외하고는 동일;
      pm_runtime_autosuspend_expiration() 이 delay가 아직 만료되지 않았다고 보고하면
      autosuspend는 적절한 시간으로 스케줄되고 0이 반환됩니다.
  int pm_runtime_resume(struct device *dev);
    – 그 디바이스의 서브 시스템 레벨 resume 콜백을 실행; 성공 시에는 0을, 그 디바이스의
      런타임 PM 상태가 이미 ‘active’이면 1을, 실패 시에는 에러 코드를 반환합니다.
      -EAGAIN 은 나중에 그 디바이스의 resume 시도가 안전할 것임을 나타냅니다. 그러나
      ‘power.runtime_error’는 추가적으로 체크되어야 합니다. -EACCES 는 ‘power.disable_depth’
      가 0이 아님을 나타냅니다.
  int pm_request_idle(struct device *dev);
    – 그 디바이스의 서브 시스템 레벨 idle 콜백 실행의 요청을 제출합니다(그 요청은
      pm_wq 안의 워크 아이템으로 나타납니다); 성공 시에는 0이, 그 요청이 큐에 들어가지
      못하면 에러 코드가 반환됩니다.
    
  int pm_request_autosuspend(struct device *dev);
    – autosuspend delay가 만료되었을 때 그 디바이스의 서브 시스템 레벨 suspend 콜백 실행을
      스케줄합니다; delay가 이미 만료되었으면, 워크 아이템은 즉시 큐에 들어갑니다.
  int pm_schedule_suspend(struct device *dev, unsigned int delay);
    – 그 디바이스의 서브 시스템 레벨 suspend 콜백을 좀 나중에 실행할 것을 스케줄합니다.
      여기서 ‘delay’는 pm_wq 안에 suspend 워크 아이템이 큐에 들어가기 전에 기다릴
      시간(밀리초) 입니다(‘delay’가 0이면, 그 워크 아이템은 즉시 큐에 들어갑니다);
      성공 시에는 0이, 그 디바이스의 PM 런타임 상태가 이미 ‘suspended’ 이면 1이,
      그 요청이 스케줄되지 않으면(또는 ‘delay’가 0일 때 큐에 들어가지 않으면)
      에러 코드가 반환됩니다; ->runtime_suspend()의 실행이 이미 스케줄되어 있고, 아직
      만료되지 않았다면, ‘delay’의 새 값은 기다릴 시간으로 사용됩니다.
  int pm_request_resume(struct device *dev);
    – 그 디바이스의 서브 시스템 레벨 resume 콜백의 실행 요청을 제출합니다(그 요청은
      pm_wq 안의 워크 아이템으로 나타납니다); 성공 시에는 0이, 그 디바이스의 런타임
      PM 상태가 이미 ‘active’이면 1이, 요청이 큐에 들어가지 못하면 에러 코드가 반환됩니다.
  void pm_runtime_get_noresume(struct device *dev);
    – 디바이스의 사용 카운터를 증가시킵니다.
  int pm_runtime_get(struct device *dev);
    – 디바이스의 사용 카운터를 증가시키고, pm_request_resume(dev) 를 실행하고,
      그 결과를 반환합니다.
  int pm_runtime_get_sync(struct device *dev);
    – 디바이스의 사용 카운터를 증가시키고, pm_runtime_resume(dev) 를 실행하고,
      그 결과를 반환합니다.
  void pm_runtime_put_noidle(struct device *dev);
    – 디바이스의 사용 카운터를 감소시킵니다.
  int pm_runtime_put(struct device *dev);
    – 디바이스의 사용 카운터를 감소시킵니다; 그 결과가 0이면, pm_request_idle(dev) 를
      실행하고, 그 결과를 반환합니다.
  int pm_runtime_put_autosuspend(struct device *dev);
    – 디바이스의 사용 카운터를 감소시킵니다; 그 결과가 0이면, pm_request_autosuspend(dev) 를
      실행하고, 그 결과를 반환합니다.
  int pm_runtime_put_sync(struct device *dev);
    – 디바이스의 사용 카운터를 감소시킵니다; 그 결과가 0이면, pm_runtime_idle(dev) 를
      실행하고, 그 결과를 반환합니다.
  int pm_runtime_put_sync_suspend(struct device *dev);
    – 디바이스의 사용 카운터를 감소시킵니다; 그 결과가 0이면, pm_runtime_suspend(dev) 를
      실행하고, 그 결과를 반환합니다.
  int pm_runtime_put_sync_autosuspend(struct device *dev);
    – 디바이스의 사용 카운터를 감소시킵니다; 그 결과가 0이면, pm_runtime_autosuspend(dev) 를
      실행하고, 그 결과를 반환합니다.
  void pm_runtime_enable(struct device *dev);
    – 디바이스의 ‘power.disable_depth’ 필드를 감소시킵니다; 그 필드가 0이면, 런타임 PM
      헬퍼 함수들이 2절에서 설명된 서브 시스템 레벨 콜백들을 실행할 수 있습니다.
  int pm_runtime_disable(struct device *dev);
    – 디바이스의 ‘power.disable_depth’ 필드를 증가시키고(그 필드의 값이 실행 전에
      0이었다면, 이 것은 서브 시스템 레벨 런타임 PM 콜백의 실행을 막습니다), 디바이스 상의
      모든 런타임 PM 동작이 완료되었거나 취소되었음을 확인합니다;
      resume 요청이 밀려있고, 요청을 만족하기 위해서 서브 시스템 레벨 resume 콜백이 실행될
      필요가 있다면 1을, 다른 경우에는 0이 반환됩니다.
  int pm_runtime_barrier(struct device *dev);
    – resume 요청이 밀려있는지 검사하고, 그런 경우에는 그 요청들을 (동기적으로)
      resume 하고, 그에 관련된 다른 밀린 런타임 PM 요청들을 취소하고, 완료를 위해
      진행 중인 모든 런타임 PM 동작들을 기다립니다; resume 요청이 밀려있고, 요청을 만족하기
      위해서 서브 시스템 레벨 resume 콜백들이 실행될 필요가 있다면 1을, 다른 경우에는
      0이 반환됩니다.
  void pm_suspend_ignore_children(struct device *dev, bool enable);
    – 디바이스의 power.ignore_children 플래그를 셋팅/해제합니다.
  int pm_runtime_set_active(struct device *dev);
    – 디바이스의 ‘power.runtime_error’ 플래그를 클리어하고, 디바이스의 런타임 PM 상태를
      ‘active’ 상태로 셋팅하고, 부모의 ‘active’ 자식들의 카운터를 적절히 업데이트합니다
      (‘power.rumtime_error’가 셋팅되어 있거나 ‘power.disable_depth’가 0보다 크면,
      이 함수를 사용하는 것만 유효합니다); 그 디바이스가 active 하지 않고
      ‘power.ignore_children’ 플래그가 해제되어 있는 부모를 가진다면, 실패하고
      에러 코드를 반환할 것입니다.
  void pm_runtime_set_suspended(struct device *dev);
    – 디바이스의 ‘power.runtime_error’ 플래그를 클리어하고, 디바이스의 런타임 PM 상태를
      ‘suspended’ 상태로 셋팅하고, 부모의 ‘active’ 자식들의 카운터를 적절히 업데이트합니다
      (‘power.rumtime_error’가 셋팅되어 있거나 ‘power.disable_depth’가 0보다 크면,
      이 함수를 사용하는 것만 유효합니다).
  bool pm_runtime_suspended(struct device *dev);
    – 디바이스의 런타임 PM 상태가 ‘suspended’ 이고, 그 ‘power.disable_depth’가 0이면,
      true를, 다른 경우는 false를 반환합니다.
  bool pm_runtime_status_suspended(struct device *dev);
    – 디바이스의 런타임 PM 상태가 ‘suspended’ 이면 true를 반환합니다.
  void pm_runtime_allow(struct device *dev);
    – 디바이스의 power.runtime_auto 플래그를 셋팅하고, 그 사용 카운터를 감소시킵니다
      (/sys/devices/…/power/control 인터페이스가 런타임에 디바이스의 파워 관리를
      효과적으로 허용하기 위해서 사용됩니다).
  void pm_runtime_forbid(struct device *dev);
    – 디바이스의 power.runtime_auto 플래그를 해제하고, 그 사용 카운터를 증가시킵니다
      (/sys/devices/…/power/control 인터페이스가 런타임에 디바이스의 파워 관리를
      효과적으로 막기 위해서 사용됩니다).
  void pm_runtime_no_callbacks(struct device *dev);
    – 디바이스의 power.no_callbacks 플래그를 셋팅하고, /sys/devices/…/power 의
      런타임 PM 속성들을 제거(또는 디바이스가 등록될 때 속성들이 추가되는 것을
      막습니다)합니다.
  void pm_runtime_irq_safe(struct device *dev);
    – 인터럽트 비활성화 상태에서 호출되는 런타임 PM 콜백을 일으키는
      디바이스의 power.irq_safe 플래그를 셋팅합니다.
  void pm_runtime_mark_last_busy(struct device *dev);
    – power.last_busy 필드를 현재 시간으로 셋팅합니다.
  void pm_runtime_use_autosuspend(struct device *dev);
    – autosuspend delay를 켜는 power.use_autosuspend 플래그를 셋팅합니다.
  void pm_runtime_dont_use_autosuspend(struct device *dev);
    – autosuspend delay를 끄는 power.use_autosuspend 플래그를 클리어합니다.
  void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
    – power.autosuspend_delay 값을 (밀리초로 나타내는) ‘delay’로 셋팅합니다;
      delay가 음수이면 런타임 suspend는 막힙니다.
  unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
    – 현재 autosuspend delay 시간이 만료될 시간을 power.last_busy와
      power.autosuspend_delay에 기초해서 계산합니다; delay 시간이 1000 ms 나
      그 이상인 경우, 만료 시간은 가장 가까운 초로 올림됩니다;
      그 딜레이 시간이 이미 만료되었거나 power.use_autosuspend가 셋팅되지 않았으면
      0을, 다른 경우에는 jiffies로 된 만료 시간을 반환합니다.
다음 헬퍼 함수들은 인터럽트 컨텍스트에서의 실행이 안전합니다:
pm_request_idle()
pm_request_autosuspend()
pm_schedule_suspend()
pm_request_resume()
pm_runtime_get_noresume()
pm_runtime_get()
pm_runtime_put_noidle()
pm_runtime_put()
pm_runtime_put_autosuspend()
pm_runtime_enable()
pm_suspend_ignore_children()
pm_runtime_set_active()
pm_runtime_set_suspended()
pm_runtime_suspended()
pm_runtime_mark_last_busy()
pm_runtime_autosuspend_expiration()
pm_runtime_irq_safe()가 호출되면 다음 헬퍼 함수들이 인터럽트 컨텍스트에서
사용될 겁니다:
pm_runtime_idle()
pm_runtime_suspend()
pm_runtime_autosuspend()
pm_runtime_resume()
pm_runtime_get_sync()
pm_runtime_put_sync()
pm_runtime_put_sync_suspend()
pm_runtime_put_sync_autosuspend()
5. 런타임 PM 초기화, 디바이스 감지하기와 제거
초기에 런타임 PM은 모든 디바이스에 비활성화됩니다. 이 것은 4절에서 설명한 런타임
PM 헬퍼 함수들의 대부분이 pm_runtime_enable()이 그 디바이스에 호출될 때까지
-EAGAIN을 반활할 것을 의미합니다.
게다가 모든 디바이스의 초기 런타임 PM 상태는 ‘suspended’입니다. 그러나 그 것이
디바이스의 실제 물리적인 상태를 반영할 필요는 없습니다. 그래서 그 디바이스가
만약 초기에 활성 상태(즉, I/O를 처리 가능한)라면, pm_runtime_enable() 이 호출되기 전에
pm_runtime_set_active()의 도움으로, 그 런타임 PM 상태를 ‘active’로 바꿔야 합니다.
그러나 그 디바이스가 부모를 갖고, 그 부모의 런타임 PM이 켜져 있다면,
그 부모의 ‘power.ignore_children’ 플래그가 셋팅되기 전까지 pm_runtime_set_active()의
호출이 그 부모에 영향을 미칠 것입니다. 다시 말해, 그런 경우 그 부모는 그 자식의 상태가
‘active’인 한, 그 자식의 런타임 PM 이 여전히 꺼져있다 하더라도
PM 코어의 헬퍼 함수들을 통해 런타임에 suspend할 수 없을 것입니다
(즉, pm_runtime_enable() 은 그 자식을 위해 아직 호출되지 않았거나,
pm_runtime_disable()이 그를 위해 호출되었습니다). 이런 이유로 pm_runtime_set_active()가
호출되고 나면, pm_runtime_enable()이 꽤 가능한 한 빨리 호출되거나, 그 런타임 PM 상태가
pm_runtime_set_suspended()의 도움을 통해 ‘suspended’로 다시 바뀌어야 합니다.
그 디바이스의 초기 기본 런타임 PM 상태(즉, ‘suspended’)가 디바이스의 실제 상태를
반영한다면, 그 버스 타입의 또는 그 드라이버의 ->probe() 콜백이 4절에서 설명한 PM 코어의
헬퍼 함수들 중 하나를 사용해서 깨울 필요가 있습니다. 이런 경우, pm_runtime_resume()이
사용되어야 합니다. 물론, 이를 위해 디바이스의 런타임 PM이 pm_runtime_enable() 호출보다
먼저 켜져 있어야 합니다.
그 디바이스 버스 타입의 또는 드라이버의 ->probe() 콜백이 pm_runtime_suspend()나
pm_runtime_idle() 또는 그 비동기적인 대응물을 실행한다면, -EAGAIN을 반환하면서
실패할 것입니다. 그 디바이스의 사용 카운터가 ->probe() 실행 전에 드라이버 코어에 의해
증가되기 때문입니다. 여전히 ->probe()가 끝나자마자 디바이스가 suspend되는 것이
바람직 할 것입니다. 그래서 드라이버 코어는 서브 시스템 레벨 idle 콜백을 그 때
불리도록 하기 위해서 pm_runtime_put_sync()를 사용합니다.
더 나아가, 드라이버 코어는 런타임 PM 콜백들을 __device_release_driver() 안의
버스 notifier 콜백과 경쟁하는 것을 막습니다. 이는 꼭 필요한데, 그 notifier는
어떤 서브 시스템들에 의해서 런타임 PM 기능에 영향을 미치는 동작을 수행하기 위해
사용되기 때문입니다. 이는 driver_sysfs_remove() 전의 pm_runtime_get_sync() 호출과 BUS_NOTIFY_UNBIND_DRIVER 통지를 통해 수행합니다. 그 루틴들이 실행되는 동안
디바이스가 suspended 상태에 있고, 다시 suspended 되는 것을 막으면, 그 디바이스를
resume 합니다.
->remove() 루틴으로부터 pm_runtime_suspend()를 호출함으로써 버스 타입과 드라이버가
디바이스들을 suspended 상태로 들어가도록 허용하기 위해, 드라이버 코어는
__device_release_driver() 안에서 BUS_NOTIFY_UNBIND_DRIVER 통지를 실행한 후에
pm_runtime_put_sync()를 실행합니다. 이것은 버스 타입과 드라이버가 ->remove() 콜백을
직접 런타임 콜백과의 경쟁과 피하게 만들기 위해서 필요할 뿐만 아니라
그 드라이버의 제거를 하는 동안 디바이스의 처리에 더욱 큰 유연함을 제공합니다.
유저 스페이스는 pm_runtime_forbid()의 호출을 일으키는 /sys/devices/…/power/control
속성의 값을 “on”으로 바꾸는 것으로써 효과적으로 디바이스의 드라이버가 런타임에
파워를 관리하는 것을 허용하지 않을 수 있습니다. 원칙적으로, 이 메카니즘은 또한
드라이버에 의해 유저 공간이 켤 때까지 효과적으로 디바이스의 런타임 파워 관리를
끄는데 사용되기도 합니다. 다시말해, 초기화 동안 드라이버는 디바이스의 런타임 PM 상태가
‘active’인지 확인하고, pm_runtime_forbid()를 호출할 수 있습니다. 그러나 유저 공간이
런타임에 디바이스의 파워를 관리하기 위해 의도적으로 이미 /sys/devices/…/power/control의
값을 “auto”로 바꾸었다면, 드라이버는 pm_runtime_forbid()를 이 방법으로 사용함으로써 혼란에
빠질 것입니다.
6. 런타임 PM과 시스템 슬립
런타임 PM과 시스템 슬립(즉, suspend-to-RAM과 suspend-to-disk로도 알려진
시스템 suspend와 하이버네이션)은 서로 두 가지 방법으로 소통합니다. 시스템 슬립이
시작될 때 디바이스가 active 상태면, 모든 것이 간단합니다. 그러나 디바이스가 이미
suspended 상태라면 어떻게 될까요?
디바이스는 아마 런타임 PM과 시스템 슬립을 위한 다른 웨이크업 셋팅을 가지고 있을 것입니다.
예를 들면, 원격 웨이크업은 런타임 suspend를 위해 켜져 있을 것이지만, 시스템 슬립을
위해서는 허용되지 않는 것(device_may_wakeup(dev)는 ‘false’를 반환)입니다.
이런 것이 발생할 때, 서브 시스템 레벨 시스템 suspend 콜백은 디바이스의 웨이크업
셋팅을 바꿀 책임이 있습니다(이것은 디바이스 드라이버의 시스템 suspend 루틴에
남겨집니다). 디바이스를 resume 하고 suspend를 다시 순서대로 할 필요도 있습니다.
드라이버가 다른 파워 레벨을 사용하거나 런타임 suspend와 시스템 슬립을 위한 다른 셋팅을
사용하면, 이 역시 같습니다.
시스템 suspend가 시작되기 전에 suspend되어 있다하더라도, 시스템이 resume하는 동안의
가장 간단한 방법은 모든 디바이스를 풀파워로 다시 돌리는 것입니다. 이를 위해서는
다음을 포함하는 여러 이유가 있습니다:
  * 디바이스는 파워 레벨, 웨이크업 셋팅이나 기타를 바꿀 필요가 있을 수 있습니다.
  
  * 원격 웨이크업 이벤트를 펌웨어가 놓칠 수 있습니다.
  
  * 어떤 디바이스의 자식이 그들 스스로를 resume함에 따라 그 디바이스를 풀파워에 있게 할
    필요가 있을 수 있습니다.
    
  * 디바이스의 상태에 대한 드라이버의 생각은 디바이스의 물리 상태와 다를 수 있습니다.
    이것은 하이버네이션에서 resume하는 동안 발생할 수 있습니다.
  * 디바이스는 리셋될 필요가 있을 수 있습니다.
  
  * 디바이스가 suspend됐었다 하더라도, 사용 카운터 > 0 이면 거의 대부분
    어쨌든 런타임 resume이 가까운 미래에 필요할 것입니다.
    
디바이스가 시스템 suspend가 시작되기 전에 suspend되었고 resume동안 풀파워로 다시
돌아왔다면, 그 런타임 PM 상태는 실제 시스템 슬립 이후의 상태를 반영하도록
업데이트되어야만 할 것입니다. 이를 위한 방법은:
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
PM 코어는 ->suspend() 콜백을 호출하기 전에 언제나 런타임 사용 카운터를 증가시키고,
->resume() 콜백을 호출한 후에는 감소시킵니다. 그래서 이처럼 런타임 PM을 일시적으로
끄는 것은 런타임 suspend를 영원히 잃어버릴 수 있는 것이 일어나지 않도록 합니다.
사용 카운터가 0이 되고 ->resume() 콜백의 반환이 따라오면, ->runtime_idle() 콜백은
평소처럼 호출될 것입니다.
그러나 어떤 시스템 상에서는 시스템 슬립이 글로벌 펌웨어나 하드웨어 동작을 통해서
들어가지 않습니다. 대신에 모든 하드웨어 컴포넌트들은 커널에 의해서 맞춰진 방법으로
직접 저전력 상태로 들어갑니다. 그럼 하드웨어 컴포넌트가 끝난 상태에서 시스템 슬립
상태로 효과적으로 따라오고, 그 시스템은 커널의 제어 아래에서 하드웨어 인터럽트나
그와 유사한 메카니즘에 의해 전체적으로 그 상태에서 깨어나게 됩니다. 그 결과로,
커널은 절대 제어권을 넘겨주지 않고, resume 하는 동안 모든 디바이스의 상태는 명확히
알려지게 됩니다. 이 경우이면서 위에 나열된 상황들이 일어나지 않는다면(특히,
시스템이 하이버네이션으로부터 깨어나지 않고 있는), 시스템 suspend가 suspended 상태에서
시작되기 전에 suspend에 들어가 있는 디바이스는 그상태로 남아있기 더 효율적이게
것입니다.
PM 코어는 런타임 PM과 시스템 suspend/resume (그리고 하이버네이션) 콜백 사이의
경쟁 상태 확률을 줄이기 위해서 다음 동작들을 수행함으로써 최선의 방법을 수행합니다:
  * 시스템 suspend 동안, 서브 시스템 레벨 .suspend() 콜백을 실행하기 바로 직전에
    각각의 디바이스마다 pm_runtime_get_noresume() 과 pm_runtime_barrier() 를
    호출합니다. 게다가 서브 시스템 레벨 .suspend() 콜백 실행 바로 직후에 각각의
    디바이스마다 pm_runtime_disable() 을 호출합니다.
  * 시스템 resume 동안, 서브 시스템 레벨 .resume() 콜백 실행 바로 직전과 직후에
    각각의 디바이스마다 pm_runtime_enable() 과 pm_runtime_put_sync()를 호출합니다.
7. 일반적인 서브 시스템 콜백들
서브 시스템들은 PM 코어에 의해 제공되는, driver/base/power_generic_ops.c에 정의된
일반적인 파워 관리 콜백들을 사용해서 코드 공간을 아껴 쓰고 싶어할 것입니다:
  int pm_generic_runtime_idle(struct device *dev);
    – 정의된다면, 이 디바이스의 드라이버에 의해 제공되는 ->runtime_idle() 콜백을
      실행하고, 반환값이 0이거나 콜백이 정의되어 있지 않으면 pm_runtime_suspend()
      를 호출합니다.
  int pm_generic_runtime_suspend(struct device *dev);
    – 이 디바이스의 드라이버에 의해 제공되는 ->runtime_suspend() 콜백을 실행하고,
      그 결과를 반환하거나, 정의되지 않은 경우 -EINVAL을 반환합니다.
  int pm_generic_runtime_resume(struct device *dev);
    – 이 디바이스의 드라이버에 의해 제공되는 ->runtime_resume() 콜백을 실행하고,
      그 결과를 반환하거나, 정의되지 않은 경우 -EINVAL을 반환합니다.
  int pm_generic_suspend(struct device *dev);
    – 그 디바이스가 런타임에 suspend되지 않았다면, 그 드라이버에 의해 제공되는
      ->suspend() 콜백을 실행하고 그 결과를 반환하거나, 정의되지 않은 경우 0을
      반환합니다.
  int pm_generic_suspend_noirq(struct device *dev);
    – pm_runtime_suspended(dev)가 “false”를 반환하면, 그 디바이스 드라이버에 의해
      제공되는 ->suspend_noirq()를 실행하고 그 결과를 반환하거나, 정의되지 않은 경우
      0을 반환합니다.
  int pm_generic_resume(struct device *dev);
    – 디바이스 드라이버에 의해 제공되는 ->resume() 콜백을 실행하고, 성공하면,
      디바이스의 런타임 PM 상태를 ‘active’로 바꿉니다.
  int pm_generic_resume_noirq(struct device *dev);
    – 이 디바이스의 드라이버에 의해 제공되는 ->resume_noirq()를 실행합니다.
  int pm_generic_freeze(struct device *dev);
    – 그 디바이스가 런타임에 suspend되지 않았다면, 그 드라이버에 의해 제공되는
      ->freeze() 콜백을 실행하고 그 결과를 반환하거나, 정의되지 않은 경우
      0을 반환합니다.
  int pm_generic_freeze_noirq(struct device *dev);
    – pm_runtime_suspended(dev)가 “false”를 반환하면, 그 드라이버에 의해 제공되는
      ->freeze_noirq() 콜백을 실행하고 그 결과를 반환하거나, 정의되지 않은 경우
      0을 반환합니다.
  int pm_generic_thaw(struct device *dev);
    – 그 디바이스가 런타임에 suspend되지 않았다면, 그 드라이버에 의해 제공되는
      ->thaw() 콜백을 실행하고 그 결과를 반환하거나, 정의되지 않은 경우 0을
      반환합니다.
  int pm_generic_thaw_noirq(struct device *dev);
    – pm_runtime_suspended(dev)가 “false”를 반환하면, 그 디바이스 드라이버가
      제공하는 ->thaw_noirq() 콜백을 실행하고 그 결과를 반환하거나, 정의되지 않은
      경우 0을 반환합니다.
  int pm_generic_poweroff(struct device *dev);
    – 그 디바이스가 런타임에 suspend되지 않았다면, 그 드라이버에 의해 제공되는
      ->poweroff() 콜백을 실행하고 그 결과를 반환하거나, 정의되지 않은 경우
      0을 반환합니다.
  int pm_generic_poweroff_noirq(struct device *dev);
    – pm_runtime_suspended(dev)가 “false”를 반환하면, 그 디바이스 드라이버가
      제공하는 ->powerfoff_noirq() 콜백을 실행하고 그 결과를 반환하거나,
      정의되지 않은 경우 0을 반환합니다.
  int pm_generic_restore(struct device *dev);
    – 그 디바이스의 드라이버가 제공하는 ->restore() 콜백을 실행하고, 성공하면,
      그 디바이스의 런타임 PM 상태를 ‘active’로 바꿉니다.
  int pm_generic_restore_noirq(struct device *dev);
    – 그 디바이스 드라이버가 제공하는 ->restore_noirq() 콜백을 실행합니다.
이 함수들은 서브 시스템 레벨 dev_pm_ops 구조체 안에 있는
->runtime_idle(), ->runtime_suspend(), ->runtime_resume(), ->suspend(),
->suspend_noirq(), ->resume(), ->resume_noirq(), ->freeze(), ->freeze_noirq(),
->thaw(), ->thaw_noirq(), ->poweroff(), ->poweroff_noirq(), ->restore(),
->restore_noirq() 콜백 포인터로 지정될 수 있습니다.
서브 시스템이 동시에 이 모두를 사용하길 원한다면, 그 dev_pm_ops 구조체 포인터에
간단히 include/linux/pm.h 에 정의된 GENERIC_SUBSYS_PM_OPS 매크로를 사용할 수
있습니다.
시스템 suspend, freeze, poweroff와 런타임 suspend 콜백을, 이와 유사하게 시스템
resume, thaw, restore와 런타임 resume 콜백을 같은 함수로 사용하고 싶은 디바이스
드라이버들은 include/linux/pm.h 안에 정의된 UNIVERSAL_DEV_PM_OPS 매크로의
도음으로 이를 담을 수 있습니다(최대한 가장 마지막 인자는 NULL로 셋팅).
8. “No-Callback” 디바이스
어떤 “디바이스들”은 그냥 그들 부모의 논리적인 서브 디바이스이고 그들 자신만의
파워를 관리할 수 없습니다. (전형적인 예제는 USB 인터페이스입니다. 전체 USB
디바이스는 저전력 모드로 들어가거나 웨이크업 요청을 보낼 수 있지만, 각자의
인터페이스들을 위해서는 불가능합니다.) 이들 디바이스들을 위한 드라이버들은
런타임 PM 콜백을 가질 필요 없습니다; 콜백이 있다면, ->runtime_suspend() 와
->runtime_resume()은 언제나 어떤 것도 하지 않고 0을 반환하고, ->runtime_idle()
은 언제나 pm_runtime_suspend()를 호출할 것입니다.
서브 시스템들은 pm_runtime_no_callbacks() 를 호출함으로써 이들 디바이스에 대해서
PM 코어에게 알려주게 됩니다. 이것은 디바이스 구조가 초기화된 후, 그리고 그것이
등록되기 전에 (디바이스 등록이 초기화 된 후라 하더라도 또한 괜찮음) 수행되어야
합니다. 이 루틴은 디바이스의 power.no_callbacks 플래그를 셋팅하고, 디버깅 용도가
아닌 런타임 PM sysfs 속성들의 생성을 막을 것입니다.
power.no_callbacks가 셋팅되었을 때, PM 코어는 ->runtime_idle(), ->runtime_suspend(),
또는 ->runtime_resume() 콜백을 실행하지 않을 것입니다. 대신에, suspend와 resume은
언제나 성공할 것으로 여겨질 것이고 idle 디바이스는 suspend되어져야만 합니다.
그 결과로서, PM 코어는 디바이스의 서브 시스템이나 드라이버에게
런타임 파워 변경에 관해서 절대 직접 알리지 않을 것입니다. 대신에, 그 디바이스의
부모의 드라이버는 그 부모의 파워 상태가 바뀌면 그 디바이스 드라이버에게 알려줄
책임을 가져야만 합니다.
9. Autosuspend, 또는 자동적으로 지연된 suspend
디바이스의 파워 상태를 바꾸는 것은 자유롭지 않습니다; 그것은 시간과 에너지 모두
요구됩니다. 디바이스는 오직 상당한 시간동안 그 상태로 남아있을 것으로 생각되는
어떤 이유가 있을 때에만 저전력 모드로 들어가야 합니다. 공통적인 휴리스틱은
어느 정도 사용되지 않은 디바이스는 사용되지 않을 책임이 있다고 말합니다;
이에 따르면, 드라이버는 어떤 최소한의 시간동안 비활성화 상태가 되기 전까지는
디바이스가 런타임에 suspend하도록 허용해서는 안됩니다. 그 휴리스틱이 최적이
아닌 것으로 밝혀지더라도, 여전히 디바이스가 저전력과 풀파워 상태 사이에 너무
빠르게 “튀는” 것을 막아야 할 것입니다.
“autosuspend” 용어는 역사적으로 남은 부분입니다. 이 것은 디바이스가 자동적으로
suspend되는 것을 의미하지 않습니다(서브 시스템이나 드라이버 역시 여전히 적절한
PM 루틴의 호출을 가집니다); 차라리 런타임 suspend는 원하는 비활동 시간이 지나면
자동적으로 지연됨을 의미합니다.
비활동은 power.last_busy 필드에 기초해서 결정됩니다. 드라이버들은 일반적으로
pm_runtime_put_autosuspend()를 호출하기 전에 I/O 를 수행한 후에, 이 필드를
업데이트 하기 위해서 pm_runtime_mark_last_busy() 를 호출해야만 합니다. 비활동
시간의 원하는 길이는 정책의 문제입니다. 서브 시스템들은 이 길이를
pm_runtime_set_autosuspend_delay() 를 호출함으로써 초기에 설정할 수 있습니다.
그러나 디바이스 등록 후에 그 길이는 유저 공간에 의해
/sys/devices/…/power/autosuspend_delay_ms 속성을 사용해서 제어되어야만 합니다.
autususpend를 사용함에 따라, 서브 시스템과 드라이버는 반드시
pm_runtime_use_autosuspend()를 (더 좋게는 디바이스 등록 전에) 호출해야만 하고,
그 이후부터 다양한 *_autosuspend() 헬퍼 함수들을 autosuspend가 아닌 대응물
대신에 사용해야만 합니다:
이 것 대신: pm_runtime_suspend    사용: pm_runtime_autosuspend;
이 것 대신: pm_schedule_suspend   사용: pm_request_autosuspend;
이 것 대신: pm_runtime_put        사용: pm_runtime_put_autosuspend;
이 것 대신: pm_runtime_put_sync   사용: pm_runtime_put_sync_autosuspend.
드라이버들은 또한 autosuspend가 아닌 헬퍼 함수들의 사용을 계속할 것입니다;
그 것들은 autosuspend delay를 취하지 않고 보통의 경우처럼 수행될 것입니다.
비슷하게 power.use_autosuspend 필드가 셋팅되어 있지 않으면 autosuspend 헬퍼
함수들은 autosuspend가 아닌 대응물들처럼 수행될 것입니다. 

어떤 상황 하에서 드라이버나 서브 시스템은 디바이스가 그 사용 카운터가 0이고
autosuspend delay 시간이 만료되었다 하더라도, 즉시 autosuspend되는 것을 막고
싶어할 것입니다. ->runtime_suspend() 콜백이 -EAGAIN 이나 -EBUSY를 반환하고,
다음 autosuspend delay 만료 시간이 (그 콜백이 pm_runtime_mark_last_busy()를
호출했다면 보통 벌어지는 것처럼) 좀 나중이라면, PM 코어는 자동으로 autosuspend를
리스케줄링할 것입니다. ->runtime_suspend() 콜백은 디바이스가 suspend되어 있는 동안,
(즉, 콜백이 실행 중인 동안) 어떤 종류의 suspend 요청도 받아들이지 않으므로 그 스스로를
리스케줄링할 수 없습니다. 

그 구현은 인터럽트 컨텍스트 안에서 비동기적인 용도를 위해 잘 짜여져 있습니다.
그러나 PM 코어는 ->runtime_suspend() 콜백을 I/O 요청의 도착과 동기화할 수 없으므로,
이러한 사용은 어쩔 수 없이 경쟁상태와 관련됩니다. 동기화는 반드시 드라이버에 의해서
그 안의 개별적인 락킹을 사용하여 처리되어야만 합니다.
뼈대 Pseudo-code 예제:

foo_read_or_write(struct foo_priv *foo, void *data)
{
        lock(&foo->private_lock);
        add_request_to_io_queue(foo, data);
        if (foo->num_pending_requests++ == 0)
                pm_runtime_get(&foo->dev);
        if (!foo->is_suspended)
                foo_process_next_request(foo);
        unlock(&foo->private_lock);
}
foo_io_completion(struct foo_priv *foo, void *req)
{
        lock(&foo->private_lock);
        if (–foo->num_pending_requests == 0) {
                pm_runtime_mark_last_busy(&foo->dev);
                pm_runtime_put_autosuspend(&foo->dev);
        } else {
                foo_process_next_request(foo);
        }
        unlock(&foo->private_lock);
        /* Send req result back to the user … */
}
int foo_runtime_suspend(struct device *dev)
{
       struct foo_priv foo = container_of(dev, …);
       int ret = 0;
       lock(&foo->private_lock);
       if (foo->num_pending_requests > 0) {
               ret = -EBUSY;
       } else {
               /* … suspend the device … */
               foo->is_suspended = 1;
       }
       unlock(&foo->private_lock);
       return ret;
}
int foo_runtime_resume(struct device *dev)
{
       struct foo_priv foo = container_of(dev, …);
       lock(&foo->private_lock);
       /* … resume the device … */
       foo->is_suspended = 0;
       pm_runtime_mark_last_busy(&foo->dev);
       if (foo->num_pending_requests > 0)
               foo_process_requests(foo);
       unlock(&foo->private_lock);
       return 0;
}
중요한 점은 autosuspend를 위해 foo_io_completion() 요청 후에 foo_runtime_suspend()
콜백이 foo_read_or_write()와 경쟁할 것이라는 것입니다. 그러므로 foo_runtime_suspend()
는 suspend 진행이 허용되기 전에 (개별 락을 잡고 있는 동안) 펜딩된 I/O 요청이 있는지
확인해야만 합니다.
덧붙여, power.autosuspend_delay 필드는 유저 공간에 의해서 어느 시점에나 바뀔 수 있습니다.
드라이버가 이 것에 대해 처리하려면 개별 락을 잡고 있는 동안 ->runtime_suspend() 콜백 안에서
pm_runtime_autosuspend_expiration() 를 호출할 수 있습니다. 이 함수는 0이 아닌 값을 반환하면,
delay는 아직 만료되지 않았고, 그 콜백은 -EAGAIN을 반환해야 합니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다