xref: /illumos-gate/usr/src/uts/sun4u/io/todsg.c (revision b6805bf78d2bbbeeaea8909a05623587b42d58b3)
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 2010 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 3drop then",
103 		    (void *)&ssc_time);
104 
105 		prom_interpret(obp_string, 0, 0, 0, 0, 0);
106 
107 		if (ssc_time == (time_t)0) {
108 			cmn_err(CE_WARN, "Initial date is invalid. "
109 			    "This can be caused by older firmware.");
110 			cmn_err(CE_CONT, "Please flashupdate the System "
111 			    "Controller firmware to the latest version.\n");
112 			cmn_err(CE_CONT, "Attempting to set the date and time "
113 			    "based on the last shutdown.\n");
114 			cmn_err(CE_CONT, "Please inspect the date and time and "
115 			    "correct if necessary.\n");
116 		}
117 
118 		hrestime.tv_sec = ssc_time;
119 
120 		DCMNERR(CE_NOTE, "todsg: _init(): time from OBP 0x%lX",
121 		    ssc_time);
122 		/*
123 		 * Verify whether the received date/clock has overflowed
124 		 * an integer(32bit), so that we capture any corrupted
125 		 * date from SC, thereby preventing boot failure.
126 		 */
127 		if (TIMESPEC_OVERFLOW(&hrestime)) {
128 			cmn_err(CE_WARN, "Date overflow detected.");
129 			cmn_err(CE_CONT, "Attempting to set the date and time "
130 			    "based on the last shutdown.\n");
131 			cmn_err(CE_CONT, "Please inspect the date and time and "
132 			    "correct if necessary.\n");
133 
134 			/*
135 			 * By setting hrestime.tv_sec to zero
136 			 * we force the vfs_mountroot() to set
137 			 * the date from the last shutdown.
138 			 */
139 			hrestime.tv_sec = (time_t)0;
140 			/*
141 			 * Save the skew so that we can update
142 			 * IOSRAM when it becomes accessible.
143 			 */
144 			skew_adjust = -ssc_time;
145 		}
146 
147 		DCMNERR(CE_NOTE, "todsg:_init(): set tod_ops");
148 
149 		tod_ops.tod_get = todsg_get;
150 		tod_ops.tod_set = todsg_set;
151 		tod_ops.tod_set_watchdog_timer = todsg_set_watchdog_timer;
152 		tod_ops.tod_clear_watchdog_timer = todsg_clear_watchdog_timer;
153 		tod_ops.tod_set_power_alarm = todsg_set_power_alarm;
154 		tod_ops.tod_clear_power_alarm = todsg_clear_power_alarm;
155 		tod_ops.tod_get_cpufrequency = todsg_get_cpufrequency;
156 	}
157 
158 	return (mod_install(&modlinkage));
159 
160 }
161 
162 int
163 _fini(void)
164 {
165 	if (strcmp(tod_module_name, "todsg") == 0)
166 		return (EBUSY);
167 	else
168 		return (mod_remove(&modlinkage));
169 }
170 
171 int
172 _info(struct modinfo *modinfop)
173 {
174 	return (mod_info(&modlinkage, modinfop));
175 }
176 
177 static int
178 update_heartbeat(void)
179 {
180 	tod_iosram_t tod_buf;
181 	int complained = 0;
182 
183 	/* Update the heartbeat */
184 	if (i_am_alive == UINT32_MAX)
185 		i_am_alive = 0;
186 	else
187 		i_am_alive++;
188 	if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_i_am_alive),
189 	    (char *)&i_am_alive, sizeof (uint32_t))) {
190 		complained++;
191 		cmn_err(CE_WARN, "update_heartbeat(): write heartbeat failed");
192 	}
193 	return (complained);
194 }
195 
196 static int
197 verify_sc_tod_version(void)
198 {
199 	uint32_t magic;
200 	tod_iosram_t tod_buf;
201 
202 	if (!todsg_use_sc)
203 		return (FALSE);
204 	/*
205 	 * read tod_version only when the first time and
206 	 * when there has been a previous sc down time
207 	 */
208 	if (!sc_tod_version || is_sc_down >= SC_DOWN_COUNT_THRESHOLD) {
209 		if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_magic),
210 		    (char *)&magic, sizeof (uint32_t)) ||
211 		    magic != TODSG_MAGIC) {
212 			cmn_err(CE_WARN, "get_sc_tod_version(): "
213 			    "TOD SRAM magic error");
214 			return (FALSE);
215 		}
216 		if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_version),
217 		    (char *)&sc_tod_version, sizeof (uint32_t))) {
218 			cmn_err(CE_WARN, "get_sc_tod_version(): "
219 			    "read tod version failed");
220 			sc_tod_version = 0;
221 			return (FALSE);
222 		}
223 	}
224 	if (sc_tod_version >= SC_TOD_MIN_REV) {
225 		return (TRUE);
226 	} else {
227 		todsg_use_sc = 0;
228 		cmn_err(CE_WARN, "todsg_get(): incorrect firmware version, "
229 		    "(%d): expected version >= %d.", sc_tod_version,
230 		    SC_TOD_MIN_REV);
231 	}
232 	return (FALSE);
233 }
234 
235 static int
236 update_tod_skew(time_t skew)
237 {
238 	time_t domain_skew;
239 	tod_iosram_t tod_buf;
240 	int complained = 0;
241 
242 	DCMNERR(CE_NOTE, "update_tod_skew(): skew  0x%lX", skew);
243 
244 	if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_domain_skew),
245 	    (char *)&domain_skew, sizeof (time_t))) {
246 		complained++;
247 		cmn_err(CE_WARN,
248 		    "update_tod_skew(): read tod domain skew failed");
249 	}
250 	domain_skew += skew;
251 	/* we shall update the skew_adjust too now */
252 	domain_skew += skew_adjust;
253 	if (!complained && iosram_write(SBBC_TOD_KEY,
254 	    OFFSET(tod_buf, tod_domain_skew), (char *)&domain_skew,
255 	    sizeof (time_t))) {
256 		complained++;
257 		cmn_err(CE_WARN,
258 		    "update_tod_skew(): write domain skew failed");
259 	}
260 	if (!complained)
261 		skew_adjust = 0;
262 	return (complained);
263 }
264 
265 /*
266  * Return time value read from IOSRAM.
267  * Must be called with tod_lock held.
268  */
269 static timestruc_t
270 todsg_get(void)
271 {
272 	tod_iosram_t tod_buf;
273 	time_t seconds;
274 	time_t domain_skew;
275 	int complained = 0;
276 	static time_t pre_seconds = (time_t)0;
277 
278 	ASSERT(MUTEX_HELD(&tod_lock));
279 
280 	if (!verify_sc_tod_version()) {
281 		/* if we can't use SC */
282 		goto return_hrestime;
283 	}
284 	if (watchdog_activated != 0 || watchdog_enable != 0)
285 		complained = update_heartbeat();
286 	if (!complained && (iosram_read(SBBC_TOD_KEY,
287 	    OFFSET(tod_buf, tod_get_value), (char *)&seconds,
288 	    sizeof (time_t)))) {
289 		complained++;
290 		cmn_err(CE_WARN, "todsg_get(): read 64-bit tod value failed");
291 	}
292 	if (!complained && skew_adjust)  {
293 		/*
294 		 * This is our first chance to update IOSRAM
295 		 * with local copy of the skew,  so update it.
296 		 */
297 		complained = update_tod_skew(0);
298 	}
299 	if (!complained && iosram_read(SBBC_TOD_KEY,
300 	    OFFSET(tod_buf, tod_domain_skew), (char *)&domain_skew,
301 	    sizeof (time_t))) {
302 		complained++;
303 		cmn_err(CE_WARN, "todsg_get(): read tod domain skew failed");
304 	}
305 
306 	if (complained) {
307 		cmn_err(CE_WARN, "todsg_get(): turned off using tod");
308 		todsg_use_sc = 0;
309 		goto return_hrestime;
310 	}
311 
312 	/*
313 	 * If the SC gets rebooted, and we are using NTP, then we need
314 	 * to sync the IOSRAM to hrestime when the SC comes back.  We
315 	 * can determine that either NTP slew (or date -a) was called if
316 	 * the global timedelta was non-zero at any point while the SC
317 	 * was away.  If timedelta remains zero throughout, then the
318 	 * default action will be to sync hrestime to IOSRAM
319 	 */
320 	if (seconds != pre_seconds) {	/* SC still alive */
321 		pre_seconds = seconds;
322 		if (is_sc_down >= SC_DOWN_COUNT_THRESHOLD && adjust_sc_down) {
323 			skew_adjust = hrestime.tv_sec - (seconds + domain_skew);
324 			complained = update_tod_skew(0);
325 			if (!complained && (iosram_read(SBBC_TOD_KEY,
326 			    OFFSET(tod_buf, tod_domain_skew),
327 			    (char *)&domain_skew, sizeof (time_t)))) {
328 				complained++;
329 				cmn_err(CE_WARN, "todsg_get(): "
330 				    "read tod domain skew failed");
331 			}
332 		}
333 		is_sc_down = 0;
334 		adjust_sc_down = 0;
335 
336 		/*
337 		 * If complained then domain_skew is invalid.
338 		 * Hand back hrestime instead.
339 		 */
340 		if (!complained) {
341 			/*
342 			 * The read was successful so ensure the failure
343 			 * flag is clear.
344 			 */
345 			tod_status_clear(TOD_GET_FAILED);
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 until
363 	 * the SC comes back up again.  Note we will return hrestime below
364 	 * which may be different to the previous TOD value we returned.
365 	 */
366 	tod_status_set(TOD_GET_FAILED);
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), (char *)&seconds,
410 	    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), (char *)&domain_skew,
417 	    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 	if (complained) {
435 		cmn_err(CE_WARN, "todsg_set(): turned off using tod");
436 		todsg_use_sc = 0;
437 	}
438 }
439 
440 static uint32_t
441 todsg_set_watchdog_timer(uint32_t timeoutval)
442 {
443 	tod_iosram_t tod_buf;
444 
445 	ASSERT(MUTEX_HELD(&tod_lock));
446 
447 	if (!verify_sc_tod_version()) {
448 		DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): "
449 		    "verify_sc_tod_version failed");
450 		return (0);
451 	}
452 	DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): "
453 	    "set watchdog timer value = %d", timeoutval);
454 
455 	if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period),
456 	    (char *)&timeoutval, sizeof (uint32_t))) {
457 		DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): "
458 		    "write new timeout value failed");
459 		return (0);
460 	}
461 	watchdog_activated = 1;
462 	return (timeoutval);
463 }
464 
465 static uint32_t
466 todsg_clear_watchdog_timer(void)
467 {
468 	tod_iosram_t tod_buf;
469 	uint32_t r_timeout_period;
470 	uint32_t w_timeout_period;
471 
472 	ASSERT(MUTEX_HELD(&tod_lock));
473 
474 	if ((watchdog_activated == 0) || !verify_sc_tod_version()) {
475 		DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): "
476 		    "either watchdog not activated or "
477 		    "verify_sc_tod_version failed");
478 		return (0);
479 	}
480 	if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period),
481 	    (char *)&r_timeout_period, sizeof (uint32_t))) {
482 		DCMNERR(CE_NOTE, "todsg_clear_watchdog_timer(): "
483 		    "read timeout value failed");
484 		return (0);
485 	}
486 	DCMNERR(CE_NOTE, "todsg_clear_watchdog_timer(): "
487 	    "clear watchdog timer (old value=%d)", r_timeout_period);
488 	w_timeout_period = 0;
489 	if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period),
490 	    (char *)&w_timeout_period, sizeof (uint32_t))) {
491 		DCMNERR(CE_NOTE, "todsg_clear_watchdog_timer(): "
492 		    "write zero timeout value failed");
493 		return (0);
494 	}
495 	watchdog_activated = 0;
496 	return (r_timeout_period);
497 }
498 
499 /*
500  * Null function.
501  */
502 /* ARGSUSED */
503 static void
504 todsg_set_power_alarm(timestruc_t ts)
505 {
506 	ASSERT(MUTEX_HELD(&tod_lock));
507 }
508 
509 /*
510  * Null function
511  */
512 static void
513 todsg_clear_power_alarm()
514 {
515 	ASSERT(MUTEX_HELD(&tod_lock));
516 }
517 
518 /*
519  * Get clock freq from the cpunode
520  */
521 uint64_t
522 todsg_get_cpufrequency(void)
523 {
524 
525 	DCMNERR(CE_NOTE, "todsg_get_cpufrequency(): frequency=%ldMHz",
526 	    cpunodes[CPU->cpu_id].clock_freq/1000000);
527 
528 	return (cpunodes[CPU->cpu_id].clock_freq);
529 }
530