本文讨论从系统级睡眠唤醒的相关代码。内核的device.txt文档也有关于设备唤醒的介绍。
首先我们来看两个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);
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。