/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Excalibur fans watchdog module */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern struct mod_ops mod_driverops; #define MINOR_DEVICE_NAME "xcalwd" /* * Define your per instance state data */ typedef struct xcalwd_state { kmutex_t lock; boolean_t started; int intvl; timeout_id_t tid; dev_info_t *dip; } xcalwd_state_t; /* * Pointer to soft states */ static void *xcalwd_statep; /* * dev_ops */ static int xcalwd_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp); static int xcalwd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int xcalwd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); /* * cb_ops */ static int xcalwd_open(dev_t *devp, int flag, int otyp, cred_t *credp); static int xcalwd_close(dev_t dev, int flag, int otyp, cred_t *credp); static int xcalwd_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp); /* * timeout handler */ static void xcalwd_timeout(void *arg); /* * cb_ops */ static struct cb_ops xcalwd_cb_ops = { xcalwd_open, /* open */ xcalwd_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ xcalwd_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* chpoll */ ddi_prop_op, /* prop_op */ NULL, /* streamtab */ D_NEW | D_MP | D_64BIT, /* cb_flag */ CB_REV, /* rev */ nodev, /* int (*cb_aread)() */ nodev /* int (*cb_awrite)() */ }; /* * dev_ops */ static struct dev_ops xcalwd_dev_ops = { DEVO_REV, /* devo_rev */ 0, /* devo_refcnt */ xcalwd_getinfo, /* getinfo */ nulldev, /* identify */ nulldev, /* probe */ xcalwd_attach, /* attach */ xcalwd_detach, /* detach */ nodev, /* devo_reset */ &xcalwd_cb_ops, /* devo_cb_ops */ NULL, /* devo_bus_ops */ NULL, /* devo_power */ ddi_quiesce_not_needed, /* devo_quiesce */ }; /* * modldrv */ static struct modldrv xcalwd_modldrv = { &mod_driverops, /* drv_modops */ "Excalibur watchdog timer v1.7 ", /* drv_linkinfo */ &xcalwd_dev_ops /* drv_dev_ops */ }; /* * modlinkage */ static struct modlinkage xcalwd_modlinkage = { MODREV_1, &xcalwd_modldrv, NULL }; int _init(void) { int error; /* * Initialize the module state structure */ error = ddi_soft_state_init(&xcalwd_statep, sizeof (xcalwd_state_t), 0); if (error) { return (error); } /* * Link the driver into the system */ error = mod_install(&xcalwd_modlinkage); if (error) { ddi_soft_state_fini(&xcalwd_statep); return (error); } return (0); } int _fini(void) { int error; error = mod_remove(&xcalwd_modlinkage); if (error != 0) { return (error); } /* * Cleanup resources allocated in _init */ ddi_soft_state_fini(&xcalwd_statep); return (0); } int _info(struct modinfo *modinfop) { return (mod_info(&xcalwd_modlinkage, modinfop)); } /*ARGSUSED*/ static int xcalwd_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) { int retval; dev_t dev = (dev_t)arg; int instance; xcalwd_state_t *tsp; retval = DDI_FAILURE; switch (cmd) { case DDI_INFO_DEVT2DEVINFO: instance = getminor(dev); tsp = ddi_get_soft_state(xcalwd_statep, instance); if (tsp == NULL) *resultp = NULL; else { *resultp = tsp->dip; retval = DDI_SUCCESS; } break; case DDI_INFO_DEVT2INSTANCE: *resultp = (void *)(uintptr_t)getminor(dev); retval = DDI_SUCCESS; break; default: break; } return (retval); } static int xcalwd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance; xcalwd_state_t *tsp; switch (cmd) { case DDI_ATTACH: instance = ddi_get_instance(dip); if (&plat_fan_blast == NULL) { cmn_err(CE_WARN, "missing plat_fan_blast function"); return (DDI_FAILURE); } if (ddi_soft_state_zalloc(xcalwd_statep, instance) != DDI_SUCCESS) { cmn_err(CE_WARN, "attach could not alloc" "%d state structure", instance); return (DDI_FAILURE); } tsp = ddi_get_soft_state(xcalwd_statep, instance); if (tsp == NULL) { cmn_err(CE_WARN, "get state failed %d", instance); return (DDI_FAILURE); } if (ddi_create_minor_node(dip, MINOR_DEVICE_NAME, S_IFCHR, instance, DDI_PSEUDO, 0) == DDI_FAILURE) { cmn_err(CE_WARN, "create minor node failed\n"); return (DDI_FAILURE); } mutex_init(&tsp->lock, NULL, MUTEX_DRIVER, NULL); tsp->started = B_FALSE; tsp->intvl = 0; tsp->tid = 0; tsp->dip = dip; ddi_report_dev(dip); return (DDI_SUCCESS); case DDI_RESUME: return (DDI_SUCCESS); default: break; } return (DDI_FAILURE); } static int xcalwd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { xcalwd_state_t *tsp; int instance; switch (cmd) { case DDI_DETACH: instance = ddi_get_instance(dip); tsp = ddi_get_soft_state(xcalwd_statep, instance); ddi_remove_minor_node(dip, NULL); mutex_destroy(&tsp->lock); ddi_soft_state_free(xcalwd_statep, instance); return (DDI_SUCCESS); case DDI_SUSPEND: return (DDI_SUCCESS); default: break; } return (DDI_FAILURE); } /* * Watchdog timeout handler that calls plat_fan_blast to take * the failsafe action. */ static void xcalwd_timeout(void *arg) { int instance = (int)(uintptr_t)arg; xcalwd_state_t *tsp; if (instance < 0) return; tsp = ddi_get_soft_state(xcalwd_statep, instance); if (tsp == NULL) return; mutex_enter(&tsp->lock); if (tsp->started == B_FALSE || tsp->tid == 0) { tsp->tid = 0; mutex_exit(&tsp->lock); return; } mutex_exit(&tsp->lock); plat_fan_blast(); } /*ARGSUSED*/ static int xcalwd_open(dev_t *devp, int flag, int otyp, cred_t *credp) { int instance; if (secpolicy_sys_config(credp, B_FALSE) != 0) return (EPERM); if (otyp != OTYP_CHR) return (EINVAL); instance = getminor(*devp); if (instance < 0) return (ENXIO); if (ddi_get_soft_state(xcalwd_statep, instance) == NULL) { return (ENXIO); } return (0); } /*ARGSUSED*/ static int xcalwd_close(dev_t dev, int flag, int otyp, cred_t *credp) { xcalwd_state_t *tsp; int instance; timeout_id_t tid; instance = getminor(dev); if (instance < 0) return (ENXIO); tsp = ddi_get_soft_state(xcalwd_statep, instance); if (tsp == NULL) return (ENXIO); mutex_enter(&tsp->lock); if (tsp->started == B_FALSE) { tsp->tid = 0; mutex_exit(&tsp->lock); return (0); } /* * The watchdog is enabled. Cancel the pending timer * and call plat_fan_blast. */ tsp->started = B_FALSE; tid = tsp->tid; tsp->tid = 0; mutex_exit(&tsp->lock); if (tid != 0) (void) untimeout(tid); plat_fan_blast(); return (0); } /* * These are private ioctls for PICL environmental control plug-in * to use. The plug-in enables the watchdog before performing * altering fan speeds. It also periodically issues a keepalive * to the watchdog to cancel and reinstate the watchdog timer. * The watchdog timeout handler when executed with the watchdog * enabled sets fans to full blast by calling plat_fan_blast. */ /*ARGSUSED*/ static int xcalwd_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred_p, int *rvalp) { int instance; xcalwd_state_t *tsp; int intvl; int o_intvl; boolean_t curstate; timeout_id_t tid; if (secpolicy_sys_config(cred_p, B_FALSE) != 0) return (EPERM); instance = getminor(dev); if (instance < 0) return (ENXIO); tsp = ddi_get_soft_state(xcalwd_statep, instance); if (tsp == NULL) return (ENXIO); switch (cmd) { case XCALWD_STOPWATCHDOG: /* * cancels any pending timer and disables the timer. */ tid = 0; mutex_enter(&tsp->lock); if (tsp->started == B_FALSE) { mutex_exit(&tsp->lock); return (0); } tid = tsp->tid; tsp->started = B_FALSE; tsp->tid = 0; mutex_exit(&tsp->lock); if (tid != 0) (void) untimeout(tid); return (0); case XCALWD_STARTWATCHDOG: if (ddi_copyin((void *)arg, &intvl, sizeof (intvl), flag)) return (EFAULT); if (intvl == 0) return (EINVAL); mutex_enter(&tsp->lock); o_intvl = tsp->intvl; mutex_exit(&tsp->lock); if (ddi_copyout((const void *)&o_intvl, (void *)arg, sizeof (o_intvl), flag)) return (EFAULT); mutex_enter(&tsp->lock); if (tsp->started == B_TRUE) { mutex_exit(&tsp->lock); return (EINVAL); } tsp->intvl = intvl; tsp->tid = realtime_timeout(xcalwd_timeout, (void *)(uintptr_t)instance, drv_usectohz(1000000) * tsp->intvl); tsp->started = B_TRUE; mutex_exit(&tsp->lock); return (0); case XCALWD_KEEPALIVE: tid = 0; mutex_enter(&tsp->lock); tid = tsp->tid; tsp->tid = 0; mutex_exit(&tsp->lock); if (tid != 0) (void) untimeout(tid); /* cancel */ mutex_enter(&tsp->lock); if (tsp->started == B_TRUE) /* reinstate */ tsp->tid = realtime_timeout(xcalwd_timeout, (void *)(uintptr_t)instance, drv_usectohz(1000000) * tsp->intvl); mutex_exit(&tsp->lock); return (0); case XCALWD_GETSTATE: mutex_enter(&tsp->lock); curstate = tsp->started; mutex_exit(&tsp->lock); if (ddi_copyout((const void *)&curstate, (void *)arg, sizeof (curstate), flag)) return (EFAULT); return (0); default: return (EINVAL); } /*NOTREACHED*/ }