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