赞
踩
芯片(SoC)上系统的电源状态有:on、idle和suspend。On表示SoC正在运行。Idle是一种中等功率模式,在这种模式下,SoC是通电的,但不执行任何任务。Suspend是一种低功耗模式,其中SoC没有供电。在此模式下,设备的功耗通常比“On” 模式低100倍。
在 Android 9 及更低版本中,libsuspend 中有一个负责发起系统挂起的线程。Android 10 在 SystemSuspend HIDL 服务中引入了等效功能。此服务位于系统映像中,由 Android 平台提供。 libsuspend 的逻辑基本保持不变,以下情况除外:阻止系统挂起的每个用户空间进程都需要与 SystemSuspend 进行通信。
在 Android 10 中,SystemSuspend 服务取代了 libsuspend。libpower 经过重新实现,目前依赖于 SystemSuspend 服务(而不是 /sys/power/wake[un]lock),且不必更改 C API。
下面的伪代码显示了如何实现 acquire_wake_lock 和 release_wake_lock。
// hardware/libhardware_legacy/power.cpp static std::unordered_map<std::string, sp<IWakeLock>> gWakeLockMap; int acquire_wake_lock(int, const char* id) { ... if (!gWakeLockMap[id]) { gWakeLockMap[id] = suspendService->acquireWakeLock(WakeLockType::PARTIAL, id); } ... return 0; } int release_wake_lock(const char* id) { ... if (gWakeLockMap[id]) { auto ret = gWakeLockMap[id]->release(); gWakeLockMap[id].clear(); return 0; } ... return -1; }
system/hardware/interfaces/suspend/1.0/default/SystemSuspend.cpp
SystemSuspend 服务使用挂起计数器跟踪发出的唤醒锁数量。它有两个执行线程:
主线程响应来自客户端的请求以分配新的唤醒锁,从而递增/递减挂起计数器。
挂起线程循环执行以下操作:
// drivers/base/power/wakeup.c 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; } static void wakeup_source_report_event(struct wakeup_source *ws, bool hard) { ws->event_count++; /* This is racy, but the counter is approximate anyway. */ if (events_check_enabled) ws->wakeup_count++; if (!ws->active) wakeup_source_activate(ws); if (hard) pm_system_wakeup(); } bool pm_wakeup_pending(void) { unsigned long flags; bool ret = false; char suspend_abort[MAX_SUSPEND_ABORT_LEN]; raw_spin_lock_irqsave(&events_lock, flags); if (events_check_enabled) { unsigned int cnt, inpr; split_counters(&cnt, &inpr); ret = (cnt != saved_count || inpr > 0); events_check_enabled = !ret; } raw_spin_unlock_irqrestore(&events_lock, flags); if (ret) { pm_pr_dbg("Wakeup pending, aborting suspend\n"); pm_print_active_wakeup_sources(); pm_get_active_wakeup_sources(suspend_abort, MAX_SUSPEND_ABORT_LEN); log_suspend_abort_reason(suspend_abort); pr_info("PM: %s\n", suspend_abort); } return ret || atomic_read(&pm_abort_suspend) > 0; }
如果(cnt != saved_count || inpr > 0) == true, 你想知道哪里触发的。你需要combined_event_count怎么改变的,因为cnt和inpr的值来自combined_event_count。总之你如下增加log
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index addbd42930bf8..6f089700a6087 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -591,7 +596,11 @@ static void wakeup_source_report_event(struct wakeup_source *ws, bool hard) ws->wakeup_count++; if (!ws->active) + { + pr_info("Wakeup source: %s\n", ws->name); + dump_stack(); wakeup_source_activate(ws); + } if (hard) pm_system_wakeup();
你得到如下的log,因此知道cnt和inpr的值的更改和cdns_hdmi_bridge_disable有关。
[42667.761490][ T2207] PM: Wakeup source: event2 [42667.761496][ T2207] CPU: 2 PID: 2207 Comm: binder:218_4 Tainted: G W OE 6.6.0-gb2ac2263d6bd-dirty #6 [42667.761504][ T2207] Hardware name: NXP i.MX8MQ EVK (DT) [42667.761508][ T2207] Call trace: [42667.761510][ T2207] dump_backtrace+0xe8/0x10c [42667.761528][ T2207] show_stack+0x18/0x28 [42667.761536][ T2207] dump_stack_lvl+0x50/0x6c [42667.761545][ T2207] wakeup_source_report_event+0x94/0x1e8 [42667.761555][ T2207] __pm_stay_awake+0x38/0x68 [42667.761562][ T2207] ep_poll_callback+0x17c/0x34c [42667.761573][ T2207] __wake_up_common+0xe8/0x188 [42667.761584][ T2207] __wake_up+0x7c/0xd0 [42667.761592][ T2207] evdev_pass_values+0x208/0x220 [42667.761603][ T2207] evdev_events+0x90/0xb0 [42667.761610][ T2207] input_pass_values+0x1f8/0x3a4 [42667.761618][ T2207] input_handle_event+0x498/0x4a8 [42667.761624][ T2207] input_event+0x60/0x88 [42667.761630][ T2207] snd_jack_report+0x24c/0x2ac [42667.761640][ T2207] snd_soc_jack_report+0xe8/0x210 [42667.761650][ T2207] plugged_cb+0x98/0xd0 [42667.761657][ T2207] cdns_hdmi_bridge_disable+0x4c/0x60 [42667.761666][ T2207] drm_atomic_bridge_chain_disable+0xb0/0xe8 [42667.761676][ T2207] drm_atomic_helper_commit_modeset_disables+0x18c/0x66c [42667.761684][ T2207] drm_atomic_helper_commit_tail_rpm+0x30/0x1f0 [42667.761691][ T2207] commit_tail+0xbc/0x170 [42667.761697][ T2207] drm_atomic_helper_commit+0x2cc/0x2f0 [42667.761704][ T2207] drm_atomic_commit+0xbc/0xec [42667.761710][ T2207] drm_atomic_helper_disable_all+0x160/0x1e0 [42667.761717][ T2207] drm_atomic_helper_suspend+0xb8/0x1b8 [42667.761724][ T2207] drm_mode_config_helper_suspend+0x30/0x74 [42667.761735][ T2207] dcss_dev_suspend+0x2c/0x78 [42667.761744][ T2207] platform_pm_suspend+0x40/0x88 [42667.761754][ T2207] dpm_run_callback+0x64/0x258 [42667.761761][ T2207] __device_suspend+0x284/0x464 [42667.761768][ T2207] dpm_suspend+0x100/0x508 [42667.761775][ T2207] dpm_suspend_start+0x84/0xc0 [42667.761783][ T2207] suspend_devices_and_enter+0x128/0xadc [42667.761792][ T2207] pm_suspend+0x2ec/0x6a8 [42667.761798][ T2207] state_store+0x104/0x140 [42667.761804][ T2207] kobj_attr_store+0x30/0x48 [42667.761813][ T2207] sysfs_kf_write+0x54/0x6c [42667.761820][ T2207] kernfs_fop_write_iter+0x104/0x1a4 [42667.761832][ T2207] vfs_write+0x1f0/0x2e4 [42667.761842][ T2207] ksys_write+0x78/0xe8 [42667.761850][ T2207] __arm64_sys_write+0x1c/0x2c [42667.761858][ T2207] invoke_syscall+0x58/0x114 [42667.761867][ T2207] el0_svc_common+0xac/0xe8 [42667.761877][ T2207] do_el0_svc+0x1c/0x28 [42667.761885][ T2207] el0_svc+0x38/0xb0 [42667.761892][ T2207] el0t_64_sync_handler+0x68/0xbc [42667.761899][ T2207] el0t_64_sync+0x1a8/0x1ac [42667.763990][ T2207] [drm] Reg val is 0x0080 [42667.868807][ T2207] [drm] hdmi phy shutdown complete
成功返回唤醒锁定的请求后,挂起线程会被阻止。
注意:互斥量始终在循环迭代结束时释放。
SystemSuspend API 包含两个接口。HIDL 接口由native进程用于获取唤醒锁,而 AIDL 接口则用于在 SystemServer 和 SystemSuspend 之间通信。
enum WakeLockType : uint32_t {
PARTIAL,
FULL
};
interface IWakeLock {
oneway release();
};
interface ISystemSuspend {
acquireWakeLock(WakeLockType type, string debugName)
generates (IWakeLock lock);
};
请求唤醒锁定的每个客户端都会收到唯一的 IWakeLock 实例。这与 /sys/power/wake_lock 不同,后者允许多个客户端使用相同名称的唤醒锁定。如果拥有 IWakeLock 实例的客户端终止,则 binder 驱动程序和 SystemSuspend 服务会将其清除。
ISuspendControlService 仅供 SystemServer 使用。
interface ISuspendCallback {
void notifyWakeup(boolean success);
}
interface ISuspendControlService {
boolean enableAutosuspend();
boolean registerCallback(ISuspendCallback callback);
boolean forceSuspend();
}
利用 Android HIDL 可带来以下好处:
dumpsys suspend_control_internal
dumpsys 是一种在 Android 设备上运行的工具,可提供有关系统服务的信息。您可以使用 Android 调试桥 (ADB) 从命令行调用 dumpsys,获取在连接的设备上运行的所有系统服务的诊断输出。
suspend_control_internal这个service的code:
system/hardware/interfaces/suspend/1.0/default/SuspendControlService.cpp
最终可以通过这个节点/sys/class/wakeup来查看wakelock的状态。最终调用在如下code:
system/hardware/interfaces/suspend/1.0/default/main.cpp
从上图的结果来看,wakelock的type有native或者kernel,native类型的wakelock通过上面的libpower进行申请和释放。kernel类型的wakelock分两种情况:
3. 具体驱动注册wakelock是通过wakeup_source_register注册的。
// drivers/base/power/wakeup.c struct wakeup_source *wakeup_source_register(struct device *dev, const char *name) { struct wakeup_source *ws; int ret; ws = wakeup_source_create(name); if (ws) { if (!dev || device_is_registered(dev)) { ret = wakeup_source_sysfs_add(dev, ws); if (ret) { wakeup_source_free(ws); return NULL; } } wakeup_source_add(ws); } return ws; } EXPORT_SYMBOL_GPL(wakeup_source_register);
// hardware/interfaces/health/aidl/default/android.hardware.health-service.example.rc
service vendor.health-default /vendor/bin/hw/android.hardware.health-service.example
class hal
user system
group system
capabilities WAKE_ALARM BLOCK_SUSPEND
file /dev/kmsg w
health HAL service 有 CAP_BLOCK_SUSPEND的能力,并且使用epoll机制监听可读事件。
// hardware/interfaces/health/utils/libhealthloop/HealthLoop.cpp int HealthLoop::RegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) { CHECK(!reject_event_register_); auto* event_handler = event_handlers_ .emplace_back(std::make_unique<EventHandler>(EventHandler{this, fd, func})) .get(); struct epoll_event ev; ev.events = EPOLLIN; if (wakeup == EVENT_WAKEUP_FD) ev.events |= EPOLLWAKEUP; ev.data.ptr = reinterpret_cast<void*>(event_handler); if (epoll_ctl(epollfd_, EPOLL_CTL_ADD, fd, &ev) == -1) { KLOG_ERROR(LOG_TAG, "epoll_ctl failed; errno=%d\n", errno); return -1; } return 0; }
epoll_ctl是syscall。
// fs/eventpoll.c SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event __user *, event) { struct epoll_event epds; if (ep_op_has_event(op) && copy_from_user(&epds, event, sizeof(struct epoll_event))) return -EFAULT; return do_epoll_ctl(epfd, op, fd, &epds, false); } static int ep_create_wakeup_source(struct epitem *epi) { struct name_snapshot n; struct wakeup_source *ws; if (!epi->ep->ws) { epi->ep->ws = wakeup_source_register(NULL, "eventpoll"); if (!epi->ep->ws) return -ENOMEM; } take_dentry_name_snapshot(&n, epi->ffd.file->f_path.dentry); ws = wakeup_source_register(NULL, n.name.name); release_dentry_name_snapshot(&n); if (!ws) return -ENOMEM; rcu_assign_pointer(epi->ws, ws); return 0; }
最终调用到上面的wakeup_source_register,从而申请一个wakelock。同理也可以调用wakeup_source_unregister来removes a wakeup source。
如果你已知一个wakelock的名字但是不清楚是usersapce那个地方设置的wakelock,你可以如下加log:
diff --git a/fs/eventpoll.c b/fs/eventpoll.c index 1d9a71a0c4c16..e00e12972479c 100644 --- a/fs/eventpoll.c +++ b/fs/eventpoll.c @@ -1413,6 +1413,10 @@ static int ep_create_wakeup_source(struct epitem *epi) take_dentry_name_snapshot(&n, epi->ffd.file->f_path.dentry); ws = wakeup_source_register(NULL, n.name.name); + if (strcmp(n.name.name, "event2") == 0) { + pr_info("6666666666666666666666666666666Name is event2\n"); + dump_stack(); + } release_dentry_name_snapshot(&n); if (!ws)
log如图下所示:
[ 39.905161][ T733] CPU: 2 PID: 733 Comm: InputReader Tainted: G OE 6.6.0-g09a4dc860532 #15 [ 39.914845][ T733] Hardware name: NXP i.MX8MQ EVK (DT) [ 39.920100][ T733] Call trace: [ 39.923253][ T733] dump_backtrace+0xe8/0x10c [ 39.927723][ T733] show_stack+0x18/0x28 [ 39.931751][ T733] dump_stack_lvl+0x50/0x6c [ 39.936125][ T733] wakeup_source_register+0x128/0x138 [ 39.941371][ T733] ep_create_wakeup_source+0x7c/0x100 [ 39.946617][ T733] ep_insert+0x2cc/0x5c4 [ 39.950731][ T733] do_epoll_ctl+0x328/0x3dc [ 39.955105][ T733] __arm64_sys_epoll_ctl+0xfc/0x19c [ 39.960175][ T733] invoke_syscall+0x58/0x114 [ 39.964638][ T733] el0_svc_common+0x80/0xe8 [ 39.969012][ T733] do_el0_svc+0x1c/0x28 [ 39.973042][ T733] el0_svc+0x38/0xb0 [ 39.976810][ T733] el0t_64_sync_handler+0x68/0xbc [ 39.981705][ T733] el0t_64_sync+0x1a8/0x1ac [ 39.988915][ T733] 6666666666666666666666666666666Name is event2 [ 39.995097][ T733] CPU: 2 PID: 733 Comm: InputReader Tainted: G OE 6.6.0-g09a4dc860532 #15 [ 40.004767][ T733] Hardware name: NXP i.MX8MQ EVK (DT) [ 40.010004][ T733] Call trace: [ 40.013154][ T733] dump_backtrace+0xe8/0x10c [ 40.017626][ T733] show_stack+0x18/0x28 [ 40.021657][ T733] dump_stack_lvl+0x50/0x6c [ 40.026034][ T733] ep_create_wakeup_source+0xf4/0x100 [ 40.031281][ T733] ep_insert+0x2cc/0x5c4 [ 40.035396][ T733] do_epoll_ctl+0x328/0x3dc [ 40.039770][ T733] __arm64_sys_epoll_ctl+0xfc/0x19c [ 40.044840][ T733] invoke_syscall+0x58/0x114 [ 40.049303][ T733] el0_svc_common+0x80/0xe8 [ 40.053679][ T733] do_el0_svc+0x1c/0x28 [ 40.057705][ T733] el0_svc+0x38/0xb0 [ 40.061469][ T733] el0t_64_sync_handler+0x68/0xbc [ 40.066364][ T733] el0t_64_sync+0x1a8/0x1ac [ 40.179525][ T1] init: processing action (sys.sysctl.extra_free_kbytes=*) from
可以看出进程/线程为T733,然后
$ ps -AT | grep 733
USER PID TID PPID VSZ RSS WCHAN ADDR S CMD
system 617 733 385 19806716 328088 do_epoll_wait 0 S InputReader
可以得出进程/线程的名字为InputReader然后在code中搜索InputReader,找到对应的目录,然后搜索epoll_ctl,你可以看到如下的代码:
frameworks/native/services/inputflinger/reader/EventHub.cpp
struct epoll_event eventItem = {};
eventItem.events = EPOLLIN | EPOLLWAKEUP;
eventItem.data.fd = mINotifyFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
这就找到了在哪注册的wakelock。
linux提供了这几种suspend: “freeze”, “standby”, “mem” and
“disk”。
内核一般支持最多四种系统睡眠状态,尽管其中三种依赖于平台支持代码来实现每个状态的底层细节。通过以下命令查看你的板子支持哪几种睡眠状态:
# cat sys/power/state
freeze mem disk
这些字符串可以是"mem", “standby”, “freeze"和"disk”,其中后三个总是分别表示上电挂起(如果支持),挂起到空闲和休眠(挂起到磁盘)。
“ mem”字符串的含义由/sys/power/mem_sleep文件控制。它包含代表可用系统suspend模式的字符串,通过将“ mem”写入/sys/power/state触发。这些模式是“s2idle”(Suspend- to - idle)、“shallow”(上电挂起)和“deep”(Suspend- to - ram)。
# cat sys/power/mem_sleep
s2idle [deep]
“s2idle”模式始终可用,而其他模式只有在平台支持的情况下才可用(如果不支持,则代表它们的字符串不存在/sys/power/mem_sleep中)。表示随后使用的suspend模式的字符串用方括号括起来(也就是上面显示的[deep])。写一个/sys/power/mem_sleep中存在的其他字符串导致suspend模式随后使用将其更改为由该字符串表示的一个。
# echo s2idle > sys/power/mem_sleep
# cat sys/power/mem_sleep
[s2idle] deep
也就是说你此时echo mem > /sys/power/state代表进入s2idle模式。
因此,有两种方法可以使系统进入Suspend-To-Idle 睡眠状态。第一个是直接将“freeze”写入/sys/power/state。第二种是将“s2idle”写入/sys/power/mem_sleep,然后将“mem”写入/sys/power/state。类似地,有两种方法可以使系统进入 Power-On Suspend 睡眠状态(在这种情况下写入控制文件的字符串分别是“standby”或“shallow”和“mem” )如果该状态平台支持。反过来,只有一种方法可以使系统进入 Suspend-To-RAM 状态(将“deep”写入/sys/power/mem_sleep,将“mem”写入/sys/power/state)。
默认挂起模式(即不向 /sys/power/mem_sleep 写入任何内容而使用的挂起模式)是"deep"(如果支持挂起到 RAM)或"s2idle",但它可以被kernel command line中"mem_sleep_default" parameter覆盖。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。