[知其然不知其所以然-7] 设备的唤醒
2015-10-19 18:07
246 查看
本文讨论从系统级睡眠唤醒的相关代码。内核的device.txt文档也有关于设备唤醒的介绍。
首先我们来看两个sysfs文件的输出:
在继续之前,我们解释一下设备的唤醒属性。
有些设备,硬件有唤醒能力,我们叫它can_wakeup,在can_wakeup之后,
如果内核允许它触发唤醒事件/打断suspend to ram流程,那么我们叫她may_wakeup,对应到内核里函数就分别是
device_can_wakeup和device_may_wakeup。
前者是设备的能力,后者则是由wakeup source这个device内置成员是否为空来决定。
前者我们不讨论了,后者我们需要说明wake source是什么东西。
wakeup source,一句话,就是打断suspend to ram的能力。
wakeup source的核心就是一个全局计数器,当大于0时,表示有唤醒事件,suspend to ram退出。
就退出挂起。然后这个event可能是各个驱动根据自己的使用情况,负责加一,减一。
同时,为了让用户态与suspend to ram交互,在wakeup source里巧妙的引入了wakeup count的概念。
逻辑就是,用户态代码在某个时刻,获取当前系统被唤醒的总次数count,记录一下(委托
内核记录到save_count),随后suspend to ram。
如果在suspend to ram的过程中,发现当前系统的被唤醒总次数不等于save_count了,
说明在这段时间内出现了唤醒事件(比如,acpi_button_notify->acpi_lid_send_state
->pm_wakeup_event:
{
,那么不允许内核继续suspend to ram,而直接退出。
这就是wakeup source的使用场合。
接着我们还要看关于acpi兼容的系统,如何产生唤醒中断?(上面的acpi_button_notify一般
是由中断发出的)。这一块我们也不绕弯子直接给出(顺序执行)
上面步骤,完全是遵照_PRW的格式,来获取他的信息,最重要的是获取_PRW里的gpe_num,用来设置gpe
enable位,(如果有_PRW的值,那么就将给此acpi_device挂载到acpi_wakeup_device_list链表去)
以及获取必要的信息:需要开启哪些power resource以支持唤醒中断触发功能。
在挂起前,以suspend to idle为例,会遍历具有唤醒功能的设备(位于acpi_wakeup_device_list),
调用acpi_enable_wakeup_device_power,来使能这个设备的power resouce,以及
设备这个设备的唤醒状态_DSW,最后开启对应的gpe_number,设置需要使能的标志:
整体流程可以关注suspend to idle的prepare实现:
其中,acpi_enable_wakeup_devices会把acpi_wakeup_device_list链表的
设备,根据device->gpe_number记录需要被设置的gpe掩码,
然后acpi_enable_all_wakeup_gpes会把这些记录的gpe_number对应的GPE bit位使能(同时也把非
enable的gpe位清零了)
至此,唤醒分析完毕。最后给出第一个/proc/acpi/wakeup的代码来源:
/drivers/acpi/proc.c, 一个设备的属性是通过device_may_wakeup来显示的:
第二个/sys/device/platfform/i8042/serio/power/wakeup的来历,我们这里记录一下如何找到内核
对应的字段?因为wakeup实在太平常的名字了, 可以先查看/sys/device/platfform/i8042/serio/power/ 下有哪些
特殊字段名字,比如有wakeup_active_count,搜索到是在/drivers/base/power/sysfs.c中:
首先我们来看两个sysfs文件的输出:
/# cat /proc/acpi/wakeup Device S-state Status Sysfs node PEG0 S4 *disabled PEGP S4 *disabled PEG1 S4 *disabled PEGP S4 *disabled PEG2 S4 *disabled PEGP S4 *disabled UAR1 S3 *disabled ECIR S4 *disabled GLAN S4 *enabled pci:0000:00:19.0 EHC1 S4 *disabled EHC2 S4 *disabled XHC S4 *enabled pci:0000:00:14.0 TPD4 S4 *disabled TPD5 S4 *disabled TPD6 S4 *disabled TPD7 S0 *disabled TPD8 S0 *disabled HDEF S4 *disabled RP01 S4 *disabled PXSX S4 *disabled RP02 S4 *disabled PXSX S4 *disabled RP03 S4 *disabled pci:0000:00:1c.0 PXSX S4 *disabled pci:0000:01:00.0 RP04 S4 *disabled PXSX S4 *disabled RP05 S4 *disabled PXSX S4 *disabled RP06 S4 *disabled pci:0000:00:1c.5 PXSX S4 *disabled pci:0000:02:00.0 RP07 S4 *disabled PXSX S4 *disabled RP08 S4 *disabled PXSX S4 *disabled PWRB S4 *enabled platform:PNP0C0C:00
/# cat /sys/devices/platform/i8042/serio0/power/wakeup disabled首先,第一个是acpi 设备特有的唤醒属性显示,第二个这是常规的设备唤醒属性。
在继续之前,我们解释一下设备的唤醒属性。
有些设备,硬件有唤醒能力,我们叫它can_wakeup,在can_wakeup之后,
如果内核允许它触发唤醒事件/打断suspend to ram流程,那么我们叫她may_wakeup,对应到内核里函数就分别是
device_can_wakeup和device_may_wakeup。
前者是设备的能力,后者则是由wakeup source这个device内置成员是否为空来决定。
static inline bool device_can_wakeup(struct device *dev) { return dev->power.can_wakeup; } static inline bool device_may_wakeup(struct device *dev) { return dev->power.can_wakeup && !!dev->power.wakeup; }
前者我们不讨论了,后者我们需要说明wake source是什么东西。
wakeup source,一句话,就是打断suspend to ram的能力。
wakeup source的核心就是一个全局计数器,当大于0时,表示有唤醒事件,suspend to ram退出。
#define IN_PROGRESS_BITS (sizeof(int) * 4) #define MAX_IN_PROGRESS ((1 << IN_PROGRESS_BITS) - 1) static void split_counters(unsigned int *cnt, unsigned int *inpr) { unsigned int comb = atomic_read(&combined_event_count); *cnt = (comb >> IN_PROGRESS_BITS); *inpr = comb & MAX_IN_PROGRESS; }inpr减1,同时cnt加1(in_process --, finish_count++)
atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);inpr++,
atomic_inc_return(&combined_event_count);在suspend to ram的核心代码里,会判断这个inpr是否大于0,是的话
就退出挂起。然后这个event可能是各个驱动根据自己的使用情况,负责加一,减一。
同时,为了让用户态与suspend to ram交互,在wakeup source里巧妙的引入了wakeup count的概念。
逻辑就是,用户态代码在某个时刻,获取当前系统被唤醒的总次数count,记录一下(委托
内核记录到save_count),随后suspend to ram。
如果在suspend to ram的过程中,发现当前系统的被唤醒总次数不等于save_count了,
说明在这段时间内出现了唤醒事件(比如,acpi_button_notify->acpi_lid_send_state
->pm_wakeup_event:
{
atomic_inc_return(&combined_event_count);
atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);}
,那么不允许内核继续suspend to ram,而直接退出。
这就是wakeup source的使用场合。
接着我们还要看关于acpi兼容的系统,如何产生唤醒中断?(上面的acpi_button_notify一般
是由中断发出的)。这一块我们也不绕弯子直接给出(顺序执行)
acpi_add_single_object acpi_bus_get_wakeup_device_flags acpi_evaluate_object(handle, "_PRW", NULL, &buffer); element = &(package->package.elements[0]); wakeup->gpe_number = element->integer.value; element = &(package->package.elements[1]); wakeup->sleep_state = element->integer.value; acpi_extract_power_resources(package, 2, &wakeup->resources); if (!acpi_match_device_ids(device, button_device_ids)) { acpi_mark_gpe_for_wake(wakeup->gpe_device, wakeup->gpe_number); device_set_wakeup_capable(&device->dev, true); return; } acpi_setup_gpe_for_wake(device->handle, wakeup->gpe_device, wakeup->gpe_number);
上面步骤,完全是遵照_PRW的格式,来获取他的信息,最重要的是获取_PRW里的gpe_num,用来设置gpe
enable位,(如果有_PRW的值,那么就将给此acpi_device挂载到acpi_wakeup_device_list链表去)
以及获取必要的信息:需要开启哪些power resource以支持唤醒中断触发功能。
在挂起前,以suspend to idle为例,会遍历具有唤醒功能的设备(位于acpi_wakeup_device_list),
调用acpi_enable_wakeup_device_power,来使能这个设备的power resouce,以及
设备这个设备的唤醒状态_DSW,最后开启对应的gpe_number,设置需要使能的标志:
int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state) { list_for_each_entry(entry, &dev->wakeup.resources, node) { struct acpi_power_resource *resource = entry->resource; acpi_power_on_unlocked(resource) } acpi_evaluate_object(dev->handle, "_DSW", &arg_list, NULL); }
acpi_set_gpe_wake_mask(dev->wakeup.gpe_device, dev->wakeup.gpe_number, ACPI_GPE_ENABLE);
ACPI_SET_BIT(gpe_register_info->enable_for_wake, (u8)register_bit);之后,在最后挂起前,根据enable_for_wake标志,使能实际的物理gpe标志位:
acpi_hw_gpe_enable_write(gpe_register_info->enable_for_wake, gpe_register_info);
整体流程可以关注suspend to idle的prepare实现:
static int acpi_freeze_prepare(void) { acpi_enable_wakeup_devices(ACPI_STATE_S0); acpi_enable_all_wakeup_gpes(); acpi_os_wait_events_complete(); enable_irq_wake(acpi_gbl_FADT.sci_interrupt); return 0; }
其中,acpi_enable_wakeup_devices会把acpi_wakeup_device_list链表的
设备,根据device->gpe_number记录需要被设置的gpe掩码,
然后acpi_enable_all_wakeup_gpes会把这些记录的gpe_number对应的GPE bit位使能(同时也把非
enable的gpe位清零了)
至此,唤醒分析完毕。最后给出第一个/proc/acpi/wakeup的代码来源:
/drivers/acpi/proc.c, 一个设备的属性是通过device_may_wakeup来显示的:
seq_printf(seq, "%c%-8s\n", dev->wakeup.flags.run_wake ? '*' : ' ', device_may_wakeup(&dev->dev) ? "enabled" : "disabled");
第二个/sys/device/platfform/i8042/serio/power/wakeup的来历,我们这里记录一下如何找到内核
对应的字段?因为wakeup实在太平常的名字了, 可以先查看/sys/device/platfform/i8042/serio/power/ 下有哪些
特殊字段名字,比如有wakeup_active_count,搜索到是在/drivers/base/power/sysfs.c中:
static struct attribute *wakeup_attrs[] = { #ifdef CONFIG_PM_SLEEP &dev_attr_wakeup.attr, &dev_attr_wakeup_count.attr, &dev_attr_wakeup_active_count.attr, &dev_attr_wakeup_abort_count.attr, &dev_attr_wakeup_expire_count.attr, &dev_attr_wakeup_active.attr, &dev_attr_wakeup_total_time_ms.attr, &dev_attr_wakeup_max_time_ms.attr, &dev_attr_wakeup_last_time_ms.attr, #ifdef CONFIG_PM_AUTOSLEEP &dev_attr_wakeup_prevent_sleep_time_ms.attr, #endif #endif NULL, };再在本文件内搜索wakeup_count(得到大体位置),再在附近搜索wakeup字段的写函数就是:
static ssize_t wake_store(struct device * dev, struct device_attribute *attr, const char * buf, size_t n) { char *cp; int len = n; if (!device_can_wakeup(dev)) return -EINVAL; cp = memchr(buf, '\n', n); if (cp) len = cp - buf; if (len == sizeof _enabled - 1 && strncmp(buf, _enabled, sizeof _enabled - 1) == 0) device_set_wakeup_enable(dev, 1); else if (len == sizeof _disabled - 1 && strncmp(buf, _disabled, sizeof _disabled - 1) == 0) device_set_wakeup_enable(dev, 0); else return -EINVAL; return n; }可以看出,就是把一个设备设置成may wake up。
相关文章推荐
- 无数据线 debug
- 数据库__SQL的四种连接-左外连接、右外连接、内连接、全连接
- 说说 XcodeGhost 这个事
- iBatis简单入门教程
- 打造自己的网络抓包工具
- 第二章:可行性研究
- java反射
- 单例模式--只有一个实例
- 怎么样才能在初入职场风生水起
- Android:倍数提高工作效率的 Android Studio 奇技
- 使用jdk自带webservice发布webservice
- 一、gradle搭建
- CALayer Animation实践
- java中date和time的区别
- Java数组过滤
- OllyDBG完美教程(超强入门级)
- 布局 能够在代码中使用的布局
- iOS UITabBar的隐藏和显示
- EF6+SQLite3数据库出现类型转换失败的问题(指定的转换无效)
- 模拟登录新浪微博(Python) - 转