电源管理

本小妞迷上赌 提交于 2019-11-26 16:45:22

极力推荐Android 开发大总结文章:欢迎收藏
程序员Android 力荐 ,Android 开发者需要的必备技能

本篇文章主要介绍 Android 开发中的电源管理部分知识点,本篇文章转载网络,通过阅读本篇文章,您将收获以下内容:

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

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

1. Sleep/Suspend

系统休眠Sleep,Linux Kernel中称作Suspend。系统进入Suspend状态确切来说时CPU进入了Suspend模式,因为对整个系统来说CPU休眠是整个系统休眠的先决条件。CPU Suspend即CPU进入Wait for interrupt状态(WFI),SW完全不跑了,停在suspend workqueue里面。
Android系统从灭屏到系统进入Suspend的大体流程框架如下:
灭屏到系统进入Suspend的大体流程
相关代码如下:

/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”的概念,即逮到机会就睡直到下次被唤醒,以适应移动设备的使用特点。

早期,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,领福利

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!