#星光计划2.0# OpenHarmony 源码解析之电源管理亮灭屏功能 原创 精华

深开鸿
发布于 2021-12-13 12:31
浏览
3收藏

作者:王奎

【本文正在参与51CTO HarmonyOS技术社区创作者激励计划-星光计划2.0】

1 简介

电源管理子系统是OpenHarmony的基本能力子系统,有电池服务组件、显示控制组件和电源管理服务组件,主要提供如下功能:

  1. 重启系统。
  2. 管理休眠运行锁。
  3. 系统电源状态查询。
  4. 充电和电池状态查询和上报。
  5. 亮灭屏管理和亮度调节。

本文重点分析亮灭屏功能,包括NAPI接口PowerMgrKernel层的实现。

1.1 电源管理相关

《OpenHarmony 源码解析之电源管理子系统 》

《OpenHarmony 源码解析之电源管理亮灭屏功能》

1.2 OpenHarmony架构图

#星光计划2.0# OpenHarmony 源码解析之电源管理亮灭屏功能-鸿蒙开发者社区

1.3 电源管理子系统架构图

#星光计划2.0# OpenHarmony 源码解析之电源管理亮灭屏功能-鸿蒙开发者社区

2 知识准备

#星光计划2.0# OpenHarmony 源码解析之电源管理亮灭屏功能-鸿蒙开发者社区

2.1 电源状态

  1. On (on) S0-Working
  2. Standby (standby) S1- CPU and RAM are powed but not executed
  3. Suspend to Ram (mem) S2- RAM is powered and the running content is saved to RAM
  4. 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

  1. 一个sysfs文件:/sys/power/wake_lock,用户程序向文件写入一个字符串,即可创建一个wakelock,该字符串就是wakelock的名字。该wakelock可以阻止系统进入低功耗模式。
  2. 一个sysfs文件:/sys/power/wake_unlock,用户程序向文件写入相同的字符串,即可注销一个wakelock
  3. 当系统中所有的wakelock都注销后,系统可以自动进入低功耗状态。
  4. 向内核其它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内部流转到suspendwakeup

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开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-12-13 12:31:31修改
5
收藏 3
回复
举报
2条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

感谢开鸿的老师们不断贡献优质内容

1
回复
2021-12-13 14:18:35
深开鸿
深开鸿 回复了 红叶亦知秋
感谢开鸿的老师们不断贡献优质内容

感谢支持

回复
2021-12-14 09:13:37
回复
    相关推荐