18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * drivers/base/power/trace.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2006 Linus Torvalds 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Trace facility for suspend/resume problems, when none of the 88c2ecf20Sopenharmony_ci * devices may be working. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "PM: " fmt 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/pm-trace.h> 138c2ecf20Sopenharmony_ci#include <linux/export.h> 148c2ecf20Sopenharmony_ci#include <linux/rtc.h> 158c2ecf20Sopenharmony_ci#include <linux/suspend.h> 168c2ecf20Sopenharmony_ci#include <linux/init.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include <linux/mc146818rtc.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include "power.h" 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/* 238c2ecf20Sopenharmony_ci * Horrid, horrid, horrid. 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * It turns out that the _only_ piece of hardware that actually 268c2ecf20Sopenharmony_ci * keeps its value across a hard boot (and, more importantly, the 278c2ecf20Sopenharmony_ci * POST init sequence) is literally the realtime clock. 288c2ecf20Sopenharmony_ci * 298c2ecf20Sopenharmony_ci * Never mind that an RTC chip has 114 bytes (and often a whole 308c2ecf20Sopenharmony_ci * other bank of an additional 128 bytes) of nice SRAM that is 318c2ecf20Sopenharmony_ci * _designed_ to keep data - the POST will clear it. So we literally 328c2ecf20Sopenharmony_ci * can just use the few bytes of actual time data, which means that 338c2ecf20Sopenharmony_ci * we're really limited. 348c2ecf20Sopenharmony_ci * 358c2ecf20Sopenharmony_ci * It means, for example, that we can't use the seconds at all 368c2ecf20Sopenharmony_ci * (since the time between the hang and the boot might be more 378c2ecf20Sopenharmony_ci * than a minute), and we'd better not depend on the low bits of 388c2ecf20Sopenharmony_ci * the minutes either. 398c2ecf20Sopenharmony_ci * 408c2ecf20Sopenharmony_ci * There are the wday fields etc, but I wouldn't guarantee those 418c2ecf20Sopenharmony_ci * are dependable either. And if the date isn't valid, either the 428c2ecf20Sopenharmony_ci * hw or POST will do strange things. 438c2ecf20Sopenharmony_ci * 448c2ecf20Sopenharmony_ci * So we're left with: 458c2ecf20Sopenharmony_ci * - year: 0-99 468c2ecf20Sopenharmony_ci * - month: 0-11 478c2ecf20Sopenharmony_ci * - day-of-month: 1-28 488c2ecf20Sopenharmony_ci * - hour: 0-23 498c2ecf20Sopenharmony_ci * - min: (0-30)*2 508c2ecf20Sopenharmony_ci * 518c2ecf20Sopenharmony_ci * Giving us a total range of 0-16128000 (0xf61800), ie less 528c2ecf20Sopenharmony_ci * than 24 bits of actual data we can save across reboots. 538c2ecf20Sopenharmony_ci * 548c2ecf20Sopenharmony_ci * And if your box can't boot in less than three minutes, 558c2ecf20Sopenharmony_ci * you're screwed. 568c2ecf20Sopenharmony_ci * 578c2ecf20Sopenharmony_ci * Now, almost 24 bits of data is pitifully small, so we need 588c2ecf20Sopenharmony_ci * to be pretty dense if we want to use it for anything nice. 598c2ecf20Sopenharmony_ci * What we do is that instead of saving off nice readable info, 608c2ecf20Sopenharmony_ci * we save off _hashes_ of information that we can hopefully 618c2ecf20Sopenharmony_ci * regenerate after the reboot. 628c2ecf20Sopenharmony_ci * 638c2ecf20Sopenharmony_ci * In particular, this means that we might be unlucky, and hit 648c2ecf20Sopenharmony_ci * a case where we have a hash collision, and we end up not 658c2ecf20Sopenharmony_ci * being able to tell for certain exactly which case happened. 668c2ecf20Sopenharmony_ci * But that's hopefully unlikely. 678c2ecf20Sopenharmony_ci * 688c2ecf20Sopenharmony_ci * What we do is to take the bits we can fit, and split them 698c2ecf20Sopenharmony_ci * into three parts (16*997*1009 = 16095568), and use the values 708c2ecf20Sopenharmony_ci * for: 718c2ecf20Sopenharmony_ci * - 0-15: user-settable 728c2ecf20Sopenharmony_ci * - 0-996: file + line number 738c2ecf20Sopenharmony_ci * - 0-1008: device 748c2ecf20Sopenharmony_ci */ 758c2ecf20Sopenharmony_ci#define USERHASH (16) 768c2ecf20Sopenharmony_ci#define FILEHASH (997) 778c2ecf20Sopenharmony_ci#define DEVHASH (1009) 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci#define DEVSEED (7919) 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cibool pm_trace_rtc_abused __read_mostly; 828c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pm_trace_rtc_abused); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic unsigned int dev_hash_value; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int set_magic_time(unsigned int user, unsigned int file, unsigned int device) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci unsigned int n = user + USERHASH*(file + FILEHASH*device); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci // June 7th, 2006 918c2ecf20Sopenharmony_ci static struct rtc_time time = { 928c2ecf20Sopenharmony_ci .tm_sec = 0, 938c2ecf20Sopenharmony_ci .tm_min = 0, 948c2ecf20Sopenharmony_ci .tm_hour = 0, 958c2ecf20Sopenharmony_ci .tm_mday = 7, 968c2ecf20Sopenharmony_ci .tm_mon = 5, // June - counting from zero 978c2ecf20Sopenharmony_ci .tm_year = 106, 988c2ecf20Sopenharmony_ci .tm_wday = 3, 998c2ecf20Sopenharmony_ci .tm_yday = 160, 1008c2ecf20Sopenharmony_ci .tm_isdst = 1 1018c2ecf20Sopenharmony_ci }; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci time.tm_year = (n % 100); 1048c2ecf20Sopenharmony_ci n /= 100; 1058c2ecf20Sopenharmony_ci time.tm_mon = (n % 12); 1068c2ecf20Sopenharmony_ci n /= 12; 1078c2ecf20Sopenharmony_ci time.tm_mday = (n % 28) + 1; 1088c2ecf20Sopenharmony_ci n /= 28; 1098c2ecf20Sopenharmony_ci time.tm_hour = (n % 24); 1108c2ecf20Sopenharmony_ci n /= 24; 1118c2ecf20Sopenharmony_ci time.tm_min = (n % 20) * 3; 1128c2ecf20Sopenharmony_ci n /= 20; 1138c2ecf20Sopenharmony_ci mc146818_set_time(&time); 1148c2ecf20Sopenharmony_ci pm_trace_rtc_abused = true; 1158c2ecf20Sopenharmony_ci return n ? -1 : 0; 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic unsigned int read_magic_time(void) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci struct rtc_time time; 1218c2ecf20Sopenharmony_ci unsigned int val; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci if (mc146818_get_time(&time) < 0) { 1248c2ecf20Sopenharmony_ci pr_err("Unable to read current time from RTC\n"); 1258c2ecf20Sopenharmony_ci return 0; 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci pr_info("RTC time: %ptRt, date: %ptRd\n", &time, &time); 1298c2ecf20Sopenharmony_ci val = time.tm_year; /* 100 years */ 1308c2ecf20Sopenharmony_ci if (val > 100) 1318c2ecf20Sopenharmony_ci val -= 100; 1328c2ecf20Sopenharmony_ci val += time.tm_mon * 100; /* 12 months */ 1338c2ecf20Sopenharmony_ci val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */ 1348c2ecf20Sopenharmony_ci val += time.tm_hour * 100 * 12 * 28; /* 24 hours */ 1358c2ecf20Sopenharmony_ci val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */ 1368c2ecf20Sopenharmony_ci return val; 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci/* 1408c2ecf20Sopenharmony_ci * This is just the sdbm hash function with a user-supplied 1418c2ecf20Sopenharmony_ci * seed and final size parameter. 1428c2ecf20Sopenharmony_ci */ 1438c2ecf20Sopenharmony_cistatic unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci unsigned char c; 1468c2ecf20Sopenharmony_ci while ((c = *data++) != 0) { 1478c2ecf20Sopenharmony_ci seed = (seed << 16) + (seed << 6) - seed + c; 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci return seed % mod; 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_civoid set_trace_device(struct device *dev) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci dev_hash_value = hash_string(DEVSEED, dev_name(dev), DEVHASH); 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ciEXPORT_SYMBOL(set_trace_device); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci/* 1598c2ecf20Sopenharmony_ci * We could just take the "tracedata" index into the .tracedata 1608c2ecf20Sopenharmony_ci * section instead. Generating a hash of the data gives us a 1618c2ecf20Sopenharmony_ci * chance to work across kernel versions, and perhaps more 1628c2ecf20Sopenharmony_ci * importantly it also gives us valid/invalid check (ie we will 1638c2ecf20Sopenharmony_ci * likely not give totally bogus reports - if the hash matches, 1648c2ecf20Sopenharmony_ci * it's not any guarantee, but it's a high _likelihood_ that 1658c2ecf20Sopenharmony_ci * the match is valid). 1668c2ecf20Sopenharmony_ci */ 1678c2ecf20Sopenharmony_civoid generate_pm_trace(const void *tracedata, unsigned int user) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci unsigned short lineno = *(unsigned short *)tracedata; 1708c2ecf20Sopenharmony_ci const char *file = *(const char **)(tracedata + 2); 1718c2ecf20Sopenharmony_ci unsigned int user_hash_value, file_hash_value; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci if (!x86_platform.legacy.rtc) 1748c2ecf20Sopenharmony_ci return; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci user_hash_value = user % USERHASH; 1778c2ecf20Sopenharmony_ci file_hash_value = hash_string(lineno, file, FILEHASH); 1788c2ecf20Sopenharmony_ci set_magic_time(user_hash_value, file_hash_value, dev_hash_value); 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ciEXPORT_SYMBOL(generate_pm_trace); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ciextern char __tracedata_start[], __tracedata_end[]; 1838c2ecf20Sopenharmony_cistatic int show_file_hash(unsigned int value) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci int match; 1868c2ecf20Sopenharmony_ci char *tracedata; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci match = 0; 1898c2ecf20Sopenharmony_ci for (tracedata = __tracedata_start ; tracedata < __tracedata_end ; 1908c2ecf20Sopenharmony_ci tracedata += 2 + sizeof(unsigned long)) { 1918c2ecf20Sopenharmony_ci unsigned short lineno = *(unsigned short *)tracedata; 1928c2ecf20Sopenharmony_ci const char *file = *(const char **)(tracedata + 2); 1938c2ecf20Sopenharmony_ci unsigned int hash = hash_string(lineno, file, FILEHASH); 1948c2ecf20Sopenharmony_ci if (hash != value) 1958c2ecf20Sopenharmony_ci continue; 1968c2ecf20Sopenharmony_ci pr_info(" hash matches %s:%u\n", file, lineno); 1978c2ecf20Sopenharmony_ci match++; 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci return match; 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic int show_dev_hash(unsigned int value) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci int match = 0; 2058c2ecf20Sopenharmony_ci struct list_head *entry; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci device_pm_lock(); 2088c2ecf20Sopenharmony_ci entry = dpm_list.prev; 2098c2ecf20Sopenharmony_ci while (entry != &dpm_list) { 2108c2ecf20Sopenharmony_ci struct device * dev = to_device(entry); 2118c2ecf20Sopenharmony_ci unsigned int hash = hash_string(DEVSEED, dev_name(dev), DEVHASH); 2128c2ecf20Sopenharmony_ci if (hash == value) { 2138c2ecf20Sopenharmony_ci dev_info(dev, "hash matches\n"); 2148c2ecf20Sopenharmony_ci match++; 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci entry = entry->prev; 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci device_pm_unlock(); 2198c2ecf20Sopenharmony_ci return match; 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic unsigned int hash_value_early_read; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ciint show_trace_dev_match(char *buf, size_t size) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci unsigned int value = hash_value_early_read / (USERHASH * FILEHASH); 2278c2ecf20Sopenharmony_ci int ret = 0; 2288c2ecf20Sopenharmony_ci struct list_head *entry; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci /* 2318c2ecf20Sopenharmony_ci * It's possible that multiple devices will match the hash and we can't 2328c2ecf20Sopenharmony_ci * tell which is the culprit, so it's best to output them all. 2338c2ecf20Sopenharmony_ci */ 2348c2ecf20Sopenharmony_ci device_pm_lock(); 2358c2ecf20Sopenharmony_ci entry = dpm_list.prev; 2368c2ecf20Sopenharmony_ci while (size && entry != &dpm_list) { 2378c2ecf20Sopenharmony_ci struct device *dev = to_device(entry); 2388c2ecf20Sopenharmony_ci unsigned int hash = hash_string(DEVSEED, dev_name(dev), 2398c2ecf20Sopenharmony_ci DEVHASH); 2408c2ecf20Sopenharmony_ci if (hash == value) { 2418c2ecf20Sopenharmony_ci int len = snprintf(buf, size, "%s\n", 2428c2ecf20Sopenharmony_ci dev_driver_string(dev)); 2438c2ecf20Sopenharmony_ci if (len > size) 2448c2ecf20Sopenharmony_ci len = size; 2458c2ecf20Sopenharmony_ci buf += len; 2468c2ecf20Sopenharmony_ci ret += len; 2478c2ecf20Sopenharmony_ci size -= len; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci entry = entry->prev; 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci device_pm_unlock(); 2528c2ecf20Sopenharmony_ci return ret; 2538c2ecf20Sopenharmony_ci} 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_cistatic int 2568c2ecf20Sopenharmony_cipm_trace_notify(struct notifier_block *nb, unsigned long mode, void *_unused) 2578c2ecf20Sopenharmony_ci{ 2588c2ecf20Sopenharmony_ci switch (mode) { 2598c2ecf20Sopenharmony_ci case PM_POST_HIBERNATION: 2608c2ecf20Sopenharmony_ci case PM_POST_SUSPEND: 2618c2ecf20Sopenharmony_ci if (pm_trace_rtc_abused) { 2628c2ecf20Sopenharmony_ci pm_trace_rtc_abused = false; 2638c2ecf20Sopenharmony_ci pr_warn("Possible incorrect RTC due to pm_trace, please use 'ntpdate' or 'rdate' to reset it.\n"); 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci break; 2668c2ecf20Sopenharmony_ci default: 2678c2ecf20Sopenharmony_ci break; 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci return 0; 2708c2ecf20Sopenharmony_ci} 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic struct notifier_block pm_trace_nb = { 2738c2ecf20Sopenharmony_ci .notifier_call = pm_trace_notify, 2748c2ecf20Sopenharmony_ci}; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_cistatic int __init early_resume_init(void) 2778c2ecf20Sopenharmony_ci{ 2788c2ecf20Sopenharmony_ci if (!x86_platform.legacy.rtc) 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci hash_value_early_read = read_magic_time(); 2828c2ecf20Sopenharmony_ci register_pm_notifier(&pm_trace_nb); 2838c2ecf20Sopenharmony_ci return 0; 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cistatic int __init late_resume_init(void) 2878c2ecf20Sopenharmony_ci{ 2888c2ecf20Sopenharmony_ci unsigned int val = hash_value_early_read; 2898c2ecf20Sopenharmony_ci unsigned int user, file, dev; 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci if (!x86_platform.legacy.rtc) 2928c2ecf20Sopenharmony_ci return 0; 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci user = val % USERHASH; 2958c2ecf20Sopenharmony_ci val = val / USERHASH; 2968c2ecf20Sopenharmony_ci file = val % FILEHASH; 2978c2ecf20Sopenharmony_ci val = val / FILEHASH; 2988c2ecf20Sopenharmony_ci dev = val /* % DEVHASH */; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci pr_info(" Magic number: %d:%d:%d\n", user, file, dev); 3018c2ecf20Sopenharmony_ci show_file_hash(file); 3028c2ecf20Sopenharmony_ci show_dev_hash(dev); 3038c2ecf20Sopenharmony_ci return 0; 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cicore_initcall(early_resume_init); 3078c2ecf20Sopenharmony_cilate_initcall(late_resume_init); 308