15de363b6SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2eb71c87aSLinus Torvalds /*
3eb71c87aSLinus Torvalds * drivers/base/power/trace.c
4eb71c87aSLinus Torvalds *
5eb71c87aSLinus Torvalds * Copyright (C) 2006 Linus Torvalds
6eb71c87aSLinus Torvalds *
7eb71c87aSLinus Torvalds * Trace facility for suspend/resume problems, when none of the
8eb71c87aSLinus Torvalds * devices may be working.
9eb71c87aSLinus Torvalds */
107a5bd127SJoe Perches #define pr_fmt(fmt) "PM: " fmt
117a5bd127SJoe Perches
12431d452aSZhonghui Fu #include <linux/pm-trace.h>
131b6bc32fSPaul Gortmaker #include <linux/export.h>
14eb71c87aSLinus Torvalds #include <linux/rtc.h>
15ba58d102SChen Yu #include <linux/suspend.h>
160560204bSJuergen Gross #include <linux/init.h>
17eb71c87aSLinus Torvalds
18463a8630SArnd Bergmann #include <linux/mc146818rtc.h>
19eb71c87aSLinus Torvalds
20eb71c87aSLinus Torvalds #include "power.h"
21eb71c87aSLinus Torvalds
22eb71c87aSLinus Torvalds /*
23eb71c87aSLinus Torvalds * Horrid, horrid, horrid.
24eb71c87aSLinus Torvalds *
25eb71c87aSLinus Torvalds * It turns out that the _only_ piece of hardware that actually
26eb71c87aSLinus Torvalds * keeps its value across a hard boot (and, more importantly, the
27eb71c87aSLinus Torvalds * POST init sequence) is literally the realtime clock.
28eb71c87aSLinus Torvalds *
29eb71c87aSLinus Torvalds * Never mind that an RTC chip has 114 bytes (and often a whole
30eb71c87aSLinus Torvalds * other bank of an additional 128 bytes) of nice SRAM that is
31eb71c87aSLinus Torvalds * _designed_ to keep data - the POST will clear it. So we literally
32eb71c87aSLinus Torvalds * can just use the few bytes of actual time data, which means that
33eb71c87aSLinus Torvalds * we're really limited.
34eb71c87aSLinus Torvalds *
35eb71c87aSLinus Torvalds * It means, for example, that we can't use the seconds at all
36eb71c87aSLinus Torvalds * (since the time between the hang and the boot might be more
37eb71c87aSLinus Torvalds * than a minute), and we'd better not depend on the low bits of
38eb71c87aSLinus Torvalds * the minutes either.
39eb71c87aSLinus Torvalds *
40eb71c87aSLinus Torvalds * There are the wday fields etc, but I wouldn't guarantee those
41eb71c87aSLinus Torvalds * are dependable either. And if the date isn't valid, either the
42eb71c87aSLinus Torvalds * hw or POST will do strange things.
43eb71c87aSLinus Torvalds *
44eb71c87aSLinus Torvalds * So we're left with:
45eb71c87aSLinus Torvalds * - year: 0-99
46eb71c87aSLinus Torvalds * - month: 0-11
47eb71c87aSLinus Torvalds * - day-of-month: 1-28
48eb71c87aSLinus Torvalds * - hour: 0-23
49eb71c87aSLinus Torvalds * - min: (0-30)*2
50eb71c87aSLinus Torvalds *
51eb71c87aSLinus Torvalds * Giving us a total range of 0-16128000 (0xf61800), ie less
52eb71c87aSLinus Torvalds * than 24 bits of actual data we can save across reboots.
53eb71c87aSLinus Torvalds *
54eb71c87aSLinus Torvalds * And if your box can't boot in less than three minutes,
55eb71c87aSLinus Torvalds * you're screwed.
56eb71c87aSLinus Torvalds *
57eb71c87aSLinus Torvalds * Now, almost 24 bits of data is pitifully small, so we need
58eb71c87aSLinus Torvalds * to be pretty dense if we want to use it for anything nice.
59eb71c87aSLinus Torvalds * What we do is that instead of saving off nice readable info,
60eb71c87aSLinus Torvalds * we save off _hashes_ of information that we can hopefully
61eb71c87aSLinus Torvalds * regenerate after the reboot.
62eb71c87aSLinus Torvalds *
63eb71c87aSLinus Torvalds * In particular, this means that we might be unlucky, and hit
64eb71c87aSLinus Torvalds * a case where we have a hash collision, and we end up not
65eb71c87aSLinus Torvalds * being able to tell for certain exactly which case happened.
66eb71c87aSLinus Torvalds * But that's hopefully unlikely.
67eb71c87aSLinus Torvalds *
68eb71c87aSLinus Torvalds * What we do is to take the bits we can fit, and split them
69eb71c87aSLinus Torvalds * into three parts (16*997*1009 = 16095568), and use the values
70eb71c87aSLinus Torvalds * for:
71eb71c87aSLinus Torvalds * - 0-15: user-settable
72eb71c87aSLinus Torvalds * - 0-996: file + line number
73eb71c87aSLinus Torvalds * - 0-1008: device
74eb71c87aSLinus Torvalds */
75eb71c87aSLinus Torvalds #define USERHASH (16)
76eb71c87aSLinus Torvalds #define FILEHASH (997)
77eb71c87aSLinus Torvalds #define DEVHASH (1009)
78eb71c87aSLinus Torvalds
79eb71c87aSLinus Torvalds #define DEVSEED (7919)
80eb71c87aSLinus Torvalds
81ba58d102SChen Yu bool pm_trace_rtc_abused __read_mostly;
82ba58d102SChen Yu EXPORT_SYMBOL_GPL(pm_trace_rtc_abused);
83ba58d102SChen Yu
84eb71c87aSLinus Torvalds static unsigned int dev_hash_value;
85eb71c87aSLinus Torvalds
set_magic_time(unsigned int user,unsigned int file,unsigned int device)86eb71c87aSLinus Torvalds static int set_magic_time(unsigned int user, unsigned int file, unsigned int device)
87eb71c87aSLinus Torvalds {
88eb71c87aSLinus Torvalds unsigned int n = user + USERHASH*(file + FILEHASH*device);
89eb71c87aSLinus Torvalds
90eb71c87aSLinus Torvalds // June 7th, 2006
91eb71c87aSLinus Torvalds static struct rtc_time time = {
92eb71c87aSLinus Torvalds .tm_sec = 0,
93eb71c87aSLinus Torvalds .tm_min = 0,
94eb71c87aSLinus Torvalds .tm_hour = 0,
95eb71c87aSLinus Torvalds .tm_mday = 7,
96eb71c87aSLinus Torvalds .tm_mon = 5, // June - counting from zero
97eb71c87aSLinus Torvalds .tm_year = 106,
98eb71c87aSLinus Torvalds .tm_wday = 3,
99eb71c87aSLinus Torvalds .tm_yday = 160,
100eb71c87aSLinus Torvalds .tm_isdst = 1
101eb71c87aSLinus Torvalds };
102eb71c87aSLinus Torvalds
103eb71c87aSLinus Torvalds time.tm_year = (n % 100);
104eb71c87aSLinus Torvalds n /= 100;
105eb71c87aSLinus Torvalds time.tm_mon = (n % 12);
106eb71c87aSLinus Torvalds n /= 12;
107eb71c87aSLinus Torvalds time.tm_mday = (n % 28) + 1;
108eb71c87aSLinus Torvalds n /= 28;
109eb71c87aSLinus Torvalds time.tm_hour = (n % 24);
110eb71c87aSLinus Torvalds n /= 24;
111eb71c87aSLinus Torvalds time.tm_min = (n % 20) * 3;
112eb71c87aSLinus Torvalds n /= 20;
113463a8630SArnd Bergmann mc146818_set_time(&time);
114ba58d102SChen Yu pm_trace_rtc_abused = true;
115eb71c87aSLinus Torvalds return n ? -1 : 0;
116eb71c87aSLinus Torvalds }
117eb71c87aSLinus Torvalds
read_magic_time(void)118eb71c87aSLinus Torvalds static unsigned int read_magic_time(void)
119eb71c87aSLinus Torvalds {
120eb71c87aSLinus Torvalds struct rtc_time time;
121eb71c87aSLinus Torvalds unsigned int val;
122eb71c87aSLinus Torvalds
123*49a76c08SMario Limonciello if (mc146818_get_time(&time, 1000) < 0) {
1240dd8d6cbSMateusz Jończyk pr_err("Unable to read current time from RTC\n");
1250dd8d6cbSMateusz Jończyk return 0;
1260dd8d6cbSMateusz Jończyk }
1270dd8d6cbSMateusz Jończyk
128a07995beSAndy Shevchenko pr_info("RTC time: %ptRt, date: %ptRd\n", &time, &time);
129eb71c87aSLinus Torvalds val = time.tm_year; /* 100 years */
130eb71c87aSLinus Torvalds if (val > 100)
131eb71c87aSLinus Torvalds val -= 100;
132eb71c87aSLinus Torvalds val += time.tm_mon * 100; /* 12 months */
133eb71c87aSLinus Torvalds val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */
134eb71c87aSLinus Torvalds val += time.tm_hour * 100 * 12 * 28; /* 24 hours */
135eb71c87aSLinus Torvalds val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */
136eb71c87aSLinus Torvalds return val;
137eb71c87aSLinus Torvalds }
138eb71c87aSLinus Torvalds
139eb71c87aSLinus Torvalds /*
140eb71c87aSLinus Torvalds * This is just the sdbm hash function with a user-supplied
141eb71c87aSLinus Torvalds * seed and final size parameter.
142eb71c87aSLinus Torvalds */
hash_string(unsigned int seed,const char * data,unsigned int mod)143eb71c87aSLinus Torvalds static unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod)
144eb71c87aSLinus Torvalds {
145eb71c87aSLinus Torvalds unsigned char c;
146eb71c87aSLinus Torvalds while ((c = *data++) != 0) {
147eb71c87aSLinus Torvalds seed = (seed << 16) + (seed << 6) - seed + c;
148eb71c87aSLinus Torvalds }
149eb71c87aSLinus Torvalds return seed % mod;
150eb71c87aSLinus Torvalds }
151eb71c87aSLinus Torvalds
set_trace_device(struct device * dev)152eb71c87aSLinus Torvalds void set_trace_device(struct device *dev)
153eb71c87aSLinus Torvalds {
1541e0b2cf9SKay Sievers dev_hash_value = hash_string(DEVSEED, dev_name(dev), DEVHASH);
155eb71c87aSLinus Torvalds }
15644bf4ceaSNigel Cunningham EXPORT_SYMBOL(set_trace_device);
157eb71c87aSLinus Torvalds
158eb71c87aSLinus Torvalds /*
159eb71c87aSLinus Torvalds * We could just take the "tracedata" index into the .tracedata
160eb71c87aSLinus Torvalds * section instead. Generating a hash of the data gives us a
161eb71c87aSLinus Torvalds * chance to work across kernel versions, and perhaps more
162eb71c87aSLinus Torvalds * importantly it also gives us valid/invalid check (ie we will
163eb71c87aSLinus Torvalds * likely not give totally bogus reports - if the hash matches,
164eb71c87aSLinus Torvalds * it's not any guarantee, but it's a high _likelihood_ that
165eb71c87aSLinus Torvalds * the match is valid).
166eb71c87aSLinus Torvalds */
generate_pm_trace(const void * tracedata,unsigned int user)167431d452aSZhonghui Fu void generate_pm_trace(const void *tracedata, unsigned int user)
168eb71c87aSLinus Torvalds {
169eb71c87aSLinus Torvalds unsigned short lineno = *(unsigned short *)tracedata;
170eb71c87aSLinus Torvalds const char *file = *(const char **)(tracedata + 2);
171eb71c87aSLinus Torvalds unsigned int user_hash_value, file_hash_value;
172eb71c87aSLinus Torvalds
1730560204bSJuergen Gross if (!x86_platform.legacy.rtc)
1740560204bSJuergen Gross return;
1750560204bSJuergen Gross
176eb71c87aSLinus Torvalds user_hash_value = user % USERHASH;
177eb71c87aSLinus Torvalds file_hash_value = hash_string(lineno, file, FILEHASH);
178eb71c87aSLinus Torvalds set_magic_time(user_hash_value, file_hash_value, dev_hash_value);
179eb71c87aSLinus Torvalds }
180431d452aSZhonghui Fu EXPORT_SYMBOL(generate_pm_trace);
181eb71c87aSLinus Torvalds
182f9723837SEric Biggers extern char __tracedata_start[], __tracedata_end[];
show_file_hash(unsigned int value)183eb71c87aSLinus Torvalds static int show_file_hash(unsigned int value)
184eb71c87aSLinus Torvalds {
185eb71c87aSLinus Torvalds int match;
186eb71c87aSLinus Torvalds char *tracedata;
187eb71c87aSLinus Torvalds
188eb71c87aSLinus Torvalds match = 0;
189f9723837SEric Biggers for (tracedata = __tracedata_start ; tracedata < __tracedata_end ;
19044bf4ceaSNigel Cunningham tracedata += 2 + sizeof(unsigned long)) {
191eb71c87aSLinus Torvalds unsigned short lineno = *(unsigned short *)tracedata;
192eb71c87aSLinus Torvalds const char *file = *(const char **)(tracedata + 2);
193eb71c87aSLinus Torvalds unsigned int hash = hash_string(lineno, file, FILEHASH);
194eb71c87aSLinus Torvalds if (hash != value)
195eb71c87aSLinus Torvalds continue;
1960295a34dSMandeep Singh Baines pr_info(" hash matches %s:%u\n", file, lineno);
197eb71c87aSLinus Torvalds match++;
198eb71c87aSLinus Torvalds }
199eb71c87aSLinus Torvalds return match;
200eb71c87aSLinus Torvalds }
201eb71c87aSLinus Torvalds
show_dev_hash(unsigned int value)202eb71c87aSLinus Torvalds static int show_dev_hash(unsigned int value)
203eb71c87aSLinus Torvalds {
204eb71c87aSLinus Torvalds int match = 0;
2052ac21c6bSJames Hogan struct list_head *entry;
206eb71c87aSLinus Torvalds
2072ac21c6bSJames Hogan device_pm_lock();
2082ac21c6bSJames Hogan entry = dpm_list.prev;
2091eede070SRafael J. Wysocki while (entry != &dpm_list) {
210eb71c87aSLinus Torvalds struct device * dev = to_device(entry);
2111e0b2cf9SKay Sievers unsigned int hash = hash_string(DEVSEED, dev_name(dev), DEVHASH);
212eb71c87aSLinus Torvalds if (hash == value) {
213fc3a8828SGreg Kroah-Hartman dev_info(dev, "hash matches\n");
214eb71c87aSLinus Torvalds match++;
215eb71c87aSLinus Torvalds }
216eb71c87aSLinus Torvalds entry = entry->prev;
217eb71c87aSLinus Torvalds }
2182ac21c6bSJames Hogan device_pm_unlock();
219eb71c87aSLinus Torvalds return match;
220eb71c87aSLinus Torvalds }
221eb71c87aSLinus Torvalds
222eb71c87aSLinus Torvalds static unsigned int hash_value_early_read;
223eb71c87aSLinus Torvalds
show_trace_dev_match(char * buf,size_t size)224d33ac60bSJames Hogan int show_trace_dev_match(char *buf, size_t size)
225d33ac60bSJames Hogan {
226d33ac60bSJames Hogan unsigned int value = hash_value_early_read / (USERHASH * FILEHASH);
227d33ac60bSJames Hogan int ret = 0;
228d33ac60bSJames Hogan struct list_head *entry;
229d33ac60bSJames Hogan
230d33ac60bSJames Hogan /*
231d33ac60bSJames Hogan * It's possible that multiple devices will match the hash and we can't
232d33ac60bSJames Hogan * tell which is the culprit, so it's best to output them all.
233d33ac60bSJames Hogan */
234d33ac60bSJames Hogan device_pm_lock();
235d33ac60bSJames Hogan entry = dpm_list.prev;
236d33ac60bSJames Hogan while (size && entry != &dpm_list) {
237d33ac60bSJames Hogan struct device *dev = to_device(entry);
238d33ac60bSJames Hogan unsigned int hash = hash_string(DEVSEED, dev_name(dev),
239d33ac60bSJames Hogan DEVHASH);
240d33ac60bSJames Hogan if (hash == value) {
241d33ac60bSJames Hogan int len = snprintf(buf, size, "%s\n",
242d33ac60bSJames Hogan dev_driver_string(dev));
243d33ac60bSJames Hogan if (len > size)
244d33ac60bSJames Hogan len = size;
245d33ac60bSJames Hogan buf += len;
246d33ac60bSJames Hogan ret += len;
247d33ac60bSJames Hogan size -= len;
248d33ac60bSJames Hogan }
249d33ac60bSJames Hogan entry = entry->prev;
250d33ac60bSJames Hogan }
251d33ac60bSJames Hogan device_pm_unlock();
252d33ac60bSJames Hogan return ret;
253d33ac60bSJames Hogan }
254d33ac60bSJames Hogan
255ba58d102SChen Yu static int
pm_trace_notify(struct notifier_block * nb,unsigned long mode,void * _unused)256ba58d102SChen Yu pm_trace_notify(struct notifier_block *nb, unsigned long mode, void *_unused)
257ba58d102SChen Yu {
258ba58d102SChen Yu switch (mode) {
259ba58d102SChen Yu case PM_POST_HIBERNATION:
260ba58d102SChen Yu case PM_POST_SUSPEND:
261ba58d102SChen Yu if (pm_trace_rtc_abused) {
262ba58d102SChen Yu pm_trace_rtc_abused = false;
263ba58d102SChen Yu pr_warn("Possible incorrect RTC due to pm_trace, please use 'ntpdate' or 'rdate' to reset it.\n");
264ba58d102SChen Yu }
265ba58d102SChen Yu break;
266ba58d102SChen Yu default:
267ba58d102SChen Yu break;
268ba58d102SChen Yu }
269ba58d102SChen Yu return 0;
270ba58d102SChen Yu }
271ba58d102SChen Yu
272ba58d102SChen Yu static struct notifier_block pm_trace_nb = {
273ba58d102SChen Yu .notifier_call = pm_trace_notify,
274ba58d102SChen Yu };
275ba58d102SChen Yu
early_resume_init(void)2760659d420SChristophe JAILLET static int __init early_resume_init(void)
277eb71c87aSLinus Torvalds {
2780560204bSJuergen Gross if (!x86_platform.legacy.rtc)
2790560204bSJuergen Gross return 0;
2800560204bSJuergen Gross
281eb71c87aSLinus Torvalds hash_value_early_read = read_magic_time();
282ba58d102SChen Yu register_pm_notifier(&pm_trace_nb);
283eb71c87aSLinus Torvalds return 0;
284eb71c87aSLinus Torvalds }
285eb71c87aSLinus Torvalds
late_resume_init(void)2860659d420SChristophe JAILLET static int __init late_resume_init(void)
287eb71c87aSLinus Torvalds {
288eb71c87aSLinus Torvalds unsigned int val = hash_value_early_read;
289eb71c87aSLinus Torvalds unsigned int user, file, dev;
290eb71c87aSLinus Torvalds
2910560204bSJuergen Gross if (!x86_platform.legacy.rtc)
2920560204bSJuergen Gross return 0;
2930560204bSJuergen Gross
294eb71c87aSLinus Torvalds user = val % USERHASH;
295eb71c87aSLinus Torvalds val = val / USERHASH;
296eb71c87aSLinus Torvalds file = val % FILEHASH;
297eb71c87aSLinus Torvalds val = val / FILEHASH;
298eb71c87aSLinus Torvalds dev = val /* % DEVHASH */;
299eb71c87aSLinus Torvalds
3000295a34dSMandeep Singh Baines pr_info(" Magic number: %d:%d:%d\n", user, file, dev);
301eb71c87aSLinus Torvalds show_file_hash(file);
302eb71c87aSLinus Torvalds show_dev_hash(dev);
303eb71c87aSLinus Torvalds return 0;
304eb71c87aSLinus Torvalds }
305eb71c87aSLinus Torvalds
306eb71c87aSLinus Torvalds core_initcall(early_resume_init);
307eb71c87aSLinus Torvalds late_initcall(late_resume_init);
308