/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * ntwdt driver * ------------ * * Subsystem Overview * ------------------ * * This is a pseudo driver for the Netra-1280 watchdog * timer (WDT). It provides for an *application-driven* * WDT (AWDT), not a traditional, hardware-based WDT. A * hardware-based feature is already present on the * Netra-1280, and it is referred to here as the * System WDT (SWDT). * * ScApp and Solaris cooperate to provide either a SWDT or * an AWDT; they are mutually-exclusive. Once in AWDT * mode, one can only transition to SWDT mode via a reboot. * This obviously gives priority to the AWDT and was done * to handle scenarios where the customer might temporarily * terminate their wdog-app in order to do some debugging, * or even to load a new version of the wdog-app. * * The wdog-app does an open() of the /dev/ntwdt device node * and then issues ioctl's to control the state of the AWDT. * The ioctl's are implemented by this driver. Only one * concurrent instance of open() is allowed. On the close(), * a watchdog timer still in progress is NOT terminated. * This allows the global state machine to monitor the * progress of a Solaris reboot. ScApp will reset Solaris * (eg, send an XIR) if the actual boot/crashdump latency * is larger than the current AWDT timeout. * * The rationale for implementing an AWDT (vs a SWDT) is * that it is more sensitive to system outage scenarios than * a SWDT. Eg, a system could be in such a failed state that * even though its clock-interrupt could still run (and the * SWDT's watchdog timer therefore re-armed), the system could * in effect have a corrupt or very poor dispatch latency. * An AWDT would be sensitive to dispatch latency issues, as * well as problems with its own execution (eg, a hang or * crash). * * Subsystem Interface Overview * ---------------------------- * * This pseudo-driver does not have any 'extern' functions. * * All system interaction is done via the traditional driver * entry points (eg, attach(9e), _init(9e)). * * All interaction with user is via the entry points in the * 'struct cb_ops' vector (eg, open(9e), ioctl(9e), and * close(9e)). * * Subsystem Implementation Overview * --------------------------------- * * ScApp and Solaris (eg, ntwdt) cooperate so that a state * machine global to ScApp and ntwdt is either in AWDT mode * or in SWDT mode. These two peers communicate via the SBBC * Mailbox that resides in IOSRAM (SBBC_MAILBOX_KEY). * They use two new mailbox messages (LW8_MBOX_WDT_GET and * LW8_MBOX_WDT_SET) and one new event (LW8_EVENT_SC_RESTARTED). * * ntwdt implements the AWDT by implementing a "virtual * WDT" (VWDT). Eg, the watchdog timer is not a traditional * counter in hardware, it is a variable in ntwdt's * softstate. The wdog-app's actions cause changes to this * and other variables in ntwdt's softstate. * * The wdog-app uses the LOMIOCDOGTIME ioctl to specify * the number of seconds in the watchdog timeout (and * therefore the VWDT). The wdog-app then uses the * LOMIOCDOGCTL ioctl to enable the wdog. This causes * ntwdt to create a Cyclic that will both decrement * the VWDT and check to see if it has expired. To keep * the VWDT from expiring, the wdog-app uses the * LOMIOCDOGPAT ioctl to re-arm (or "pat") the watchdog. * This sets the VWDT value to that specified in the * last LOMIOCDOGTIME ioctl. The wdog-app can use the * LOMIOCDOGSTATE ioctl to query the state of the VWDT. * * The wdog-app can also specify how Recovery is to be * done. The only choice is whether to do a crashdump * or not. If ntwdt computes a VWDT expiration, then * ntwdt initiates the Recovery, else ScApp will. Eg, * a hang in Solaris will be sensed by ScApp and not * ntwdt. The wdog-app specifies the Recovery policy * via the DOGCTL ioctl. * * Timeout Expiration * ------------------ * In our implementation, ScApp senses a watchdog * expiration the same way it historically has: * by reading a well-known area of IOSRAM (SBBC_TOD_KEY) * to see if the timestamp associated with a * Solaris-generated "heartbeat" field is older * than the currently specified timeout (which is * also specified in this same IOSRAM section). * * What is different when ntwdt is running is that * ntwdt is responsible for updating the Heartbeat, * and not the normal client (todsg). When ntwdt * puts the system in AWDT mode, it disables todsg's * updating of the Heartbeat by changing the state of * a pair of kernel tunables (watchdog_activated and * watchdog_enable). ntwdt then takes responsibility * for updating the Heartbeat. It does this by * updating the Heartbeat from the Cyclic that is * created when the user enables the AWDT (DOGCTL) * or specifies a new timeout value (DOGTIME). * * As long as the AWDT is enabled, ntwdt will update * the real system Heartbeat. As a result, ScApp * will conclude that Solaris is still running. If * the user stops re-arming the VWDT or Solaris * hangs (eg), ntwdt will stop updating the Heartbeat. * * Note that ntwdt computes expiration via the * repeatedly firing Cyclic, and ScApp computes * expiration via a cessation of Heartbeat update. * Since Heartbeat update stops once user stops * re-arming the VWDT (ie, DOGPAT ioctl), ntwdt * will compute a timeout at t(x), and ScApp will * compute a timeout at t(2x), where 'x' is the * current timeout value. When ntwdt computes * the expiration, ntwdt masks this asymmetry. * * Lifecycle Events * ---------------- * * ntwdt only handles one of the coarse-grained * "lifecycle events" (eg, entering OBP, shutdown, * power-down, DR) that are possible during a Solaris * session: a panic. (Note that ScApp handles one * of the others: "entering OBP"). Other than these, * a user choosing such a state transition must first * use the wdog-app to disable the watchdog, else * an expiration could occur. * * Solaris handles a panic by registering a handler * that's called during the panic. The handler will * set the watchdog timeout to the value specified * in the NTWDT_BOOT_TIMEOUT_PROP driver Property. * Again, this value should be greater than the actual * Solaris reboot/crashdump latency. * * When the user enters OBP via the System Controller, * ScApp will disable the watchdog (from ScApp's * perspective), but it will not communicate this to * ntwdt. After having exited OBP, the wdog-app can * be used to enable or disable the watchdog (which * will get both ScApp and ntwdt in-sync). * * Locking * ------- * * ntwdt has code running at three interrupt levels as * well as base level. * * The ioctls run at base level in User Context. The * driver's entry points run at base level in Kernel * Context. * * ntwdt's three interrupt levels are used by: * * o LOCK_LEVEL : * the Cyclic used to manage the VWDT is initialized * to CY_LOCK_LEVEL * * o DDI_SOFTINT_MED : * the SBBC mailbox implementation registers the * specified handlers at this level * * o DDI_SOFTINT_LOW : * this level is used by two handlers. One handler * is triggered by the LOCK_LEVEL Cyclic. The other * handler is triggered by the DDI_SOFTINT_MED * handler registered to handle SBBC mailbox events. * * The centralizing concept is that the ntwdt_wdog_mutex * in the driver's softstate is initialized to have an * interrupt-block-cookie corresponding to DDI_SOFTINT_LOW. * * As a result, any base level code grabs ntwdt_wdog_mutex * before doing work. Also, any handler running at interrupt * level higher than DDI_SOFTINT_LOW "posts down" so that * a DDI_SOFTINT_LOW handler is responsible for executing * the "real work". Each DDI_SOFTINT_LOW handler also * first grabs ntwdt_wdog_mutex, and so base level is * synchronized with all interrupt levels. * * Note there's another mutex in the softstate: ntwdt_mutex. * This mutex has few responsibilities. However, this * locking order must be followed: ntwdt_wdog_mutex is * held first, and then ntwdt_mutex. This choice results * from the fact that the number of dynamic call sites * for ntwdt_wdog_mutex is MUCH greater than that of * ntwdt_mutex. As a result, almost all uses of * ntwdt_wdog_mutex do not even require ntwdt_mutex to * be held, which saves resources. * * Driver Properties * ----------------- * * "ddi-forceattach=1;" * ------------------ * * Using this allows our driver to be automatically * loaded at boot-time AND to not be removed from memory * solely due to memory-pressure. * * Being loaded at boot allows ntwdt to (as soon as * possible) tell ScApp of the current mode of the * state-machine (eg, SWDT). This is needed for the case * when Solaris is re-loaded while in AWDT mode; having * Solaris communicate ASAP with ScApp reduces the duration * of any "split-brain" scenario where ScApp and Solaris * are not in the same mode. * * Having ntwdt remain in memory even after a close() * allows ntwdt to answer any SBBC mailbox commands * that ScApp sends (as the mailbox infrastructure is * not torn down until ntwdt is detach()'d). Specifically, * ScApp could be re-loaded after AWDT mode had been * entered and the wdog-app had close()'d ntwdt. ScApp * will then eventually send a LW8_EVENT_SC_RESTARTED * mailbox event in order to learn the current state of * state-machine. Having ntwdt remain loaded allows this * event to never go unanswered. * * "ntwdt-boottimeout=600;" * ---------------------- * * This specifies the watchdog timeout value (in seconds) to * use when ntwdt is aware of the need to reboot/reload Solaris. * * ntwdt will update ScApp by setting the watchdog timeout * to the specified number of seconds when either a) Solaris * panics or b) the VWDT expires. Note that this is only done * if the user has chosen to enable Reset. * * ntwdt boundary-checks the specified value, and if out-of-range, * it initializes the watchdog timeout to a default value of * NTWDT_DEFAULT_BOOT_TIMEOUT seconds. Note that this is a * default value and is not a *minimum* value. The valid range * for the watchdog timeout is between one second and * NTWDT_MAX_TIMEOUT seconds, inclusive. * * If ntwdt-boottimeout is set to a value less than an actual * Solaris boot's latency, ScApp will reset Solaris during boot. * Note that a continuous series of ScApp-induced resets will * not occur; ScApp only resets Solaris on the first transition * into the watchdog-expired state. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * tunables */ int ntwdt_disable_timeout_action = 0; #ifdef DEBUG /* * tunable to simulate a Solaris hang. If is non-zero, then * no system heartbeats ("hardware patting") will be done, * even though all AWDT machinery is functioning OK. */ int ntwdt_stop_heart; #endif /* * Driver Property */ #define NTWDT_BOOT_TIMEOUT_PROP "ntwdt-boottimeout" /* * watchdog-timeout values (in seconds): * * NTWDT_DEFAULT_BOOT_TIMEOUT: the default value used if * this driver is aware of the * reboot. * * NTWDT_MAX_TIMEOUT: max value settable by app (via the * LOMIOCDOGTIME ioctl) */ #define NTWDT_DEFAULT_BOOT_TIMEOUT (10*60) #define NTWDT_MAX_TIMEOUT (180*60) #define NTWDT_CYCLIC_CHK_PERCENT (20) #define NTWDT_MINOR_NODE "awdt" #define OFFSET(base, field) ((char *)&base.field - (char *)&base) #define NTWDT_SUCCESS 0 #define NTWDT_FAILURE 1 typedef struct { callb_id_t ntwdt_panic_cb; } ntwdt_callback_ids_t; static ntwdt_callback_ids_t ntwdt_callback_ids; /* MBOX_EVENT_LW8 that is sent in IOSRAM Mailbox: */ static lw8_event_t lw8_event; /* payload */ static sbbc_msg_t sbbc_msg; /* message */ static ddi_softintr_t ntwdt_mbox_softint_id; static ddi_softintr_t ntwdt_cyclic_softint_id; /* * VWDT (i.e., Virtual Watchdog Timer) state */ typedef struct { kmutex_t ntwdt_wdog_mutex; ddi_iblock_cookie_t ntwdt_wdog_mtx_cookie; int ntwdt_wdog_enabled; /* wdog enabled ? */ int ntwdt_reset_enabled; /* reset enabled ? */ int ntwdt_timer_running; /* wdog running ? */ int ntwdt_wdog_expired; /* wdog expired ? */ int ntwdt_is_initial_enable; /* 1st wdog-enable? */ uint32_t ntwdt_boot_timeout; /* timeout for boot */ uint32_t ntwdt_secs_remaining; /* expiration timer */ uint8_t ntwdt_wdog_action; /* Reset action */ uint32_t ntwdt_wdog_timeout; /* timeout in seconds */ hrtime_t ntwdt_cyclic_interval; /* cyclic interval */ cyc_handler_t ntwdt_cycl_hdlr; cyc_time_t ntwdt_cycl_time; kmutex_t ntwdt_event_lock; /* lock */ uint64_t ntwdt_wdog_flags; } ntwdt_wdog_t; /* ntwdt_wdog_flags */ #define NTWDT_FLAG_SKIP_CYCLIC 0x1 /* skip next Cyclic */ /* macros to set/clear one bit in ntwdt_wdog_flags */ #define NTWDT_FLAG_SET(p, f)\ ((p)->ntwdt_wdog_flags |= NTWDT_FLAG_##f) #define NTWDT_FLAG_CLR(p, f)\ ((p)->ntwdt_wdog_flags &= ~NTWDT_FLAG_##f) /* softstate */ typedef struct { kmutex_t ntwdt_mutex; dev_info_t *ntwdt_dip; /* dip */ int ntwdt_open_flag; /* file open ? */ ntwdt_wdog_t *ntwdt_wdog_state; /* wdog state */ cyclic_id_t ntwdt_cycl_id; } ntwdt_state_t; static void *ntwdt_statep; /* softstate */ static dev_info_t *ntwdt_dip; /* * if non-zero, then the app-wdog feature is available on * this system configuration. */ static int ntwdt_watchdog_available; /* * if non-zero, then application has used the LOMIOCDOGCTL * ioctl at least once in order to Enable the app-wdog. * Also, if this is non-zero, then system is in AWDT mode, * else it is in SWDT mode. */ static int ntwdt_watchdog_activated; #define getstate(minor) \ ((ntwdt_state_t *)ddi_get_soft_state(ntwdt_statep, (minor))) static int ntwdt_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int ntwdt_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int ntwdt_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); static int ntwdt_open(dev_t *, int, int, cred_t *); static int ntwdt_close(dev_t, int, int, cred_t *); static int ntwdt_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); static void ntwdt_reprogram_wd(ntwdt_state_t *); static boolean_t ntwdt_panic_cb(void *arg, int code); static void ntwdt_start_timer(ntwdt_state_t *); static void ntwdt_stop_timer(void *); static void ntwdt_stop_timer_lock(void *arg); static void ntwdt_add_callbacks(ntwdt_state_t *ntwdt_ptr); static void ntwdt_remove_callbacks(); static void ntwdt_cyclic_pat(void *arg); static void ntwdt_enforce_timeout(); static void ntwdt_pat_hw_watchdog(); static int ntwdt_set_cfgvar(int var, int val); static void ntwdt_set_cfgvar_noreply(int var, int val); static int ntwdt_read_props(ntwdt_state_t *); static int ntwdt_add_mbox_handlers(ntwdt_state_t *); static int ntwdt_set_hw_timeout(uint32_t period); static int ntwdt_remove_mbox_handlers(void); static uint_t ntwdt_event_data_handler(char *arg); static uint_t ntwdt_mbox_softint(char *arg); static uint_t ntwdt_cyclic_softint(char *arg); static int ntwdt_lomcmd(int cmd, intptr_t arg); static int ntwdt_chk_wdog_support(); static int ntwdt_chk_sc_support(); static int ntwdt_set_swdt_state(); static void ntwdt_swdt_to_awdt(ntwdt_wdog_t *); static void ntwdt_arm_vwdt(ntwdt_wdog_t *wdog_state); #ifdef DEBUG static int ntwdt_get_cfgvar(int var, int *val); #endif struct cb_ops ntwdt_cb_ops = { ntwdt_open, /* open */ ntwdt_close, /* close */ nulldev, /* strategy */ nulldev, /* print */ nulldev, /* dump */ nulldev, /* read */ nulldev, /* write */ ntwdt_ioctl, /* ioctl */ nulldev, /* devmap */ nulldev, /* mmap */ nulldev, /* segmap */ nochpoll, /* poll */ ddi_prop_op, /* cb_prop_op */ NULL, /* streamtab */ D_MP | D_NEW }; static struct dev_ops ntwdt_ops = { DEVO_REV, /* Devo_rev */ 0, /* Refcnt */ ntwdt_info, /* Info */ nulldev, /* Identify */ nulldev, /* Probe */ ntwdt_attach, /* Attach */ ntwdt_detach, /* Detach */ nodev, /* Reset */ &ntwdt_cb_ops, /* Driver operations */ 0, /* Bus operations */ NULL /* Power */ }; static struct modldrv modldrv = { &mod_driverops, /* This one is a driver */ "ntwdt-Netra-T12", /* Name of the module. */ &ntwdt_ops, /* Driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL }; /* * Flags to set in ntwdt_debug. * * Use either the NTWDT_DBG or NTWDT_NDBG macros */ #define WDT_DBG_ENTRY 0x00000001 /* drv entry points */ #define WDT_DBG_HEART 0x00000002 /* system heartbeat */ #define WDT_DBG_VWDT 0x00000004 /* virtual WDT */ #define WDT_DBG_EVENT 0x00000010 /* SBBC Mbox events */ #define WDT_DBG_PROT 0x00000020 /* SC/Solaris protocol */ #define WDT_DBG_IOCTL 0x00000040 /* ioctl's */ uint64_t ntwdt_debug; /* enables tracing of module's activity */ /* used in non-debug version of module */ #define NTWDT_NDBG(flag, msg) { if ((ntwdt_debug & (flag)) != 0) \ (void) printf msg; } #ifdef DEBUG typedef struct { uint32_t ntwdt_wd1; uint8_t ntwdt_wd2; } ntwdt_data_t; #define NTWDTIOCSTATE _IOWR('a', 0xa, ntwdt_data_t) #define NTWDTIOCPANIC _IOR('a', 0xb, uint32_t) /* used in debug version of module */ #define NTWDT_DBG(flag, msg) { if ((ntwdt_debug & (flag)) != 0) \ (void) printf msg; } #else #define NTWDT_DBG(flag, msg) #endif int _init(void) { int error = 0; NTWDT_DBG(WDT_DBG_ENTRY, ("_init")); /* Initialize the soft state structures */ if ((error = ddi_soft_state_init(&ntwdt_statep, sizeof (ntwdt_state_t), 1)) != 0) { return (error); } /* Install the loadable module */ if ((error = mod_install(&modlinkage)) != 0) { ddi_soft_state_fini(&ntwdt_statep); } return (error); } int _info(struct modinfo *modinfop) { NTWDT_DBG(WDT_DBG_ENTRY, ("_info")); return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int error; NTWDT_DBG(WDT_DBG_ENTRY, ("_fini")); error = mod_remove(&modlinkage); if (error == 0) { ddi_soft_state_fini(&ntwdt_statep); } return (error); } static int ntwdt_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance; ntwdt_state_t *ntwdt_ptr = NULL; ntwdt_wdog_t *wdog_state = NULL; cyc_handler_t *hdlr = NULL; NTWDT_DBG(WDT_DBG_ENTRY, ("attach: dip/cmd: 0x%p/%d", (void *)dip, cmd)); switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: return (DDI_SUCCESS); default: return (DDI_FAILURE); } /* see if app-wdog is supported on our config */ if (ntwdt_chk_wdog_support() != 0) return (DDI_FAILURE); /* (unsolicitedly) send SWDT state to ScApp via mailbox */ (void) ntwdt_set_swdt_state(); instance = ddi_get_instance(dip); ASSERT(instance == 0); if (ddi_soft_state_zalloc(ntwdt_statep, instance) != DDI_SUCCESS) { return (DDI_FAILURE); } ntwdt_ptr = ddi_get_soft_state(ntwdt_statep, instance); ASSERT(ntwdt_ptr != NULL); ntwdt_dip = dip; ntwdt_ptr->ntwdt_dip = dip; ntwdt_ptr->ntwdt_cycl_id = CYCLIC_NONE; mutex_init(&ntwdt_ptr->ntwdt_mutex, NULL, MUTEX_DRIVER, NULL); /* * Initialize the watchdog structure */ ntwdt_ptr->ntwdt_wdog_state = kmem_zalloc(sizeof (ntwdt_wdog_t), KM_SLEEP); wdog_state = ntwdt_ptr->ntwdt_wdog_state; /* * Create an iblock-cookie so that ntwdt_wdog_mutex can be * used at User Context and Interrupt Context. */ if (ddi_get_soft_iblock_cookie(dip, DDI_SOFTINT_LOW, &wdog_state->ntwdt_wdog_mtx_cookie) != DDI_SUCCESS) { cmn_err(CE_WARN, "init of iblock cookie failed " "for ntwdt_wdog_mutex"); goto err1; } else { mutex_init(&wdog_state->ntwdt_wdog_mutex, NULL, MUTEX_DRIVER, (void *)wdog_state->ntwdt_wdog_mtx_cookie); } mutex_init(&wdog_state->ntwdt_event_lock, NULL, MUTEX_DRIVER, NULL); /* Cyclic fires once per second: */ wdog_state->ntwdt_cyclic_interval = NANOSEC; /* interpret our .conf file. */ (void) ntwdt_read_props(ntwdt_ptr); /* init the Cyclic that drives the VWDT */ hdlr = &wdog_state->ntwdt_cycl_hdlr; hdlr->cyh_level = CY_LOCK_LEVEL; hdlr->cyh_func = ntwdt_cyclic_pat; hdlr->cyh_arg = (void *)ntwdt_ptr; /* Register handler for SBBC Mailbox events */ if (ntwdt_add_mbox_handlers(ntwdt_ptr) != DDI_SUCCESS) goto err2; /* Softint that will be triggered by Cyclic that drives VWDT */ if (ddi_add_softintr(dip, DDI_SOFTINT_LOW, &ntwdt_cyclic_softint_id, NULL, NULL, ntwdt_cyclic_softint, (caddr_t)ntwdt_ptr) != DDI_SUCCESS) { cmn_err(CE_WARN, "failed to add cyclic softintr"); goto err3; } /* Register callbacks for various system events, e.g. panic */ ntwdt_add_callbacks(ntwdt_ptr); /* * Create Minor Node as last activity. This prevents * application from accessing our implementation until it * is initialized. */ if (ddi_create_minor_node(dip, NTWDT_MINOR_NODE, S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_FAILURE) { cmn_err(CE_WARN, "failed to create Minor Node: %s", NTWDT_MINOR_NODE); goto err4; } /* Display our driver info in the banner */ ddi_report_dev(dip); return (DDI_SUCCESS); err4: ntwdt_remove_callbacks(); ddi_remove_softintr(ntwdt_cyclic_softint_id); err3: (void) ntwdt_remove_mbox_handlers(); err2: mutex_destroy(&wdog_state->ntwdt_event_lock); mutex_destroy(&wdog_state->ntwdt_wdog_mutex); err1: kmem_free(wdog_state, sizeof (ntwdt_wdog_t)); ntwdt_ptr->ntwdt_wdog_state = NULL; mutex_destroy(&ntwdt_ptr->ntwdt_mutex); ddi_soft_state_free(ntwdt_statep, instance); ntwdt_dip = NULL; return (DDI_FAILURE); } /* * Do static checks to see if the app-wdog feature is supported in * the current configuration. * * If the kernel debugger was booted, then we disallow the app-wdog * feature, as we assume the user will be interested more in * debuggability of system than its ability to support an app-wdog. * (Note that the System Watchdog (SWDT) can still be available). * * If the currently loaded version of ScApp does not understand one * of the IOSRAM mailbox messages that is specific to the app-wdog * protocol, then we disallow use of the app-wdog feature (else * we could have a "split-brain" scenario where Solaris supports * app-wdog but ScApp doesn't). * * Note that there is no *dynamic* checking of whether ScApp supports * the wdog protocol. Eg, if a new version of ScApp was loaded out * from under Solaris, then once in AWDT mode, Solaris has no way * of knowing that (a possibly older version of) ScApp was loaded. */ static int ntwdt_chk_wdog_support() { int retval = ENOTSUP; int rv; if ((boothowto & RB_DEBUG) != 0) { cmn_err(CE_WARN, "kernel debugger was booted; " "application watchdog is not available."); return (retval); } /* * if ScApp does not support the MBOX_GET cmd, then * it does not support the app-wdog feature. Also, * if there is *any* type of SBBC Mailbox error at * this point, we will disable the app watchdog * feature. */ if ((rv = ntwdt_chk_sc_support()) != 0) { if (rv == EINVAL) cmn_err(CE_WARN, "ScApp does not support " "the application watchdog feature."); else cmn_err(CE_WARN, "SBBC mailbox had error;" "application watchdog is not available."); retval = rv; } else { ntwdt_watchdog_available = 1; retval = 0; } NTWDT_DBG(WDT_DBG_PROT, ("app-wdog is %savailable", (ntwdt_watchdog_available != 0) ? "" : "not ")); return (retval); } /* * Check to see if ScApp supports the app-watchdog feature. * * Do this by sending one of the mailbox commands that is * specific to the app-wdog protocol. If ScApp does not * return an error code, we will assume it understands it * (as well as the remainder of the app-wdog protocol). * * Notes: * ntwdt_lomcmd() will return EINVAL if ScApp does not * understand the message. The underlying sbbc_mbox_ * utility function returns SG_MBOX_STATUS_ILLEGAL_PARAMETER * ("illegal ioctl parameter"). */ static int ntwdt_chk_sc_support() { lw8_get_wdt_t get_wdt; return (ntwdt_lomcmd(LW8_MBOX_WDT_GET, (intptr_t)&get_wdt)); } static int ntwdt_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance = ddi_get_instance(dip); ntwdt_state_t *ntwdt_ptr = NULL; NTWDT_DBG(WDT_DBG_ENTRY, ("detach: dip/cmd: 0x%p/%d", (void *)dip, cmd)); ntwdt_ptr = ddi_get_soft_state(ntwdt_statep, instance); if (ntwdt_ptr == NULL) { return (DDI_FAILURE); } switch (cmd) { case DDI_SUSPEND: return (DDI_SUCCESS); case DDI_DETACH: /* * release resources in opposite (LIFO) order as * were allocated in attach(9f). */ ddi_remove_minor_node(dip, NULL); ntwdt_stop_timer_lock((void *)ntwdt_ptr); ntwdt_remove_callbacks(); ddi_remove_softintr(ntwdt_cyclic_softint_id); (void) ntwdt_remove_mbox_handlers(); mutex_destroy(&ntwdt_ptr->ntwdt_wdog_state->ntwdt_event_lock); mutex_destroy(&ntwdt_ptr->ntwdt_wdog_state->ntwdt_wdog_mutex); kmem_free(ntwdt_ptr->ntwdt_wdog_state, sizeof (ntwdt_wdog_t)); ntwdt_ptr->ntwdt_wdog_state = NULL; mutex_destroy(&ntwdt_ptr->ntwdt_mutex); ddi_soft_state_free(ntwdt_statep, instance); ntwdt_dip = NULL; return (DDI_SUCCESS); default: return (DDI_FAILURE); } } /* * Register the SBBC Mailbox handlers. * * Currently, only one handler is used. It processes the MBOX_EVENT_LW8 * Events that are sent by ScApp. Of the Events that are sent, only * the Event declaring that ScApp is coming up from a reboot * (LW8_EVENT_SC_RESTARTED) is processed. * * sbbc_mbox_reg_intr registers the handler so that it executes at * a DDI_SOFTINT_MED priority. */ static int ntwdt_add_mbox_handlers(ntwdt_state_t *ntwdt_ptr) { int err; /* * We need two interrupt handlers to handle the SBBC mbox * events. The sbbc_mbox_xxx implementation will * trigger our ntwdt_event_data_handler, which itself will * trigger our ntwdt_mbox_softint. As a result, we'll * register ntwdt_mbox_softint first, to ensure it cannot * be called (until its caller, ntwdt_event_data_handler) * is registered. */ /* * add the softint that will do the real work of handling the * LW8_SC_RESTARTED_EVENT sent from ScApp. */ if (ddi_add_softintr(ntwdt_ptr->ntwdt_dip, DDI_SOFTINT_LOW, &ntwdt_mbox_softint_id, NULL, NULL, ntwdt_mbox_softint, (caddr_t)ntwdt_ptr) != DDI_SUCCESS) { cmn_err(CE_WARN, "Failed to add MBOX_EVENT_LW8 softintr"); return (DDI_FAILURE); } /* * Register an interrupt handler with the SBBC mailbox utility. * This handler will get called on each event of each type of * MBOX_EVENT_LW8 events. However, it will only conditionally * trigger the worker-handler (ntwdt_mbox_softintr). */ sbbc_msg.msg_buf = (caddr_t)&lw8_event; sbbc_msg.msg_len = sizeof (lw8_event); err = sbbc_mbox_reg_intr(MBOX_EVENT_LW8, ntwdt_event_data_handler, &sbbc_msg, NULL, &ntwdt_ptr->ntwdt_wdog_state->ntwdt_event_lock); if (err != 0) { cmn_err(CE_WARN, "Failed to register SBBC MBOX_EVENT_LW8" " handler. err=%d", err); ddi_remove_softintr(ntwdt_mbox_softint_id); return (DDI_FAILURE); } return (DDI_SUCCESS); } /* * Unregister the SBBC Mailbox handlers that were registered * by ntwdt_add_mbox_handlers. */ static int ntwdt_remove_mbox_handlers(void) { int rv = DDI_SUCCESS; int err; /* * unregister the two handlers that cooperate to handle * the LW8_SC_RESTARTED_EVENT. Note that they are unregistered * in LIFO order (as compared to how they were registered). */ err = sbbc_mbox_unreg_intr(MBOX_EVENT_LW8, ntwdt_event_data_handler); if (err != 0) { cmn_err(CE_WARN, "Failed to unregister sbbc MBOX_EVENT_LW8 " "handler. Err=%d", err); rv = DDI_FAILURE; } /* remove the associated softint */ ddi_remove_softintr(ntwdt_mbox_softint_id); return (rv); } _NOTE(ARGSUSED(0)) static int ntwdt_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { dev_t dev; int instance; int error = DDI_SUCCESS; if (result == NULL) return (DDI_FAILURE); switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: dev = (dev_t)arg; if (getminor(dev) == 0) *result = (void *)ntwdt_dip; else error = DDI_FAILURE; break; case DDI_INFO_DEVT2INSTANCE: dev = (dev_t)arg; instance = getminor(dev); *result = (void *)(uintptr_t)instance; break; default: error = DDI_FAILURE; } return (error); } /* * Open the device this driver manages. * * Ensure the caller is a privileged process, else * a non-privileged user could cause denial-of-service * and/or negatively impact reliability/availability. * * Ensure there is only one concurrent open(). */ _NOTE(ARGSUSED(1)) static int ntwdt_open(dev_t *devp, int flag, int otyp, cred_t *credp) { int inst = getminor(*devp); int ret = 0; ntwdt_state_t *ntwdt_ptr = getstate(inst); NTWDT_DBG(WDT_DBG_ENTRY, ("open: inst/soft: %d/0x%p", inst, (void *)ntwdt_ptr)); /* ensure caller is a privileged process */ if (drv_priv(credp) != 0) return (EPERM); /* * Check for a Deferred Attach scenario. * Return ENXIO so DDI framework will call * attach() and then retry the open(). */ if (ntwdt_ptr == NULL) return (ENXIO); mutex_enter(&ntwdt_ptr->ntwdt_wdog_state->ntwdt_wdog_mutex); mutex_enter(&ntwdt_ptr->ntwdt_mutex); if (ntwdt_ptr->ntwdt_open_flag != 0) ret = EAGAIN; else ntwdt_ptr->ntwdt_open_flag = 1; mutex_exit(&ntwdt_ptr->ntwdt_mutex); mutex_exit(&ntwdt_ptr->ntwdt_wdog_state->ntwdt_wdog_mutex); return (ret); } /* * Close the device this driver manages. * * Notes: * * The close() can happen while the AWDT is running ! * (and nothing is done, eg, to disable the watchdog * or to stop updating the system heartbeat). This * is the desired behavior, as this allows for the * case of monitoring a Solaris reboot in terms * of watchdog expiration. */ _NOTE(ARGSUSED(1)) static int ntwdt_close(dev_t dev, int flag, int otyp, cred_t *credp) { int inst = getminor(dev); ntwdt_state_t *ntwdt_ptr = getstate(inst); NTWDT_DBG(WDT_DBG_ENTRY, ("close: inst/soft: %d/0x%p", inst, (void *)ntwdt_ptr)); if (ntwdt_ptr == NULL) return (ENXIO); mutex_enter(&ntwdt_ptr->ntwdt_wdog_state->ntwdt_wdog_mutex); mutex_enter(&ntwdt_ptr->ntwdt_mutex); if (ntwdt_ptr->ntwdt_open_flag != 0) { ntwdt_ptr->ntwdt_open_flag = 0; } mutex_exit(&ntwdt_ptr->ntwdt_mutex); mutex_exit(&ntwdt_ptr->ntwdt_wdog_state->ntwdt_wdog_mutex); return (0); } _NOTE(ARGSUSED(4)) static int ntwdt_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { int inst = getminor(dev); int retval = 0; ntwdt_state_t *ntwdt_ptr = NULL; ntwdt_wdog_t *wdog_state; if ((ntwdt_ptr = getstate(inst)) == NULL) return (ENXIO); /* Only allow ioctl's if Solaris/ScApp support app-wdog */ if (ntwdt_watchdog_available == 0) return (ENXIO); wdog_state = ntwdt_ptr->ntwdt_wdog_state; switch (cmd) { case LOMIOCDOGSTATE: { /* * Return the state of the AWDT to the application. */ lom_dogstate_t lom_dogstate; mutex_enter(&wdog_state->ntwdt_wdog_mutex); lom_dogstate.reset_enable = wdog_state->ntwdt_reset_enabled; lom_dogstate.dog_enable = wdog_state->ntwdt_wdog_enabled; lom_dogstate.dog_timeout = wdog_state->ntwdt_wdog_timeout; mutex_exit(&wdog_state->ntwdt_wdog_mutex); NTWDT_DBG(WDT_DBG_IOCTL, ("DOGSTATE: wdog/reset/timeout:" " %d/%d/%d", lom_dogstate.dog_enable, lom_dogstate.reset_enable, lom_dogstate.dog_timeout)); if (ddi_copyout((caddr_t)&lom_dogstate, (caddr_t)arg, sizeof (lom_dogstate_t), mode) != 0) { retval = EFAULT; } break; } case LOMIOCDOGCTL: { /* * Allow application to control whether watchdog * is {dis,en}abled and whether Reset is * {dis,en}abled. */ lom_dogctl_t lom_dogctl; if (ddi_copyin((caddr_t)arg, (caddr_t)&lom_dogctl, sizeof (lom_dogctl_t), mode) != 0) { retval = EFAULT; break; } NTWDT_DBG(WDT_DBG_IOCTL, ("DOGCTL: wdog/reset:" " %d/%d", lom_dogctl.dog_enable, lom_dogctl.reset_enable)); mutex_enter(&wdog_state->ntwdt_wdog_mutex); if (wdog_state->ntwdt_wdog_timeout == 0) { /* * then LOMIOCDOGTIME has never been used * to setup a valid timeout. */ retval = EINVAL; goto end; } /* * Return error for the non-sensical combination: * "enable Reset" and "disable watchdog". */ if (lom_dogctl.dog_enable == 0 && lom_dogctl.reset_enable != 0) { retval = EINVAL; goto end; } /* * Store the user-specified state in our softstate. * Note that our implementation here is stateless. * Eg, we do not disallow an "enable the watchdog" * command when the watchdog is currently enabled. * This is needed (at least in the case) when * the user enters OBP via ScApp/lom. In that case, * ScApp disables the watchdog, but does not inform * Solaris. As a result, an ensuing, unfiltered DOGCTL * to enable the watchdog is required. */ wdog_state->ntwdt_reset_enabled = lom_dogctl.reset_enable; wdog_state->ntwdt_wdog_enabled = lom_dogctl.dog_enable; if (wdog_state->ntwdt_wdog_enabled != 0) { /* * then user wants to enable watchdog. * Arm the watchdog timer and start the * Cyclic, if it is not running. */ ntwdt_arm_vwdt(wdog_state); if (wdog_state->ntwdt_timer_running == 0) { ntwdt_start_timer(ntwdt_ptr); } } else { /* * user wants to disable the watchdog. * Note that we do not set ntwdt_secs_remaining * to zero; that could cause a false expiration. */ if (wdog_state->ntwdt_timer_running != 0) { ntwdt_stop_timer(ntwdt_ptr); } } /* * Send a permutation of mailbox commands to * ScApp that describes the current state of the * watchdog timer. Note that the permutation * depends on whether this is the first * Enabling of the watchdog or not. */ if (wdog_state->ntwdt_wdog_enabled != 0 && wdog_state->ntwdt_is_initial_enable == 0) { /* switch from SWDT to AWDT mode */ ntwdt_swdt_to_awdt(wdog_state); /* Tell ScApp we're in AWDT mode */ (void) ntwdt_set_cfgvar(LW8_WDT_PROP_MODE, LW8_PROP_MODE_AWDT); } /* Inform ScApp of the choices made by the app */ (void) ntwdt_set_cfgvar(LW8_WDT_PROP_WDT, wdog_state->ntwdt_wdog_enabled); (void) ntwdt_set_cfgvar(LW8_WDT_PROP_RECOV, wdog_state->ntwdt_reset_enabled); if (wdog_state->ntwdt_wdog_enabled != 0 && wdog_state->ntwdt_is_initial_enable == 0) { /* * Clear tod_iosram_t.tod_timeout_period, * which is used in SWDT part of state * machine. (If this field is non-zero, * ScApp assumes that Solaris' SWDT is active). * * Clearing this is useful in case SC reboots * while Solaris is running, as ScApp will read * a zero and not assume SWDT is running. */ (void) ntwdt_set_hw_timeout(0); /* "the first watchdog-enable has been seen" */ wdog_state->ntwdt_is_initial_enable = 1; } mutex_exit(&wdog_state->ntwdt_wdog_mutex); break; } case LOMIOCDOGTIME: { /* * Allow application to set the period (in seconds) * of the watchdog timeout. */ uint32_t lom_dogtime; if (ddi_copyin((caddr_t)arg, (caddr_t)&lom_dogtime, sizeof (uint32_t), mode) != 0) { retval = EFAULT; break; } NTWDT_DBG(WDT_DBG_IOCTL, ("DOGTIME: %u seconds", lom_dogtime)); /* Ensure specified timeout is within range. */ if ((lom_dogtime == 0) || (lom_dogtime > NTWDT_MAX_TIMEOUT)) { retval = EINVAL; break; } mutex_enter(&wdog_state->ntwdt_wdog_mutex); wdog_state->ntwdt_wdog_timeout = lom_dogtime; /* * If watchdog is currently running, re-arm the * watchdog timeout with the specified value. */ if (wdog_state->ntwdt_timer_running != 0) { ntwdt_arm_vwdt(wdog_state); } /* Tell ScApp of the specified timeout */ (void) ntwdt_set_cfgvar(LW8_WDT_PROP_TO, lom_dogtime); mutex_exit(&wdog_state->ntwdt_wdog_mutex); break; } case LOMIOCDOGPAT: { /* * Allow user to re-arm ("pat") the watchdog. */ NTWDT_DBG(WDT_DBG_IOCTL, ("DOGPAT")); mutex_enter(&wdog_state->ntwdt_wdog_mutex); /* * If watchdog is not enabled or underlying * Cyclic timer is not running, exit. */ if (!(wdog_state->ntwdt_wdog_enabled && wdog_state->ntwdt_timer_running)) goto end; if (wdog_state->ntwdt_wdog_expired == 0) { /* then VWDT has not expired; re-arm it */ ntwdt_arm_vwdt(wdog_state); NTWDT_DBG(WDT_DBG_VWDT, ("VWDT re-armed:" " %d seconds", wdog_state->ntwdt_secs_remaining)); } mutex_exit(&wdog_state->ntwdt_wdog_mutex); break; } #ifdef DEBUG case NTWDTIOCPANIC: { /* * Use in unit/integration testing to test our * panic-handler code. */ cmn_err(CE_PANIC, "NTWDTIOCPANIC: force a panic"); break; } case NTWDTIOCSTATE: { /* * Allow application to read wdog state from the * SC (and *not* the driver's softstate). * * Return state of: * o recovery-enabled * o current timeout value */ ntwdt_data_t ntwdt_data; int action; int timeout; int ret; mutex_enter(&wdog_state->ntwdt_wdog_mutex); ret = ntwdt_get_cfgvar(LW8_WDT_PROP_TO, &timeout); ret |= ntwdt_get_cfgvar(LW8_WDT_PROP_RECOV, &action); mutex_exit(&wdog_state->ntwdt_wdog_mutex); bzero((caddr_t)&ntwdt_data, sizeof (ntwdt_data)); if (ret != NTWDT_SUCCESS) { retval = EIO; break; } NTWDT_DBG(WDT_DBG_IOCTL, ("NTWDTIOCSTATE:" " timeout/action: %d/%d", timeout, action)); ntwdt_data.ntwdt_wd1 = (uint32_t)timeout; ntwdt_data.ntwdt_wd2 = (uint8_t)action; if (ddi_copyout((caddr_t)&ntwdt_data, (caddr_t)arg, sizeof (ntwdt_data_t), mode) != 0) { retval = EFAULT; } break; } #endif default: retval = EINVAL; break; } return (retval); end: mutex_exit(&wdog_state->ntwdt_wdog_mutex); return (retval); } /* * Arm the Virtual Watchdog Timer (VWDT). * * Assign the current watchdog timeout (ntwdt_wdog_timeout) * to the softstate variable representing the watchdog * timer (ntwdt_secs_remaining). * * To ensure (from ntwdt's perspective) that any actual * timeout expiration is at least as large as the expected * timeout, conditionally set/clear a bit that will be * checked in the Cyclic's softint. * * If the Cyclic has been started, the goal is to ignore * the _next_ firing of the Cyclic, as that firing will * NOT represent a full, one-second period. If the Cyclic * has NOT been started yet, then do not ignore the next * Cyclic's firing, as that's the First One, and it was * programmed to fire at a specific time (see ntwdt_start_timer). */ static void ntwdt_arm_vwdt(ntwdt_wdog_t *wdog_state) { /* arm the watchdog timer (VWDT) */ wdog_state->ntwdt_secs_remaining = wdog_state->ntwdt_wdog_timeout; if (wdog_state->ntwdt_timer_running != 0) NTWDT_FLAG_SET(wdog_state, SKIP_CYCLIC); else NTWDT_FLAG_CLR(wdog_state, SKIP_CYCLIC); } /* * Switch from SWDT mode to AWDT mode. */ _NOTE(ARGSUSED(0)) static void ntwdt_swdt_to_awdt(ntwdt_wdog_t *wdog_state) { ASSERT(wdog_state->ntwdt_is_initial_enable == 0); /* * Disable SWDT. If SWDT is currently active, * display a message so user knows that SWDT Mode * has terminated. */ if (watchdog_enable != 0 || watchdog_activated != 0) cmn_err(CE_NOTE, "Hardware watchdog disabled"); watchdog_enable = 0; watchdog_activated = 0; /* "we are in AWDT mode" */ ntwdt_watchdog_activated = 1; NTWDT_DBG(WDT_DBG_VWDT, ("AWDT is enabled")); } /* * This is the Cyclic that runs at a multiple of the * AWDT's watchdog-timeout period. This Cyclic runs at * LOCK_LEVEL (eg, CY_LOCK_LEVEL) and will post a * soft-interrupt in order to complete all processing. * * Executing at LOCK_LEVEL gives this function a high * interrupt priority, while performing its work via * a soft-interrupt allows for a consistent (eg, MT-safe) * view of driver softstate between User and Interrupt * context. * * Context: * interrupt context: Cyclic framework calls at * CY_LOCK_LEVEL (=> 10) */ _NOTE(ARGSUSED(0)) static void ntwdt_cyclic_pat(void *arg) { /* post-down to DDI_SOFTINT_LOW */ ddi_trigger_softintr(ntwdt_cyclic_softint_id); } /* * This is the soft-interrupt triggered by the AWDT * Cyclic. * * This softint does all the work re: computing whether * the VWDT expired. It grabs ntwdt_wdog_mutex * so User Context code (eg, the IOCTLs) cannot run, * and then it tests whether the VWDT expired. If it * hasn't, it decrements the VWDT timer by the amount * of the Cyclic's period. If the timer has expired, * it initiates Recovery (based on what user specified * in LOMIOCDOGCTL). * * This function also updates the normal system "heartbeat". * * Context: * interrupt-context: DDI_SOFTINT_LOW */ static uint_t ntwdt_cyclic_softint(char *arg) { ntwdt_state_t *ntwdt_ptr = (ntwdt_state_t *)arg; ntwdt_wdog_t *wdog_state; wdog_state = ntwdt_ptr->ntwdt_wdog_state; mutex_enter(&wdog_state->ntwdt_wdog_mutex); if ((wdog_state->ntwdt_wdog_flags & NTWDT_FLAG_SKIP_CYCLIC) != 0) { /* * then skip all processing by this interrupt. * (see ntwdt_arm_vwdt()). */ wdog_state->ntwdt_wdog_flags &= ~NTWDT_FLAG_SKIP_CYCLIC; goto end; } if (wdog_state->ntwdt_timer_running == 0 || (ntwdt_ptr->ntwdt_cycl_id == CYCLIC_NONE) || (wdog_state->ntwdt_wdog_enabled == 0)) goto end; /* re-arm ("pat") the hardware watchdog */ ntwdt_pat_hw_watchdog(); /* Decrement the VWDT and see if it has expired. */ if (--wdog_state->ntwdt_secs_remaining == 0) { cmn_err(CE_WARN, "application-watchdog expired"); wdog_state->ntwdt_wdog_expired = 1; if (wdog_state->ntwdt_reset_enabled != 0) { /* * Update ScApp so that the new wdog-timeout * value is as specified in the * NTWDT_BOOT_TIMEOUT_PROP driver Property. * This timeout is assumedly larger than the * actual Solaris reboot time. This will allow * our forced-reboot to not cause an unplanned * (series of) watchdog expiration(s). */ if (ntwdt_disable_timeout_action == 0) ntwdt_reprogram_wd(ntwdt_ptr); mutex_exit(&wdog_state->ntwdt_wdog_mutex); NTWDT_DBG(WDT_DBG_VWDT, ("recovery being done")); ntwdt_enforce_timeout(); } else { NTWDT_DBG(WDT_DBG_VWDT, ("no recovery being done")); wdog_state->ntwdt_wdog_enabled = 0; /* * Tell ScApp to disable wdog; this prevents * the "2x-timeout" artifact. Eg, Solaris * times-out at t(x) and ScApp times-out at t(2x), * where (x==ntwdt_wdog_timeout). */ (void) ntwdt_set_cfgvar(LW8_WDT_PROP_WDT, wdog_state->ntwdt_wdog_enabled); } /* Schedule Callout to stop this Cyclic */ (void) timeout(ntwdt_stop_timer_lock, ntwdt_ptr, 0); } else { _NOTE(EMPTY) NTWDT_DBG(WDT_DBG_VWDT, ("time remaining in VWDT: %d" " seconds", wdog_state->ntwdt_secs_remaining)); } end: mutex_exit(&wdog_state->ntwdt_wdog_mutex); return (DDI_INTR_CLAIMED); } /* * Program the AWDT watchdog-timeout value to that specified * in the NTWDT_BOOT_TIMEOUT_PROP driver Property. However, * only do this if the AWDT is in the correct state. * * Caller's Context: * o interrupt context: (from software-interrupt) * o during a panic */ static void ntwdt_reprogram_wd(ntwdt_state_t *ntwdt_ptr) { ntwdt_wdog_t *wdog_state = ntwdt_ptr->ntwdt_wdog_state; /* * Program the AWDT watchdog-timeout value only if the * watchdog is enabled, the user wants to do recovery, * ("reset is enabled") and the AWDT timer is currently * running. */ if (wdog_state->ntwdt_wdog_enabled != 0 && wdog_state->ntwdt_reset_enabled != 0 && wdog_state->ntwdt_timer_running != 0) { if (ddi_in_panic() != 0) (void) ntwdt_set_cfgvar_noreply(LW8_WDT_PROP_TO, wdog_state->ntwdt_boot_timeout); else (void) ntwdt_set_cfgvar(LW8_WDT_PROP_TO, wdog_state->ntwdt_boot_timeout); } } /* * This is the callback that was registered to run during a panic. * It will set the watchdog-timeout value to be that as specified * in the NTWDT_BOOT_TIMEOUT_PROP driver Property. * * Note that unless this Property's value specifies a timeout * that's larger than the actual reboot latency, ScApp will * experience a timeout and initiate Recovery. */ _NOTE(ARGSUSED(1)) static boolean_t ntwdt_panic_cb(void *arg, int code) { ASSERT(ddi_in_panic() != 0); ntwdt_reprogram_wd((ntwdt_state_t *)arg); return (B_TRUE); } /* * Initialize the Cyclic that is used to monitor the VWDT. */ static void ntwdt_start_timer(ntwdt_state_t *ntwdt_ptr) { ntwdt_wdog_t *wdog_state = ntwdt_ptr->ntwdt_wdog_state; cyc_handler_t *hdlr = &wdog_state->ntwdt_cycl_hdlr; cyc_time_t *when = &wdog_state->ntwdt_cycl_time; /* * Init Cyclic so its first expiry occurs wdog-timeout * seconds from the current, absolute time. */ when->cyt_interval = wdog_state->ntwdt_cyclic_interval; when->cyt_when = gethrtime() + when->cyt_interval; wdog_state->ntwdt_wdog_expired = 0; wdog_state->ntwdt_timer_running = 1; mutex_enter(&cpu_lock); if (ntwdt_ptr->ntwdt_cycl_id == CYCLIC_NONE) ntwdt_ptr->ntwdt_cycl_id = cyclic_add(hdlr, when); mutex_exit(&cpu_lock); NTWDT_DBG(WDT_DBG_VWDT, ("AWDT's cyclic-driven timer is started")); } /* * Stop the cyclic that is used to monitor the VWDT (and * was Started by ntwdt_start_timer). * * Context: per the Cyclic API, cyclic_remove cannot be called * from interrupt-context. Note that when this is * called via a Callout, it's called from base level. */ static void ntwdt_stop_timer(void *arg) { ntwdt_state_t *ntwdt_ptr = (void *)arg; ntwdt_wdog_t *wdog_state = ntwdt_ptr->ntwdt_wdog_state; mutex_enter(&cpu_lock); if (ntwdt_ptr->ntwdt_cycl_id != CYCLIC_NONE) cyclic_remove(ntwdt_ptr->ntwdt_cycl_id); mutex_exit(&cpu_lock); wdog_state->ntwdt_timer_running = 0; ntwdt_ptr->ntwdt_cycl_id = CYCLIC_NONE; NTWDT_DBG(WDT_DBG_VWDT, ("AWDT's cyclic-driven timer is stopped")); } /* * Stop the cyclic that is used to monitor the VWDT (and * do it in a thread-safe manner). * * This is a wrapper function for the core function, * ntwdt_stop_timer. Both functions are useful, as some * callers will already have the appropriate mutex locked, and * other callers will not. */ static void ntwdt_stop_timer_lock(void *arg) { ntwdt_state_t *ntwdt_ptr = (void *)arg; ntwdt_wdog_t *wdog_state = ntwdt_ptr->ntwdt_wdog_state; mutex_enter(&wdog_state->ntwdt_wdog_mutex); ntwdt_stop_timer(arg); mutex_exit(&wdog_state->ntwdt_wdog_mutex); } /* * Add callbacks needed to react to major system state transitions. */ static void ntwdt_add_callbacks(ntwdt_state_t *ntwdt_ptr) { /* register a callback that's called during a panic */ ntwdt_callback_ids.ntwdt_panic_cb = callb_add(ntwdt_panic_cb, (void *)ntwdt_ptr, CB_CL_PANIC, "ntwdt_panic_cb"); } /* * Remove callbacks added by ntwdt_add_callbacks. */ static void ntwdt_remove_callbacks() { (void) callb_delete(ntwdt_callback_ids.ntwdt_panic_cb); } /* * Initiate a Reset (as a result of the VWDT timeout expiring). */ static void ntwdt_enforce_timeout() { if (ntwdt_disable_timeout_action != 0) { cmn_err(CE_NOTE, "OS timeout expired, taking no action"); return; } NTWDT_DBG(WDT_DBG_VWDT, ("VWDT expired; do a crashdump")); (void) kadmin(A_DUMP, AD_BOOT, NULL, kcred); cmn_err(CE_PANIC, "kadmin(A_DUMP, AD_BOOT) failed"); _NOTE(NOTREACHED) } /* * Interpret the Properties from driver's config file. */ static int ntwdt_read_props(ntwdt_state_t *ntwdt_ptr) { ntwdt_wdog_t *wdog_state; int boot_timeout; wdog_state = ntwdt_ptr->ntwdt_wdog_state; /* * interpret Property that specifies how long * the watchdog-timeout should be set to when * Solaris panics. Assumption is that this value * is larger than the amount of time it takes * to reboot and write crashdump. If not, * ScApp could induce a reset, due to an expired * watchdog-timeout. */ wdog_state->ntwdt_boot_timeout = NTWDT_DEFAULT_BOOT_TIMEOUT; boot_timeout = ddi_prop_get_int(DDI_DEV_T_ANY, ntwdt_ptr->ntwdt_dip, DDI_PROP_DONTPASS, NTWDT_BOOT_TIMEOUT_PROP, -1); if (boot_timeout != -1 && boot_timeout > 0 && boot_timeout <= NTWDT_MAX_TIMEOUT) { wdog_state->ntwdt_boot_timeout = boot_timeout; } else { _NOTE(EMPTY) NTWDT_DBG(WDT_DBG_ENTRY, (NTWDT_BOOT_TIMEOUT_PROP ": using default of %d seconds.", wdog_state->ntwdt_boot_timeout)); } return (DDI_SUCCESS); } /* * Write state of SWDT to ScApp. * * Currently, this function is only called on attach() * of our driver. * * Note that we do not need to call this function, eg, * in response to a solicitation from ScApp (eg, * the LW8_SC_RESTARTED_EVENT). * * Context: * called in Kernel Context */ static int ntwdt_set_swdt_state() { /* * note that ScApp only needs this one * variable when system is in SWDT mode. */ (void) ntwdt_set_cfgvar(LW8_WDT_PROP_MODE, LW8_PROP_MODE_SWDT); return (0); } /* * Write all AWDT state to ScApp via the SBBC mailbox * in IOSRAM. Note that the permutation of Writes * is as specified in the design spec. * * Notes: caller must perform synchronization so that * this series of Writes is consistent as viewed * by ScApp (eg, there is no LW8_WDT_xxx mailbox * command that contains "all Properties"; each * Property must be written individually). */ static int ntwdt_set_awdt_state(ntwdt_wdog_t *rstatep) { /* ScApp expects values in this order: */ (void) ntwdt_set_cfgvar(LW8_WDT_PROP_MODE, ntwdt_watchdog_activated != 0); (void) ntwdt_set_cfgvar(LW8_WDT_PROP_TO, rstatep->ntwdt_wdog_timeout); (void) ntwdt_set_cfgvar(LW8_WDT_PROP_RECOV, rstatep->ntwdt_reset_enabled); (void) ntwdt_set_cfgvar(LW8_WDT_PROP_WDT, rstatep->ntwdt_wdog_enabled); return (NTWDT_SUCCESS); } /* * Write a specified WDT Property (and Value) to ScApp. * * is passed in the LW8_MBOX_WDT_SET * (SBBC) mailbox message. The SBBC mailbox resides in * IOSRAM. * * Note that this function is responsible for ensuring that * a driver-specific representation of a mailbox is * mapped into the representation that is expected by ScApp * (eg, see LW8_WDT_PROP_RECOV). */ static int ntwdt_set_cfgvar(int var, int val) { int rv; int mbox_val; lw8_set_wdt_t set_wdt; switch (var) { case LW8_WDT_PROP_RECOV: #ifdef DEBUG NTWDT_DBG(WDT_DBG_PROT, ("MBOX_SET of 'recovery-enabled':" " %s (%d)", (val != 0) ? "enabled" : "disabled", val)); #endif mbox_val = (val != 0) ? LW8_PROP_RECOV_ENABLED : LW8_PROP_RECOV_DISABLED; break; case LW8_WDT_PROP_WDT: #ifdef DEBUG NTWDT_DBG(WDT_DBG_PROT, ("MBOX_SET of 'wdog-enabled':" " %s (%d)", (val != 0) ? "enabled" : "disabled", val)); #endif mbox_val = (val != 0) ? LW8_PROP_WDT_ENABLED : LW8_PROP_WDT_DISABLED; break; case LW8_WDT_PROP_TO: #ifdef DEBUG NTWDT_DBG(WDT_DBG_PROT, ("MBOX_SET of 'wdog-timeout':" " %d seconds", val)); #endif mbox_val = val; break; case LW8_WDT_PROP_MODE: #ifdef DEBUG NTWDT_DBG(WDT_DBG_PROT, ("MBOX_SET of 'wdog-mode':" " %s (%d)", (val != LW8_PROP_MODE_SWDT) ? "AWDT" : "SWDT", val)); #endif mbox_val = val; break; default: ASSERT(0); _NOTE(NOTREACHED) } set_wdt.property_id = var; set_wdt.value = mbox_val; rv = ntwdt_lomcmd(LW8_MBOX_WDT_SET, (intptr_t)&set_wdt); if (rv != 0) { _NOTE(EMPTY) NTWDT_DBG(WDT_DBG_PROT, ("MBOX_SET of prop/val %d/%d " "failed: %d", var, mbox_val, rv)); } return (rv); } static void ntwdt_set_cfgvar_noreply(int var, int val) { (void) ntwdt_set_cfgvar(var, val); } #ifdef DEBUG /* * Read a specified WDT Property from ScApp. * * is passed in the Request of the LW8_MBOX_WDT_GET * (SBBC) mailbox message, and the Property's * is returned in the message's Response. The SBBC mailbox * resides in IOSRAM. */ static int ntwdt_get_cfgvar(int var, int *val) { lw8_get_wdt_t get_wdt; int rv; rv = ntwdt_lomcmd(LW8_MBOX_WDT_GET, (intptr_t)&get_wdt); if (rv != 0) { _NOTE(EMPTY) NTWDT_DBG(WDT_DBG_PROT, ("MBOX_GET failed: %d", rv)); } else { switch (var) { case LW8_WDT_PROP_RECOV: *val = (uint8_t)get_wdt.recovery_enabled; NTWDT_DBG(WDT_DBG_PROT, ("MBOX_GET of 'reset-enabled':" " %s (%d)", (*val != 0) ? "enabled" : "disabled", *val)); break; case LW8_WDT_PROP_WDT: *val = (uint8_t)get_wdt.watchdog_enabled; NTWDT_DBG(WDT_DBG_PROT, ("MBOX_GET of 'wdog-enabled':" " %s (%d)", (*val != 0) ? "enabled" : "disabled", *val)); break; case LW8_WDT_PROP_TO: *val = (uint8_t)get_wdt.timeout; NTWDT_DBG(WDT_DBG_PROT, ("MBOX_GET of 'wdog-timeout':" " %d seconds", *val)); break; default: ASSERT(0); _NOTE(NOTREACHED) } } return (rv); } #endif /* * Update the real system "heartbeat", which resides in IOSRAM. * This "heartbeat" is normally used in SWDT Mode, but when * in AWDT Mode, ScApp also uses its value to determine if Solaris * is up-and-running. */ static void ntwdt_pat_hw_watchdog() { tod_iosram_t tod_buf; static uint32_t i_am_alive = 0; #ifdef DEBUG if (ntwdt_stop_heart != 0) return; #endif /* Update the system heartbeat */ if (i_am_alive == UINT32_MAX) i_am_alive = 0; else i_am_alive++; NTWDT_DBG(WDT_DBG_HEART, ("update heartbeat: %d", i_am_alive)); if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_i_am_alive), (char *)&i_am_alive, sizeof (uint32_t))) { cmn_err(CE_WARN, "ntwdt_pat_hw_watchdog(): " "write heartbeat failed"); } } /* * Write the specified value to the system's normal (IOSRAM) * location that's used to specify Solaris' watchdog-timeout * on Serengeti platforms. * * In SWDT Mode, this location can hold values [0,n). * In AWDT Mode, this location must have value 0 (else * after a ScApp-reboot, ScApp could mistakenly interpret * that the system is in SWDT Mode). */ static int ntwdt_set_hw_timeout(uint32_t period) { tod_iosram_t tod_buf; int rv; rv = iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period), (char *)&period, sizeof (uint32_t)); if (rv != 0) cmn_err(CE_WARN, "write of %d for TOD timeout " "period failed: %d", period, rv); return (rv); } /* * Soft-interrupt handler that is triggered when ScApp wants * to know the current state of the app-wdog. * * Grab ntwdt_wdog_mutex so that we synchronize with any * concurrent User Context and Interrupt Context activity. Call * a function that writes a permutation of the watchdog state * to the SC, then release the mutex. * * We grab the mutex not only so that each variable is consistent * but also so that the *permutation* of variables is consistent. * I.e., any set of one or more variables (that we write to SC * using multiple mailbox commands) will truly be seen as a * consistent snapshot. Note that if our protocol had a MBOX_SET * command that allowed writing all watchdog state in one * command, then the lock-hold latency would be greatly reduced. * To our advantage, this softint normally executes very * infrequently. * * Context: * called at Interrupt Context (DDI_SOFTINT_LOW) */ static uint_t ntwdt_mbox_softint(char *arg) { ntwdt_wdog_t *wdog_state; wdog_state = ((ntwdt_state_t *)arg)->ntwdt_wdog_state; ASSERT(wdog_state != NULL); mutex_enter(&wdog_state->ntwdt_wdog_mutex); /* tell ScApp state of AWDT */ (void) ntwdt_set_awdt_state(wdog_state); mutex_exit(&wdog_state->ntwdt_wdog_mutex); return (DDI_INTR_CLAIMED); } /* * Handle MBOX_EVENT_LW8 Events that are sent from ScApp. * * The only (sub-)type of Event we handle is the * LW8_EVENT_SC_RESTARTED Event. We handle this by triggering * a soft-interrupt only if we are in AWDT mode. * * ScApp sends this Event when it wants to learn the current * state of the AWDT variables. Design-wise, this is used to * handle the case where the SC reboots while the system is in * AWDT mode (if the SC reboots in SWDT mode, then ScApp * already knows all necessary info and therefore won't send * this Event). * * Context: * function is called in Interrupt Context (at DDI_SOFTINT_MED) * and we conditionally trigger a softint that will run at * DDI_SOFTINT_LOW. Note that function executes at * DDI_SOFTINT_MED due to how this handler was registered by * the implementation of sbbc_mbox_reg_intr(). * * Notes: * Currently, the LW8_EVENT_SC_RESTARTED Event is only sent * by SC when in AWDT mode. */ static uint_t ntwdt_event_data_handler(char *arg) { lw8_event_t *payload; sbbc_msg_t *msg; if (arg == NULL) { return (DDI_INTR_CLAIMED); } msg = (sbbc_msg_t *)arg; if (msg->msg_buf == NULL) { return (DDI_INTR_CLAIMED); } payload = (lw8_event_t *)msg->msg_buf; switch (payload->event_type) { case LW8_EVENT_SC_RESTARTED: /* * then SC probably was rebooted, and it therefore * needs to know what the current state of AWDT is. */ NTWDT_DBG(WDT_DBG_EVENT, ("LW8_EVENT_SC_RESTARTED " "received in %s mode", (ntwdt_watchdog_activated != 0) ? "AWDT" : "SWDT")); if (ntwdt_watchdog_activated != 0) { /* then system is in AWDT mode */ ddi_trigger_softintr(ntwdt_mbox_softint_id); } break; default: NTWDT_DBG(WDT_DBG_EVENT, ("MBOX_EVENT_LW8: %d", payload->event_type)); break; } return (DDI_INTR_CLAIMED); } /* * Send an SBBC Mailbox command to ScApp. * * Use the sbbc_mbox_request_response utility function to * send the Request and receive the optional Response. * * Context: * can be called from Interrupt Context or User Context. */ static int ntwdt_lomcmd(int cmd, intptr_t arg) { sbbc_msg_t request; sbbc_msg_t *reqp; sbbc_msg_t response; sbbc_msg_t *resp; int rv = 0; reqp = &request; bzero((caddr_t)&request, sizeof (request)); reqp->msg_type.type = LW8_MBOX; reqp->msg_type.sub_type = (uint16_t)cmd; resp = &response; bzero((caddr_t)&response, sizeof (response)); resp->msg_type.type = LW8_MBOX; resp->msg_type.sub_type = (uint16_t)cmd; switch (cmd) { case LW8_MBOX_WDT_GET: reqp->msg_len = 0; reqp->msg_buf = (caddr_t)NULL; resp->msg_len = sizeof (lw8_get_wdt_t); resp->msg_buf = (caddr_t)arg; break; case LW8_MBOX_WDT_SET: reqp->msg_len = sizeof (lw8_set_wdt_t); reqp->msg_buf = (caddr_t)arg; resp->msg_len = 0; resp->msg_buf = (caddr_t)NULL; break; default: return (EINVAL); } rv = sbbc_mbox_request_response(reqp, resp, LW8_DEFAULT_MAX_MBOX_WAIT_TIME); if ((rv) || (resp->msg_status != SG_MBOX_STATUS_SUCCESS)) { NTWDT_NDBG(WDT_DBG_PROT, ("SBBC mailbox error:" " (rv/msg_status)=(%d/%d)", rv, resp->msg_status)); /* errors from sgsbbc */ if (resp->msg_status > 0) { return (resp->msg_status); } /* errors from ScApp */ switch (resp->msg_status) { case SG_MBOX_STATUS_ILLEGAL_PARAMETER: /* illegal ioctl parameter */ return (EINVAL); default: return (EIO); } } return (0); }