Add some basic resume trace facilities
Considering that there isn't a lot of hw we can depend on during resume, this is about as good as it gets. This is x86-only for now, although the basic concept (and most of the code) will certainly work on almost any platform. Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
parent
d384ea691f
commit
eb71c87a49
|
@ -37,6 +37,13 @@ SECTIONS
|
||||||
|
|
||||||
RODATA
|
RODATA
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
__tracedata_start = .;
|
||||||
|
.tracedata : AT(ADDR(.tracedata) - LOAD_OFFSET) {
|
||||||
|
*(.tracedata)
|
||||||
|
}
|
||||||
|
__tracedata_end = .;
|
||||||
|
|
||||||
/* writeable */
|
/* writeable */
|
||||||
.data : AT(ADDR(.data) - LOAD_OFFSET) { /* Data */
|
.data : AT(ADDR(.data) - LOAD_OFFSET) { /* Data */
|
||||||
*(.data)
|
*(.data)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
obj-y := shutdown.o
|
obj-y := shutdown.o
|
||||||
obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o
|
obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o
|
||||||
|
obj-$(CONFIG_PM_TRACE) += trace.o
|
||||||
|
|
||||||
ifeq ($(CONFIG_DEBUG_DRIVER),y)
|
ifeq ($(CONFIG_DEBUG_DRIVER),y)
|
||||||
EXTRA_CFLAGS += -DDEBUG
|
EXTRA_CFLAGS += -DDEBUG
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
* drivers/base/power/trace.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006 Linus Torvalds
|
||||||
|
*
|
||||||
|
* Trace facility for suspend/resume problems, when none of the
|
||||||
|
* devices may be working.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/resume-trace.h>
|
||||||
|
#include <linux/rtc.h>
|
||||||
|
|
||||||
|
#include <asm/rtc.h>
|
||||||
|
|
||||||
|
#include "power.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Horrid, horrid, horrid.
|
||||||
|
*
|
||||||
|
* It turns out that the _only_ piece of hardware that actually
|
||||||
|
* keeps its value across a hard boot (and, more importantly, the
|
||||||
|
* POST init sequence) is literally the realtime clock.
|
||||||
|
*
|
||||||
|
* Never mind that an RTC chip has 114 bytes (and often a whole
|
||||||
|
* other bank of an additional 128 bytes) of nice SRAM that is
|
||||||
|
* _designed_ to keep data - the POST will clear it. So we literally
|
||||||
|
* can just use the few bytes of actual time data, which means that
|
||||||
|
* we're really limited.
|
||||||
|
*
|
||||||
|
* It means, for example, that we can't use the seconds at all
|
||||||
|
* (since the time between the hang and the boot might be more
|
||||||
|
* than a minute), and we'd better not depend on the low bits of
|
||||||
|
* the minutes either.
|
||||||
|
*
|
||||||
|
* There are the wday fields etc, but I wouldn't guarantee those
|
||||||
|
* are dependable either. And if the date isn't valid, either the
|
||||||
|
* hw or POST will do strange things.
|
||||||
|
*
|
||||||
|
* So we're left with:
|
||||||
|
* - year: 0-99
|
||||||
|
* - month: 0-11
|
||||||
|
* - day-of-month: 1-28
|
||||||
|
* - hour: 0-23
|
||||||
|
* - min: (0-30)*2
|
||||||
|
*
|
||||||
|
* Giving us a total range of 0-16128000 (0xf61800), ie less
|
||||||
|
* than 24 bits of actual data we can save across reboots.
|
||||||
|
*
|
||||||
|
* And if your box can't boot in less than three minutes,
|
||||||
|
* you're screwed.
|
||||||
|
*
|
||||||
|
* Now, almost 24 bits of data is pitifully small, so we need
|
||||||
|
* to be pretty dense if we want to use it for anything nice.
|
||||||
|
* What we do is that instead of saving off nice readable info,
|
||||||
|
* we save off _hashes_ of information that we can hopefully
|
||||||
|
* regenerate after the reboot.
|
||||||
|
*
|
||||||
|
* In particular, this means that we might be unlucky, and hit
|
||||||
|
* a case where we have a hash collision, and we end up not
|
||||||
|
* being able to tell for certain exactly which case happened.
|
||||||
|
* But that's hopefully unlikely.
|
||||||
|
*
|
||||||
|
* What we do is to take the bits we can fit, and split them
|
||||||
|
* into three parts (16*997*1009 = 16095568), and use the values
|
||||||
|
* for:
|
||||||
|
* - 0-15: user-settable
|
||||||
|
* - 0-996: file + line number
|
||||||
|
* - 0-1008: device
|
||||||
|
*/
|
||||||
|
#define USERHASH (16)
|
||||||
|
#define FILEHASH (997)
|
||||||
|
#define DEVHASH (1009)
|
||||||
|
|
||||||
|
#define DEVSEED (7919)
|
||||||
|
|
||||||
|
static unsigned int dev_hash_value;
|
||||||
|
|
||||||
|
static int set_magic_time(unsigned int user, unsigned int file, unsigned int device)
|
||||||
|
{
|
||||||
|
unsigned int n = user + USERHASH*(file + FILEHASH*device);
|
||||||
|
|
||||||
|
// June 7th, 2006
|
||||||
|
static struct rtc_time time = {
|
||||||
|
.tm_sec = 0,
|
||||||
|
.tm_min = 0,
|
||||||
|
.tm_hour = 0,
|
||||||
|
.tm_mday = 7,
|
||||||
|
.tm_mon = 5, // June - counting from zero
|
||||||
|
.tm_year = 106,
|
||||||
|
.tm_wday = 3,
|
||||||
|
.tm_yday = 160,
|
||||||
|
.tm_isdst = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
time.tm_year = (n % 100);
|
||||||
|
n /= 100;
|
||||||
|
time.tm_mon = (n % 12);
|
||||||
|
n /= 12;
|
||||||
|
time.tm_mday = (n % 28) + 1;
|
||||||
|
n /= 28;
|
||||||
|
time.tm_hour = (n % 24);
|
||||||
|
n /= 24;
|
||||||
|
time.tm_min = (n % 20) * 3;
|
||||||
|
n /= 20;
|
||||||
|
set_rtc_time(&time);
|
||||||
|
return n ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int read_magic_time(void)
|
||||||
|
{
|
||||||
|
struct rtc_time time;
|
||||||
|
unsigned int val;
|
||||||
|
|
||||||
|
get_rtc_time(&time);
|
||||||
|
printk("Time: %2d:%02d:%02d Date: %02d/%02d/%02d\n",
|
||||||
|
time.tm_hour, time.tm_min, time.tm_sec,
|
||||||
|
time.tm_mon, time.tm_mday, time.tm_year);
|
||||||
|
val = time.tm_year; /* 100 years */
|
||||||
|
if (val > 100)
|
||||||
|
val -= 100;
|
||||||
|
val += time.tm_mon * 100; /* 12 months */
|
||||||
|
val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */
|
||||||
|
val += time.tm_hour * 100 * 12 * 28; /* 24 hours */
|
||||||
|
val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is just the sdbm hash function with a user-supplied
|
||||||
|
* seed and final size parameter.
|
||||||
|
*/
|
||||||
|
static unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod)
|
||||||
|
{
|
||||||
|
unsigned char c;
|
||||||
|
while ((c = *data++) != 0) {
|
||||||
|
seed = (seed << 16) + (seed << 6) - seed + c;
|
||||||
|
}
|
||||||
|
return seed % mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_trace_device(struct device *dev)
|
||||||
|
{
|
||||||
|
dev_hash_value = hash_string(DEVSEED, dev->bus_id, DEVHASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We could just take the "tracedata" index into the .tracedata
|
||||||
|
* section instead. Generating a hash of the data gives us a
|
||||||
|
* chance to work across kernel versions, and perhaps more
|
||||||
|
* importantly it also gives us valid/invalid check (ie we will
|
||||||
|
* likely not give totally bogus reports - if the hash matches,
|
||||||
|
* it's not any guarantee, but it's a high _likelihood_ that
|
||||||
|
* the match is valid).
|
||||||
|
*/
|
||||||
|
void generate_resume_trace(void *tracedata, unsigned int user)
|
||||||
|
{
|
||||||
|
unsigned short lineno = *(unsigned short *)tracedata;
|
||||||
|
const char *file = *(const char **)(tracedata + 2);
|
||||||
|
unsigned int user_hash_value, file_hash_value;
|
||||||
|
|
||||||
|
user_hash_value = user % USERHASH;
|
||||||
|
file_hash_value = hash_string(lineno, file, FILEHASH);
|
||||||
|
set_magic_time(user_hash_value, file_hash_value, dev_hash_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern char __tracedata_start, __tracedata_end;
|
||||||
|
static int show_file_hash(unsigned int value)
|
||||||
|
{
|
||||||
|
int match;
|
||||||
|
char *tracedata;
|
||||||
|
|
||||||
|
match = 0;
|
||||||
|
for (tracedata = &__tracedata_start ; tracedata < &__tracedata_end ; tracedata += 6) {
|
||||||
|
unsigned short lineno = *(unsigned short *)tracedata;
|
||||||
|
const char *file = *(const char **)(tracedata + 2);
|
||||||
|
unsigned int hash = hash_string(lineno, file, FILEHASH);
|
||||||
|
if (hash != value)
|
||||||
|
continue;
|
||||||
|
printk(" hash matches %s:%u\n", file, lineno);
|
||||||
|
match++;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int show_dev_hash(unsigned int value)
|
||||||
|
{
|
||||||
|
int match = 0;
|
||||||
|
struct list_head * entry = dpm_active.prev;
|
||||||
|
|
||||||
|
while (entry != &dpm_active) {
|
||||||
|
struct device * dev = to_device(entry);
|
||||||
|
unsigned int hash = hash_string(DEVSEED, dev->bus_id, DEVHASH);
|
||||||
|
if (hash == value) {
|
||||||
|
printk(" hash matches device %s\n", dev->bus_id);
|
||||||
|
match++;
|
||||||
|
}
|
||||||
|
entry = entry->prev;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int hash_value_early_read;
|
||||||
|
|
||||||
|
static int early_resume_init(void)
|
||||||
|
{
|
||||||
|
hash_value_early_read = read_magic_time();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int late_resume_init(void)
|
||||||
|
{
|
||||||
|
unsigned int val = hash_value_early_read;
|
||||||
|
unsigned int user, file, dev;
|
||||||
|
|
||||||
|
user = val % USERHASH;
|
||||||
|
val = val / USERHASH;
|
||||||
|
file = val % FILEHASH;
|
||||||
|
val = val / FILEHASH;
|
||||||
|
dev = val /* % DEVHASH */;
|
||||||
|
|
||||||
|
printk(" Magic number: %d:%d:%d\n", user, file, dev);
|
||||||
|
show_file_hash(file);
|
||||||
|
show_dev_hash(dev);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
core_initcall(early_resume_init);
|
||||||
|
late_initcall(late_resume_init);
|
|
@ -114,6 +114,7 @@ static inline unsigned int get_rtc_time(struct rtc_time *time)
|
||||||
/* Set the current date and time in the real time clock. */
|
/* Set the current date and time in the real time clock. */
|
||||||
static inline int set_rtc_time(struct rtc_time *time)
|
static inline int set_rtc_time(struct rtc_time *time)
|
||||||
{
|
{
|
||||||
|
unsigned long flags;
|
||||||
unsigned char mon, day, hrs, min, sec;
|
unsigned char mon, day, hrs, min, sec;
|
||||||
unsigned char save_control, save_freq_select;
|
unsigned char save_control, save_freq_select;
|
||||||
unsigned int yrs;
|
unsigned int yrs;
|
||||||
|
@ -131,7 +132,7 @@ static inline int set_rtc_time(struct rtc_time *time)
|
||||||
if (yrs > 255) /* They are unsigned */
|
if (yrs > 255) /* They are unsigned */
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
spin_lock_irq(&rtc_lock);
|
spin_lock_irqsave(&rtc_lock, flags);
|
||||||
#ifdef CONFIG_MACH_DECSTATION
|
#ifdef CONFIG_MACH_DECSTATION
|
||||||
real_yrs = yrs;
|
real_yrs = yrs;
|
||||||
leap_yr = ((!((yrs + 1900) % 4) && ((yrs + 1900) % 100)) ||
|
leap_yr = ((!((yrs + 1900) % 4) && ((yrs + 1900) % 100)) ||
|
||||||
|
@ -152,7 +153,7 @@ static inline int set_rtc_time(struct rtc_time *time)
|
||||||
* whether the chip is in binary mode or not.
|
* whether the chip is in binary mode or not.
|
||||||
*/
|
*/
|
||||||
if (yrs > 169) {
|
if (yrs > 169) {
|
||||||
spin_unlock_irq(&rtc_lock);
|
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +188,7 @@ static inline int set_rtc_time(struct rtc_time *time)
|
||||||
CMOS_WRITE(save_control, RTC_CONTROL);
|
CMOS_WRITE(save_control, RTC_CONTROL);
|
||||||
CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
|
CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
|
||||||
|
|
||||||
spin_unlock_irq(&rtc_lock);
|
spin_unlock_irqrestore(&rtc_lock, flags);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef RESUME_TRACE_H
|
||||||
|
#define RESUME_TRACE_H
|
||||||
|
|
||||||
|
#ifdef CONFIG_PM_TRACE
|
||||||
|
|
||||||
|
struct device;
|
||||||
|
extern void set_trace_device(struct device *);
|
||||||
|
extern void generate_resume_trace(void *tracedata, unsigned int user);
|
||||||
|
|
||||||
|
#define TRACE_DEVICE(dev) set_trace_device(dev)
|
||||||
|
#define TRACE_RESUME(user) do { \
|
||||||
|
void *tracedata; \
|
||||||
|
asm volatile("movl $1f,%0\n" \
|
||||||
|
".section .tracedata,\"a\"\n" \
|
||||||
|
"1:\t.word %c1\n" \
|
||||||
|
"\t.long %c2\n" \
|
||||||
|
".previous" \
|
||||||
|
:"=r" (tracedata) \
|
||||||
|
: "i" (__LINE__), "i" (__FILE__)); \
|
||||||
|
generate_resume_trace(tracedata, user); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define TRACE_DEVICE(dev) do { } while (0)
|
||||||
|
#define TRACE_RESUME(dev) do { } while (0)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
|
@ -36,6 +36,15 @@ config PM_DEBUG
|
||||||
code. This is helpful when debugging and reporting various PM bugs,
|
code. This is helpful when debugging and reporting various PM bugs,
|
||||||
like suspend support.
|
like suspend support.
|
||||||
|
|
||||||
|
config PM_TRACE
|
||||||
|
bool "Suspend/resume event tracing"
|
||||||
|
depends on PM && PM_DEBUG && X86
|
||||||
|
default y
|
||||||
|
---help---
|
||||||
|
This enables some cheesy code to save the last PM event point in the
|
||||||
|
RTC across reboots, so that you can debug a machine that just hangs
|
||||||
|
during suspend (or more commonly, during resume).
|
||||||
|
|
||||||
config SOFTWARE_SUSPEND
|
config SOFTWARE_SUSPEND
|
||||||
bool "Software Suspend"
|
bool "Software Suspend"
|
||||||
depends on PM && SWAP && (X86 && (!SMP || SUSPEND_SMP)) || ((FRV || PPC32) && !SMP)
|
depends on PM && SWAP && (X86 && (!SMP || SUSPEND_SMP)) || ((FRV || PPC32) && !SMP)
|
||||||
|
|
Loading…
Reference in New Issue