xref: /illumos-gate/usr/src/uts/sun4u/io/todopl.c (revision cf9a187ca73bc899270af62ab1cd99f2e205cf41)
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 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * tod driver module for OPL (implements a soft tod)
30  */
31 
32 #include <sys/types.h>
33 #include <sys/param.h>
34 #include <sys/sysmacros.h>
35 #include <sys/systm.h>
36 #include <sys/errno.h>
37 #include <sys/modctl.h>
38 #include <sys/autoconf.h>
39 #include <sys/debug.h>
40 #include <sys/clock.h>
41 #include <sys/cmn_err.h>
42 #include <sys/prom_plat.h>
43 #include <sys/cpuvar.h>
44 #include <sys/opl_module.h>
45 
46 /*
47  * Debug stuff
48  */
49 #ifdef DEBUG
50 int todopl_debug = 0;
51 #define	TODOPL_DEBUG(args)  if (todopl_debug) cmn_err args
52 #else
53 #define	TODOPL_DEBUG(args)
54 #endif
55 
56 #define	abs(x)	((x) < 0 ? -(x) : (x))
57 
58 #define	TODOPL_SET_THRESHOLD	30
59 
60 static timestruc_t	todopl_get(void);
61 static void		todopl_set(timestruc_t);
62 static uint_t		todopl_set_watchdog_timer(uint_t);
63 static uint_t		todopl_clear_watchdog_timer(void);
64 static void		todopl_set_power_alarm(timestruc_t);
65 static void		todopl_clear_power_alarm(void);
66 static uint64_t		todopl_get_cpufrequency(void);
67 
68 /*
69  * Module linkage information for the kernel.
70  */
71 static struct modlmisc modlmisc = {
72 	&mod_miscops, "Soft tod module for OPL 1.11"
73 };
74 
75 static struct modlinkage modlinkage = {
76 	MODREV_1, (void *)&modlmisc, NULL
77 };
78 
79 /*
80  * The TOD OPL logic description.
81  *
82  * The todopl driver uses promif functions prom_opl_get_tod() and
83  * prom_opl_set_diff(). These functions call FJSV,get-tod and
84  * FJSV,set-domain-time OBP client services.
85  *
86  * At the system boot or reboot:
87  *
88  *    FJSV,tod-get
89  * OS  --------->   OBP     SCF I/F
90  *                         ----------->  XSCF
91  *                         <-----------
92  *     <--------            time, diff
93  *    time+diff, stick
94  *
95  * Note that on first powerup domain boot, diff is zero.
96  *
97  * When system updates the time via date(1m):
98  *
99  *   FJSV,set-domain-time
100  * OS   --------->   OBP                      SRAM
101  *      diff_delta        diff += diff_delta ------------->  XSCF
102  *
103  * diff_delta = new time -  current domain time (hrestime)
104  *
105  *
106  * In theory, FJSV,get-tod and FJSV,set-domain-time should never fails.
107  * But, if call to FJSV,get-tod fails on boot, the domain will be unable
108  * to calculate "diff" properly and synchronization between Domain and
109  * SP will be broken. In this particular case, we notify users that
110  * "there is no time synchronization" and the logic will attempt to
111  * resync with the SP whenever the OS tries to do a TOD update.
112  * (e.g. via date(1m) or NTP).
113  */
114 
115 static	int enable_time_sync = 1;
116 
117 int
118 _init(void)
119 {
120 	int64_t	stick;
121 	time_t	obp_time = 0;
122 	int64_t obp_stick;
123 
124 	if (strcmp(tod_module_name, "todopl") == 0) {
125 		/*
126 		 * Get TOD time from OBP and adjust it.
127 		 */
128 		prom_opl_get_tod(&obp_time, &obp_stick);
129 
130 		TODOPL_DEBUG((CE_NOTE, "todopl: OBP time 0x%lx stick 0x%lx\n",
131 			obp_time, obp_stick));
132 
133 		if (obp_time != 0) {
134 			/*
135 			 * adjust OBP time by stick counts
136 			 */
137 			stick_timestamp(&stick);
138 			obp_time += ((stick - obp_stick) / system_clock_freq);
139 
140 			TODOPL_DEBUG((CE_NOTE,
141 				"todopl: cpu stick 0x%lx sys_time 0x%lx\n",
142 				stick, obp_time));
143 		} else {
144 			/*
145 			 * A date of zero causes the root filesystem driver
146 			 * to try to set the date from the last shutdown.
147 			 */
148 			enable_time_sync = 0;
149 			cmn_err(CE_WARN, "Initial date is invalid.");
150 			cmn_err(CE_CONT, "Attempting to set the date and time "
151 				"based on the last shutdown.\n");
152 			cmn_err(CE_CONT, "The time could not be synchronized "
153 				"between Domain and Service Processor.\n");
154 			cmn_err(CE_CONT, "Please inspect the date and time and "
155 				"correct if necessary.\n");
156 		}
157 
158 		hrestime.tv_sec = obp_time;
159 
160 		/*
161 		 * Check that the date has not overflowed a 32-bit integer.
162 		 */
163 		if (TIMESPEC_OVERFLOW(&hrestime)) {
164 			cmn_err(CE_WARN, "Date overflow detected.");
165 			cmn_err(CE_CONT, "Attempting to set the date and time "
166 				"based on the last shutdown.\n");
167 			cmn_err(CE_CONT, "Please inspect the date and time and "
168 				"correct if necessary.\n");
169 
170 			hrestime.tv_sec = (time_t)0;
171 		}
172 
173 		tod_ops.tod_get = todopl_get;
174 		tod_ops.tod_set = todopl_set;
175 		tod_ops.tod_set_watchdog_timer = todopl_set_watchdog_timer;
176 		tod_ops.tod_clear_watchdog_timer = todopl_clear_watchdog_timer;
177 		tod_ops.tod_set_power_alarm = todopl_set_power_alarm;
178 		tod_ops.tod_clear_power_alarm = todopl_clear_power_alarm;
179 		tod_ops.tod_get_cpufrequency = todopl_get_cpufrequency;
180 
181 		/*
182 		 * Flag warning if user tried to use hardware watchdog
183 		 */
184 		if (watchdog_enable) {
185 			cmn_err(CE_WARN, "Hardware watchdog unavailable");
186 		}
187 	}
188 
189 	return (mod_install(&modlinkage));
190 }
191 
192 int
193 _fini(void)
194 {
195 	if (strcmp(tod_module_name, "todopl") == 0)
196 		return (EBUSY);
197 	else
198 		return (mod_remove(&modlinkage));
199 }
200 
201 int
202 _info(struct modinfo *modinfop)
203 {
204 	return (mod_info(&modlinkage, modinfop));
205 }
206 
207 
208 /*
209  * OPL tod_get is simplified to return hrestime
210  * Must be called with tod_lock held.
211  */
212 static timestruc_t
213 todopl_get(void)
214 {
215 	ASSERT(MUTEX_HELD(&tod_lock));
216 	return (hrestime);
217 }
218 
219 /*
220  * Must be called with tod_lock held.
221  *
222  * When running NTP, tod_set is called at least once per second in order
223  * to update the hardware clock. To minimize pressure on SP, we want only
224  * to record significant time changes on the SP (when date(1M) is run).
225  * We have 30 seconds threshold requirement before recording the time change.
226  */
227 /* ARGSUSED */
228 static void
229 todopl_set(timestruc_t ts)
230 {
231 	ASSERT(MUTEX_HELD(&tod_lock));
232 
233 	if (abs(ts.tv_sec - hrestime.tv_sec) > TODOPL_SET_THRESHOLD) {
234 		/*
235 		 * Send time difference to SP
236 		 */
237 		if (enable_time_sync)
238 			prom_opl_set_diff(ts.tv_sec - hrestime.tv_sec);
239 		else {
240 			/*
241 			 * We did not get a successful initial time
242 			 * update/sync from the SP via OBP during boot.
243 			 * Try again here.
244 			 */
245 			time_t  obp_time = 0;
246 			int64_t obp_stick;
247 			int64_t stick;
248 
249 			prom_opl_get_tod(&obp_time, &obp_stick);
250 
251 			if (obp_time != 0) {
252 				/*
253 				 * adjust OBP time by stick counts
254 				 */
255 				stick_timestamp(&stick);
256 				obp_time += ((stick - obp_stick) /
257 					system_clock_freq);
258 
259 				/*
260 				 * Sync up by computing the diff using the
261 				 * newly acquired SP/OBP reference time
262 				 */
263 				prom_opl_set_diff(ts.tv_sec - obp_time);
264 
265 				enable_time_sync = 1;
266 			}
267 		}
268 		TODOPL_DEBUG((CE_NOTE, "todopl_set: new domain time 0x%lx\n",
269 			ts.tv_sec));
270 	}
271 }
272 
273 /*
274  * No watchdog function.
275  */
276 /* ARGSUSED */
277 static uint_t
278 todopl_set_watchdog_timer(uint_t timeoutval)
279 {
280 	ASSERT(MUTEX_HELD(&tod_lock));
281 	return (0);
282 }
283 
284 /*
285  * No watchdog function
286  */
287 static uint_t
288 todopl_clear_watchdog_timer(void)
289 {
290 	ASSERT(MUTEX_HELD(&tod_lock));
291 	return (0);
292 }
293 
294 /*
295  * Null function.
296  */
297 /* ARGSUSED */
298 static void
299 todopl_set_power_alarm(timestruc_t ts)
300 {
301 	ASSERT(MUTEX_HELD(&tod_lock));
302 }
303 
304 /*
305  * Null function
306  */
307 static void
308 todopl_clear_power_alarm()
309 {
310 	ASSERT(MUTEX_HELD(&tod_lock));
311 }
312 
313 /*
314  * Get clock freq from the cpunode.  This function is only called
315  * when use_stick = 0, otherwise, system_clock_freq gets used instead.
316  */
317 uint64_t
318 todopl_get_cpufrequency(void)
319 {
320 	return (cpunodes[CPU->cpu_id].clock_freq);
321 }
322