xref: /openbmc/linux/drivers/base/power/trace.c (revision 7d7ae873b5e0f46d19e5dc818d1a7809e4b7cc81)
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