xref: /illumos-gate/usr/src/uts/sun4u/io/todsg.c (revision 88294e09b5c27cbb12b6735e2fb247a86b76666d)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * tod driver module for Serengeti
28  * This module implements a soft tod since
29  * Serengeti has no tod part.
30  */
31 
32 #include <sys/modctl.h>
33 #include <sys/systm.h>
34 #include <sys/cpuvar.h>
35 #include <sys/promif.h>
36 #include <sys/sgsbbc_iosram.h>
37 #include <sys/todsg.h>
38 #include <sys/cmn_err.h>
39 #include <sys/time.h>
40 #include <sys/sysmacros.h>
41 #include <sys/clock.h>
42 
43 #if defined(DEBUG) || defined(lint)
44 static int todsg_debug = 0;
45 #define	DCMNERR if (todsg_debug) cmn_err
46 #else
47 #define	DCMNERR
48 #endif /* DEBUG */
49 
50 #define	OFFSET(base, field)	((char *)&base.field - (char *)&base)
51 #define	SC_DOWN_COUNT_THRESHOLD	2
52 #define	SC_TOD_MIN_REV		2
53 
54 static timestruc_t	todsg_get(void);
55 static void		todsg_set(timestruc_t);
56 static uint32_t		todsg_set_watchdog_timer(uint_t);
57 static uint32_t		todsg_clear_watchdog_timer(void);
58 static void		todsg_set_power_alarm(timestruc_t);
59 static void		todsg_clear_power_alarm(void);
60 static uint64_t		todsg_get_cpufrequency(void);
61 static int 		update_heartbeat(void);
62 static int		verify_sc_tod_version(void);
63 static int 		update_tod_skew(time_t skew);
64 
65 static uint32_t i_am_alive = 0;
66 static uint32_t sc_tod_version = 0;
67 static time_t 	skew_adjust = 0;
68 static int 	is_sc_down = 0;
69 static int	adjust_sc_down = 0;
70 
71 /*
72  * Module linkage information for the kernel.
73  */
74 static struct modlmisc modlmisc = {
75 	&mod_miscops, "Serengeti tod module"
76 };
77 
78 static struct modlinkage modlinkage = {
79 	MODREV_1, (void *)&modlmisc, NULL
80 };
81 
82 int
83 _init(void)
84 {
85 
86 	DCMNERR(CE_NOTE, "todsg:_init(): begins");
87 
88 	if (strcmp(tod_module_name, "todsg") == 0) {
89 		time_t ssc_time = (time_t)0;
90 		char obp_string[80];
91 
92 		/*
93 		 * To obtain the initial start of day time, we use an
94 		 * OBP callback; this is because the iosram is not yet
95 		 * accessible from the OS at this early stage of startup.
96 		 */
97 
98 		/*
99 		 * Set the string to pass to OBP
100 		 */
101 		(void) sprintf(obp_string,
102 			"h# %p \" unix-get-tod\" $find if execute else "
103 			"3drop then",
104 			(void *)&ssc_time);
105 
106 		prom_interpret(obp_string, 0, 0, 0, 0, 0);
107 
108 		if (ssc_time == (time_t)0) {
109 			cmn_err(CE_WARN, "Initial date is invalid. "
110 				"This can be caused by older firmware.");
111 			cmn_err(CE_CONT, "Please flashupdate the System "
112 				"Controller firmware to the latest version.\n");
113 			cmn_err(CE_CONT, "Attempting to set the date and time "
114 				"based on the last shutdown.\n");
115 			cmn_err(CE_CONT, "Please inspect the date and time and "
116 				"correct if necessary.\n");
117 		}
118 
119 		hrestime.tv_sec = ssc_time;
120 
121 		DCMNERR(CE_NOTE, "todsg: _init(): time from OBP 0x%lX",
122 				ssc_time);
123 		/*
124 		 * Verify whether the received date/clock has overflowed
125 		 * an integer(32bit), so that we capture any corrupted
126 		 * date from SC, thereby preventing boot failure.
127 		 */
128 		if (TIMESPEC_OVERFLOW(&hrestime)) {
129 			cmn_err(CE_WARN, "Date overflow detected.");
130 			cmn_err(CE_CONT, "Attempting to set the date and time "
131 				"based on the last shutdown.\n");
132 			cmn_err(CE_CONT, "Please inspect the date and time and "
133 				"correct if necessary.\n");
134 
135 			/*
136 			 * By setting hrestime.tv_sec to zero
137 			 * we force the vfs_mountroot() to set
138 			 * the date from the last shutdown.
139 			 */
140 			hrestime.tv_sec = (time_t)0;
141 			/*
142 			 * Save the skew so that we can update
143 			 * IOSRAM when it becomes accessible.
144 			 */
145 			skew_adjust = -ssc_time;
146 		}
147 
148 		DCMNERR(CE_NOTE, "todsg:_init(): set tod_ops");
149 
150 		tod_ops.tod_get = todsg_get;
151 		tod_ops.tod_set = todsg_set;
152 		tod_ops.tod_set_watchdog_timer = todsg_set_watchdog_timer;
153 		tod_ops.tod_clear_watchdog_timer = todsg_clear_watchdog_timer;
154 		tod_ops.tod_set_power_alarm = todsg_set_power_alarm;
155 		tod_ops.tod_clear_power_alarm = todsg_clear_power_alarm;
156 		tod_ops.tod_get_cpufrequency = todsg_get_cpufrequency;
157 	}
158 
159 	return (mod_install(&modlinkage));
160 
161 }
162 
163 int
164 _fini(void)
165 {
166 	if (strcmp(tod_module_name, "todsg") == 0)
167 		return (EBUSY);
168 	else
169 		return (mod_remove(&modlinkage));
170 }
171 
172 int
173 _info(struct modinfo *modinfop)
174 {
175 	return (mod_info(&modlinkage, modinfop));
176 }
177 
178 static int
179 update_heartbeat(void)
180 {
181 	tod_iosram_t tod_buf;
182 	int complained = 0;
183 
184 	/* Update the heartbeat */
185 	if (i_am_alive == UINT32_MAX)
186 		i_am_alive = 0;
187 	else
188 		i_am_alive++;
189 	if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_i_am_alive),
190 			(char *)&i_am_alive, sizeof (uint32_t))) {
191 		complained++;
192 		cmn_err(CE_WARN, "update_heartbeat(): write heartbeat failed");
193 	}
194 	return (complained);
195 }
196 
197 static int
198 verify_sc_tod_version(void)
199 {
200 	uint32_t magic;
201 	tod_iosram_t tod_buf;
202 
203 	if (!todsg_use_sc)
204 		return (FALSE);
205 	/*
206 	 * read tod_version only when the first time and
207 	 * when there has been a previous sc down time
208 	 */
209 	if (!sc_tod_version || is_sc_down >= SC_DOWN_COUNT_THRESHOLD) {
210 		if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_magic),
211 			(char *)&magic, sizeof (uint32_t)) ||
212 				magic != TODSG_MAGIC) {
213 			cmn_err(CE_WARN, "get_sc_tod_version(): "
214 						"TOD SRAM magic error");
215 			return (FALSE);
216 		}
217 		if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_version),
218 			(char *)&sc_tod_version, sizeof (uint32_t))) {
219 			cmn_err(CE_WARN, "get_sc_tod_version(): "
220 				"read tod version failed");
221 			sc_tod_version = 0;
222 			return (FALSE);
223 		}
224 	}
225 	if (sc_tod_version >= SC_TOD_MIN_REV) {
226 		return (TRUE);
227 	} else {
228 		todsg_use_sc = 0;
229 		cmn_err(CE_WARN,
230 			"todsg_get(): incorrect firmware version, "
231 			"(%d): expected version >= %d.",
232 			sc_tod_version, SC_TOD_MIN_REV);
233 	}
234 	return (FALSE);
235 }
236 
237 static int
238 update_tod_skew(time_t skew)
239 {
240 	time_t domain_skew;
241 	tod_iosram_t tod_buf;
242 	int complained = 0;
243 
244 	DCMNERR(CE_NOTE, "update_tod_skew(): skew  0x%lX", skew);
245 
246 	if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_domain_skew),
247 			(char *)&domain_skew, sizeof (time_t))) {
248 		complained++;
249 		cmn_err(CE_WARN, "update_tod_skew(): "
250 				"read tod domain skew failed");
251 	}
252 	domain_skew += skew;
253 	/* we shall update the skew_adjust too now */
254 	domain_skew += skew_adjust;
255 	if (!complained && iosram_write(SBBC_TOD_KEY,
256 			OFFSET(tod_buf, tod_domain_skew),
257 				(char *)&domain_skew, sizeof (time_t))) {
258 		complained++;
259 		cmn_err(CE_WARN, "update_tod_skew(): "
260 				"write domain skew failed");
261 	}
262 	if (!complained)
263 		skew_adjust = 0;
264 	return (complained);
265 }
266 
267 
268 /*
269  * Return time value read from IOSRAM.
270  * Must be called with tod_lock held.
271  */
272 static timestruc_t
273 todsg_get(void)
274 {
275 	tod_iosram_t tod_buf;
276 	time_t seconds;
277 	time_t domain_skew;
278 	int complained = 0;
279 	static time_t pre_seconds = (time_t)0;
280 
281 	ASSERT(MUTEX_HELD(&tod_lock));
282 
283 	if (!verify_sc_tod_version()) {
284 		/* if we can't use SC */
285 		goto return_hrestime;
286 	}
287 	if (watchdog_activated != 0 || watchdog_enable != 0)
288 		complained = update_heartbeat();
289 	if (!complained && (iosram_read(SBBC_TOD_KEY,
290 			OFFSET(tod_buf, tod_get_value),
291 			(char *)&seconds, sizeof (time_t)))) {
292 		complained++;
293 		cmn_err(CE_WARN, "todsg_get(): read 64-bit tod value failed");
294 	}
295 	if (!complained && skew_adjust)  {
296 		/*
297 		 * This is our first chance to update IOSRAM
298 		 * with local copy of the skew,  so update it.
299 		 */
300 		complained = update_tod_skew(0);
301 	}
302 	if (!complained && iosram_read(SBBC_TOD_KEY,
303 			OFFSET(tod_buf, tod_domain_skew),
304 			(char *)&domain_skew, sizeof (time_t))) {
305 		complained++;
306 		cmn_err(CE_WARN, "todsg_get(): read tod domain skew failed");
307 	}
308 
309 	if (complained) {
310 		cmn_err(CE_WARN, "todsg_get(): turned off using tod");
311 		todsg_use_sc = 0;
312 		goto return_hrestime;
313 	}
314 
315 	/*
316 	 * If the SC gets rebooted, and we are using NTP, then we need
317 	 * to sync the IOSRAM to hrestime when the SC comes back.  We
318 	 * can determine that either NTP slew (or date -a) was called if
319 	 * the global timedelta was non-zero at any point while the SC
320 	 * was away.  If timedelta remains zero throughout, then the
321 	 * default action will be to sync hrestime to IOSRAM
322 	 */
323 	if (seconds != pre_seconds) {	/* SC still alive */
324 		pre_seconds = seconds;
325 		if (is_sc_down >= SC_DOWN_COUNT_THRESHOLD && adjust_sc_down) {
326 			skew_adjust = hrestime.tv_sec - (seconds + domain_skew);
327 			complained = update_tod_skew(0);
328 			if (!complained && (iosram_read(SBBC_TOD_KEY,
329 				OFFSET(tod_buf, tod_domain_skew),
330 				(char *)&domain_skew, sizeof (time_t)))) {
331 				complained++;
332 				cmn_err(CE_WARN, "todsg_get(): "
333 					"read tod domain skew failed");
334 			}
335 		}
336 		is_sc_down = 0;
337 		adjust_sc_down = 0;
338 
339 		/*
340 		 * If complained then domain_skew is invalid.
341 		 * Hand back hrestime instead.
342 		 */
343 		if (!complained) {
344 			timestruc_t ts = {0, 0};
345 			ts.tv_sec = seconds + domain_skew;
346 			return (ts);
347 		} else {
348 			goto return_hrestime;
349 		}
350 	}
351 
352 	/* SC/TOD is down */
353 	is_sc_down++;
354 	if (timedelta != 0) {
355 		adjust_sc_down = 1;
356 	}
357 
358 return_hrestime:
359 	/*
360 	 * We need to inform the tod_validate code to stop checking till
361 	 * SC come back up again. Note that we will return hrestime below
362 	 * which can be different that the previous TOD value we returned
363 	 */
364 	tod_fault_reset();
365 	return (hrestime);
366 }
367 
368 static void
369 todsg_set(timestruc_t ts)
370 {
371 	int complained = 0;
372 	tod_iosram_t tod_buf;
373 	time_t domain_skew;
374 	time_t seconds;
375 	time_t hwtod;
376 
377 	ASSERT(MUTEX_HELD(&tod_lock));
378 
379 	if (!verify_sc_tod_version()) {
380 		/* if we can't use SC */
381 		return;
382 	}
383 	/*
384 	 * If the SC is down just note the fact that we should
385 	 * have adjusted the hardware skew which caters for calls
386 	 * to stime(). (eg NTP step, as opposed to NTP skew)
387 	 */
388 	if (is_sc_down) {
389 		adjust_sc_down = 1;
390 		return;
391 	}
392 	/*
393 	 * reason to update i_am_alive here:
394 	 * To work around a generic Solaris bug that can
395 	 * cause tod_get() to be starved by too frequent
396 	 * calls to the stime() system call.
397 	 */
398 	if (watchdog_activated != 0 || watchdog_enable != 0)
399 		complained = update_heartbeat();
400 
401 	/*
402 	 * We are passed hrestime from clock.c so we need to read the
403 	 * IOSRAM for the hardware's idea of the time to see if we need
404 	 * to update the skew.
405 	 */
406 	if (!complained && (iosram_read(SBBC_TOD_KEY,
407 			OFFSET(tod_buf, tod_get_value),
408 			(char *)&seconds, sizeof (time_t)))) {
409 		complained++;
410 		cmn_err(CE_WARN, "todsg_set(): read 64-bit tod value failed");
411 	}
412 
413 	if (!complained && iosram_read(SBBC_TOD_KEY,
414 			OFFSET(tod_buf, tod_domain_skew),
415 			(char *)&domain_skew, sizeof (time_t))) {
416 		complained++;
417 		cmn_err(CE_WARN, "todsg_set(): read tod domain skew failed");
418 	}
419 
420 	/*
421 	 * Only update the skew if the time passed differs from
422 	 * what the hardware thinks & no errors talking to SC
423 	 */
424 	if (!complained && (ts.tv_sec != (seconds + domain_skew))) {
425 		hwtod = seconds + domain_skew;
426 		complained = update_tod_skew(ts.tv_sec - hwtod);
427 
428 		DCMNERR(CE_NOTE, "todsg_set(): set time %lX (%lX)%s",
429 			ts.tv_sec, hwtod, complained ? " failed" : "");
430 
431 	}
432 
433 	if (complained) {
434 		cmn_err(CE_WARN, "todsg_set(): turned off using tod");
435 		todsg_use_sc = 0;
436 	}
437 }
438 
439 static uint32_t
440 todsg_set_watchdog_timer(uint32_t timeoutval)
441 {
442 	tod_iosram_t tod_buf;
443 
444 	ASSERT(MUTEX_HELD(&tod_lock));
445 
446 	if (!verify_sc_tod_version()) {
447 		DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): "
448 			"verify_sc_tod_version failed");
449 		return (0);
450 	}
451 	DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): "
452 		"set watchdog timer value = %d", timeoutval);
453 
454 	if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period),
455 			(char *)&timeoutval, sizeof (uint32_t))) {
456 		DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): "
457 			"write new timeout value failed");
458 		return (0);
459 	}
460 	watchdog_activated = 1;
461 	return (timeoutval);
462 }
463 
464 static uint32_t
465 todsg_clear_watchdog_timer(void)
466 {
467 	tod_iosram_t tod_buf;
468 	uint32_t r_timeout_period;
469 	uint32_t w_timeout_period;
470 
471 	ASSERT(MUTEX_HELD(&tod_lock));
472 
473 	if ((watchdog_activated == 0) || !verify_sc_tod_version()) {
474 		DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): "
475 			"either watchdog not activated or "
476 			"verify_sc_tod_version failed");
477 		return (0);
478 	}
479 	if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period),
480 			(char *)&r_timeout_period, sizeof (uint32_t))) {
481 		DCMNERR(CE_NOTE, "todsg_clear_watchdog_timer(): "
482 			"read timeout value failed");
483 		return (0);
484 	}
485 	DCMNERR(CE_NOTE, "todsg_clear_watchdog_timer(): "
486 		"clear watchdog timer (old value=%d)", r_timeout_period);
487 	w_timeout_period = 0;
488 	if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period),
489 			(char *)&w_timeout_period, sizeof (uint32_t))) {
490 		DCMNERR(CE_NOTE, "todsg_clear_watchdog_timer(): "
491 			"write zero timeout value failed");
492 		return (0);
493 	}
494 	watchdog_activated = 0;
495 	return (r_timeout_period);
496 }
497 
498 /*
499  * Null function.
500  */
501 /* ARGSUSED */
502 static void
503 todsg_set_power_alarm(timestruc_t ts)
504 {
505 	ASSERT(MUTEX_HELD(&tod_lock));
506 }
507 
508 /*
509  * Null function
510  */
511 static void
512 todsg_clear_power_alarm()
513 {
514 	ASSERT(MUTEX_HELD(&tod_lock));
515 }
516 
517 /*
518  * Get clock freq from the cpunode
519  */
520 uint64_t
521 todsg_get_cpufrequency(void)
522 {
523 
524 	DCMNERR(CE_NOTE, "todsg_get_cpufrequency(): frequency=%ldMHz",
525 		cpunodes[CPU->cpu_id].clock_freq/1000000);
526 
527 	return (cpunodes[CPU->cpu_id].clock_freq);
528 }
529