xref: /illumos-gate/usr/src/uts/sun4u/excalibur/io/xcalwd.c (revision 46b592853d0f4f11781b6b0a7533f267c6aee132)
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 /*
28  * Excalibur fans watchdog module
29  */
30 
31 #include <sys/conf.h>
32 #include <sys/types.h>
33 #include <sys/mkdev.h>
34 #include <sys/ddi.h>
35 #include <sys/stat.h>
36 #include <sys/modctl.h>
37 #include <sys/sunddi.h>
38 #include <sys/sunndi.h>
39 #include <sys/ksynch.h>
40 #include <sys/file.h>
41 #include <sys/errno.h>
42 #include <sys/open.h>
43 #include <sys/cred.h>
44 #include <sys/xcalwd.h>
45 #include <sys/policy.h>
46 #include <sys/platform_module.h>
47 
48 extern	struct	mod_ops	mod_driverops;
49 
50 #define	MINOR_DEVICE_NAME	"xcalwd"
51 
52 /*
53  * Define your per instance state data
54  */
55 typedef	struct xcalwd_state {
56 	kmutex_t	lock;
57 	boolean_t	started;
58 	int		intvl;
59 	timeout_id_t	tid;
60 	dev_info_t	*dip;
61 } xcalwd_state_t;
62 
63 /*
64  * Pointer to soft states
65  */
66 static	void	*xcalwd_statep;
67 
68 /*
69  * dev_ops
70  */
71 static	int	xcalwd_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd,
72 	void *arg, void **resultp);
73 static	int	xcalwd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
74 static	int	xcalwd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
75 
76 /*
77  * cb_ops
78  */
79 static	int	xcalwd_open(dev_t *devp, int flag, int otyp, cred_t *credp);
80 static	int	xcalwd_close(dev_t dev, int flag, int otyp, cred_t *credp);
81 static	int	xcalwd_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
82 			cred_t *credp, int *rvalp);
83 /*
84  * timeout handler
85  */
86 static	void	xcalwd_timeout(void *arg);
87 
88 /*
89  * cb_ops
90  */
91 static struct cb_ops xcalwd_cb_ops = {
92 	xcalwd_open,			/* open */
93 	xcalwd_close,			/* close */
94 	nodev,				/* strategy */
95 	nodev,				/* print */
96 	nodev,				/* dump */
97 	nodev,				/* read */
98 	nodev,				/* write */
99 	xcalwd_ioctl,			/* ioctl */
100 	nodev,				/* devmap */
101 	nodev,				/* mmap */
102 	nodev,				/* segmap */
103 	nochpoll,			/* chpoll */
104 	ddi_prop_op,			/* prop_op */
105 	NULL,				/* streamtab */
106 	D_NEW | D_MP | D_64BIT,		/* cb_flag */
107 	CB_REV,				/* rev */
108 	nodev,				/* int (*cb_aread)() */
109 	nodev				/* int (*cb_awrite)() */
110 };
111 
112 /*
113  * dev_ops
114  */
115 static struct dev_ops xcalwd_dev_ops = {
116 	DEVO_REV,			/* devo_rev */
117 	0,				/* devo_refcnt */
118 	xcalwd_getinfo,			/* getinfo */
119 	nulldev,			/* identify */
120 	nulldev,			/* probe */
121 	xcalwd_attach,			/* attach */
122 	xcalwd_detach,			/* detach */
123 	nodev,				/* devo_reset */
124 	&xcalwd_cb_ops,			/* devo_cb_ops */
125 	NULL,				/* devo_bus_ops */
126 	NULL,				/* devo_power */
127 	ddi_quiesce_not_needed,			/* devo_quiesce */
128 };
129 
130 /*
131  * modldrv
132  */
133 static struct modldrv xcalwd_modldrv = {
134 	&mod_driverops,			/* drv_modops */
135 	"Excalibur watchdog timer v1.7 ",	/* drv_linkinfo */
136 	&xcalwd_dev_ops		/* drv_dev_ops */
137 };
138 
139 /*
140  * modlinkage
141  */
142 static struct modlinkage xcalwd_modlinkage = {
143 	MODREV_1,
144 	&xcalwd_modldrv,
145 	NULL
146 };
147 
148 int
149 _init(void)
150 {
151 	int		error;
152 
153 	/*
154 	 * Initialize the module state structure
155 	 */
156 	error = ddi_soft_state_init(&xcalwd_statep,
157 	    sizeof (xcalwd_state_t), 0);
158 	if (error) {
159 		return (error);
160 	}
161 
162 	/*
163 	 * Link the driver into the system
164 	 */
165 	error = mod_install(&xcalwd_modlinkage);
166 	if (error) {
167 		ddi_soft_state_fini(&xcalwd_statep);
168 		return (error);
169 	}
170 	return (0);
171 }
172 
173 int
174 _fini(void)
175 {
176 	int		error;
177 
178 	error = mod_remove(&xcalwd_modlinkage);
179 	if (error != 0) {
180 		return (error);
181 	}
182 
183 	/*
184 	 * Cleanup resources allocated in _init
185 	 */
186 	ddi_soft_state_fini(&xcalwd_statep);
187 	return (0);
188 }
189 
190 int
191 _info(struct modinfo *modinfop)
192 {
193 	return (mod_info(&xcalwd_modlinkage, modinfop));
194 }
195 
196 /*ARGSUSED*/
197 static int
198 xcalwd_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd,
199 	void *arg, void **resultp)
200 {
201 	int	retval;
202 	dev_t	dev = (dev_t)arg;
203 	int	instance;
204 	xcalwd_state_t	*tsp;
205 
206 	retval = DDI_FAILURE;
207 	switch (cmd) {
208 	case DDI_INFO_DEVT2DEVINFO:
209 		instance = getminor(dev);
210 		tsp = ddi_get_soft_state(xcalwd_statep, instance);
211 		if (tsp == NULL)
212 			*resultp = NULL;
213 		else {
214 			*resultp = tsp->dip;
215 			retval = DDI_SUCCESS;
216 		}
217 		break;
218 	case DDI_INFO_DEVT2INSTANCE:
219 		*resultp = (void *)(uintptr_t)getminor(dev);
220 		retval = DDI_SUCCESS;
221 		break;
222 	default:
223 		break;
224 	}
225 	return (retval);
226 }
227 
228 static int
229 xcalwd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
230 {
231 	int		instance;
232 	xcalwd_state_t	*tsp;
233 
234 	switch (cmd) {
235 	case DDI_ATTACH:
236 		instance = ddi_get_instance(dip);
237 
238 		if (&plat_fan_blast == NULL) {
239 			cmn_err(CE_WARN, "missing plat_fan_blast function");
240 			return (DDI_FAILURE);
241 		}
242 
243 		if (ddi_soft_state_zalloc(xcalwd_statep, instance) !=
244 		    DDI_SUCCESS) {
245 			cmn_err(CE_WARN, "attach could not alloc"
246 			    "%d state structure", instance);
247 			return (DDI_FAILURE);
248 		}
249 
250 		tsp = ddi_get_soft_state(xcalwd_statep, instance);
251 		if (tsp == NULL) {
252 			cmn_err(CE_WARN, "get state failed %d",
253 			    instance);
254 			return (DDI_FAILURE);
255 		}
256 
257 		if (ddi_create_minor_node(dip, MINOR_DEVICE_NAME,
258 		    S_IFCHR, instance, DDI_PSEUDO, NULL) == DDI_FAILURE) {
259 			cmn_err(CE_WARN, "create minor node failed\n");
260 			return (DDI_FAILURE);
261 		}
262 
263 		mutex_init(&tsp->lock, NULL, MUTEX_DRIVER, NULL);
264 		tsp->started = B_FALSE;
265 		tsp->intvl = 0;
266 		tsp->tid = 0;
267 		tsp->dip = dip;
268 		ddi_report_dev(dip);
269 		return (DDI_SUCCESS);
270 
271 	case DDI_RESUME:
272 		return (DDI_SUCCESS);
273 	default:
274 		break;
275 	}
276 	return (DDI_FAILURE);
277 }
278 
279 static int
280 xcalwd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
281 {
282 	xcalwd_state_t	*tsp;
283 	int			instance;
284 
285 	switch (cmd) {
286 	case DDI_DETACH:
287 		instance = ddi_get_instance(dip);
288 		tsp = ddi_get_soft_state(xcalwd_statep, instance);
289 		ddi_remove_minor_node(dip, NULL);
290 		mutex_destroy(&tsp->lock);
291 		ddi_soft_state_free(xcalwd_statep, instance);
292 		return (DDI_SUCCESS);
293 	case DDI_SUSPEND:
294 		return (DDI_SUCCESS);
295 	default:
296 		break;
297 	}
298 	return (DDI_FAILURE);
299 }
300 
301 /*
302  * Watchdog timeout handler that calls plat_fan_blast to take
303  * the failsafe action.
304  */
305 static void
306 xcalwd_timeout(void *arg)
307 {
308 	int	instance = (int)(uintptr_t)arg;
309 	xcalwd_state_t	*tsp;
310 
311 	if (instance < 0)
312 		return;
313 
314 	tsp = ddi_get_soft_state(xcalwd_statep, instance);
315 	if (tsp == NULL)
316 		return;
317 
318 	mutex_enter(&tsp->lock);
319 	if (tsp->started == B_FALSE || tsp->tid == 0) {
320 		tsp->tid = 0;
321 		mutex_exit(&tsp->lock);
322 		return;
323 	}
324 	mutex_exit(&tsp->lock);
325 
326 	plat_fan_blast();
327 }
328 
329 /*ARGSUSED*/
330 static int
331 xcalwd_open(dev_t *devp, int flag, int otyp, cred_t *credp)
332 {
333 	int			instance;
334 
335 	if (secpolicy_sys_config(credp, B_FALSE) != 0)
336 		return (EPERM);
337 
338 	if (otyp != OTYP_CHR)
339 		return (EINVAL);
340 
341 	instance = getminor(*devp);
342 	if (instance < 0)
343 		return (ENXIO);
344 
345 	if (ddi_get_soft_state(xcalwd_statep, instance) == NULL) {
346 		return (ENXIO);
347 	}
348 
349 	return (0);
350 }
351 
352 /*ARGSUSED*/
353 static int
354 xcalwd_close(dev_t dev, int flag, int otyp, cred_t *credp)
355 {
356 	xcalwd_state_t	*tsp;
357 	int			instance;
358 	timeout_id_t		tid;
359 
360 	instance = getminor(dev);
361 	if (instance < 0)
362 		return (ENXIO);
363 	tsp = ddi_get_soft_state(xcalwd_statep, instance);
364 	if (tsp == NULL)
365 		return (ENXIO);
366 
367 	mutex_enter(&tsp->lock);
368 	if (tsp->started == B_FALSE) {
369 		tsp->tid = 0;
370 		mutex_exit(&tsp->lock);
371 		return (0);
372 	}
373 	/*
374 	 * The watchdog is enabled. Cancel the pending timer
375 	 * and call plat_fan_blast.
376 	 */
377 	tsp->started = B_FALSE;
378 	tid = tsp->tid;
379 	tsp->tid = 0;
380 	mutex_exit(&tsp->lock);
381 	if (tid != 0)
382 		(void) untimeout(tid);
383 	plat_fan_blast();
384 
385 	return (0);
386 }
387 
388 /*
389  * These are private ioctls for PICL environmental control plug-in
390  * to use. The plug-in enables the watchdog before performing
391  * altering fan speeds. It also periodically issues a keepalive
392  * to the watchdog to cancel and reinstate the watchdog timer.
393  * The watchdog timeout handler when executed with the watchdog
394  * enabled sets fans to full blast by calling plat_fan_blast.
395  */
396 /*ARGSUSED*/
397 static int
398 xcalwd_ioctl(dev_t dev, int cmd, intptr_t arg, int flag,
399 			cred_t *cred_p, int *rvalp)
400 {
401 	int		instance;
402 	xcalwd_state_t	*tsp;
403 	int		intvl;
404 	int		o_intvl;
405 	boolean_t	curstate;
406 	timeout_id_t	tid;
407 
408 	if (secpolicy_sys_config(cred_p, B_FALSE) != 0)
409 		return (EPERM);
410 
411 	instance = getminor(dev);
412 	if (instance < 0)
413 		return (ENXIO);
414 
415 	tsp = ddi_get_soft_state(xcalwd_statep, instance);
416 	if (tsp == NULL)
417 		return (ENXIO);
418 
419 	switch (cmd) {
420 	case XCALWD_STOPWATCHDOG:
421 		/*
422 		 * cancels any pending timer and disables the timer.
423 		 */
424 		tid = 0;
425 		mutex_enter(&tsp->lock);
426 		if (tsp->started == B_FALSE) {
427 			mutex_exit(&tsp->lock);
428 			return (0);
429 		}
430 		tid = tsp->tid;
431 		tsp->started = B_FALSE;
432 		tsp->tid = 0;
433 		mutex_exit(&tsp->lock);
434 		if (tid != 0)
435 			(void) untimeout(tid);
436 		return (0);
437 	case XCALWD_STARTWATCHDOG:
438 		if (ddi_copyin((void *)arg, &intvl, sizeof (intvl), flag))
439 			return (EFAULT);
440 		if (intvl == 0)
441 			return (EINVAL);
442 
443 		mutex_enter(&tsp->lock);
444 		o_intvl = tsp->intvl;
445 		mutex_exit(&tsp->lock);
446 
447 		if (ddi_copyout((const void *)&o_intvl, (void *)arg,
448 		    sizeof (o_intvl), flag))
449 			return (EFAULT);
450 
451 		mutex_enter(&tsp->lock);
452 		if (tsp->started == B_TRUE) {
453 			mutex_exit(&tsp->lock);
454 			return (EINVAL);
455 		}
456 		tsp->intvl = intvl;
457 		tsp->tid = realtime_timeout(xcalwd_timeout,
458 		    (void *)(uintptr_t)instance,
459 		    drv_usectohz(1000000) * tsp->intvl);
460 		tsp->started = B_TRUE;
461 		mutex_exit(&tsp->lock);
462 		return (0);
463 	case XCALWD_KEEPALIVE:
464 		tid = 0;
465 		mutex_enter(&tsp->lock);
466 		tid = tsp->tid;
467 		tsp->tid = 0;
468 		mutex_exit(&tsp->lock);
469 		if (tid != 0)
470 			(void) untimeout(tid);	/* cancel */
471 
472 		mutex_enter(&tsp->lock);
473 		if (tsp->started == B_TRUE)	/* reinstate */
474 			tsp->tid = realtime_timeout(xcalwd_timeout,
475 			    (void *)(uintptr_t)instance,
476 			    drv_usectohz(1000000) * tsp->intvl);
477 		mutex_exit(&tsp->lock);
478 		return (0);
479 	case XCALWD_GETSTATE:
480 		mutex_enter(&tsp->lock);
481 		curstate = tsp->started;
482 		mutex_exit(&tsp->lock);
483 		if (ddi_copyout((const void *)&curstate, (void *)arg,
484 		    sizeof (curstate), flag))
485 			return (EFAULT);
486 		return (0);
487 	default:
488 		return (EINVAL);
489 	}
490 	/*NOTREACHED*/
491 }
492