程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> Power Management

Power Management

編輯:關於C
Power Management
迫切的想知道power management 的流程。
對整個流程稍微整理了下


1、autosleep 流程
上層操作底層的/sys/power/autosleep 這個接口進行操作底層。在main .c 裡面會創建一個autosleep 接口,對應main.c 裡面的store 函數。
所對應的source code 在:/kernel/kernel/main.c 裡面

static ssize_t autosleep_store(struct kobject *kobj,
                   struct kobj_attribute *attr,
                   const char *buf, size_t n)
{
    suspend_state_t state = decode_state(buf, n);
    int error;


    if (state == PM_SUSPEND_ON
        && strcmp(buf, off) && strcmp(buf, off
))
        return -EINVAL;

    error = pm_autosleep_set_state(state);
    old_state=state;
    return error ? error : n;
}



當底層接受到上層傳遞到的值進行一些列的操作,有很多的state 狀態:
#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 1)
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
當底層接受到上層的state 之後,主要是調用pm_autosleep_set_state 這個函數進行power management 的管理。
它的實現如下:
int pm_autosleep_set_state(suspend_state_t state)
{

#ifndef CONFIG_HIBERNATION
    if (state >= PM_SUSPEND_MAX)
        return -EINVAL;
#endif

    __pm_stay_awake(autosleep_ws);

    mutex_lock(&autosleep_lock);

    autosleep_state = state;

    __pm_relax(autosleep_ws);

    if (state > PM_SUSPEND_ON) {
        pm_wakep_autosleep_enabled(true);

        //Add a timer to trigger wakelock debug
        pr_info([PM]unattended_timer: mod_timer (auto_sleep)
);
        mod_timer(&unattended_timer, jiffies + msecs_to_jiffies(PM_UNATTENDED_TIMEOUT));

        queue_up_suspend_work();
    } else {
        pm_wakep_autosleep_enabled(false);
        //Add a timer to trigger wakelock debug
        pr_info([PM]unattended_timer: del_timer (late_resume)
);
        del_timer(&unattended_timer);
    }

    mutex_unlock(&autosleep_lock);
    return 0;
}


調用的流程:
1、 pm_wakep_autosleep_enabled 設置所注冊的wake source 的auto sleep 全部設置成1,ws->autosleep_enabled = set;
2、調用queue_up_suspend_work(); 讓設備進入suspend 狀態。
static DECLARE_WORK(suspend_work, try_to_suspend);
調用try_to_suspend 函數。

pm_wakep_autosleep_enabled 這個函數還是很重要的,我們在下面的wake_lock 章節會詳細講解。


在底層裡面的用autosleep_state 這個全部變量去保存目前的suspend 的狀態。

if (autosleep_state >= PM_SUSPEND_MAX)
hibernate(); //進入冬眠模式
else
pm_suspend(autosleep_state); 進入suspend 的模式
我們主要看下pm_suspend 函數的實現:

int pm_suspend(suspend_state_t state)
{
    int error;

    if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
        return -EINVAL;
    pm_suspend_marker(entry);
    error = enter_state(state);
    if (error) {
        suspend_stats.fail++;
        dpm_save_failed_errno(error);
    } else {
        suspend_stats.success++;
    }
    pm_suspend_marker(exit);
    return error;
}
EXPORT_SYMBOL(pm_suspend);


然後調用enter_state(state);函數進入所要進入的狀態。

主要調用下面兩個函數:
error = suspend_prepare
分配console ,發送PM_SUSPEND_PREPARE這個notify 事件。最後還有的是調用suspend_freeze_processes 凍結所有的進程。
error = suspend_devices_and_enter(state);
函數實現如下:

int suspend_devices_and_enter(suspend_state_t state)
{
    int error;
    bool wakeup = false;

    if (!suspend_ops)
        return -ENOSYS;

    trace_machine_suspend(state);
    if (suspend_ops->begin) {//判斷suspend_ops 裡面有沒有begin 函數,關於suspend_ops 的賦值,我們會單獨有一個標題講解
        error = suspend_ops->begin(state);
        if (error)
            goto Close;
    }

    //Add a timer to trigger wakelock debug
    pr_info([PM]unattended_timer: del_timer
);
    del_timer ( &unattended_timer );

    suspend_console();//console 進行冬眠
    suspend_test_start();
    error = dpm_suspend_start(PMSG_SUSPEND);
    if (error) {
        printk(KERN_ERR PM: Some devices failed to suspend
);
        goto Recover_platform;
    }
    suspend_test_finish(suspend devices);
    if (suspend_test(TEST_DEVICES))
        goto Recover_platform;

    do {
        error = suspend_enter(state, &wakeup);
    } while (!error && !wakeup
        && suspend_ops->suspend_again && suspend_ops->suspend_again());

 Resume_devices:
    suspend_test_start();
    dpm_resume_end(PMSG_RESUME);
    suspend_test_finish(resume devices);
    resume_console();

    //Add a timer to trigger wakelock debug
    pr_info([PM]unattended_timer: mod_timer
);
    mod_timer(&unattended_timer, jiffies + msecs_to_jiffies(PM_UNATTENDED_TIMEOUT));

 Close:
    if (suspend_ops->end)
        suspend_ops->end();
    trace_machine_suspend(PWR_EVENT_EXIT);
    return error;

 Recover_platform:
    if (suspend_ops->recover)
        suspend_ops->recover();
    goto Resume_devices;
}



suspend_console(); //凍結終端
suspend_test_start//打出suspend 開始的時間

其中dpm_suspend_start 試下如下:
int dpm_suspend_start(pm_message_t state)
{
int error;

error = dpm_prepare(state); 為設備准備鏈表
if (error) {
suspend_stats.failed_prepare++;
dpm_save_failed_step(SUSPEND_PREPARE);
} else
error = dpm_suspend(state);將准備的設備進入suspend
return error;
}
EXPORT_SYMBOL_GPL(dpm_suspend_start);
在dpm_suspendl裡面,我們會將while (!list_empty(&dpm_list)) {
struct device *dev = to_device(dpm_list.next); dpm_list 裡面所有的設備都遍歷出來,
最終哦將遍歷出來的設備添加到另外一個鏈表裡面去:list_move_tail(&dev->power.entry, &dpm_prepared_list.至於dpm_list 是如何來的,我們在另外一個章節進行講解。

接下來dpm_suspend(state); 進行suspend 設備。
dpm_suspend 的函數實現如下:

int dpm_suspend(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error = 0;

might_sleep();

mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
while (!list_empty(&dpm_prepared_list)) {
struct device *dev = to_device(dpm_prepared_list.prev);

get_device(dev);
mutex_unlock(&dpm_list_mtx);

error = device_suspend(dev);

mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, , error);
dpm_save_failed_dev(dev_name(dev));
put_device(dev);
break;
}
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &dpm_suspended_list);
put_device(dev);
if (async_error)
break;
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
if (!error)
error = async_error;
if (error) {
suspend_stats.failed_suspend++;
dpm_save_failed_step(SUSPEND_SUSPEND);
} else
dpm_show_time(starttime, state, NULL);
return error;
}
然後調用device_suspend 函數
-----》__device_suspend ------》if (dev->pm_domain) {
info = power domain ;
callback = pm_op(&dev->pm_domain->ops, state);
goto Run;
}

if (dev->type && dev->type->pm) {
info = type ;
callback = pm_op(dev->type->pm, state);
goto Run;
}

if (dev->class) {
if (dev->class->pm) {
info = class ;
callback = pm_op(dev->class->pm, state);
goto Run;
} else if (dev->class->suspend) {
pm_dev_dbg(dev, state, legacy class );
error = legacy_suspend(dev, state, dev->class->suspend);
goto End;
}
}

if (dev->bus) {
if (dev->bus->pm) {
info = bus ;
callback = pm_op(dev->bus->pm, state);
} else if (dev->bus->suspend) {
pm_dev_dbg(dev, state, legacy bus );
error = legacy_suspend(dev, state, dev->bus->suspend);
goto End;
}
}

調用pm_op函數調用設備、class、 bus 等的suspend函數,同樣的resume 函數也是一樣的流程。
dpm_suspend_start 函數執行完了之後。我們再次回到suspend_devices_and_enter 函數中來。



do {
error = suspend_enter(state, &wakeup);
} while (!error && !wakeup
&& suspend_ops->suspend_again && suspend_ops->suspend_again());

這裡是一個死循環。只有wakeup 為1 的時候,我們才會調出這個循環。
我們設備的suspend的時候有很多的操作,每一個操作都放在不同的鏈表裡面,然後我們根據不同suspend的類型,進行不同的操作。
舉例如下:
error = dpm_suspend_start(PMSG_SUSPEND);

error = dpm_prepare(state); 會調用到to_device(dpm_list.next); 這個鏈表 ,這個鏈表是我們在創建device 的時候,我們所有創建設備的power 控制的結點。遍歷過dpm_list 的鏈表後,我們將設備放到dpm_prepared_list 鏈表裡面,再進行dpm_prepared_list 這個鏈表的操作。下面的幾種狀態也是同樣的效果,只是不同的操作罷了。

2、設備的注冊
我們在注冊一個設備的時候,通常會調用device_register 進行設備的注冊。下面設備的流程,我們只care power management 部分,別的細節暫時不care 。

整個設備注冊的流程:

return device_add(dev); ----》
device_pm_add(dev);-------》

void device_pm_add(struct device *dev)
{
    pr_debug(PM: Adding info for %s:%s
,
         dev->bus ? dev->bus->name : No Bus, dev_name(dev));
    mutex_lock(&dpm_list_mtx);
    if (dev->parent && dev->parent->power.is_prepared)
        dev_warn(dev, parent %s should not be sleeping
,
            dev_name(dev->parent));
    list_add_tail(&dev->power.entry, &dpm_list);
    dev_pm_qos_constraints_init(dev);
    mutex_unlock(&dpm_list_mtx);
}


上面的注冊會將設備添加到dpm_list裡面。 list_add_tail(&dev->power.entry, &dpm_list);
3、suspend_ops 的操作

在整個power management 裡面經常會調用到suspend_ops 這個操作集合裡面提供的interface 進行
suspend_ops 是存放在kerel/kernel/suspend.c 裡面的全部變量。 suspend_set_ops 這個函數會為suspend_set_ops 這個變量進行賦值。
void suspend_set_ops(const struct platform_suspend_ops *ops)
{
lock_system_sleep();
suspend_ops = ops;
unlock_system_sleep();
}

suspend_set_ops 是在/kernel/arch/arm/mach-msm/pm-8x60.c 裡面。

static int __init msm_pm_init(void)
{
enum msm_pm_time_stats_id enable_stats[] = {
MSM_PM_STAT_IDLE_WFI,
MSM_PM_STAT_RETENTION,
MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE,
MSM_PM_STAT_IDLE_POWER_COLLAPSE,
MSM_PM_STAT_SUSPEND,
};
msm_pm_mode_sysfs_add();
msm_pm_add_stats(enable_stats, ARRAY_SIZE(enable_stats));
suspend_set_ops(&msm_pm_ops);


msm_pm_ops 的實現如下:
static const struct platform_suspend_ops msm_pm_ops = {
.enter = msm_pm_enter,
.valid = suspend_valid_only_mem,
.prepare_late = msm_suspend_prepare,
.wake = msm_suspend_wake,
};


看到沒有,這個裡面實現ops 裡面的enter、valid 、prepare_late 、wake 函數。
這些函數在suspend 的時候都會調用到,如:
static int suspend_prepare(void)
{
int error;

if (!suspend_ops || !suspend_ops->enter)
return -EPERM;


這裡就在調用enter 函數。

4.設備resume 的流程

當我們按下power key 之後,我們的設備會進入suspend ,整個流程如上面講解suspend 的流程一樣。
source code :


do {
error = suspend_enter(state, &wakeup);
} while (!error && !wakeup
&& suspend_ops->suspend_again && suspend_ops->suspend_again());

suspend_enter 函數的實現:

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
    int error;

    if (suspend_ops->prepare) {
        error = suspend_ops->prepare();
        if (error)
            goto Platform_finish;
    }

    error = dpm_suspend_end(PMSG_SUSPEND);
    if (error) {
        printk(KERN_ERR PM: Some devices failed to power down
);
        goto Platform_finish;
    }

    if (suspend_ops->prepare_late) {
        error = suspend_ops->prepare_late();
        if (error)
            goto Platform_wake;
    }

    if (suspend_test(TEST_PLATFORM))
        goto Platform_wake;

    error = disable_nonboot_cpus();
    if (error || suspend_test(TEST_CPUS))
        goto Enable_cpus;

    arch_suspend_disable_irqs();
    BUG_ON(!irqs_disabled());

    error = syscore_suspend();
    if (!error) {
        *wakeup = pm_wakeup_pending();
        if (!(suspend_test(TEST_CORE) || *wakeup)) {
            error = suspend_ops->enter(state);
            events_check_enabled = false;
        }
        syscore_resume();
    }

    arch_suspend_enable_irqs();
    BUG_ON(irqs_disabled());

 Enable_cpus:
    enable_nonboot_cpus();

 Platform_wake:
    if (suspend_ops->wake)
        suspend_ops->wake();

    dpm_resume_start(PMSG_RESUME);

 Platform_finish:
    if (suspend_ops->finish)
        suspend_ops->finish();

    return error;
}




當我們進入suspend 的時候,我們最後會調用到suspend_ops->enter 函數裡面,當執行到這個函數的時候,我們進入了要求的suspend 狀態,只有當喚醒的時候才會返回。關於suspend_ops->enter 函數的實現,我們在講解suspend_ops的時候裡面有賦值(msm_pm_enter)。

其實我目前還沒有明白為何進入msm_pm_enter 函數為何就停止在哪裡?
設置CPU 的寄存器,讓整個CPU 進入睡眠,進入睡眠之後,執行的code 就停止執行了, 就停止在那裡了,當我們按下power button 的時候,CPU 又起來來,code 又繼續執行了,所以就走下面的resume 流程。
power button 是硬件設計好了的wake source 源,硬件設計好了的。



當返回的時候,我們就會調用resume設備的 函數。

Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish(resume devices);
resume_console();

其實resume 的過程就和suspend 的流程差不多,也是從設備的鏈表裡面遍歷設備,最後調用設備的resume 函數。
因為下面執行的是goto 語句,所以依次就會執行:


syscore_resume();
}

arch_suspend_enable_irqs(); //打開中斷
BUG_ON(irqs_disabled());

Enable_cpus:
enable_nonboot_cpus();//啟動非啟動CPU


Platform_wake:
if (suspend_ops->wake)
suspend_ops->wake();

dpm_resume_start(PMSG_RESUME);//Execute noirq and early device callbacks.

Platform_finish:
if (suspend_ops->finish)
就是整個resume 設備的流程。

5.wake_lock機制
基本原理如下:當啟動一個應用程序的時候,它都可以申請一個wake_lock喚醒鎖,每當申請成功之後都會在內核中注冊一下(通知系統內核,現在已經有鎖被申請),當應用程序在某種情況下釋放wake_lock的時候,會注銷之前所申請的wake_lock。特別要注意的是:只要是系統中有一個wake_lock的時候,系統此時都不能進行睡眠。
下面將講解上層獲得wake_lock 的整個過程。
frameworks/base/services/java/com/android/server/power/PowerManagerService.java
        public void acquire() {
            synchronized (this) {
                mReferenceCount += 1;
                if (mReferenceCount == 1) {
                    if (DEBUG_SPEW) {
                        Slog.d(TAG, Acquiring suspend blocker  + mName + .);
                    }
                    nativeAcquireSuspendBlocker(mName);
                }
            }
        }


會調用nativeAcquireSuspendBlocker 這個 函數的實現是放在jni 裡面的:
frameworks/base/services/jni/com_android_server_power_PowerManagerService.cpp

static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) {
ScopedUtfChars name(env, nameStr);
acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str());
}

最後調用acquire_wake_lock 進行申請wake_lock
enum {
PARTIAL_WAKE_LOCK = 1, // the cpu stays on, but the screen is off
FULL_WAKE_LOCK = 2 // the screen is also on
};
通過注釋也可以看出這兩種wake_lock 的區別。
我們繼續trace code :
acquire_wake_lock的實現:
hardware/libhardware_legacy/power/power.c

int
acquire_wake_lock(int lock, const char* id)
{
initialize_fds();//獲得我們設備結點的設備描述符號,並將我們獲得的設備描述符號放到g_fds 這個數組裡面。或者 /sys/power/wake_lock,/sys/power/wake_unlock,這兩個設備結點的設備描述符號。

// ALOGI(acquire_wake_lock lock=%d id='%s' , lock, id);

if (g_error) return g_error;

int fd;

if (lock == PARTIAL_WAKE_LOCK) {
fd = g_fds[ACQUIRE_PARTIAL_WAKE_LOCK];//獲得wake_lock 的設備節點的設備描述符
}
else {
return EINVAL;
}

return write(fd, id, strlen(id));//我們獲得對應的設備描述符後,我們直接操作描述符。

}
write(fd, id, strlen(id) 會調用到wake_lock 裡面的store 函數。在下面的小章節會講解到wakelock.c 這個文件。
5.1.wake_lock_store
當我們使用到wake_lock 設備節點的時候,最終調用到wake_lock_store 函數。
static ssize_t wake_lock_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int error = pm_wake_lock(buf);
return error ? error : n;
}
pm_wake_lock函數的實現如下:

int pm_wake_lock(const char *buf)
{
    const char *str = buf;
    struct wakelock *wl;
    u64 timeout_ns = 0;
    size_t len;
    int ret = 0;

    while (*str && !isspace(*str))
        str++;

    len = str - buf;
    if (!len)
        return -EINVAL;

    if (*str && *str != '
') {
        /* Find out if there's a valid timeout string appended. */
        ret = kstrtou64(skip_spaces(str), 10, &timeout_ns);
        if (ret)
            return -EINVAL;
    }

    mutex_lock(&wakelocks_lock);

    wl = wakelock_lookup_add(buf, len, true);
    if (IS_ERR(wl)) {
        ret = PTR_ERR(wl);
        goto out;
    }
    if (timeout_ns) {
        u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;

        do_div(timeout_ms, NSEC_PER_MSEC);
        __pm_wakeup_event(&wl->ws, timeout_ms);
    } else {
        __pm_stay_awake(&wl->ws);
    }

    wakelocks_lru_most_recent(wl);

 out:
    mutex_unlock(&wakelocks_lock);
    return ret;
}


上面的函數主要講解wakelock_lookup_add
會調用到void wakeup_source_add(struct wakeup_source *ws)函數將我們申請的wake_lock 添加到
list_add_rcu(&ws->entry, &wakeup_sources); wakeup_sources 鏈表中去,我們在suspend 的時候會去遍歷wakeup_sources 這個鏈表裡面的wake source 的狀態。
並根據 timeout_ns 的時間進行調用不同的函數,最終都是將對應的wake_source 設置為true. ws->active = true;

if (timeout_ns) { u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;

do_div(timeout_ms, NSEC_PER_MSEC);
__pm_wakeup_event(&wl->ws, timeout_ms);
} else {
__pm_stay_awake(&wl->ws);
}

現在我們再回到上面講解的pm_wakep_autosleep_enabled 這個函數裡面:
void pm_wakep_autosleep_enabled(bool set)
{
    struct wakeup_source *ws;
    ktime_t now = ktime_get();

    rcu_read_lock();
    list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
        spin_lock_irq(&ws->lock);
        if (ws->autosleep_enabled != set) {
            ws->autosleep_enabled = set;
            if (ws->active) {
                if (set)
                    ws->start_prevent_time = now;
                else
                    update_prevent_sleep_time(ws, now);
            }
        }
        spin_unlock_irq(&ws->lock);
    }
    rcu_read_unlock();
}
#endif /* CONFIG_PM_AUTOSLEEP *

/


上面在進入suspend 的時候會判斷ws->active ,如果為true 就不會進入suspend .


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved