162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * drivers/base/power/trace.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2006 Linus Torvalds 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Trace facility for suspend/resume problems, when none of the 862306a36Sopenharmony_ci * devices may be working. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci#define pr_fmt(fmt) "PM: " fmt 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/pm-trace.h> 1362306a36Sopenharmony_ci#include <linux/export.h> 1462306a36Sopenharmony_ci#include <linux/rtc.h> 1562306a36Sopenharmony_ci#include <linux/suspend.h> 1662306a36Sopenharmony_ci#include <linux/init.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <linux/mc146818rtc.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include "power.h" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/* 2362306a36Sopenharmony_ci * Horrid, horrid, horrid. 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * It turns out that the _only_ piece of hardware that actually 2662306a36Sopenharmony_ci * keeps its value across a hard boot (and, more importantly, the 2762306a36Sopenharmony_ci * POST init sequence) is literally the realtime clock. 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * Never mind that an RTC chip has 114 bytes (and often a whole 3062306a36Sopenharmony_ci * other bank of an additional 128 bytes) of nice SRAM that is 3162306a36Sopenharmony_ci * _designed_ to keep data - the POST will clear it. So we literally 3262306a36Sopenharmony_ci * can just use the few bytes of actual time data, which means that 3362306a36Sopenharmony_ci * we're really limited. 3462306a36Sopenharmony_ci * 3562306a36Sopenharmony_ci * It means, for example, that we can't use the seconds at all 3662306a36Sopenharmony_ci * (since the time between the hang and the boot might be more 3762306a36Sopenharmony_ci * than a minute), and we'd better not depend on the low bits of 3862306a36Sopenharmony_ci * the minutes either. 3962306a36Sopenharmony_ci * 4062306a36Sopenharmony_ci * There are the wday fields etc, but I wouldn't guarantee those 4162306a36Sopenharmony_ci * are dependable either. And if the date isn't valid, either the 4262306a36Sopenharmony_ci * hw or POST will do strange things. 4362306a36Sopenharmony_ci * 4462306a36Sopenharmony_ci * So we're left with: 4562306a36Sopenharmony_ci * - year: 0-99 4662306a36Sopenharmony_ci * - month: 0-11 4762306a36Sopenharmony_ci * - day-of-month: 1-28 4862306a36Sopenharmony_ci * - hour: 0-23 4962306a36Sopenharmony_ci * - min: (0-30)*2 5062306a36Sopenharmony_ci * 5162306a36Sopenharmony_ci * Giving us a total range of 0-16128000 (0xf61800), ie less 5262306a36Sopenharmony_ci * than 24 bits of actual data we can save across reboots. 5362306a36Sopenharmony_ci * 5462306a36Sopenharmony_ci * And if your box can't boot in less than three minutes, 5562306a36Sopenharmony_ci * you're screwed. 5662306a36Sopenharmony_ci * 5762306a36Sopenharmony_ci * Now, almost 24 bits of data is pitifully small, so we need 5862306a36Sopenharmony_ci * to be pretty dense if we want to use it for anything nice. 5962306a36Sopenharmony_ci * What we do is that instead of saving off nice readable info, 6062306a36Sopenharmony_ci * we save off _hashes_ of information that we can hopefully 6162306a36Sopenharmony_ci * regenerate after the reboot. 6262306a36Sopenharmony_ci * 6362306a36Sopenharmony_ci * In particular, this means that we might be unlucky, and hit 6462306a36Sopenharmony_ci * a case where we have a hash collision, and we end up not 6562306a36Sopenharmony_ci * being able to tell for certain exactly which case happened. 6662306a36Sopenharmony_ci * But that's hopefully unlikely. 6762306a36Sopenharmony_ci * 6862306a36Sopenharmony_ci * What we do is to take the bits we can fit, and split them 6962306a36Sopenharmony_ci * into three parts (16*997*1009 = 16095568), and use the values 7062306a36Sopenharmony_ci * for: 7162306a36Sopenharmony_ci * - 0-15: user-settable 7262306a36Sopenharmony_ci * - 0-996: file + line number 7362306a36Sopenharmony_ci * - 0-1008: device 7462306a36Sopenharmony_ci */ 7562306a36Sopenharmony_ci#define USERHASH (16) 7662306a36Sopenharmony_ci#define FILEHASH (997) 7762306a36Sopenharmony_ci#define DEVHASH (1009) 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci#define DEVSEED (7919) 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cibool pm_trace_rtc_abused __read_mostly; 8262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(pm_trace_rtc_abused); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic unsigned int dev_hash_value; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic int set_magic_time(unsigned int user, unsigned int file, unsigned int device) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci unsigned int n = user + USERHASH*(file + FILEHASH*device); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci // June 7th, 2006 9162306a36Sopenharmony_ci static struct rtc_time time = { 9262306a36Sopenharmony_ci .tm_sec = 0, 9362306a36Sopenharmony_ci .tm_min = 0, 9462306a36Sopenharmony_ci .tm_hour = 0, 9562306a36Sopenharmony_ci .tm_mday = 7, 9662306a36Sopenharmony_ci .tm_mon = 5, // June - counting from zero 9762306a36Sopenharmony_ci .tm_year = 106, 9862306a36Sopenharmony_ci .tm_wday = 3, 9962306a36Sopenharmony_ci .tm_yday = 160, 10062306a36Sopenharmony_ci .tm_isdst = 1 10162306a36Sopenharmony_ci }; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci time.tm_year = (n % 100); 10462306a36Sopenharmony_ci n /= 100; 10562306a36Sopenharmony_ci time.tm_mon = (n % 12); 10662306a36Sopenharmony_ci n /= 12; 10762306a36Sopenharmony_ci time.tm_mday = (n % 28) + 1; 10862306a36Sopenharmony_ci n /= 28; 10962306a36Sopenharmony_ci time.tm_hour = (n % 24); 11062306a36Sopenharmony_ci n /= 24; 11162306a36Sopenharmony_ci time.tm_min = (n % 20) * 3; 11262306a36Sopenharmony_ci n /= 20; 11362306a36Sopenharmony_ci mc146818_set_time(&time); 11462306a36Sopenharmony_ci pm_trace_rtc_abused = true; 11562306a36Sopenharmony_ci return n ? -1 : 0; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic unsigned int read_magic_time(void) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci struct rtc_time time; 12162306a36Sopenharmony_ci unsigned int val; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (mc146818_get_time(&time, 1000) < 0) { 12462306a36Sopenharmony_ci pr_err("Unable to read current time from RTC\n"); 12562306a36Sopenharmony_ci return 0; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci pr_info("RTC time: %ptRt, date: %ptRd\n", &time, &time); 12962306a36Sopenharmony_ci val = time.tm_year; /* 100 years */ 13062306a36Sopenharmony_ci if (val > 100) 13162306a36Sopenharmony_ci val -= 100; 13262306a36Sopenharmony_ci val += time.tm_mon * 100; /* 12 months */ 13362306a36Sopenharmony_ci val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */ 13462306a36Sopenharmony_ci val += time.tm_hour * 100 * 12 * 28; /* 24 hours */ 13562306a36Sopenharmony_ci val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */ 13662306a36Sopenharmony_ci return val; 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci/* 14062306a36Sopenharmony_ci * This is just the sdbm hash function with a user-supplied 14162306a36Sopenharmony_ci * seed and final size parameter. 14262306a36Sopenharmony_ci */ 14362306a36Sopenharmony_cistatic unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci unsigned char c; 14662306a36Sopenharmony_ci while ((c = *data++) != 0) { 14762306a36Sopenharmony_ci seed = (seed << 16) + (seed << 6) - seed + c; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci return seed % mod; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_civoid set_trace_device(struct device *dev) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci dev_hash_value = hash_string(DEVSEED, dev_name(dev), DEVHASH); 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ciEXPORT_SYMBOL(set_trace_device); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci/* 15962306a36Sopenharmony_ci * We could just take the "tracedata" index into the .tracedata 16062306a36Sopenharmony_ci * section instead. Generating a hash of the data gives us a 16162306a36Sopenharmony_ci * chance to work across kernel versions, and perhaps more 16262306a36Sopenharmony_ci * importantly it also gives us valid/invalid check (ie we will 16362306a36Sopenharmony_ci * likely not give totally bogus reports - if the hash matches, 16462306a36Sopenharmony_ci * it's not any guarantee, but it's a high _likelihood_ that 16562306a36Sopenharmony_ci * the match is valid). 16662306a36Sopenharmony_ci */ 16762306a36Sopenharmony_civoid generate_pm_trace(const void *tracedata, unsigned int user) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci unsigned short lineno = *(unsigned short *)tracedata; 17062306a36Sopenharmony_ci const char *file = *(const char **)(tracedata + 2); 17162306a36Sopenharmony_ci unsigned int user_hash_value, file_hash_value; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (!x86_platform.legacy.rtc) 17462306a36Sopenharmony_ci return; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci user_hash_value = user % USERHASH; 17762306a36Sopenharmony_ci file_hash_value = hash_string(lineno, file, FILEHASH); 17862306a36Sopenharmony_ci set_magic_time(user_hash_value, file_hash_value, dev_hash_value); 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ciEXPORT_SYMBOL(generate_pm_trace); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ciextern char __tracedata_start[], __tracedata_end[]; 18362306a36Sopenharmony_cistatic int show_file_hash(unsigned int value) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci int match; 18662306a36Sopenharmony_ci char *tracedata; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci match = 0; 18962306a36Sopenharmony_ci for (tracedata = __tracedata_start ; tracedata < __tracedata_end ; 19062306a36Sopenharmony_ci tracedata += 2 + sizeof(unsigned long)) { 19162306a36Sopenharmony_ci unsigned short lineno = *(unsigned short *)tracedata; 19262306a36Sopenharmony_ci const char *file = *(const char **)(tracedata + 2); 19362306a36Sopenharmony_ci unsigned int hash = hash_string(lineno, file, FILEHASH); 19462306a36Sopenharmony_ci if (hash != value) 19562306a36Sopenharmony_ci continue; 19662306a36Sopenharmony_ci pr_info(" hash matches %s:%u\n", file, lineno); 19762306a36Sopenharmony_ci match++; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci return match; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic int show_dev_hash(unsigned int value) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci int match = 0; 20562306a36Sopenharmony_ci struct list_head *entry; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci device_pm_lock(); 20862306a36Sopenharmony_ci entry = dpm_list.prev; 20962306a36Sopenharmony_ci while (entry != &dpm_list) { 21062306a36Sopenharmony_ci struct device * dev = to_device(entry); 21162306a36Sopenharmony_ci unsigned int hash = hash_string(DEVSEED, dev_name(dev), DEVHASH); 21262306a36Sopenharmony_ci if (hash == value) { 21362306a36Sopenharmony_ci dev_info(dev, "hash matches\n"); 21462306a36Sopenharmony_ci match++; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci entry = entry->prev; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci device_pm_unlock(); 21962306a36Sopenharmony_ci return match; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic unsigned int hash_value_early_read; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ciint show_trace_dev_match(char *buf, size_t size) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci unsigned int value = hash_value_early_read / (USERHASH * FILEHASH); 22762306a36Sopenharmony_ci int ret = 0; 22862306a36Sopenharmony_ci struct list_head *entry; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* 23162306a36Sopenharmony_ci * It's possible that multiple devices will match the hash and we can't 23262306a36Sopenharmony_ci * tell which is the culprit, so it's best to output them all. 23362306a36Sopenharmony_ci */ 23462306a36Sopenharmony_ci device_pm_lock(); 23562306a36Sopenharmony_ci entry = dpm_list.prev; 23662306a36Sopenharmony_ci while (size && entry != &dpm_list) { 23762306a36Sopenharmony_ci struct device *dev = to_device(entry); 23862306a36Sopenharmony_ci unsigned int hash = hash_string(DEVSEED, dev_name(dev), 23962306a36Sopenharmony_ci DEVHASH); 24062306a36Sopenharmony_ci if (hash == value) { 24162306a36Sopenharmony_ci int len = snprintf(buf, size, "%s\n", 24262306a36Sopenharmony_ci dev_driver_string(dev)); 24362306a36Sopenharmony_ci if (len > size) 24462306a36Sopenharmony_ci len = size; 24562306a36Sopenharmony_ci buf += len; 24662306a36Sopenharmony_ci ret += len; 24762306a36Sopenharmony_ci size -= len; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci entry = entry->prev; 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci device_pm_unlock(); 25262306a36Sopenharmony_ci return ret; 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_cistatic int 25662306a36Sopenharmony_cipm_trace_notify(struct notifier_block *nb, unsigned long mode, void *_unused) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci switch (mode) { 25962306a36Sopenharmony_ci case PM_POST_HIBERNATION: 26062306a36Sopenharmony_ci case PM_POST_SUSPEND: 26162306a36Sopenharmony_ci if (pm_trace_rtc_abused) { 26262306a36Sopenharmony_ci pm_trace_rtc_abused = false; 26362306a36Sopenharmony_ci pr_warn("Possible incorrect RTC due to pm_trace, please use 'ntpdate' or 'rdate' to reset it.\n"); 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci break; 26662306a36Sopenharmony_ci default: 26762306a36Sopenharmony_ci break; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci return 0; 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic struct notifier_block pm_trace_nb = { 27362306a36Sopenharmony_ci .notifier_call = pm_trace_notify, 27462306a36Sopenharmony_ci}; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistatic int __init early_resume_init(void) 27762306a36Sopenharmony_ci{ 27862306a36Sopenharmony_ci if (!x86_platform.legacy.rtc) 27962306a36Sopenharmony_ci return 0; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci hash_value_early_read = read_magic_time(); 28262306a36Sopenharmony_ci register_pm_notifier(&pm_trace_nb); 28362306a36Sopenharmony_ci return 0; 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_cistatic int __init late_resume_init(void) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci unsigned int val = hash_value_early_read; 28962306a36Sopenharmony_ci unsigned int user, file, dev; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci if (!x86_platform.legacy.rtc) 29262306a36Sopenharmony_ci return 0; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci user = val % USERHASH; 29562306a36Sopenharmony_ci val = val / USERHASH; 29662306a36Sopenharmony_ci file = val % FILEHASH; 29762306a36Sopenharmony_ci val = val / FILEHASH; 29862306a36Sopenharmony_ci dev = val /* % DEVHASH */; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci pr_info(" Magic number: %d:%d:%d\n", user, file, dev); 30162306a36Sopenharmony_ci show_file_hash(file); 30262306a36Sopenharmony_ci show_dev_hash(dev); 30362306a36Sopenharmony_ci return 0; 30462306a36Sopenharmony_ci} 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_cicore_initcall(early_resume_init); 30762306a36Sopenharmony_cilate_initcall(late_resume_init); 308