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_sectohz(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_sectohz(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 }