cregit-Linux how code gets into the kernel

Release 4.11 drivers/base/power/trace.c

/*
 * 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/pm-trace.h>
#include <linux/export.h>
#include <linux/rtc.h>
#include <linux/suspend.h>

#include <linux/mc146818rtc.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)


bool pm_trace_rtc_abused __read_mostly;

EXPORT_SYMBOL_GPL(pm_trace_rtc_abused);


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; mc146818_set_time(&time); pm_trace_rtc_abused = true; return n ? -1 : 0; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds17597.22%133.33%
Chen Yu42.22%133.33%
Arnd Bergmann10.56%133.33%
Total180100.00%3100.00%


static unsigned int read_magic_time(void) { struct rtc_time time; unsigned int val; mc146818_get_time(&time); pr_info("RTC time: %2d:%02d:%02d, date: %02d/%02d/%02d\n", time.tm_hour, time.tm_min, time.tm_sec, time.tm_mon + 1, time.tm_mday, time.tm_year % 100); 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; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds12594.70%120.00%
Rafael J. Wysocki53.79%240.00%
Mandeep Singh Baines10.76%120.00%
Arnd Bergmann10.76%120.00%
Total132100.00%5100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds61100.00%1100.00%
Total61100.00%1100.00%


void set_trace_device(struct device *dev) { dev_hash_value = hash_string(DEVSEED, dev_name(dev), DEVHASH); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds2187.50%150.00%
Kay Sievers312.50%150.00%
Total24100.00%2100.00%

EXPORT_SYMBOL(set_trace_device); /* * 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_pm_trace(const 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); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds7497.37%133.33%
Jan Beulich11.32%133.33%
Fu Zhonghui11.32%133.33%
Total76100.00%3100.00%

EXPORT_SYMBOL(generate_pm_trace); 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 += 2 + sizeof(unsigned long)) { 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; pr_info(" hash matches %s:%u\n", file, lineno); match++; } return match; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds10092.59%133.33%
Nigel Cunningham76.48%133.33%
Mandeep Singh Baines10.93%133.33%
Total108100.00%3100.00%


static int show_dev_hash(unsigned int value) { int match = 0; struct list_head *entry; device_pm_lock(); entry = dpm_list.prev; while (entry != &dpm_list) { struct device * dev = to_device(entry); unsigned int hash = hash_string(DEVSEED, dev_name(dev), DEVHASH); if (hash == value) { dev_info(dev, "hash matches\n"); match++; } entry = entry->prev; } device_pm_unlock(); return match; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds7680.85%120.00%
James Hogan1010.64%120.00%
Kay Sievers33.19%120.00%
Greg Kroah-Hartman33.19%120.00%
Rafael J. Wysocki22.13%120.00%
Total94100.00%5100.00%

static unsigned int hash_value_early_read;
int show_trace_dev_match(char *buf, size_t size) { unsigned int value = hash_value_early_read / (USERHASH * FILEHASH); int ret = 0; struct list_head *entry; /* * It's possible that multiple devices will match the hash and we can't * tell which is the culprit, so it's best to output them all. */ device_pm_lock(); entry = dpm_list.prev; while (size && entry != &dpm_list) { struct device *dev = to_device(entry); unsigned int hash = hash_string(DEVSEED, dev_name(dev), DEVHASH); if (hash == value) { int len = snprintf(buf, size, "%s\n", dev_driver_string(dev)); if (len > size) len = size; buf += len; ret += len; size -= len; } entry = entry->prev; } device_pm_unlock(); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
James Hogan140100.00%1100.00%
Total140100.00%1100.00%


static int pm_trace_notify(struct notifier_block *nb, unsigned long mode, void *_unused) { switch (mode) { case PM_POST_HIBERNATION: case PM_POST_SUSPEND: if (pm_trace_rtc_abused) { pm_trace_rtc_abused = false; pr_warn("Possible incorrect RTC due to pm_trace, please use 'ntpdate' or 'rdate' to reset it.\n"); } break; default: break; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Chen Yu52100.00%1100.00%
Total52100.00%1100.00%

static struct notifier_block pm_trace_nb = { .notifier_call = pm_trace_notify, };
static int early_resume_init(void) { hash_value_early_read = read_magic_time(); register_pm_notifier(&pm_trace_nb); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds1672.73%150.00%
Chen Yu627.27%150.00%
Total22100.00%2100.00%


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 */; pr_info(" Magic number: %d:%d:%d\n", user, file, dev); show_file_hash(file); show_dev_hash(dev); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds7498.67%150.00%
Mandeep Singh Baines11.33%150.00%
Total75100.00%2100.00%

core_initcall(early_resume_init); late_initcall(late_resume_init);

Overall Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds77873.33%16.25%
James Hogan15014.14%212.50%
Chen Yu868.11%16.25%
Nigel Cunningham161.51%16.25%
Rafael J. Wysocki70.66%318.75%
Kay Sievers60.57%16.25%
Fu Zhonghui30.28%16.25%
Greg Kroah-Hartman30.28%16.25%
Mandeep Singh Baines30.28%16.25%
Arnd Bergmann30.28%16.25%
Paul Gortmaker30.28%16.25%
Eric Biggers20.19%16.25%
Jan Beulich10.09%16.25%
Total1061100.00%16100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.