[知其然不知其所以然-7] 设备的唤醒

本文讨论从系统级睡眠唤醒的相关代码。内核的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);

上面步骤,完全是遵照_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。



你可能感兴趣的:([知其然不知其所以然-7] 设备的唤醒)