写点什么

Wakeup Source 框架设计与实现

作者:EquatorCoco
  • 2024-06-07
    福建
  • 本文字数:9140 字

    阅读完需:约 30 分钟

Wakeup Source 为系统组件提供了投票机制,以便低功耗子系统判断当前是否可以进入休眠。


Wakeup Source(后简称:WS) 模块可与内核中的其他模块或者上层服务交互,并最终体现在对睡眠锁的控制上。



1. 模块功能说明


WS 的处理逻辑基本上是围绕 combined_event_count 变量展开的,这个变量高 16 位记录系统已处理的所有的唤醒事件总数,低 16 位记录在处理中的唤醒事件总数。每次持锁时,处理中的唤醒事件记录(低 16 位)会加 1;每次释放锁时,处理中的唤醒事件记录(低 16 位)会减 1,同时已处理的唤醒事件记录(高 16 位)会加 1。


对于每次系统能否进入休眠,通过判断是否有正在处理中的唤醒事件(低 16 位)来决定。该模块实现主要的功能:


  • 持锁和释放锁

  • 注册和注销锁

  • 查询激活状态锁个数


2. 主要数据结构


2.1 wakeup_source 结构体

@include/linux/pm_wakeup.h/** * struct wakeup_source - Representation of wakeup sources * * @name: Name of the wakeup source * @id: Wakeup source id * @entry: Wakeup source list entry * @lock: Wakeup source lock * @wakeirq: Optional device specific wakeirq * @timer: Wakeup timer list * @timer_expires: Wakeup timer expiration * @total_time: Total time this wakeup source has been active. * @max_time: Maximum time this wakeup source has been continuously active. * @last_time: Monotonic clock when the wakeup source's was touched last time. * @prevent_sleep_time: Total time this source has been preventing autosleep. * @event_count: Number of signaled wakeup events. * @active_count: Number of times the wakeup source was activated. * @relax_count: Number of times the wakeup source was deactivated. * @expire_count: Number of times the wakeup source's timeout has expired. * @wakeup_count: Number of times the wakeup source might abort suspend. * @dev: Struct device for sysfs statistics about the wakeup source. * @active: Status of the wakeup source. * @autosleep_enabled: Autosleep is active, so update @prevent_sleep_time. */struct wakeup_source {	const char 		*name; //ws 名称	int			id;  //WS系统给本ws分配的ID	struct list_head	entry; //用于把本ws节点维护到WS系统的全局链表中	spinlock_t		lock;	struct wake_irq		*wakeirq; //与本ws节点绑定的唤醒中断相关的结构体,用户可自行把指定中断与ws绑定	struct timer_list	timer; //超时锁使用,如定义本ws为超时锁,指定在一定时间后释放锁	unsigned long		timer_expires;//超时锁超时时间	ktime_t total_time; //本ws激活的总时长	ktime_t max_time;   //在ws激活历史中,最长一次的激活时间	ktime_t last_time;  //最后一次访问本ws的时间	ktime_t start_prevent_time; //本ws最近一次阻止autosleep进入休眠的时间戳	ktime_t prevent_sleep_time; //因本ws导致的阻止autosleep进入休眠的总时间	unsigned long		event_count; //事件次数,本ws被持锁(不考虑是否已持锁),则加1并作记录	unsigned long		active_count;//激活次数,本ws仅在首次持锁(激活)时加1(已持锁则不加1,锁释放后再次持锁则加1)	unsigned long		relax_count; //释放次数,与 active_count 相对	unsigned long		expire_count; //超时锁超时次数	unsigned long		wakeup_count; //与event_count一样,但受events_check_enabled 使能标记控制	struct device		*dev; //与本ws绑定的设备	bool			active:1; //标记是否处于激活状态	bool			autosleep_enabled:1; //标记是否使能autosleep};
复制代码


2.2 核心变量


2.2.1 combined_event_count 变量


static atomic_t combined_event_count = ATOMIC_INIT(0);该变量是 1 个组合计数变量,高 16 位记录唤醒事件的总数,低 16 位记录正在处理中的唤醒事件的总数。系统根据低 16 位(正在处理中的唤醒事件)来判断是否可以进入休眠。


2.2.2 wakeup_sources 变量


static LIST_HEAD(wakeup_sources);所有通过调用 wakeup_source_register()注册的 ws 全部维护在此链表中,以便系统进行维护。


2.3 主要函数分析


Wakeup Source 对外提供的主要接口:


  • wakeup_source_register()wakeup_source_unregister()分别用于注册与注销一个 ws

  • __pm_stay_awake()__pm_relax(),针对 ws 类型对象提供持锁与释放锁接口

  • (device_set_wakeup_capable()+device_wakeup_enable()/device_wakeup_disable()/device_set_wakeup_enable())/device_init_wakeup()给设备配置是否支持唤醒以及注册/注销 ws 的接口

  • pm_stay_awake()pm_relax(),针对 device 类型对象提供持锁与释放锁接口


2.3.1 wakeup_source_register()/wakeup_source_unregister() 接口


wakeup_source_register()函数为 dev 设备创建 ws,并将创建的 ws 添加到全局链表wakeup_sources中,方便后续维护,并在 sysfs 系统中创建节点/sys/class/wakeup/wakeup<id>/,便于获取 ws 相关信息。

@drivers/base/power/wakeup.c/** * wakeup_source_register - Create wakeup source and add it to the list. * @dev: Device this wakeup source is associated with (or NULL if virtual). * @name: Name of the wakeup source to register. */struct wakeup_source *wakeup_source_register(struct device *dev,					     const char *name){	struct wakeup_source *ws;	int ret;
ws = wakeup_source_create(name); //分配内存,设置ws的name和id if (ws) { if (!dev || device_is_registered(dev)) { //在sysfs下为该ws创建dev, /sys/class/wakeup/wakeup<id>/ ret = wakeup_source_sysfs_add(dev, ws); if (ret) { wakeup_source_free(ws); return NULL; } } wakeup_source_add(ws); //设置超时回调函数并将ws添加到wakeup_sources链表 } return ws;}@drivers/base/power/wakeup_stats.cstatic struct device *wakeup_source_device_create(struct device *parent, struct wakeup_source *ws){ struct device *dev = NULL; int retval = -ENODEV;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); device_initialize(dev); dev->devt = MKDEV(0, 0); dev->class = wakeup_class; //ws dev挂于wakeup类 dev->parent = parent; dev->groups = wakeup_source_groups; dev->release = device_create_release; dev_set_drvdata(dev, ws); device_set_pm_not_required(dev); retval = kobject_set_name(&dev->kobj, "wakeup%d", ws->id); retval = device_add(dev); return dev;}//ws dev存在的属性: /sys/class/wakeup/wakeup<id>/static struct attribute *wakeup_source_attrs[] = { &dev_attr_name.attr, //RO, ws 名称 &dev_attr_active_count.attr, //RO, 激活次数 &dev_attr_event_count.attr, //RO, 持锁次数 &dev_attr_wakeup_count.attr, //RO, 同event_count,但受events_check_enabled使能标记 &dev_attr_expire_count.attr, //RO, 超时次数 &dev_attr_active_time_ms.attr, //RO, 如当前处于激活状态,显示已激活时间 &dev_attr_total_time_ms.attr, //RO, 总激活时间 &dev_attr_max_time_ms.attr, //RO, 最长激活时间 &dev_attr_last_change_ms.attr, //RO, 最近一次激活时的时间戳 &dev_attr_prevent_suspend_time_ms.attr, //RO, 阻止autosleep进入休眠的总时间 NULL,};ATTRIBUTE_GROUPS(wakeup_source);
复制代码


wakeup_source_unregister() 接口删除了已注册的 ws,移除了 sysfs 系统中的节点并释放占用的系统资源。

@drivers/base/power/wakeup.cvoid wakeup_source_unregister(struct wakeup_source *ws){	if (ws) {		wakeup_source_remove(ws); //从wakeup_sources队列移除并删除其定时器		if (ws->dev)			wakeup_source_sysfs_remove(ws);//移除该ws在sysfs系统中的信息
wakeup_source_destroy(ws); }}void wakeup_source_destroy(struct wakeup_source *ws){ __pm_relax(ws); //释放该ws wakeup_source_record(ws);//如果该ws被持锁过,则将其记录叠加到deleted_ws这个ws上 wakeup_source_free(ws);//释放内存资源}
static struct wakeup_source deleted_ws = {//用于保存已移除ws的记录 .name = "deleted", .lock = __SPIN_LOCK_UNLOCKED(deleted_ws.lock),};
static void wakeup_source_record(struct wakeup_source *ws){ unsigned long flags;
spin_lock_irqsave(&deleted_ws.lock, flags);
if (ws->event_count) {//如果该ws被持锁过,则将记录都叠加到deleted_ws这个ws上 deleted_ws.total_time = ktime_add(deleted_ws.total_time, ws->total_time); deleted_ws.prevent_sleep_time = ktime_add(deleted_ws.prevent_sleep_time, ws->prevent_sleep_time); deleted_ws.max_time = ktime_compare(deleted_ws.max_time, ws->max_time) > 0 ? deleted_ws.max_time : ws->max_time; deleted_ws.event_count += ws->event_count; deleted_ws.active_count += ws->active_count; deleted_ws.relax_count += ws->relax_count; deleted_ws.expire_count += ws->expire_count; deleted_ws.wakeup_count += ws->wakeup_count; }
spin_unlock_irqrestore(&deleted_ws.lock, flags);}
复制代码


2.3.2 __pm_stay_awake()/__pm_relax() 接口


__pm_stay_awake() 用于上锁 ws 来阻止系统休眠。

@drivers/base/power/wakeup.cvoid __pm_stay_awake(struct wakeup_source *ws){	unsigned long flags;
if (!ws) return;
spin_lock_irqsave(&ws->lock, flags);
wakeup_source_report_event(ws, false);//纪录该ws的信息 del_timer(&ws->timer); ws->timer_expires = 0;
spin_unlock_irqrestore(&ws->lock, flags);}static void wakeup_source_report_event(struct wakeup_source *ws, bool hard){ ws->event_count++; //持锁次数加1 /* This is racy, but the counter is approximate anyway. */ if (events_check_enabled) ws->wakeup_count++;
if (!ws->active) //ws还未激活情况下,激活ws wakeup_source_activate(ws);
if (hard) //如果需要,可以强制阻止系统休眠 pm_system_wakeup();}static void wakeup_source_activate(struct wakeup_source *ws){ unsigned int cec;
if (WARN_ONCE(wakeup_source_not_registered(ws), "unregistered wakeup source\n")) return;
ws->active = true; ws->active_count++; //激活次数加1 ws->last_time = ktime_get(); //纪录最后操作该锁的时间戳 if (ws->autosleep_enabled) //如果autosleep已使能,则记录该ws阻止休眠时时间戳 ws->start_prevent_time = ws->last_time;
/* Increment the counter of events in progress. */ cec = atomic_inc_return(&combined_event_count); //combined_event_count低16位加1
trace_wakeup_source_activate(ws->name, cec);}
复制代码


__pm_relax() 用于将持有的睡眠锁释放掉,并在检测到combined_event_count低 16 位为 0(表示当前没有在处理的 ws)时会触发wakeup_count_wait_queue等待队列运行,如果工作队列满足睡眠条件,则继续进入睡眠流程,该机制是通过pm_get_wakeup_count()接口与 autosleep 配合使用的

@drivers/base/power/wakeup.cvoid __pm_relax(struct wakeup_source *ws){	unsigned long flags;
if (!ws) return;
spin_lock_irqsave(&ws->lock, flags); if (ws->active) //如果ws已激活,则去激活该ws wakeup_source_deactivate(ws); spin_unlock_irqrestore(&ws->lock, flags);}
static void wakeup_source_deactivate(struct wakeup_source *ws){ unsigned int cnt, inpr, cec; ktime_t duration; ktime_t now;
ws->relax_count++; //释放次数加1 /* * __pm_relax() may be called directly or from a timer function. * If it is called directly right after the timer function has been * started, but before the timer function calls __pm_relax(), it is * possible that __pm_stay_awake() will be called in the meantime and * will set ws->active. Then, ws->active may be cleared immediately * by the __pm_relax() called from the timer function, but in such a * case ws->relax_count will be different from ws->active_count. */ if (ws->relax_count != ws->active_count) { ws->relax_count--; //未解决定时锁与主动调用释放锁并发操作时出现冲突做的处理 return; }
ws->active = false;
now = ktime_get(); duration = ktime_sub(now, ws->last_time); ws->total_time = ktime_add(ws->total_time, duration); //叠加总的持锁时间 if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time)) ws->max_time = duration; //更新最长持锁时间
ws->last_time = now; //纪录最后操作该锁的时间戳 del_timer(&ws->timer); ws->timer_expires = 0;
if (ws->autosleep_enabled)//如果autosleep已使能,更新该ws阻止系统休眠的时长 update_prevent_sleep_time(ws, now);
/* * Increment the counter of registered wakeup events and decrement the * couter of wakeup events in progress simultaneously. */ cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);//combined_event_count高16位加1 trace_wakeup_source_deactivate(ws->name, cec);
split_counters(&cnt, &inpr);//拆分出combined_event_count高16位和低16位 if (!inpr && waitqueue_active(&wakeup_count_wait_queue))//如果该ws已经无正在处理的唤醒事件,则通知PM core wake_up(&wakeup_count_wait_queue);}
复制代码


注:同个 ws 连续使用多次__pm_stay_awake()__pm_relax()只会增加/减少一次combined_event_count低 16 位(表示正在处理中的事件总数),只要__pm_relax()被调用就会释放锁。


2.3.3 pm_get_wakeup_count()接口


该函数主要是获取已处理的 wakeup event 数量(combined_event_count高 16 位)与正在处理的 wakeup event 数量是否为 0(combined_event_count低 16 位)。

bool pm_get_wakeup_count(unsigned int *count, bool block){	unsigned int cnt, inpr;
if (block) { DEFINE_WAIT(wait); //定义名为wait的等待队列入口
for (;;) { prepare_to_wait(&wakeup_count_wait_queue, &wait, TASK_INTERRUPTIBLE); //准备 wakeup_count_wait_queue 等待队列 split_counters(&cnt, &inpr); if (inpr == 0 || signal_pending(current)) break; pm_print_active_wakeup_sources(); schedule(); //调度到其他线程 } //__pm_relax() 里wake_up(&wakeup_count_wait_queue);会触发调度到此处 finish_wait(&wakeup_count_wait_queue, &wait); }
split_counters(&cnt, &inpr); *count = cnt; return !inpr; //返回0表示有待处理事件,返回1表示无待处理事件}
复制代码


1.如果入参block为 0,则仅仅对入参count赋值当前已处理的 wakeup event 总数,并返回当前是否有待处理 wakeup event(返回 0 表示有待处理事件,返回 1 表示无待处理事件)。2.如果入参block为 1,则需要一直等到待处理事件为 0(combined_event_count低 16 位为 0)或者当前挂起进程有事件需要处理时才退出。该处理分支的wait等待队列会在__pm_relax()满足睡眠条件时触发调度运行,即finish_wait().


2.3.4 pm_wakeup_pending() 接口


该函数的功能是确认当前是否满足休眠条件,返回 true 表示可以休眠,false 表示不可休眠。

bool pm_wakeup_pending(void){	unsigned long flags;	bool ret = false;
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(); }
return ret || atomic_read(&pm_abort_suspend) > 0;}
复制代码


判断允许休眠的依据:1.已处理的 wakeup event 数量与已记录的数量(saved_count)一致,且 2.待处理的 wakeup event 数量为 0,且 3.原子量pm_abort_suspend为 0(该值大于 0 表示睡眠流程中出现了唤醒中断或事件,唤醒事件通过调用pm_system_wakeup()来给pm_abort_suspend加 1 操作。)


2.3.5 device 与 wakeup_source 关联处理的接口


kernel 抽象出的 device 数据结构存放着 power manager 相关的信息,其中就存放着 wakeup source 数据结构,如下:

//代码格式错误,仅为呈现数据结构,请忽略格式。struct device {	// @power:	For device power management.	struct dev_pm_info	power {		unsigned int		can_wakeup:1; //需置1才允许使用wakeup source		struct wakeup_source	*wakeup; 	};};
复制代码


wakeup source 框架中为此提供了大量相关的接口直接操作某个 dev 的 ws,接口如下:


  • int device_wakeup_enable(struct device *dev):注册设备的 wakeup source1.以 dev 名注册个 ws,并指定该 ws dev 的 parent 为当前 dev2.将注册的 ws 关联到 dev->power.wakeup,如果存在 wakeirq,也会一起绑定到该 ws 上。

  • int device_wakeup_disable(struct device *dev):注销设备的 wakeup source1.取消已注册的 ws 与 dev->power.wakeup 的关联 2.注销 ws

  • void device_set_wakeup_capable(struct device *dev, bool capable):设置设备是否支持 wakeup source1.设置 dev->power.can_wakeup2.如果设备支持 wakeup,则为其创建属性文件(位于/sys/devices/<dev_name>/power/下);如果设备不支持 wakeup,则不会移除相关属性文件。


static struct attribute *wakeup_attrs[] = {#ifdef CONFIG_PM_SLEEP	&dev_attr_wakeup.attr, //RW,可写入enabled/disabled动态配置是否支持wakeup	&dev_attr_wakeup_count.attr, //RO, 读取该dev ws的wakeup_count	&dev_attr_wakeup_active_count.attr, //RO, 读取该dev ws的active_count	&dev_attr_wakeup_abort_count.attr, //RO, 读取该dev ws的wakeup_count	&dev_attr_wakeup_expire_count.attr, //RO, 读取该dev ws的expire_count	&dev_attr_wakeup_active.attr, //RO, 读取该dev ws的active状态	&dev_attr_wakeup_total_time_ms.attr, //RO, 读取该dev ws的total_time	&dev_attr_wakeup_max_time_ms.attr, //RO, 读取该dev ws的max_time	&dev_attr_wakeup_last_time_ms.attr, //RO, 读取该dev ws的last_time#ifdef CONFIG_PM_AUTOSLEEP	&dev_attr_wakeup_prevent_sleep_time_ms.attr, //RO, 读取该dev ws的prevent_sleep_time#endif#endif	NULL,};
复制代码


  • int device_init_wakeup(struct device *dev, bool enable):一步到位直接配置是否支持 wakeup 并且注册/注销 ws

int device_init_wakeup(struct device *dev, bool enable){	int ret = 0;
if (enable) { device_set_wakeup_capable(dev, true); ret = device_wakeup_enable(dev); } else { device_wakeup_disable(dev); device_set_wakeup_capable(dev, false); }
return ret;}
复制代码


  • int device_set_wakeup_enable(struct device *dev, bool enable):设置设备是否能通过 ws 唤醒系统,注册/注销 ws

int device_set_wakeup_enable(struct device *dev, bool enable){	return enable ? device_wakeup_enable(dev) : device_wakeup_disable(dev);}
复制代码


  • void pm_stay_awake(struct device *dev):持锁设备的 ws,不让设备休眠,实际是调用__pm_stay_awake(dev->power.wakeup);实现

  • void pm_relax(struct device *dev):释放设备的 ws,允许设备休眠,实际是调用__pm_relax(dev->power.wakeup);实现


总结:1.device_set_wakeup_capable() 用于设置是否支持 wakeup,并提供属性节点,便于调试 2.device_wakeup_enable()/device_wakeup_disable()/device_set_wakeup_enable()主要是注册/注销设备 ws,需在device_set_wakeup_capable()enabled 的前提下才能使用。3.device_init_wakeup() 通常使用在默认支持 wakeup 的 device 上,在 probe/remove 时分别 enable/disable。4.pm_stay_awake()/pm_relax()主要是持有/释放 ws 锁,阻止/允许系统休眠


3. 主要工作时序


1)device 或者其他需要上锁的模块调用device_init_wakeup()/wakeup_source_register()来注册 ws2)在处理业务时,为了防止系统进入睡眠流程,设备或模块可以通过调用pm_stay_awake()/__pm_stay_awake()来持锁 ws 阻止休眠 3)当业务处理完成后,设备或模块可以调用pm_relax()/__pm_relax()来释放 ws 允许系统休眠 4)在__pm_relax()释放锁时,会检查当前是否有正在处理的持锁事件,如果没有,则触发wakeup_count_wait_queue5)wakeup_count_wait_queue所在的pm_get_wakeup_count()接口会返回到 autosleep 的工作队列中继续走休眠流程



4. 调试节点


  1. 获取所有 wakeup source 信息节点:cat /d/wakeup_sources列出所有 wakeup_source 当前的信息,包括:name,active_count,event_count,wakeup_count,expire_count,active_since,total_time,max_time,last_change,prevent_suspend_time。注:代码实现在 @drivers/base/power/wakeup.c

  2. 从 wakeup 类下获取某个 ws 的信息:/sys/class/wakeup/wakeup<id>/wakeup 类下汇总了所有已注册的 ws,该节点下存在属性:name, active_count, event_count, wakeup_count,expire_count, active_time_ms, total_time_ms, max_time_ms, last_change_ms, prevent_suspend_time_ms。注:代码实现在 @drivers/base/power/wakeup_stats.c

  3. 从 device 节点下获取该设备的 ws 信息:/sys/devices/<dev_name>/power/该节点存在如下属性信息:wakeup(是否支持唤醒),wakeup_count, wakeup_active_count, wakeup_abort_count, wakeup_expire_count, wakeup_active, wakeup_total_time_ms, max_time_ms, last_time_ms, prevent_sleep_time_ms。注:代码实现在 @drivers/base/power/sysfs.c


注:本文是基于内核 kernel-5.10 展开。上述分析基于 32 位系统,若是 64 位系统,则 combined_event_count 会被拆分成 2 个 32 位分别来纪录唤醒事件的总数和正在处理中的唤醒事件的总数


文章转载自:Jayfan_Ma

原文链接:https://www.cnblogs.com/jiafan-ma/p/18200874

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
Wakeup Source框架设计与实现_Linux_EquatorCoco_InfoQ写作社区