linux runtime pm機制的深入理解
一:runtime機制說明
何為runtime機制?也就是系統在非睡眠狀態,裝置在空閒時可以進入runtime suspend狀態同時不依賴系統wake_lock機制,非空閒時執行runtime resume使得裝置進入正常工作狀態。
主要程式碼放在Runtime.c (drivers\base\power)中,同時附帶的Runtime_pm.txt (documentation\power)有詳細
說明。要使得裝置可以進入runtime_idle與runtime_suspend必須滿足device的2個引數usage_count與child_count同時為0。
1:操作
2:操作child_count通常由子裝置來完成父裝置child_count的增加與減少。child_count可以理解該裝置活躍的子裝置的個數。
通常由子裝置睡後來讓父裝置進入休眠,依次遞迴進行。
二:下面重點分析一下rpm_suspend與rpm_resume
static int rpm_suspend(struct device *dev, int rpmflags)
__releases(&dev->power.lock) __acquires(&dev->power.lock)
{
int (*callback)(struct device *);
struct device *parent = NULL;
struct rpm_qos_data qos;
int retval;
trace_rpm_suspend(dev, rpmflags);
repeat:
retval = rpm_check_suspend_allowed(dev); //在這裡檢查usage_count與child_count,當非0存在時將使得該函式直接return。
if (retval < 0)
; /* Conditions are wrong. */
/* Synchronous suspends are not allowed in the RPM_RESUMING state. */
else if (dev->power.runtime_status == RPM_RESUMING &&
!(rpmflags & RPM_ASYNC))
retval = -EAGAIN;
if (retval)
goto out;
----------
/* Carry out an asynchronous or a synchronous suspend. */
if (rpmflags & RPM_ASYNC) { //非同步情況,提交一個工作佇列後退出
dev->power.request = (rpmflags & RPM_AUTO) ?
RPM_REQ_AUTOSUSPEND : RPM_REQ_SUSPEND;
if (!dev->power.request_pending) {
dev->power.request_pending = true;
queue_work(pm_wq, &dev->power.work);
}
goto out;
}
--------------
/*
下面是選擇回撥函式,如果裝置是掛在platform下面的,將執行pm_generic_runtime_suspend()後執行device runtime suspend callback
這裡的callback也就是我們在驅動裡面註冊的idle,suspend,resume callback;
static const struct dev_pm_ops platform_dev_pm_ops = {
.runtime_suspend = pm_generic_runtime_suspend,
.runtime_resume = pm_generic_runtime_resume,
.runtime_idle = pm_generic_runtime_idle,
USE_PLATFORM_PM_SLEEP_OPS
};
int pm_generic_runtime_suspend(struct device *dev)
{
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
int ret;
ret = pm && pm->runtime_suspend ? pm->runtime_suspend(dev) : 0;
return ret;
}
*/
if (dev->pm_domain)
callback = dev->pm_domain->ops.runtime_suspend;
else if (dev->type && dev->type->pm)
callback = dev->type->pm->runtime_suspend;
else if (dev->class && dev->class->pm)
callback = dev->class->pm->runtime_suspend;
else if (dev->bus && dev->bus->pm)
callback = dev->bus->pm->runtime_suspend;
else
callback = NULL;
if (!callback && dev->driver && dev->driver->pm)
callback = dev->driver->pm->runtime_suspend;
retval = rpm_callback(callback, dev);//執行回撥函式。
if (retval)
goto fail;
no_callback:
__update_runtime_status(dev, RPM_SUSPENDED);
pm_runtime_deactivate_timer(dev);
if (dev->parent) {
parent = dev->parent;
atomic_add_unless(&parent->power.child_count, -1, 0);
}//如果該裝置父裝置存在,由於上面該裝置已經執行到runtime suspend callback了,那麼這時將父裝置的子裝置減一。
----------
/* Maybe the parent is now able to suspend. */
if (parent && !parent->power.ignore_children && !dev->power.irq_safe) {
spin_unlock(&dev->power.lock);
spin_lock(&parent->power.lock);
rpm_idle(parent, RPM_ASYNC);
spin_unlock(&parent->power.lock);
spin_lock(&dev->power.lock);
}//嘗試讓父裝置進入idle。
-------------
}
static int rpm_resume(struct device *dev, int rpmflags)
__releases(&dev->power.lock) __acquires(&dev->power.lock)
{
int (*callback)(struct device *);
struct device *parent = NULL;
int retval = 0;
trace_rpm_resume(dev, rpmflags);
repeat:
if (dev->power.runtime_error)
retval = -EINVAL;
else if (dev->power.disable_depth > 0)//進行條件判斷,disable_depth為禁止runtime,在pm_runtime_enable()時會置為0;
retval = -EACCES;
if (retval)
goto out;
--------
if (dev->power.runtime_status == RPM_ACTIVE) {//當前狀態為active時直接推出;
retval = 1;
goto out;
}
---------
/* Carry out an asynchronous or a synchronous resume. */
if (rpmflags & RPM_ASYNC) {
dev->power.request = RPM_REQ_RESUME;
if (!dev->power.request_pending) {
dev->power.request_pending = true;
queue_work(pm_wq, &dev->power.work);//非同步情況,提交一個工作佇列後退出
}
retval = 0;
goto out;
}
if (!parent && dev->parent) {
/*
* Increment the parent's usage counter and resume it if
* necessary. Not needed if dev is irq-safe; then the
* parent is permanently resumed.
*/
parent = dev->parent;
if (dev->power.irq_safe)
goto skip_parent;
spin_unlock(&dev->power.lock);
pm_runtime_get_noresume(parent);//在該裝置resume前要先resume父裝置,There Increment the parent's usage counter;
spin_lock(&parent->power.lock);
/*
* We can resume if the parent's runtime PM is disabled or it
* is set to ignore children.
*/
if (!parent->power.disable_depth
&& !parent->power.ignore_children) {
rpm_resume(parent, 0); //resume父裝置
if (parent->power.runtime_status != RPM_ACTIVE)
retval = -EBUSY;
}
spin_unlock(&parent->power.lock);
spin_lock(&dev->power.lock);
if (retval)
goto out;
goto repeat;
}
skip_parent:
if (dev->power.no_callbacks)
goto no_callback; /* Assume success. */
dev->power.suspend_time = ktime_set(0, 0);
dev->power.max_time_suspended_ns = -1;
__update_runtime_status(dev, RPM_RESUMING);
/*選擇同時執行裝置resume callback*/
if (dev->pm_domain)
callback = dev->pm_domain->ops.runtime_resume;
else if (dev->type && dev->type->pm)
callback = dev->type->pm->runtime_resume;
else if (dev->class && dev->class->pm)
callback = dev->class->pm->runtime_resume;
else if (dev->bus && dev->bus->pm)
callback = dev->bus->pm->runtime_resume;
else
callback = NULL;
if (!callback && dev->driver && dev->driver->pm)
callback = dev->driver->pm->runtime_resume;
retval = rpm_callback(callback, dev);
if (retval) {
__update_runtime_status(dev, RPM_SUSPENDED);
pm_runtime_cancel_pending(dev);
} else {
no_callback:
__update_runtime_status(dev, RPM_ACTIVE);
if (parent)
atomic_inc(&parent->power.child_count);//該裝置喚醒後增加父裝置的子裝置活躍數 child_count+1;
}
wake_up_all(&dev->power.wait_queue);
if (!retval)
rpm_idle(dev, RPM_ASYNC);
out:
if (parent && !dev->power.irq_safe) {
spin_unlock_irq(&dev->power.lock);
pm_runtime_put(parent); //減去剛才增加的the parent's usage counter;
spin_lock_irq(&dev->power.lock);
}
trace_rpm_return_int(dev, _THIS_IP_, retval);
return retval;
}
RPM機制總體來說就是管理匯流排結構和主次裝置結構的電源,因為體系結構的因素,device套device的情況,最後就會形成一個device大樹。runtime這套機制就可以從根到樹頂都能管理得到。
通過上面分析我們知道當休眠時,先由子裝置睡眠再讓父裝置休眠,遞迴進行,由下而上休眠。當喚醒時,是由上而下,遞迴喚醒。
三:例項分析
1:以msm_sdcc.c為例分析使用runtime輔助函式來實現device runtime功能:
在sdio資料讀寫完成後會呼叫到msmsdcc_disable(),之前會呼叫到msmsdcc_enable()。
以此來實現runtime功能。
static int msmsdcc_disable(struct mmc_host *mmc)
{
---------
if (host->plat->disable_runtime_pm)
return -ENOTSUPP;
rc = pm_runtime_put_sync(mmc->parent); //使用runtime輔助函式,先將usage_count減一,再讓裝置進入idle
---------
}
static inline int pm_runtime_put_sync(struct device *dev)
{
return __pm_runtime_idle(dev, RPM_GET_PUT);
}
int pm_generic_runtime_idle(struct device *dev)
{
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
if (pm && pm->runtime_idle) {
int ret = pm->runtime_idle(dev);//呼叫裝置idle的callback
if (ret)
return ret;
}
pm_runtime_suspend(dev);//發起runtime suspend動作
return 0;
}
static int msmsdcc_enable(struct mmc_host *mmc)
{
--------
rc = pm_runtime_get_sync(dev);//使用runtime輔助函式,先將usage_count加一,再讓裝置進入resume
--------
}
static inline int pm_runtime_get_sync(struct device *dev)
{
return __pm_runtime_resume(dev, RPM_GET_PUT);
}
2:分析一段關於usb runtime的log:
<4>[ 22.281829] rpm_suspend name=usb1 usage_count=0 child_count=0 //device name為usb1 父裝置名為msm_hsic_host,進入rpm_suspend
<4>[ 22.281860] rpm_suspend name=usb1 parent_name=msm_hsic_host p_usage_count=0 p_child_count=1 //在這裡usb還沒有進入suspend,故打印出的父裝置的活躍子裝置為1;
<4>[ 22.281890] rpm_suspend dev parent's child_count -1 //msm_hsic_host 的child_count -1;
<4>[ 22.281890] rpm_suspend name=usb1 usage_count=0 child_count=0
<4>[ 22.281921] rpm_suspend name=usb1 parent_name=msm_hsic_host p_usage_count=0 p_child_count=0 //msm_hsic_host 的child_count為0;
<4>[ 22.281951] rpm_suspend try dev parent going to sleep //讓父裝置進入休眠
<4>[ 22.281951] rpm_suspend name=usb1 usage_count=0 child_count=0
<4>[ 22.281982] rpm_suspend name=usb1 parent_name=msm_hsic_host p_usage_count=0 p_child_count=0
<4>[ 22.282135] rpm_suspend name=msm_hsic_host usage_count=0 child_count=0 //msm_hsic_host進入rpm_suspend,由上面發起
<4>[ 22.282165] rpm_suspend name=msm_hsic_host parent_name=platform p_usage_count=0 p_child_count=6 //msm_hsic_host的父裝置還有6個活躍的子裝置
<4>[ 22.282165] rpm_suspend dev parent's child_count -1 //msm_hsic_host已經休眠,讓父裝置的活躍的子裝置減一;
<4>[ 22.282196] rpm_suspend name=msm_hsic_host usage_count=0 child_count=0
<4>[ 22.282226] rpm_suspend name=msm_hsic_host parent_name=platform p_usage_count=0 p_child_count=5
<4>[ 22.282226] rpm_suspend try dev parent going to sleep //試圖讓platform進入休眠
<4>[ 22.282257] rpm_suspend name=msm_hsic_host usage_count=0 child_count=0
<4>[ 22.282257] rpm_suspend name=msm_hsic_host parent_name=platform p_usage_count=0 p_child_count=5
<4>[ 22.635589] rpm_resume name=msm_hsic_host usage_count=1 child_count=0 //裝置名為msm_hsic_host的進入rpm_resume,usage_count=1是由發起者增加的。
<4>[ 22.635620] rpm_resume name=msm_hsic_host parent_name=platform p_usage_count=0 p_child_count=5
<4>[ 22.635650] rpm_resume add parent usage count
<4>[ 22.635650] rpm_resume name=msm_hsic_host usage_count=1 child_count=0
<4>[ 22.635650] rpm_resume name=msm_hsic_host parent_name=platform p_usage_count=1 p_child_count=5
<4>[ 22.635772] rpm_resume name=msm_hsic_host usage_count=0 child_count=0
<4>[ 22.635772] rpm_resume name=msm_hsic_host parent_name=platform p_usage_count=1 p_child_count=5
<4>[ 22.635803] rpm_resume add parent child_count +1
<4>[ 22.635803] rpm_resume name=msm_hsic_host usage_count=0 child_count=0
<4>[ 22.635833] rpm_resume name=msm_hsic_host parent_name=platform p_usage_count=1 p_child_count=6
<4>[ 22.635833] rpm_resume over name=msm_hsic_host usage_count=0 child_count=0
<4>[ 22.635864] rpm_resume over name=msm_hsic_host parent_name=platform p_usage_count=0 p_child_count=6
<4>[ 22.635864] rpm_resume name=usb1 usage_count=1 child_count=0 //裝置為usb1進入rpm_resume
<4>[ 22.635894] rpm_resume name=usb1 parent_name=msm_hsic_host p_usage_count=0 p_child_count=0
<4>[ 22.635894] rpm_resume add parent usage count
<4>[ 22.635894] rpm_resume name=usb1 usage_count=1 child_count=0
<4>[ 22.635925] rpm_resume name=usb1 parent_name=msm_hsic_host p_usage_count=1 p_child_count=0
<4>[ 22.671600] rpm_resume name=usb1 usage_count=1 child_count=0
<4>[ 22.671630] rpm_resume name=usb1 parent_name=msm_hsic_host p_usage_count=1 p_child_count=0
<4>[ 22.671630] rpm_resume add parent child_count +1
<4>[ 22.671661] rpm_resume name=usb1 usage_count=1 child_count=0
<4>[ 22.671661] rpm_resume name=usb1 parent_name=msm_hsic_host p_usage_count=1 p_child_count=1
<4>[ 22.671691] rpm_resume over name=usb1 usage_count=1 child_count=0
<4>[ 22.671691] rpm_resume over name=usb1 parent_name=msm_hsic_host p_usage_count=0 p_child_count=1
上面log充分說明了runtime_suspend是由下到上的,而runtime_resume是由上到下的過程。
深入理解runtime pm機制,將會為除錯裝置不能進入runtime suspend打下堅實基礎。
通常AP通過USB來外掛第三方MODEM,肯定會遇見這種問題。由於usb通訊速率能達到480Mbps,而sdio 100Mbps hs uart 4M,很明顯usb通訊很有優勢,尤其在4G越來越普及的情況下。現在就是越來越來的AP+MODEM方案通過usb來通訊的。