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
48extern	struct	mod_ops	mod_driverops;
49
50#define	MINOR_DEVICE_NAME	"xcalwd"
51
52/*
53 * Define your per instance state data
54 */
55typedef	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 */
66static	void	*xcalwd_statep;
67
68/*
69 * dev_ops
70 */
71static	int	xcalwd_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd,
72	void *arg, void **resultp);
73static	int	xcalwd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
74static	int	xcalwd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
75
76/*
77 * cb_ops
78 */
79static	int	xcalwd_open(dev_t *devp, int flag, int otyp, cred_t *credp);
80static	int	xcalwd_close(dev_t dev, int flag, int otyp, cred_t *credp);
81static	int	xcalwd_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
82			cred_t *credp, int *rvalp);
83/*
84 * timeout handler
85 */
86static	void	xcalwd_timeout(void *arg);
87
88/*
89 * cb_ops
90 */
91static 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 */
115static 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 */
133static 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 */
142static struct modlinkage xcalwd_modlinkage = {
143	MODREV_1,
144	&xcalwd_modldrv,
145	NULL
146};
147
148int
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
173int
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
190int
191_info(struct modinfo *modinfop)
192{
193	return (mod_info(&xcalwd_modlinkage, modinfop));
194}
195
196/*ARGSUSED*/
197static int
198xcalwd_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
228static int
229xcalwd_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
279static int
280xcalwd_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 */
305static void
306xcalwd_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*/
330static int
331xcalwd_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*/
353static int
354xcalwd_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*/
397static int
398xcalwd_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