mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
power: add partial-resume framework
Partial resume refers to the concept of not waking up userspace when the kernel comes out of suspend for certain types of events that we wish to discard. An example is a network packet that can be disacarded in the kernel, or spurious wakeup event that we wish to ignore. Partial resume allows drivers to register callbacks, one one hand, and provides hooks into the PM's suspend/resume mechanism, on the other. When a device resumes from suspend, the core suspend/resume code invokes partialresume to check to see if the set of wakeup interrupts all have matching handlers. If this is not the case, the PM subsystem can continue to resume as before. If all of the wakeup sources have matching handlers, then those are invoked in turn (and can block), and if all of them reach consensus that the reason for the wakeup can be ignored, they say so to the PM subsystem, which goes right back into suspend. This latter support is implemented in a separate change. Signed-off-by: Iliyan Malchev <malchev@google.com> Change-Id: Id50940bb22a550b413412264508d259f7121d442
This commit is contained in:
parent
7e87a4dc87
commit
4e0c8780cc
4 changed files with 256 additions and 0 deletions
55
include/linux/partialresume.h
Normal file
55
include/linux/partialresume.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/* include/linux/partialresume.h
|
||||
*
|
||||
* Copyright (C) 2015 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_PARTIALRESUME_H
|
||||
#define _LINUX_PARTIALRESUME_H
|
||||
|
||||
#ifdef CONFIG_PARTIALRESUME
|
||||
|
||||
#include <linux/list.h>
|
||||
|
||||
struct partial_resume_stats {
|
||||
unsigned total;
|
||||
unsigned total_yes;
|
||||
};
|
||||
|
||||
struct partial_resume {
|
||||
struct list_head next_handler;
|
||||
struct list_head next_match;
|
||||
int irq;
|
||||
struct partial_resume_stats stats;
|
||||
void *private;
|
||||
bool (*partial_resume)(struct partial_resume *);
|
||||
};
|
||||
|
||||
int register_partial_resume(struct partial_resume *handler);
|
||||
void unregister_partial_resume(struct partial_resume *handler);
|
||||
|
||||
bool suspend_again_match(const struct list_head *irqs,
|
||||
const struct list_head *unfinished);
|
||||
bool suspend_again_consensus(void);
|
||||
|
||||
#else /* !CONFIG_PARTIALRESUME */
|
||||
|
||||
struct partial_resume;
|
||||
static inline int register_partial_resume(struct partial_resume *handler) { return 0; }
|
||||
static inline void unregister_partial_resume(struct partial_resume *handler) {}
|
||||
static inline bool suspend_again_match(const struct list_head *irqs) { return false; }
|
||||
static inline bool suspend_again_consensus(void) { return false; }
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -277,3 +277,14 @@ config SUSPEND_TIME
|
|||
Prints the time spent in suspend in the kernel log, and
|
||||
keeps statistics on the time spent in suspend in
|
||||
/sys/kernel/debug/suspend_time
|
||||
|
||||
config PARTIALRESUME
|
||||
bool "Partial-resume framework"
|
||||
---help---
|
||||
Provides hooks for drivers to register partial-resume handlers.
|
||||
Similar to suspend_again support already in kernel, except that it
|
||||
operates with kernel threads unfrozen and userspace still frozen,
|
||||
allowing callbacks to block on work queues and kernel threads.
|
||||
Partial resume will occur only if all wakeup sources have
|
||||
partial-resume handlers associated with them, and they all return
|
||||
true.
|
||||
|
|
|
@ -15,3 +15,4 @@ obj-$(CONFIG_PM_WAKELOCKS) += wakelock.o
|
|||
obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o
|
||||
|
||||
obj-$(CONFIG_SUSPEND) += wakeup_reason.o
|
||||
obj-$(CONFIG_PARTIALRESUME) += partialresume.o
|
||||
|
|
189
kernel/power/partialresume.c
Normal file
189
kernel/power/partialresume.c
Normal file
|
@ -0,0 +1,189 @@
|
|||
/* kernel/power/partialresume.c
|
||||
*
|
||||
* Copyright (C) 2015 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/partialresume.h>
|
||||
#include <linux/wakeup_reason.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
|
||||
static DEFINE_MUTEX(pr_handlers_lock);
|
||||
static LIST_HEAD(pr_handlers);
|
||||
static LIST_HEAD(pr_matches);
|
||||
static struct partial_resume_stats match_stats;
|
||||
static struct partial_resume_stats consensus_stats;
|
||||
static struct kobject *partialresume;
|
||||
|
||||
bool suspend_again_match(const struct list_head *irqs,
|
||||
const struct list_head *unfinished)
|
||||
{
|
||||
const struct wakeup_irq_node *i;
|
||||
struct partial_resume *h, *match;
|
||||
|
||||
INIT_LIST_HEAD(&pr_matches);
|
||||
|
||||
match_stats.total++;
|
||||
|
||||
if (!irqs || list_empty(irqs))
|
||||
return false;
|
||||
|
||||
list_for_each_entry(i, irqs, next) {
|
||||
match = NULL;
|
||||
list_for_each_entry(h, &pr_handlers, next_handler) {
|
||||
if (i->irq == h->irq) {
|
||||
match = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
pr_debug("%s: wakeup irq %d does not have a handler\n", __func__, i->irq);
|
||||
return false;
|
||||
}
|
||||
list_add(&match->next_match, &pr_matches);
|
||||
}
|
||||
|
||||
match_stats.total_yes++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool suspend_again_consensus(void)
|
||||
{
|
||||
struct partial_resume *h;
|
||||
|
||||
BUG_ON(list_empty(&pr_matches));
|
||||
list_for_each_entry(h, &pr_matches, next_match) {
|
||||
h->stats.total++;
|
||||
if (!h->partial_resume(h)) {
|
||||
pr_debug("%s: partial-resume for %d: false\n", __func__, h->irq);
|
||||
return false;
|
||||
}
|
||||
h->stats.total_yes++;
|
||||
pr_debug("%s: partial-resume for %d: true\n", __func__, h->irq);
|
||||
}
|
||||
|
||||
consensus_stats.total_yes++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int register_partial_resume(struct partial_resume *handler)
|
||||
{
|
||||
struct partial_resume *e;
|
||||
|
||||
if (!handler || !handler->irq || !handler->partial_resume)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&pr_handlers_lock);
|
||||
list_for_each_entry(e, &pr_handlers, next_handler) {
|
||||
if (e->irq == handler->irq) {
|
||||
if (e->partial_resume == handler->partial_resume)
|
||||
return 0;
|
||||
pr_err("%s: error registering %pF for irq %d: "\
|
||||
"%pF already registered\n",
|
||||
__func__,
|
||||
handler->partial_resume,
|
||||
e->irq,
|
||||
e->partial_resume);
|
||||
mutex_unlock(&pr_handlers_lock);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
list_add(&handler->next_handler, &pr_handlers);
|
||||
mutex_unlock(&pr_handlers_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void unregister_partial_resume(struct partial_resume *handler)
|
||||
{
|
||||
mutex_lock(&pr_handlers_lock);
|
||||
list_del(&handler->next_handler);
|
||||
mutex_unlock(&pr_handlers_lock);
|
||||
}
|
||||
|
||||
|
||||
static ssize_t partialresume_stats_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
ssize_t offset = 0;
|
||||
struct partial_resume *h;
|
||||
|
||||
offset += sprintf(buf + offset, "global: %d %d %d \n",
|
||||
match_stats.total,
|
||||
match_stats.total_yes,
|
||||
consensus_stats.total_yes);
|
||||
|
||||
mutex_lock(&pr_handlers_lock);
|
||||
|
||||
list_for_each_entry(h, &pr_handlers, next_handler) {
|
||||
offset += sprintf(buf + offset, "%d: %d %d\n",
|
||||
h->irq,
|
||||
h->stats.total,
|
||||
h->stats.total_yes);
|
||||
}
|
||||
|
||||
mutex_unlock(&pr_handlers_lock);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static struct kobj_attribute partialresume_stats = __ATTR_RO(partialresume_stats);
|
||||
|
||||
static struct attribute *partialresume_stats_attrs[] = {
|
||||
&partialresume_stats.attr,
|
||||
NULL,
|
||||
};
|
||||
static struct attribute_group partialresume_stats_attr_group = {
|
||||
.attrs = partialresume_stats_attrs,
|
||||
};
|
||||
|
||||
int __init partial_resume_init(void)
|
||||
{
|
||||
int rc = -EIO;
|
||||
|
||||
partialresume = kobject_create_and_add("partialresume", kernel_kobj);
|
||||
if (!partialresume) {
|
||||
pr_warning("%s: failed to create a sysfs kobject\n", __func__);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rc = sysfs_create_group(partialresume, &partialresume_stats_attr_group);
|
||||
if (rc) {
|
||||
pr_warning("%s: failed to create a sysfs group\n", __func__);
|
||||
goto fail_kobject_put;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
#if 0
|
||||
fail_remove_group:
|
||||
sysfs_remove_group(partialresume, &partialresume_stats_attr_group);
|
||||
#endif
|
||||
fail_kobject_put:
|
||||
kobject_put(partialresume);
|
||||
fail:
|
||||
return rc;
|
||||
}
|
||||
|
||||
subsys_initcall(partial_resume_init);
|
Loading…
Reference in a new issue