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