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