#星光计划2.0# OpenHarmony 源码解析之电源管理亮灭屏功能 原创 精华
作者:王奎
【本文正在参与51CTO HarmonyOS技术社区创作者激励计划-星光计划2.0】
1 简介
电源管理子系统是OpenHarmony的基本能力子系统,有电池服务组件、显示控制组件和电源管理服务组件,主要提供如下功能:
- 重启系统。
- 管理休眠运行锁。
- 系统电源状态查询。
- 充电和电池状态查询和上报。
- 亮灭屏管理和亮度调节。
本文重点分析亮灭屏功能,包括NAPI接口
、PowerMgr
和Kernel
层的实现。
1.1 电源管理相关
1.2 OpenHarmony架构图
1.3 电源管理子系统架构图
2 知识准备
2.1 电源状态
- On (on) S0-Working
- Standby (standby) S1- CPU and RAM are powed but not executed
- Suspend to Ram (mem) S2- RAM is powered and the running content is saved to RAM
- Suspend to Disk (disk) S3 - All content is saved to Disk and power down
S0状态也就是计算机正常工作状态。
S1状态简称standby状态,此状态下CPU处于低功耗状态,并且没有数据保存到RAM或者disk中,此状态待机和恢复通常很快。
S2状态简称STR,此状态下计算机会冻结所有的活动并将当前工作状态保存到RAM中,然后关闭屏幕进入低功耗模式,通常睡眠和唤醒需要几秒。
S3状态简称SRD或者Hibernate,代表冬眠,意识是比较长久,一般在window系统中常见到。此状态下计算机将所有活动的状态保存到磁盘中,然后处于关机状态,此模式下是不耗电的,而相比之前的模式,休眠和唤醒的速度都比较慢。
注:S0->S3功耗由大到小,唤醒速度由快到慢。
模式 | 描述 |
---|---|
freeze | 冻结I/O设备,将它们置于低功耗状态,使处理器进入空闲状态,唤醒最快,耗电比其它standby, mem, disk方式高 |
standby | 除了冻结I/O设备外,还会暂停系统,唤醒较快,耗电比其它 mem, disk方式高 |
mem | 将运行状态数据存到内存,并关闭外设,进入等待模式,唤醒较慢,耗电比disk方式高 |
disk | 将运行状态数据存到硬盘,然后关机,唤醒最慢. 对于嵌入式系统,由于没有硬盘,所以一般不支持 |
查看系统支持的睡眠方式:
//ubuntu系统:
#cat /sys/power/state
freeze standby mem disk
//rk3566和自研板
# cat /sys/power/state
freeze mem
切换为睡眠模式:
#echo mem > /sys/power/state
2.2 wakeup count
wakeup count的存在,是为了解决Sleep和Wakeup之间的同步问题。
wakeup_count是内核用来保存当前wakeup event发生的计数,用户空间程序在写入state切换状态之前,应先读取wakeup_count并把获得的count写回给wakeup_count,内核会比对写回的count和当前的count是否一致,用户空间程序检测到写入正确后,可以继续对state的写入,以便发起一次状态切换。
wakeup count的系统文件:/sys/power/wakeup_count。
2.3 wakelocks
- 一个
sysfs
文件:/sys/power/wake_lock
,用户程序向文件写入一个字符串,即可创建一个wakelock
,该字符串就是wakelock
的名字。该wakelock
可以阻止系统进入低功耗模式。 - 一个
sysf
s文件:/sys/power/wake_unlock
,用户程序向文件写入相同的字符串,即可注销一个wakelock
。 - 当系统中所有的
wakelock
都注销后,系统可以自动进入低功耗状态。 - 向内核其它
driver
也提供了wakelock
的创建和注销接口,允许driver
创建wakelock
以阻止睡眠、注销wakelock
以允许睡眠。
3 整体流程代码
3.1 NAPI接口
在原来power
模块增加休眠和唤醒接口
static napi_value PowerInit(napi_env env, napi_value exports)
{
POWER_HILOGD(MODULE_JS_NAPI, "%{public}s: enter", __func__);
napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("shutdownDevice", ShutdownDevice),
DECLARE_NAPI_FUNCTION("rebootDevice", RebootDevice),
DECLARE_NAPI_FUNCTION("isScreenOn", IsScreenOn),
DECLARE_NAPI_FUNCTION("wakeupDevice", WakeupDevice), //新增的唤醒接口
DECLARE_NAPI_FUNCTION("suspendDevice", SuspendDevice), //新增的休眠接口
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
POWER_HILOGD(MODULE_JS_NAPI, "%{public}s: exit", __func__);
return exports;
}
//唤醒接口实现,应用在调用此接口前先调用power模块已提供的IsScreenOn接口
static napi_value WakeupDevice(napi_env env, napi_callback_info info)
{
POWER_HILOGD(MODULE_JS_NAPI, "%{public}s: enter, %{public}s", __func__, "wake up device");
size_t argc = 1;
napi_value args[1] = { 0 };
napi_value jsthis;
void *data = nullptr;
napi_status status = napi_get_cb_info(env, info, &argc, args, &jsthis, &data);
NAPI_ASSERT(env, (status == napi_ok) && (argc >= 1), "failed to get cb info");
napi_valuetype type = napi_undefined;
NAPI_CALL(env, napi_typeof(env, args[0], &type));
NAPI_ASSERT(env, type == napi_string, "wrong argument type. string expected.");
char reason[REASON_MAX] = { 0 };
size_t reasonLen = 0;
status = napi_get_value_string_utf8(env, args[0], reason, REASON_MAX - 1, &reasonLen);
if (status != napi_ok) {
POWER_HILOGE(MODULE_JS_NAPI, "%{public}s: get reason failed", __func__);
return nullptr;
}
g_powerMgrClient.WakeupDevice(WakeupDeviceType::WAKEUP_DEVICE_APPLICATION,std::string(reason));
POWER_HILOGD(MODULE_JS_NAPI, "%{public}s: reason %{public}s, exit", __func__, reason);
return nullptr;
}
//休眠接口,应用在调用此接口前先调用power模块已提供的IsScreenOn接口
static napi_value SuspendDevice(napi_env env, napi_callback_info info)
{
POWER_HILOGD(MODULE_JS_NAPI, "%{public}s: enter, %{public}s", __func__, "suspend device");
size_t argc = 1;
napi_value args[1] = { 0 };
napi_value jsthis;
void *data = nullptr;
napi_status status = napi_get_cb_info(env, info, &argc, args, &jsthis, &data);
NAPI_ASSERT(env, (status == napi_ok) && (argc >= 1), "failed to get cb info");
napi_valuetype type = napi_undefined;
NAPI_CALL(env, napi_typeof(env, args[0], &type));
NAPI_ASSERT(env, type == napi_string, "wrong argument type. string expected.");
char reason[REASON_MAX] = { 0 };
size_t reasonLen = 0;
status = napi_get_value_string_utf8(env, args[0], reason, REASON_MAX - 1, &reasonLen);
if (status != napi_ok) {
POWER_HILOGE(MODULE_JS_NAPI, "%{public}s: get reason failed", __func__);
return nullptr;
}
g_powerMgrClient.SuspendDevice(SuspendDeviceType::SUSPEND_DEVICE_REASON_APPLICATION,true);
POWER_HILOGD(MODULE_JS_NAPI, "%{public}s: reason %{public}s, exit", __func__, reason);
return nullptr;
}
3.2 PowerMgr接口
接口处理的后续流程比较通用,powermgrclient->powermgrserviceproxy->powermgrservicestub->powermgrservice
。
powermgr
内部流转到suspend
和wakeup
:
base\powermgr\power_manager\services\native\src\actions\default\device_state_action.cpp:
//系统挂起时先灭屏再休眠
void DeviceStateAction::Suspend(int64_t callTimeMs, SuspendDeviceType type, uint32_t flags)
{
DisplayManager::SetScreenState(ScreenState::SCREEN_STATE_OFF);//调用displaymgr的屏幕关闭
SystemSuspendController::GetInstance().EnableSuspend();//系统休眠
}
//唤醒系统时先唤醒再亮屏
void DeviceStateAction::Wakeup(int64_t callTimeMs, WakeupDeviceType type, const string& details,
const string& pkgName)
{
SystemSuspendController::GetInstance().DisableSuspend();//系统唤醒
DisplayManager::SetScreenState(ScreenState::SCREEN_STATE_ON);//调用displaymgr的屏幕打开
}
上面的suspend/wakeup
函数分两支,其中一支是调用DisplayManager
的屏幕打开、关闭功能实现不完整,Display
驱动部分引用的动态库,代码部分和实际日志不符。另一支是系统休眠、唤醒处理。
3.3 系统休眠&唤醒
先看系统休眠、唤醒处理,目前自研板支持两种模式freeze mem
一级待机和二级待机,而powermgr
是使用的mem
模式。
//base\powermgr\power_manager\services\native\src\actions\default\system_suspend_controller.cpp
//目前系统目录没有下面这两个文件影响休眠和唤醒功能:
1)/sys/power/wake_lock //该wakelock可以阻止系统进入低功耗模式。
2)/sys/power/wake_unlock //写入相同的字符串,即可注销一个wakelock
//挂起,系统进入低功耗模式
void SystemSuspendController::EnableSuspend()
{
std::lock_guard lock(mutex_);
sc_->EnableSuspend();//睡眠的具体操作。
if (!suspendEnabled_) {
rlh_->Release(WAKEUP_HOLDER); //wake_unlock写入"OHOSPowerMgr.WakeupHolder";允许睡眠
suspendEnabled_ = true;
}
}
//唤醒,即阻止系统进入低功耗模式。
void SystemSuspendController::DisableSuspend()
{
std::lock_guard lock(mutex_);
if (suspendEnabled_) {
rlh_->Acquire(WAKEUP_HOLDER); //wake_lock写入"OHOSPowerMgr.WakeupHolder";阻止睡眠
suspendEnabled_ = false;
}
}
//base\powermgr\power_manager\services\native\src\actions\default\suspend\suspend_controller.cpp:
static constexpr const char * const SUSPEND_STATE = "mem";
static constexpr const char * const SUSPEND_STATE_PATH = "/sys/power/state";
static constexpr const char * const WAKEUP_COUNT_PATH = "/sys/power/wakeup_count";
void SuspendController::EnableSuspend()
{
suspend_->Start();//启动AutoSuspendLoop线程
POWER_HILOGI(MODULE_SERVICE, "AutoSuspend enabled");
}
void SuspendController::AutoSuspend::AutoSuspendLoop()
{
while (true) {
std::this_thread::sleep_for(waitTime_);
const std::string wakeupCount = WaitWakeupCount(); //在rk3566和自研板上阻塞在读取/sys/power/wakeup_count文件,应该是系统不支持
if (wakeupCount.empty()) {
continue;
}
waitingFunc_();//阻塞函数,只有等到休眠条件才会继续执行。
if (!WriteWakeupCount(wakeupCount)) {//写入wakeupcount
continue;
}
bool success = SuspendEnter();//写入mem
if (!success) {
POWER_HILOGE(MODULE_SERVICE, "Start suspend failed!");
}
}
}
//读取wakeupcount
std::string SuspendController::AutoSuspend::WaitWakeupCount()
{
if (wakeupCountFd < 0) {
wakeupCountFd = UniqueFd(TEMP_FAILURE_RETRY(open(WAKEUP_COUNT_PATH, O_RDWR | O_CLOEXEC)));
}
std::string wakeupCount;
bool ret = LoadStringFromFd(wakeupCountFd, wakeupCount);
if (!ret) {
POWER_HILOGW(MODULE_SERVICE, "Read wakeup count failed!");
return std::string();
}
return wakeupCount;
}
//写入wakeupcount
bool SuspendController::AutoSuspend::WriteWakeupCount(std::string wakeupCount)
{
if (wakeupCountFd < 0) {
return false;
}
bool ret = SaveStringToFd(wakeupCountFd, wakeupCount.c_str());
if (!ret) {
POWER_HILOGE(MODULE_SERVICE, "Failed to write the wakeup count!");
}
return ret;
}
//通过向/sys/power/state写入mem实现挂起功能。
bool SuspendController::AutoSuspend::SuspendEnter()
{
static bool inited = false;
static UniqueFd suspendStateFd(TEMP_FAILURE_RETRY(open(SUSPEND_STATE_PATH, O_RDWR | O_CLOEXEC)));
if (!inited) {
if (suspendStateFd < 0) {
POWER_HILOGE(MODULE_SERVICE, "Failed to open the suspending state fd!");
return false;
}
inited = true;
}
bool ret = SaveStringToFd(suspendStateFd, SUSPEND_STATE);
if (!ret) {
POWER_HILOGE(MODULE_SERVICE, "Failed to write the suspending state!");
}
return ret;
}
3.4 屏幕开关
再看调用DisplayManager
的屏幕打开、关闭功能实现,有一部分是私有实现,只提供了动态库,最终会调用hdf_disp
驱动接口进行屏幕的打开、关闭操作。
//base\powermgr\display_manager\service\native\src\screen_action.cpp:
bool ScreenAction::SetPowerState(ScreenState state __attribute__((__unused__)))
{
int32_t dispErr_ = DISPLAY_SUCCESS;
if (!hdiFuncs_) {
DISPLAY_HILOGE(MODULE_SERVICE, "Invalid device functions");
return false;
}
if (ScreenState::SCREEN_STATE_ON == state)
{
dispErr_ = hdiFuncs_->SetDisplayPowerStatus(0,DispPowerStatus::POWER_STATUS_ON);
}
else
{
dispErr_ = hdiFuncs_->SetDisplayPowerStatus(0,DispPowerStatus::POWER_STATUS_OFF);
}
DISPLAY_HILOGE(MODULE_SERVICE, "SetDisplayPowerStatus:%{public}u result:%{public}d",ToUnderlying(state),dispErr_);
return dispErr_ == DISPLAY_SUCCESS;
}
//drivers\peripheral\display\interfaces\include\display_device.h:
SetDisplayPowerStatus:接口说明:
/**
* @brief Sets the power status.
*
* When the OS enters the sleep mode or wakes up from the sleep mode, the display service or
* the power management module can set the power status of the display device, so that the driver IC
* of the device can normally enter the specified state.
*
* @param devId Indicates the ID of a display device. The value ranges from 0 to 4, where 0 indicates
* the first display device and 4 indicates the last display device.
* @param status Indicates the power status to set. The display service determines whether to set the
* display device to the on or off state based on this setting. For details, see @link PowerStatus}.
*
* @return Returns 0 if the operation is successful; returns an error code defined in {@link DispErrCode} otherwise.
* @since 1.0
* @version 1.0
*/
int32_t (*SetDisplayPowerStatus)(uint32_t devId, DispPowerStatus status);
下面是调用hdf_disp
驱动部分,依然属于私有实现,这部分可以看到执行日志,但是代码还不完整:
//drivers\peripheral\display\hal\disp_hal.c:
static int32_t DispCmdSend(const uint32_t cmd, struct HdfSBuf *reqData, struct HdfSBuf *respData)
{
struct HdfIoService *dispService = NULL;
HDF_LOGE("%s:add by wangkui", __func__);
dispService = HdfIoServiceBind(DISP_SERVICE_NAME);//此处是找hdf_disp驱动服务。
if ((dispService == NULL) || (dispService->dispatcher == NULL) || (dispService->dispatcher->Dispatch == NULL)) {
HDF_LOGE("%s:bad remote service found modified by wangkui", __func__);
goto EXIT;
}
int32_t ret = dispService->dispatcher->Dispatch(&dispService->object, cmd, reqData, respData);
if (ret != DISPLAY_SUCCESS) {
HDF_LOGE("%s: cmd=%u, ret=%d", __func__, cmd, ret);
goto EXIT;
}
HDF_LOGI("%s: cmd=%u, ret=%d", __func__, cmd, ret);
HdfIoServiceRecycle(dispService);
return DISPLAY_SUCCESS;
EXIT:
HdfIoServiceRecycle(dispService);
return DISPLAY_FAILURE;
}
//drivers\framework\core\shared\src\hdf_io_service.c:
struct HdfIoService *HdfIoServiceBind(const char *serviceName)
{
return HdfIoServiceAdapterObtain(serviceName);
}
//drivers\framework\core\adapter\syscall\src\hdf_syscall_adapter.c:
struct HdfIoService *HdfIoServiceAdapterObtain(const char *serviceName)
{
struct HdfSyscallAdapter *adapter = NULL;
struct HdfIoService *ioService = NULL;
char *devNodePath = NULL;
char *realPath = NULL;
const char *devPath = DEV_NODE_PATH;
if (access(DEV_NODE_PATH, F_OK) != 0) {
devPath = DEV_PATH;
}
devNodePath = OsalMemCalloc(PATH_MAX);
realPath = OsalMemCalloc(PATH_MAX);
if (devNodePath == NULL || realPath == NULL) {
HDF_LOGE("%s: out of memory", __func__);
goto out;
}
if (sprintf_s(devNodePath, PATH_MAX - 1, "%s%s", devPath, serviceName) < 0) {
HDF_LOGE("Failed to get the node path");
goto out;
}
if (realpath(devNodePath, realPath) == NULL) {//此处发现找不到hdf_disp文件
HDF_LOGE("%s: file name %{public}s is invalid", __func__, devNodePath);
if (HdfLoadDriverByServiceName(serviceName) != HDF_SUCCESS) {//通过hdf_disp服务名查找
HDF_LOGE("%s: load %{public}s driver failed,", __func__, serviceName);
goto out;
}
if (realpath(devNodePath, realPath) == NULL) {
HDF_LOGE("%s: file name %{public}s is invalid", __func__, devNodePath);
goto out;
}
}
adapter = (struct HdfSyscallAdapter *)OsalMemCalloc(sizeof(struct HdfSyscallAdapter));
if (adapter == NULL) {
HDF_LOGE("Failed to allocate SyscallAdapter");
goto out;
}
DListHeadInit(&adapter->listenerList);
if (OsalMutexInit(&adapter->mutex)) {
HDF_LOGE("%s: Failed to create mutex", __func__);
OsalMemFree(adapter);
goto out;
}
adapter->fd = open(realPath, O_RDWR);
if (adapter->fd < 0) {
HDF_LOGE("Open file node %{public}s failed, (%d)%{public}s", realPath, errno, strerror(errno));
OsalMutexDestroy(&adapter->mutex);
OsalMemFree(adapter);
goto out;
}
ioService = &adapter->super;
static struct HdfIoDispatcher dispatch = {
.Dispatch = HdfSyscallAdapterDispatch,
};
ioService->dispatcher = &dispatch;
out:
OsalMemFree(devNodePath);
OsalMemFree(realPath);
return ioService;
}
int32_t HdfLoadDriverByServiceName(const char *serviceName)
{
int32_t ret = HDF_FAILURE;
struct HdfSBuf *data = NULL;
if (serviceName == NULL || strcmp(serviceName, DEV_MGR_NODE) == 0) {
HDF_LOGE("failed to get %s service,call self!!!", serviceName);//如果是dev_mgr则返回,防止死循环。
return ret;
}
struct HdfIoService *ioService = HdfIoServiceBind(DEV_MGR_NODE);//这里找dev_mgr,根据日志看也没找到,所以没发现hdf_disp服务。
if (ioService == NULL) {
HDF_LOGE("failed to get %s service", DEV_MGR_NODE);
return ret;
}
data = HdfSBufObtainDefaultSize();
if (data == NULL) {
HDF_LOGE("failed to obtain sbuf data");
ret = HDF_DEV_ERR_NO_MEMORY;
goto out;
}
if (!HdfSbufWriteString(data, serviceName)) {
HDF_LOGE("failed to write sbuf");
ret = HDF_FAILURE;
goto out;
}
ret = ioService->dispatcher->Dispatch(&ioService->object, DEVMGR_LOAD_SERVICE, data, NULL);
if (ret != HDF_SUCCESS) {
HDF_LOGE("failed to load khdf driver %s", serviceName);
}
out:
HdfIoServiceRecycle(ioService);
HdfSBufRecycle(data);
return ret;
}
HDF_DISP
驱动主要功能是:
1)接受和管理显示面板panel
的接口注册;
2)接收HDI
层发来的显示相关操作,然后调用显示面板驱动的接口进行panel
的开、关、亮度调节,以及状态查看的操作。
其主要目的是屏蔽各类显示面板驱动的具体实现,向上提供统一的显示接口。
4.系统休眠&唤醒的Kernel层实现
4.1 wakelocks
4.1.1 Kernel节点文件创建
/sys/power/wake_lock & wake_unlock
的设备节点文件的创建在kernel\linux-4.19\kernel\power\main.c
文件中实现。
power目录下的文件都是按照此方法注册,且根据版本构建的宏定义开关可以有选择性的创建。
#define power_attr(_name) \
static struct kobj_attribute _name##_attr = { \
.attr = { \
.name = __stringify(_name), \
.mode = 0644, \
}, \
.show = _name##_show, \
.store = _name##_store, \
}
power_attr(wake_lock);
power_attr(wake_unlock);
//根据宏定义开关注册sleep、autosleep、suspend、wakelock等。
static struct attribute * g[] = {
&state_attr.attr,
#ifdef CONFIG_PM_TRACE
&pm_trace_attr.attr,
&pm_trace_dev_match_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP
&pm_async_attr.attr,
&wakeup_count_attr.attr,
#ifdef CONFIG_SUSPEND
&mem_sleep_attr.attr,
#endif
#ifdef CONFIG_PM_AUTOSLEEP
&autosleep_attr.attr,
#endif
#ifdef CONFIG_PM_WAKELOCKS //宏定义必须打开才会创建
&wake_lock_attr.attr,
&wake_unlock_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP_DEBUG
&pm_test_attr.attr,
&pm_print_times_attr.attr,
&pm_wakeup_irq_attr.attr,
&pm_debug_messages_attr.attr,
#endif
#endif
#ifdef CONFIG_FREEZER
&pm_freeze_timeout_attr.attr,
#endif
NULL,
};
static const struct attribute_group attr_group = {
.attrs = g,
};
static const struct attribute_group *attr_groups[] = {
&attr_group,
#ifdef CONFIG_PM_SLEEP
&suspend_attr_group,
#endif
NULL,
};
static int __init pm_init(void)
{
int error = pm_start_workqueue();
if (error)
return error;
hibernate_image_size_init();
hibernate_reserved_size_init();
pm_states_init();
power_kobj = kobject_create_and_add("power", NULL);//先创建power目录
if (!power_kobj)
return -ENOMEM;
error = sysfs_create_groups(power_kobj, attr_groups);//创建power目录下的其它设备节点。
if (error)
return error;
pm_print_times_init();
return pm_autosleep_init();
}
core_initcall(pm_init);
4.1.2 创建一个wakelock
入口在kernel/power/main.c
中实现:
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;
}
上面直接调用kernel\linux-4.19\kernel\power\wakelock.c中的pm_wake_lock:
//用于上报一个wakeup event,输入参数为一个字符串,如:"wakelocktest 1000"
int pm_wake_lock(const char *buf)
{
const char *str = buf;
struct wakelock *wl;
u64 timeout_ns = 0;
size_t len;
int ret = 0;
//检查调用的进程是否有执行阻止系统休眠的权限,PowerMgr打包在foundation进程,所以需要修改foundation进程的权限,增加BLOCK_SUSPEND
//./foundation/appexecfwk/standard/sa_profile/foundation.rc: capabilities SYS_PTRACE KILL SYS_BOOT
//./foundation/appexecfwk/standard/sa_profile/foundation.cfg: "caps" : ["SYS_PTRACE", "KILL", "SYS_BOOT"]
if (!capable(CAP_BLOCK_SUSPEND))
return -EPERM;
//解析字符串,取出wakelock的名字和超时时间,超时时间可以不带。
while (*str && !isspace(*str))
str++;
len = str - buf;
if (!len)
return -EINVAL;
if (*str && *str != '\n') {
/* 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);
//向wakelock维护的红黑树tree中添加新的wakelock,且注册wakeup source,name就是入参的buf。
wl = wakelock_lookup_add(buf, len, true);
if (IS_ERR(wl)) {
ret = PTR_ERR(wl);
goto out;
}
//1)__pm_stay_awake,通知PM core,注册的wakeup source产生了wakeup event,且正在处理,因此不允许系统suspend;
//2)__pm_relax,通知PM core,注册的wakeup source没有正在处理的wakeup event,允许系统suspend;
//3)__pm_wakeup_event,为上边两个接口的功能组合,通知PM core,注册的wakeup source产生了wakeup event,会在timeout_ms毫秒内处理结束,由PM core自动调用__pm_relax。
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);//wakelocks有GC机制,通过LRU策略进行淘汰长时间不活动的wakelock
out:
mutex_unlock(&wakelocks_lock);
return ret;
}
4.1.3 注销一个wakelock
入口在kernel/power/main.c
中实现:
static ssize_t wake_unlock_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int error = pm_wake_unlock(buf);
return error ? error : n;
}
上面直接调用kernel\linux-4.19\kernel\power\wakelock.c中的pm_wake_unlock:
//入参为字符串,跟创建wakelock一样的名字。
int pm_wake_unlock(const char *buf)
{
struct wakelock *wl;
size_t len;
int ret = 0;
if (!capable(CAP_BLOCK_SUSPEND))//检查权限
return -EPERM;
//解析字符串,获取wakelock名字
len = strlen(buf);
if (!len)
return -EINVAL;
if (buf[len-1] == '\n')
len--;
if (!len)
return -EINVAL;
mutex_lock(&wakelocks_lock);
//查找是否有相同name的wakelock。如果有,直接返回wakelock的指针;如果没有,退出。
wl = wakelock_lookup_add(buf, len, false);
if (IS_ERR(wl)) {
ret = PTR_ERR(wl);
goto out;
}
__pm_relax(wl->ws);//通知PM core,注册的wakeup source没有正在处理的wakeup event,允许系统suspend;
//wakelocks有GC机制,通过LRU策略进行淘汰长时间不活动的wakelock
wakelocks_lru_most_recent(wl);
wakelocks_gc();//执行GC回收。
out:
mutex_unlock(&wakelocks_lock);
return ret;
}
还有wakelock的查看接口,本文不列出了。
4.2 wakeup_count
4.2.1 Kernel节点文件创建
参考wakelocks。
4.2.2 读取wakeup count
从/sys/power/wakeup_count
读取wakeup_count
,当有正在处理的event
事件此处可能会阻塞。
/**
*pm_get_wakeup_count-读取已注册的wakeup事件数。
*@count:用于存储值的地址。
*@block:是否阻塞。
*将注册的wakeup事件数存储在@count中。如果@block为true,阻止直到当前的wakeup事件数已处理的值为零。
*如果当前正在处理的wakeup事件数为非零范围“false”。否则返回“true”。
*/
bool pm_get_wakeup_count(unsigned int *count, bool block)
{
unsigned int cnt, inpr;
pr_debug("pm_get_wakeup_count block: %d\n", block);
if (block) {
DEFINE_WAIT(wait);
for (;;) {
prepare_to_wait(&wakeup_count_wait_queue, &wait,
TASK_INTERRUPTIBLE);
split_counters(&cnt, &inpr);
if (inpr == 0 || signal_pending(current))
break;
pm_print_active_wakeup_sources();//debug日志打印出active的事件,以此判断阻塞在何处。
schedule();
}
finish_wait(&wakeup_count_wait_queue, &wait);
}
split_counters(&cnt, &inpr);
*count = cnt;
return !inpr;
}
static atomic_t combined_event_count = ATOMIC_INIT(0);
#define IN_PROGRESS_BITS (sizeof(int) * 4) //16
#define MAX_IN_PROGRESS ((1 << IN_PROGRESS_BITS) - 1) //0xFFFF
static void split_counters(unsigned int *cnt, unsigned int *inpr)
{
unsigned int comb = atomic_read(&combined_event_count);
//高16位存储已处理完的wakeup个数,低16位存储正在处理(即Active)的wakeup个数
//秒!正在处理的wakeup event和已处理的wakeup event在一个变量中维护。
*cnt = (comb >> IN_PROGRESS_BITS);
*inpr = comb & MAX_IN_PROGRESS;
}
4.2.3 写入wakeup count
上面读取的wakeup_count
写入到/sys/power/wakeup_count
,如果count
不同,则说明有新的event
事件产生,或者有正在处理的event
事件,即inpr
不为零,则说明此时不适宜执行suspend
。
/**
*pm_save_wakeup_count-保存当前已注册的wakeup事件数。
*@count:与当前已注册wakeup事件数进行比较的值。
*如果@count等于当前注册的wakeup事件数,且当前正在处理的wakeup事件数为零,并将当前的wakeup count保存在saved count变量中,启用wakeup事件检测。
*否则禁用wakeup事件检测并返回“false”。
*/
bool pm_save_wakeup_count(unsigned int count)
{
unsigned int cnt, inpr;
unsigned long flags;
events_check_enabled = false;
raw_spin_lock_irqsave(&events_lock, flags);
split_counters(&cnt, &inpr);
if (cnt == count && inpr == 0) {
saved_count = count;
events_check_enabled = true;
}
raw_spin_unlock_irqrestore(&events_lock, flags);
return events_check_enabled;
}
4.3 suspend
向/sys/power/state
写入mem
后就进入Kernel
层的suspend
流程,入口还是kernel\linux-4.19\kernel\power\main.c
:
//切换休眠状态
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
suspend_state_t state;
int error;
error = pm_autosleep_lock();
if (error)
return error;
if (pm_autosleep_state() > PM_SUSPEND_ON) {
error = -EBUSY;
goto out;
}
state = decode_state(buf, n);//将“mem”转为对应的数字
if (state < PM_SUSPEND_MAX) {
if (state == PM_SUSPEND_MEM)
state = mem_sleep_current;
error = pm_suspend(state);//核心的kernel休眠流程
} else if (state == PM_SUSPEND_MAX) {
error = hibernate();
} else {
error = -EINVAL;
}
out:
pm_autosleep_unlock();
return error ? error : n;
}
suspend
实现非常复杂,本文只列出关键的流程:
//kernel\linux-4.19\kernel\power\suspend.c
int pm_suspend(suspend_state_t state)
{
error = enter_state(state);
}
static int enter_state(suspend_state_t state)
{
trace_suspend_resume(TPS("suspend_enter"), state, true);
if (state == PM_SUSPEND_TO_IDLE) {
} else if (!valid_state(state)) {//检查平台是否支持电源管理,即全局suspend_ops有没有被赋值,并调用其suspend_ops->valid()
return -EINVAL;
}
error = suspend_prepare(state);//准备挂起
error = suspend_devices_and_enter(state);//让设备进入suspend状态
suspend_finish();
}
//kernel\linux-4.19\kernel\power\suspend.c
static int suspend_prepare(suspend_state_t state)
{
//kernel\linux-4.19\kernel\power\console.c
//切换挂起、恢复执行内核触发的VT开关,切换到SUSPEND_CONSOLE
pm_prepare_console();
//kernel/power/main.c
//通知驱动程序准备suspend
error = __pm_notifier_call_chain(PM_SUSPEND_PREPARE, -1, &nr_calls);
//kernel\linux-4.19\kernel\power\power.h
//冻结App和内核线程
error = suspend_freeze_processes();
}
//kernel\linux-4.19\kernel\power\suspend.c
////挂起设备并进入系统睡眠状态
int suspend_devices_and_enter(suspend_state_t state)
{
//如果平台相关的代码有begin函数就去调用它,suspend_ops->begin(state)
error = platform_suspend_begin(state);
//kernel\linux-4.19\kernel\printk\printk.c
//挂起console子系统,此时不能用printk()调试。
suspend_console();
suspend_test_start();
error = dpm_suspend_start(PMSG_SUSPEND);
suspend_test_finish("suspend devices");
do {
//关闭核心模块,如cpu等,并设置唤醒源,如果电源键按下则会进入唤醒流程。
error = suspend_enter(state, &wakeup);
} while (!error && !wakeup && platform_suspend_again(state));
Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
resume_console();
}
//kernel\linux-4.19\kernel\power\suspend.c
static void suspend_finish(void)
{
//唤醒应用程序
suspend_thaw_processes();
//通知关注这个事件的App程序,对全局pm_chain_head->head中的每一个都调用其notifier_call()
pm_notifier_call_chain(PM_POST_SUSPEND);
//返回用户空间
pm_restore_console();
}
总结
本文主要和大家分享了OpenHarmony电源管理子系统中关于亮灭屏的实现细节,包括NAPI接口、PowerMgr的流程以及Kernel实现等,做了较为详细的代码说明,希望通过本文您能初步掌握电源管理子系统的关键功能和核心流程。关于OpenHarmony其它子系统的分析,请关注后续文章。
更多原创内容请关注:开鸿 HarmonyOS 学院
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
感谢开鸿的老师们不断贡献优质内容
感谢支持