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
_init(void)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
_fini(void)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
_info(struct modinfo * modinfop)191 _info(struct modinfo *modinfop)
192 {
193 return (mod_info(&xcalwd_modlinkage, modinfop));
194 }
195
196 /*ARGSUSED*/
197 static int
xcalwd_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** resultp)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
xcalwd_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)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, 0) == 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
xcalwd_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)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
xcalwd_timeout(void * arg)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
xcalwd_open(dev_t * devp,int flag,int otyp,cred_t * credp)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
xcalwd_close(dev_t dev,int flag,int otyp,cred_t * credp)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
xcalwd_ioctl(dev_t dev,int cmd,intptr_t arg,int flag,cred_t * cred_p,int * rvalp)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