Files
kernel_google_b1c1/include/linux/wakeup_reason.h
Kelly Rossmoyer a26ead8e2a irq: wakeup logging for bad wakes
Multiple device families have hit issues recently in which recurring,
undesired kernel wakeups occur that only get logged as "unknown" by
BatteryStats.  Investigation has revealed that this can occur for a few
different reasons, including:
* driver configuring IRQ with IRQF_NO_SUSPEND when it is actually
  wakeup capable and should use enable_irq_wake() instead
* driver configuring IRQ with both IRQF_NO_SUSPEND and enable_irq_wake()
  even though the kernel documentation clearly states that that is not
  appropriate
* assertion of unmapped HW IRQs
* wakeups handled by TZ (e.g. watchdog, which leaves no evidence of the
  wake reason once TZ handling has completed)

When these wakeups occur, the wakeup_reason logging in the kernel
doesn't capture the precipitating event, which results in the sysfs node
containing last wake reason remaining empty.  When BatteryStats sees
that, it logs the wakeup reason as "unknown". The only other two
formats that it supports (when a reason is present) are:
* "Abort: <reason>" - indicates that an attempt to suspend was aborted
  for the specified reason
* "<IRQ> <name>" - indicates that an interrupt with the specified
  virtual (_not_ HW) IRQ# and action name woke the kernel

This CL includes changes that address the second and third scenarios
listed above (i.e. _not_ the case where a driver uses IRQF_NO_SUSPEND
when it should use enable_irq_wake()).  It adds a new
log_bad_wake_reason() function to our existing wakeup_reason code, adds
a call to that function from the GIC driver when an unmapped HW IRQ is
detected (which should pretty much never occur on a healthy device),
and adds a call to irq_may_run() - which executes for virtually _every_
interrupt - in cases where a non-wakeup-armed interrupt turns out to
be misconfigured with IRQF_NO_SUSPEND and enable_irq_wake().

Bug: 127863679
Bug: 127862997
Test: STILL UNDER TEST, DO NOT MERGE
Change-Id: I838c77a19767b66820cc9dc3715923533c0e2f43
Signed-off-by: Kelly Rossmoyer <krossmo@google.com>
Signed-off-by: Thierry Strudel <tstrudel@google.com>
2019-04-09 14:28:30 -07:00

106 lines
3.2 KiB
C

/*
* include/linux/wakeup_reason.h
*
* Logs the reason which caused the kernel to resume
* from the suspend mode.
*
* Copyright (C) 2014 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_WAKEUP_REASON_H
#define _LINUX_WAKEUP_REASON_H
#include <linux/types.h>
#include <linux/completion.h>
#define MAX_SUSPEND_ABORT_LEN 256
struct wakeup_irq_node {
/* @leaf is a linked list of all leaf nodes in the interrupts trees.
*/
struct list_head next;
/* @irq: IRQ number of this node.
*/
int irq;
struct irq_desc *desc;
/* @siblings contains the list of irq nodes at the same depth; at a
* depth of zero, this is the list of base wakeup interrupts.
*/
struct list_head siblings;
/* @parent: only one node in a siblings list has a pointer to the
* parent; that node is the head of the list of siblings.
*/
struct wakeup_irq_node *parent;
/* @child: any node can have one child
*/
struct wakeup_irq_node *child;
/* @handled: this flag is set to true when the interrupt handler (one of
* handle_.*_irq in kernel/irq/handle.c) for this node gets called; it is set
* to false otherwise. We use this flag to determine whether a subtree rooted
* at a node has been handled. When all trees rooted at
* base-wakeup-interrupt nodes have been handled, we stop logging
* potential wakeup interrupts, and construct the list of actual
* wakeups from the leaves of these trees.
*/
bool handled;
};
#ifdef CONFIG_DEDUCE_WAKEUP_REASONS
/* Called in the resume path, with interrupts and nonboot cpus disabled; on
* need for a spinlock.
*/
static inline void start_logging_wakeup_reasons(void)
{
extern bool log_wakeups;
extern struct completion wakeups_completion;
ACCESS_ONCE(log_wakeups) = true;
init_completion(&wakeups_completion);
}
static inline bool logging_wakeup_reasons_nosync(void)
{
extern bool log_wakeups;
return ACCESS_ONCE(log_wakeups);
}
static inline bool logging_wakeup_reasons(void)
{
smp_rmb();
return logging_wakeup_reasons_nosync();
}
bool log_possible_wakeup_reason(int irq,
struct irq_desc *desc,
bool (*handler)(struct irq_desc *));
#else
static inline void start_logging_wakeup_reasons(void) {}
static inline bool logging_wakeup_reasons_nosync(void) { return false; }
static inline bool logging_wakeup_reasons(void) { return false; }
static inline bool log_possible_wakeup_reason(int irq,
struct irq_desc *desc,
bool (*handler)(struct irq_desc *)) { return true; }
#endif
const struct list_head*
get_wakeup_reasons(unsigned long timeout, struct list_head *unfinished);
void log_base_wakeup_reason(int irq);
void clear_wakeup_reasons(void);
void log_suspend_abort_reason(const char *fmt, ...);
void log_bad_wake_reason(const char *fmt, ...);
int check_wakeup_reason(int irq);
#endif /* _LINUX_WAKEUP_REASON_H */