作者:王奎
【本文正在参与51CTO HarmonyOS技术社区创作者激励计划-星光计划2.0】
1 简介
电源管理子系统是OpenHarmony的基本能力子系统,有电池服务组件、显示控制组件和电源管理服务组件,主要提供如下功能:
- 重启系统。
- 管理休眠运行锁。
- 系统电源状态查询。
- 充电和电池状态查询和上报。
- 亮灭屏管理和亮度调节。
本文重点分析亮灭屏功能,包括NAPI接口
、PowerMgr
和Kernel
层的实现。
1.1 电源管理相关
《OpenHarmony 源码解析之电源管理子系统 》
《OpenHarmony 源码解析之电源管理亮灭屏功能》
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 |
将运行状态数据存到硬盘,然后关机,唤醒最慢. 对于嵌入式系统,由于没有硬盘,所以一般不支持 |
查看系统支持的睡眠方式:
切换为睡眠模式:
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;
}
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;
}
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;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
3.2 PowerMgr接口
接口处理的后续流程比较通用,powermgrclient->powermgrserviceproxy->powermgrservicestub->powermgrservice
。
powermgr
内部流转到suspend
和wakeup
:
上面的suspend/wakeup
函数分两支,其中一支是调用DisplayManager
的屏幕打开、关闭功能实现不完整,Display
驱动部分引用的动态库,代码部分和实际日志不符。另一支是系统休眠、唤醒处理。
3.3 系统休眠&唤醒
先看系统休眠、唤醒处理,目前自研板支持两种模式freeze mem
一级待机和二级待机,而powermgr
是使用的mem
模式。
3.4 屏幕开关
再看调用DisplayManager
的屏幕打开、关闭功能实现,有一部分是私有实现,只提供了动态库,最终会调用hdf_disp
驱动接口进行屏幕的打开、关闭操作。
下面是调用hdf_disp
驱动部分,依然属于私有实现,这部分可以看到执行日志,但是代码还不完整:
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);
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;
}
struct HdfIoService *HdfIoServiceBind(const char *serviceName)
{
return HdfIoServiceAdapterObtain(serviceName);
}
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_LOGE("%s: file name %{public}s is invalid", __func__, devNodePath);
if (HdfLoadDriverByServiceName(serviceName) != HDF_SUCCESS) {
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);
return ret;
}
struct HdfIoService *ioService = HdfIoServiceBind(DEV_MGR_NODE);
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;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
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目录下的文件都是按照此方法注册,且根据版本构建的宏定义开关可以有选择性的创建。
4.1.2 创建一个wakelock
入口在kernel/power/main.c
中实现:
上面直接调用kernel\linux-4.19\kernel\power\wakelock.c中的pm_wake_lock:
4.1.3 注销一个wakelock
入口在kernel/power/main.c
中实现:
上面直接调用kernel\linux-4.19\kernel\power\wakelock.c中的pm_wake_unlock:
还有wakelock的查看接口,本文不列出了。
4.2 wakeup_count
4.2.1 Kernel节点文件创建
参考wakelocks。
4.2.2 读取wakeup count
从/sys/power/wakeup_count
读取wakeup_count
,当有正在处理的event
事件此处可能会阻塞。
4.2.3 写入wakeup count
上面读取的wakeup_count
写入到/sys/power/wakeup_count
,如果count
不同,则说明有新的event
事件产生,或者有正在处理的event
事件,即inpr
不为零,则说明此时不适宜执行suspend
。
4.3 suspend
向/sys/power/state
写入mem
后就进入Kernel
层的suspend
流程,入口还是kernel\linux-4.19\kernel\power\main.c
:
suspend
实现非常复杂,本文只列出关键的流程:
总结
本文主要和大家分享了OpenHarmony电源管理子系统中关于亮灭屏的实现细节,包括NAPI接口、PowerMgr的流程以及Kernel实现等,做了较为详细的代码说明,希望通过本文您能初步掌握电源管理子系统的关键功能和核心流程。关于OpenHarmony其它子系统的分析,请关注后续文章。
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
感谢开鸿的老师们不断贡献优质内容
感谢支持