Commit eb71c87a authored by Linus Torvalds's avatar Linus Torvalds

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: default avatarLinus Torvalds <torvalds@osdl.org>
parent d384ea69
...@@ -37,6 +37,13 @@ SECTIONS ...@@ -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)
......
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
......
/*
* 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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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;
} }
......
#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 ...@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment