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_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 }