電源管理

極力推薦Android 開發大總結文章:歡迎收藏
程序員Android 力薦 ,Android 開發者須要的必備技能java

本篇文章主要介紹 Android 開發中的電源管理部分知識點,本篇文章轉載網絡,經過閱讀本篇文章,您將收穫如下內容:node

  1. Sleep/Suspend
  2. SPM
  3. wakeup 喚醒源

原文地址:http://www.robinheztto.com/2017/04/20/android-power-basic/linux

1. Sleep/Suspend

系統休眠Sleep,Linux Kernel中稱做Suspend。系統進入Suspend狀態確切來講時CPU進入了Suspend模式,由於對整個系統來講CPU休眠是整個系統休眠的先決條件。CPU Suspend即CPU進入Wait for interrupt狀態(WFI),SW徹底不跑了,停在suspend workqueue裏面。
Android系統從滅屏到系統進入Suspend的大致流程框架以下:
滅屏到系統進入Suspend的大致流程
相關代碼以下:android

/frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
/frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
/system/core/libsuspend/
/kernel-x.x/kernel/power/

2.SPM

SPM即System Power Manager,管理着包括AP,Modem,Connectivity等子系統。在CPU進入WFI狀態後,整個系統就依靠SPM監控各個子系統的狀態來控制睡眠/喚醒的流程。
SPM控制Cpu Suspend以後系統是否能掉到最小電流,當系統的關鍵資源(memory、clock)沒有任何使用的時候,它就會讓系統進入一個真正的深睡狀態(最小電流),但只要檢測到有任何資源請求還沒釋放,系統就沒法降到底電流。在底電流的debug流程中,不只僅要看CPU有沒有Suspend成功,還要看SPM的狀態是否正確。程序員

MTK平臺:
CPU在進入WFI狀態前會把SPM的firmware寫入到SPM裏的可編程控制器PCM中(Programmable Command Master),而後PCM就依據firmware的邏輯來控制SPM的工做。系統中存在32k,26M二個時鐘,系統工做在最小電流的時候,SPM只依靠32K時鐘工做,所以要判斷系統是否是已經到深休狀態,就要看26M有沒有關閉。編程

26M 時鐘邏輯

如上圖所示,26M時鐘有沒有關,只須要看SCLKENA信號有沒有關閉,而SPM對這個信號的輸出以及子系統的信號輸入,都記錄在SPM的寄存器裏面,這個就是咱們經過log排查的依據。
相關代碼以下:微信

/kernel-x.x/drivers/misc/mediatek/base/power/spm_vx/

3. wakeup 喚醒源

主要涉及如下代碼網絡

kernel/msm-4.4/drivers/base/power/wakeup.c
kernel/msm-4.4/include/linux/pm_wakeup.h
kernel/msm-4.4/include/linux/pm.h
kernel/msm-4.4/include/linux/device.h

Android以Linux kernel爲系統內核,其電源管理也是基於Linux電源管理子系統的,可是因爲傳統的Linux系統主要針對PC而非移動設備,對於PC系統,Linux默認是在用戶再也不使用時纔再觸發系統進入休眠(STR、Standby、Hibernate),可是對於移動設備的使用特色而言,並無明確的再也不使用設備的時候,用戶隨時隨地均可能須要使用設備,因而在Android上就有提出了「Opportunistic suspend」的概念,即逮到機會就睡直到下次被喚醒,以適應移動設備的使用特色。app

早期,Android爲解決該問題,在標準Linux的基礎上增長了Early Suspend和Late Resume機制,Early suspend是在熄屏後,提早將一些不會用到的設備(好比背光、重力感應器和觸摸屏等設備)關閉,但此時系統仍能持有wake lock後臺運行。該方案將Linux原來suspend的流程改變,並增長了Android本身的處理函數,與kernel的流程與機制相沖突,另一個問題就是存在suspend和wakeup events之間的同步問題,好比當系統進入suspend流程中,會進行freeze process,device prepared,device suspend,disabled irq等操做,若是在suspend流程中有wakeup events產生,而此時系統沒法從suspend過程當中喚醒。框架

後來Linux加入wakeup events framework,包括wake lock、wakeup count、autosleep等機制。用來解決system suspend和system wakeup events之間的同步問題。同時在Android4.4中,Android中也去掉了以前的」wakelocks」機制,利用wakeup events framework從新設計了wakelocks,而且維持上層API不變。

system suspend和system wakeup events之間的同步問題:

  • 內核空間的同步:
    wakeup events產生後,一般是以中斷的形式通知device driver。driver會處理events,處理的過程當中,系統不能suspend。
  • 用戶空間的同步:
    通常狀況下,driver對wakeup events處理後,會交給用戶空間程序繼續處理,處理的過程,也不容許suspend。這又能夠分爲兩種狀況:
    進行後續處理的用戶進程,根本沒有機會被調度,即該wakeup events沒法上報到用戶空間。
    進行後續處理的用戶進程被調度,處理的過程當中(以及處理結束後,決定終止suspend操做),系統不能suspend。(wake lock功能)
    wakeup events framework主要解決上述三個同步問題,內核空間的同步(framework的核心功能),用戶空間的同步情形1(wakeup count功能),用戶空間的同步情形2(wake lock功能)。

代碼分析
下面是wakeup events framework的architecture圖(來自蝸窩科技)

wakeup events framework sysfs將設備的wakeup信息,以sysfs的形式提供到用戶空間,供用戶空間訪問(在drivers/base/power/sysfs.c中實現)。

wakeup source

在kernel中,只有設備才能喚醒系統,但並非全部設備都具有喚醒系統的能力,具有喚醒能力的設備即「wakeup source」,會在設備結構體struce device中標誌該設備具備喚醒能力。
kernel/msm-4.4/include/linux/device.h

struct device {  
    ...  
    struct dev_pm_info  power;   
    struct dev_pm_domain    *pm_domain;  
    ...  
}

kernel/msm-4.4/include/linux/pm_wakeup.h

static inline bool device_can_wakeup(struct device *dev)
{
    return dev->power.can_wakeup;
}

static inline bool device_may_wakeup(struct device *dev)
{
    return dev->power.can_wakeup && !!dev->power.wakeup;
}

由device_can_wakeup()函數可知,經過dev->power.can_wakeup來判斷該設備是否能喚醒系統,struct dev_pm_info的定義以下:

kernel/msm-4.4/include/linux/pm.h

.....
    unsigned int        can_wakeup:1;
  ......
#ifdef CONFIG_PM_SLEEP
  ......
    struct wakeup_source    *wakeup;
  ......
#else
    unsigned int        should_wakeup:1;
#endif
  ......
};

struct dev_pm_info中的can_wakeup標識該設備是否具備喚醒能力。具有喚醒能力的設備在sys/devices/xxx/下存在power相關目錄,用於提供全部的wakeup信息,這些信息是以struct wakeup_source的結構組織,即struct dev_pm_info中的wakeup指針。

kernel/msm-4.4/include/linux/pm_wakeup.h

/**
 * struct wakeup_source - Representation of wakeup sources
 *
 * @name: 喚醒源的名字
 * @entry: 用來將喚醒源掛到鏈表上,用於管理
 * @lock: 同步機制,用於訪問鏈表時使用
 * @wakeirq:Optional device specific wakeirq
 * @timer: 定時器,用於設置該喚醒源的超時時間
 * @timer_expires:  定時器的超時時間
 * @total_time:  wakeup source處於active狀態的總時間,可指示該wakeup source對應的設備的繁忙程度、耗電等級
 * @max_time: wakeup source處於active狀態的最長時間(越長越不合理)
 * @last_time: wakeup source處於active狀態的上次時間
 * @prevent_sleep_time: wakeup source阻止系統自動休眠的總時間
 * @event_count:  wakeup source上報wakeup event的個數
 * @active_count: wakeup source處於active狀態的次數
 * @relax_count: wakeup source處於deactive狀態的次數
 * @expire_count: wakeup source timeout次數
 * @wakeup_count: wakeup source abort睡眠的次數
 * @active: wakeup source的狀態
 * @has_timeout: The wakeup source has been activated with a timeout.
 */
struct wakeup_source {
    const char      *name;
    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;
    bool            active:1;
    bool            autosleep_enabled:1;
};

wakeup source表明一個具備喚醒能力的設備,該設備產生的能夠喚醒系統的事件,就稱做wakeup event。當wakeup source產生wakeup event時,須要將wakeup source切換爲activate狀態;當wakeup event處理完畢後,要切換爲deactivate狀態。當wakeup source產生wakeup event時,須要切換到activate狀態,但並非每次都須要切換,所以有可能已經處於activate狀態了。所以active_count可能小於event_count,換句話說,頗有可能在前一個wakeup event沒被處理完時,又產生了一個,這從必定程度上反映了wakeup source所表明的設備的繁忙程度。wakeup source在suspend過程當中產生wakeup event的話,就會終止suspend過程,wakeup_count記錄了wakeup source終止suspend過程的次數(若是發現系統老是suspend失敗,檢查一下各個wakeup source的該變量,就能夠知道問題出在誰身上了)。
爲了方便查看系統的wakeup sources的信息,linux系統在/sys/kernel/debug下建立了一個」wakeup_sources」文件,此文件記錄了系統的喚醒源的詳細信息。

kernel/msm-4.4/drivers/base/power/wakeup.c

static int wakeup_sources_stats_show(struct seq_file *m, void *unused)
{
    struct wakeup_source *ws;

    seq_puts(m, "name\t\tactive_count\tevent_count\twakeup_count\t"
        "expire_count\tactive_since\ttotal_time\tmax_time\t"
        "last_change\tprevent_suspend_time\n");

    rcu_read_lock();
    list_for_each_entry_rcu(ws, &wakeup_sources, entry)
        print_wakeup_source_stats(m, ws);
    rcu_read_unlock();

    print_wakeup_source_stats(m, &deleted_ws);

    return 0;
}

static int wakeup_sources_stats_open(struct inode *inode, struct file *file)
{
    return single_open(file, wakeup_sources_stats_show, NULL);
}

static const struct file_operations wakeup_sources_stats_fops = {
    .owner = THIS_MODULE,
    .open = wakeup_sources_stats_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = single_release,
};

static int __init wakeup_sources_debugfs_init(void)
{
    wakeup_sources_stats_dentry = debugfs_create_file("wakeup_sources",
            S_IRUGO, NULL, NULL, &wakeup_sources_stats_fops);
    return 0;
}

postcore_initcall(wakeup_sources_debugfs_init);

以下圖示,經過cat /sys/kernel/debug/wakeup_sources獲取wakeup_sources信息:


機制
wakeup events framework中抽象了wakeup source和wakeup event的概念,向各個device driver提供wakeup source的註冊、使能等及wakeup event的上報、中止等接口,同時也向上層提供wakeup event的查詢接口,以判斷是否能夠suspend或者是否須要終止正在進行的suspend。

pm_stay_awake()

當設備有wakeup event正在處理時,須要調用該接口通知PM core,該接口的實現以下:

kernel/msm-4.4/drivers/base/power/wakeup.c

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);
}

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);
    del_timer(&ws->timer);
    ws->timer_expires = 0;

    spin_unlock_irqrestore(&ws->lock, flags);
}

static void wakeup_source_report_event(struct wakeup_source *ws)
{
    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);
}

pm_stay_awake中直接調用pm_stay_awake,\pm_stay_awake直接調用wakeup_source_report_event。
kernel/msm-4.4/drivers/base/power/wakeup.c

static void wakeup_source_report_event(struct wakeup_source *ws)
{
    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);
}

wakeup_source_report_event中增長wakeup source的event_count次數,即表示該source又產生了一個event。而後根據events_check_enabled變量的狀態,增長wakeup_count。若是wakeup source沒有active,則調用wakeup_source_activate進行activate操做。

kernel/msm-4.4/drivers/base/power/wakeup.c

static void wakeup_source_activate(struct wakeup_source *ws)
{
    unsigned int cec;

    /*
     * active wakeup source should bring the system
     * out of PM_SUSPEND_FREEZE state
     */
    freeze_wake();

    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);
}

wakeup_source_activate中首先調用freeze_wake,將系統從suspend to freeze狀態下喚醒,而後設置active標誌,增長active_count,更新last_time。若是使能了autosleep,更新start_prevent_time,此刻該wakeup source會開始阻止系統auto sleep。增長「wakeup events in progress」計數,增長該計數意味着系統正在處理的wakeup event數目不爲零,即系統不能suspend。

pm_relax()
pm_relaxpm_stay_awake成對出現,用於在wakeup event處理結束後通知PM core,其實現以下

kernel/msm-4.4/drivers/base/power/wakeup.c

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);
}

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);
}

pm_relax中直接調用pm_relax,\pm_relax判斷wakeup source若是處於active狀態,則調用wakeup_source_deactivate接口,deactivate該wakeup source

kernel/msm-4.4/drivers/base/power/wakeup.c

static void wakeup_source_deactivate(struct wakeup_source *ws)
{
    unsigned int cnt, inpr, cec;
    ktime_t duration;
    ktime_t now;

    ws->relax_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
     * couter 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);
}

至此,本篇已結束,若有不對的地方,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!

微信關注公衆號: 程序員Android,領福利

相關文章
相關標籤/搜索