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)
44static 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
54static timestruc_t	todsg_get(void);
55static void		todsg_set(timestruc_t);
56static uint32_t		todsg_set_watchdog_timer(uint_t);
57static uint32_t		todsg_clear_watchdog_timer(void);
58static void		todsg_set_power_alarm(timestruc_t);
59static void		todsg_clear_power_alarm(void);
60static uint64_t		todsg_get_cpufrequency(void);
61static int 		update_heartbeat(void);
62static int		verify_sc_tod_version(void);
63static int 		update_tod_skew(time_t skew);
64
65static uint32_t i_am_alive = 0;
66static uint32_t sc_tod_version = 0;
67static time_t 	skew_adjust = 0;
68static int 	is_sc_down = 0;
69static int	adjust_sc_down = 0;
70
71/*
72 * Module linkage information for the kernel.
73 */
74static struct modlmisc modlmisc = {
75	&mod_miscops, "Serengeti tod module"
76};
77
78static struct modlinkage modlinkage = {
79	MODREV_1, (void *)&modlmisc, NULL
80};
81
82int
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
162int
163_fini(void)
164{
165	if (strcmp(tod_module_name, "todsg") == 0)
166		return (EBUSY);
167	else
168		return (mod_remove(&modlinkage));
169}
170
171int
172_info(struct modinfo *modinfop)
173{
174	return (mod_info(&modlinkage, modinfop));
175}
176
177static int
178update_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
196static int
197verify_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
235static int
236update_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 */
269static timestruc_t
270todsg_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
360return_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
370static void
371todsg_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
440static uint32_t
441todsg_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
465static uint32_t
466todsg_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 */
503static void
504todsg_set_power_alarm(timestruc_t ts)
505{
506	ASSERT(MUTEX_HELD(&tod_lock));
507}
508
509/*
510 * Null function
511 */
512static void
513todsg_clear_power_alarm()
514{
515	ASSERT(MUTEX_HELD(&tod_lock));
516}
517
518/*
519 * Get clock freq from the cpunode
520 */
521uint64_t
522todsg_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