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:
Linus Torvalds 2006-06-24 14:27:42 -07:00
parent d384ea691f
commit eb71c87a49
6 changed files with 279 additions and 3 deletions

View File

@ -37,6 +37,13 @@ SECTIONS
RODATA
. = ALIGN(4);
__tracedata_start = .;
.tracedata : AT(ADDR(.tracedata) - LOAD_OFFSET) {
*(.tracedata)
}
__tracedata_end = .;
/* writeable */
.data : AT(ADDR(.data) - LOAD_OFFSET) { /* Data */
*(.data)

View File

@ -1,5 +1,6 @@
obj-y := shutdown.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)
EXTRA_CFLAGS += -DDEBUG

228
drivers/base/power/trace.c Normal file
View File

@ -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);

View File

@ -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. */
static inline int set_rtc_time(struct rtc_time *time)
{
unsigned long flags;
unsigned char mon, day, hrs, min, sec;
unsigned char save_control, save_freq_select;
unsigned int yrs;
@ -131,7 +132,7 @@ static inline int set_rtc_time(struct rtc_time *time)
if (yrs > 255) /* They are unsigned */
return -EINVAL;
spin_lock_irq(&rtc_lock);
spin_lock_irqsave(&rtc_lock, flags);
#ifdef CONFIG_MACH_DECSTATION
real_yrs = yrs;
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.
*/
if (yrs > 169) {
spin_unlock_irq(&rtc_lock);
spin_unlock_irqrestore(&rtc_lock, flags);
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_freq_select, RTC_FREQ_SELECT);
spin_unlock_irq(&rtc_lock);
spin_unlock_irqrestore(&rtc_lock, flags);
return 0;
}

View File

@ -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

View File

@ -36,6 +36,15 @@ config PM_DEBUG
code. This is helpful when debugging and reporting various PM bugs,
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
bool "Software Suspend"
depends on PM && SWAP && (X86 && (!SMP || SUSPEND_SMP)) || ((FRV || PPC32) && !SMP)