root/usr/src/uts/sun4u/lw8/io/ntwdt.c
/*
 * 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 <sys/note.h>
#include <sys/types.h>
#include <sys/callb.h>
#include <sys/stat.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/ddi_impldefs.h>
#include <sys/kmem.h>
#include <sys/devops.h>
#include <sys/cyclic.h>
#include <sys/uadmin.h>
#include <sys/lw8_impl.h>
#include <sys/sgsbbc.h>
#include <sys/sgsbbc_iosram.h>
#include <sys/sgsbbc_mailbox.h>
#include <sys/todsg.h>
#include <sys/mem_config.h>
#include <sys/lom_io.h>
#include <sys/reboot.h>
#include <sys/clock.h>


/*
 * 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(9e).
                 */
                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.
 *
 * <Property, Value> 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 <Value> 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.
 *
 * <Property> is passed in the Request of the LW8_MBOX_WDT_GET
 * (SBBC) mailbox message, and the Property's <Value>
 * 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);
}