/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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
* 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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This module is used to monitor and control watchdog timer for
* UltraSPARC-IIi CPU in Snowbird
*/
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <alloca.h>
#include <limits.h>
#include <libintl.h>
#include <syslog.h>
#include <locale.h>
#include <picl.h>
#include <picltree.h>
#include <libnvpair.h>
#include <poll.h>
#include <errno.h>
#include <syslog.h>
#include <sys/priocntl.h>
#include <sys/rtpriocntl.h>
#include <sys/tspriocntl.h>
#include <sys/fsspriocntl.h>
#include <stropts.h>
#include <synch.h>
#include <signal.h>
#include <thread.h>
#include <picldefs.h>
#include <smclib.h>
#include "piclwatchdog.h"
/* debug variables */
static int wd_debug = 0;
static int count = 0;
typedef struct { /* used to keep track of time taken for last 5 pats */
int res_seq;
int req_seq;
} wd_time_t;
/* global declarations */
static int props_created = 0;
/* ptree interface */
static void wd_picl_register(void);
static void wd_picl_init(void);
static void wd_picl_fini(void);
static void wd_state_change_evhandler(const char *,
const void *, size_t, void *);
/* local functions */
static int wd_write_timeout(ptree_warg_t *, const void *);
static int wd_write_action(ptree_warg_t *, const void *);
static int wd_read_action(ptree_rarg_t *, void *);
static int wd_read_timeout(ptree_rarg_t *, void *);
extern int wd_get_chassis_type();
"SUNW_picl_watchdog",
};
/*
* This function parses wd.conf file to set the tunables
* tunables at present: patting thread priority, pat time, wd_enable
*/
static void
{
return;
}
continue;
}
if (last) {
} else {
continue;
}
continue;
}
(char **)NULL, 10);
errno = 0;
if (errno != 0) {
pat_time = 0;
}
wd_enable = 0;
}
} else { /* unknown token */
continue;
}
}
}
/*
* read the SMC watchdog registers
*/
static int
{
int rc = 0, i;
/* initialize the request packet */
DEFAULT_SEQN, 0);
/* make a call to smc library to send cmd */
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
return (PICL_FAILURE);
}
/* read 8 bytes */
for (i = 0; i < WD_REGISTER_LEN; i++) {
}
return (PICL_SUCCESS);
}
/*
* get the HEALTHY# line state
* Return -1 for Error
* 0 for HEALTHY# down
* 1 for HEALTHY# up
*/
static int
{
/* initialize the request packet */
DEFAULT_SEQN, 0);
/* make a call to smc library to send cmd */
WD_POLL_TIMEOUT) != SMC_SUCCESS) {
return (-1);
}
}
/*ARGSUSED*/
static void
{
}
/*
* posts picl-state-change event if there is change in watchdog-timer state
*/
static picl_errno_t
{
return (PICL_FAILURE);
}
return (PICL_NOSPACE);
}
return (PICL_FAILURE);
}
nodeh)) != 0) {
return (PICL_FAILURE);
}
state)) != 0) {
return (PICL_FAILURE);
}
NULL)) != 0) {
return (PICL_FAILURE);
}
event_completion_handler)) != PICL_SUCCESS) {
return (rc);
}
return (PICL_SUCCESS);
}
/*
* Updates the State value in picl tree and posts a state-change event
*/
static void
{
switch (stat) {
case WD_ARMED:
sizeof (state));
break;
case WD_DISARMED:
sizeof (state));
break;
case WD_EXPIRED:
sizeof (state));
break;
default:
return;
}
(void) mutex_lock(&data_lock);
switch (level) {
case WD1:
break;
case WD2:
break;
case WD1_2:
break;
default:
return;
}
(void) mutex_unlock(&data_lock);
if (!state_configured) {
return;
}
switch (level) {
case WD1:
state)) != PICL_SUCCESS) {
}
break;
case WD2:
state)) != PICL_SUCCESS) {
}
break;
case WD1_2:
state)) != PICL_SUCCESS) {
}
state)) != PICL_SUCCESS) {
}
break;
}
}
/*
* Sends a command to SMC to reset the watchdog-timers
*/
static int
wd_pat()
{
int rc = 0;
if (seq < WD_MAX_SEQN) {
} else {
seq = 1;
}
if (wd_debug & WD_TIME_DEBUG) {
}
/* initialize the request packet */
DEFAULT_SEQN, 0);
/* make a call to smc library to send cmd */
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
return (PICL_FAILURE);
}
if (wd_debug & WD_TIME_DEBUG) {
count++;
} else {
count = 0;
}
}
return (PICL_SUCCESS);
}
/* used to set the new values for watchdog and start the watchdog */
static int
{
int rc = 0;
return (PICL_FAILURE);
}
if (patting_option == ENABLE_AUTO_PAT) {
}
/* initialize the request packet */
/* make a call to smc library to send cmd */
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
return (PICL_FAILURE);
}
/* reset the watchdog timer */
DEFAULT_SEQN, 0);
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
return (PICL_FAILURE);
}
return (PICL_SUCCESS);
}
/*
* Validates timeout and action fields and arms the watchdog-timers
*/
static int
{
int rc;
if (wd_data.wd1_timeout >= 0) {
} else {
wd_time1 = 0;
}
if (wd_data.wd2_timeout >= 0) {
} else {
wd_time2 = 0;
}
/* check the HELATHY# status if action is alarm */
rc = wd_get_healthy_status();
if (rc == WD_HEALTHY_DOWN) {
return (PICL_FAILURE);
} else if (rc == -1) {
return (PICL_FAILURE);
}
}
} else {
}
} else {
}
return (rc);
}
/*
* This is thread is a RealTime class thread. This thread pats the
* watchdog-timers in regular intervals before the expiry.
*/
/*ARGSUSED*/
static void *
{
long nano_sec;
long sleep_time;
for (;;) {
(void) mutex_lock(&patting_lock);
}
(void) mutex_unlock(&patting_lock);
/* reset pat-time to zero */
pat_time = 0; /* tunable */
}
/* change the priority of thread to realtime class */
}
} else {
}
switch (wd_data.wd1_timeout) {
case 0:
if (wd_data.wd2_timeout >= 0) {
}
} else {
}
/* no need to pat */
(void) mutex_lock(&patting_lock);
(void) mutex_unlock(&patting_lock);
continue;
case -1:
if (wd_data.wd2_timeout < 0) {
(void) mutex_lock(&patting_lock);
(void) mutex_unlock(&patting_lock);
continue;
}
} else {
}
/* no need to pat */
(void) mutex_lock(&patting_lock);
(void) mutex_unlock(&patting_lock);
continue;
default:
break;
}
if (pat_time == 0) {
} else {
}
}
if (pat_time <= 0) {
(void) mutex_lock(&patting_lock);
(void) mutex_unlock(&patting_lock);
continue;
}
(void) mutex_lock(&patting_lock);
(void) mutex_unlock(&patting_lock);
continue;
} else {
}
if (wd_data.wd2_timeout >= 0) {
}
(void) mutex_lock(&patting_lock);
(void) mutex_unlock(&patting_lock);
continue;
}
do /* pat the watchdog until expiry or user disarm */
{
(void) mutex_lock(&patting_lock);
if (state == WD_NORESET) {
(void) mutex_unlock(&patting_lock);
break;
}
} else {
}
(void) mutex_unlock(&patting_lock);
(void) wd_pat();
}
}
/*NOTREACHED*/
return (NULL);
}
/*
* returns 0 if owner is not alive
* returns 1 if owner is alive
* returns -1 if there is no active owner
*/
static int
{
if (pid == -1) {
return (-1);
}
/* check if the file exists or not */
errno = 0;
return (1);
}
return (0);
} else {
}
return (-1);
}
/*
* Sends a cmd to SMC to stop watchdog timers
*/
static int
wd_stop()
{
int rc = 0;
if (wd_get_reg_dump(buffer) != 0) {
return (PICL_FAILURE);
}
/* clear the expiration flags */
/* initialize the request packet */
/* make a call to smc library to send cmd */
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
return (PICL_FAILURE);
}
return (PICL_SUCCESS);
}
/*
* Function used by volatile callback function for wd-op property
* under controller. This is used to arm, disarm the watchdog-timers
* in response to user actions
*/
static int
{
(void) mutex_lock(&data_lock);
(void) mutex_unlock(&data_lock);
switch (flag) {
case USER_ARMED_WD:
/* watchdog can only be armed if all the timers are disarmed */
if (wd1_state != WD_DISARMED) {
rc = PICL_FAILURE;
break;
}
if (wd2_state != WD_DISARMED) {
rc = PICL_FAILURE;
break;
}
/* check the HELATHY# status if action is alarm */
if (wd_data.wd1_timeout >= 0) {
rc = wd_get_healthy_status();
if (rc == WD_HEALTHY_DOWN) {
return (PICL_FAILURE);
} else if (rc == -1) {
return (PICL_FAILURE);
} else {
rc = PICL_SUCCESS;
}
}
}
/* signal the patting thread */
(void) mutex_lock(&patting_lock);
(void) cond_signal(&patting_cv);
(void) mutex_unlock(&patting_lock);
break;
case USER_DISARMED_WD:
/*
* if the caller doesnot own watchdog services,
* check to see if the owner is still alive using procfs
*/
switch (is_owner_alive()) {
case -1:
if ((wd1_state != WD_DISARMED) ||
(wd2_state != WD_DISARMED)) {
break;
}
/* watchdog is already disarmed */
return (PICL_FAILURE);
case 1:
/* owner is still alive, deny the operation */
return (PICL_PERMDENIED);
default:
break;
}
}
/* watchdog is running */
(void) mutex_lock(&patting_lock);
(void) cond_signal(&patting_cv);
(void) mutex_unlock(&patting_lock);
}
break;
case USER_ARMED_PAT_WD: /* for debug purposes only */
/*
* first arm-pat operation is used for arming the watchdog
* subsequent arm-pat operations will be used for patting
* the watchdog
*/
/* WD is stopped */
if (wd_data.wd1_timeout >= 0) {
}
if (wd_data.wd2_timeout >= 0) {
}
} else {
return (rc);
}
} else { /* WD is running */
return (PICL_INVALIDARG);
}
/* check if OS is patting the watchdog or not */
(void) mutex_lock(&patting_lock);
(void) mutex_unlock(&patting_lock);
return (PICL_INVALIDARG);
}
/* check if the process owns the WD services */
return (PICL_PERMDENIED);
}
}
break;
default:
break;
} /* switch */
return (rc);
}
/*ARGSUSED*/
static int
{
/* only after state is configured */
if (!state_configured) {
return (PICL_PERMDENIED);
}
}
/* only super user can write this property */
return (PICL_PERMDENIED);
}
rc = PICL_SUCCESS;
}
rc = PICL_SUCCESS;
}
/* for debug purpose only */
rc = PICL_SUCCESS;
}
if (rc == PICL_SUCCESS) {
} else {
}
if (rc == PICL_SUCCESS) {
switch (flag) {
case USER_ARMED_PAT_WD:
case USER_ARMED_WD:
/* get the process id of client */
} else {
pid = -1;
}
break;
case USER_DISARMED_WD:
/* reset the pid */
pid = -1;
default:
break;
}
}
return (rc);
}
/* volatile call back function to read the watchdog L1 status */
/*ARGSUSED*/
static int
{
(void) mutex_lock(&data_lock);
switch (wd_data.wd1_run_state) {
case WD_EXPIRED:
break;
case WD_DISARMED:
break;
case WD_ARMED:
break;
default:
rc = PICL_FAILURE;
}
(void) mutex_unlock(&data_lock);
return (rc);
}
/*
* this function is used to read the state of L2 timer
*/
static int
{
int rc;
(void) mutex_lock(&data_lock);
/* we already have the latest state */
(void) mutex_unlock(&data_lock);
return (PICL_SUCCESS);
}
(void) mutex_unlock(&data_lock);
/* read watchdog registers */
return (rc);
}
if (buffer[0] & WD_WD_RUNNING) {
return (PICL_SUCCESS);
}
if (buffer[3] != 0) {
(void) mutex_lock(&data_lock);
(void) mutex_unlock(&data_lock);
}
return (PICL_SUCCESS);
}
/* volatile call back function to read the watchdog L2 status */
/*ARGSUSED*/
static int
{
PICL_SUCCESS) {
return (rc);
}
/* copy the present state in user buffer */
switch (present_status) {
case WD_ARMED:
break;
case WD_EXPIRED:
break;
case WD_DISARMED:
break;
}
return (PICL_SUCCESS);
}
/* this thread listens for watchdog expiry events */
/*ARGSUSED*/
static void *
{
int poll_retval;
int i;
for (;;) {
if (props_created == 0)
continue;
switch (poll_retval) {
case 0:
break;
case -1:
break;
default:
/* something happened */
sizeof (sc_rspmsg_t))) < 0) {
break;
}
(void) mutex_lock(&data_lock);
(void) mutex_unlock(&data_lock);
continue;
}
(void) mutex_lock(&patting_lock);
(void) cond_signal(&patting_cv);
(void) mutex_unlock(&patting_lock);
if (wd_debug & WD_TIME_DEBUG) {
for (i = 0; i < NUMBER_OF_READINGS; i++) {
"res_seq = %d, time = %lld nsec",
}
}
if (wd_data.reboot_action) {
wd_data.reboot_action = 0;
(void) system(SHUTDOWN_CMD);
}
}
break;
} /* switch */
}
/*NOTREACHED*/
return (NULL);
}
/*
* This function reads the hardware state and gets the status of
* watchdog-timers
*/
static int
{
/* read watchdog registers */
return (rc);
}
/* get action */
}
}
if (buffer[0] & WD_WD_RUNNING) {
return (PICL_SUCCESS);
}
if (buffer[3] != 0) {
return (PICL_SUCCESS);
} else {
return (PICL_SUCCESS);
}
}
/* read the smc hardware and intialize the internal state */
static void
{
/* defualt state is expired ??? */
}
switch (state.present_state) {
case WD_EXPIRED:
case WD_DISARMED:
else
break;
case WD_ARMED:
/*
* get the present values and restart the
* watchdog from os level and continue to pat
*/
(void) wd_stop();
}
}
/*
* wrapper for ptree interface to create property
*/
static int
int ptype, /* PICL property type */
int pmode, /* PICL access mode */
char *pname, /* property name */
int (*readfn)(ptree_rarg_t *, void *),
int (*writefn)(ptree_warg_t *, const void *),
void *vbuf) /* initial value */
{
if (rc != PICL_SUCCESS) {
return (rc);
}
if (rc != PICL_SUCCESS) {
return (rc);
}
return (PICL_SUCCESS);
}
/* Create and add Watchdog properties */
static void
{
int rc;
/* get picl root node handle */
return;
}
/* get picl platform node handle */
&platformh)) != PICL_SUCCESS) {
return;
}
/* get the picl sysmgmt node handle */
&sysmgmt_h)) != PICL_SUCCESS) {
return;
}
/* start creating the watchdog nodes and properties */
return;
}
/* Add wd-op property to watchdog controller node */
&(wd_data.wd_ops_hdl),
(void *)buf)) != PICL_SUCCESS) {
return;
}
/* create L1 node and add to controller */
return;
}
/* create L2 node and add to controller */
return;
}
/* create watchdog properties */
/* create state property here */
sizeof (buf));
return;
}
return;
}
/* create timeout property here */
sizeof (timeout1), PICL_PROP_WATCHDOG_TIMEOUT,
PICL_SUCCESS) {
return;
}
PICL_SUCCESS) {
return;
}
/* create wd_action property here */
sizeof (buf));
(void *)buf)) != PICL_SUCCESS) {
return;
}
(void *)buf)) != PICL_SUCCESS) {
return;
}
}
static int
{
int rtnval;
return (rtnval);
}
static int
{
int cc;
return (-1);
}
/* get exclusive access for set and reset commands of watchdog */
(char *)&wd_cmdspec);
if (cc < 0) {
return (-1);
}
return (wd_fd);
}
static int
{
int cc;
return (-1);
}
/* request for watchdog expiry notification */
(char *)&wd_cmdspec);
if (cc < 0) {
return (-1);
}
return (polling_fd);
}
/* read the ENVIRONMENT variables and initialize tunables */
static void
{
char *val;
int intval = 0;
/* read frutree debug flag value */
errno = 0;
if (errno == 0) {
}
}
}
/*
* PTREE Entry Points
*/
/* picl-state-change event handler */
/*ARGSUSED*/
static void
{
char *value;
return;
}
/* neglect all events if wd props are already created */
if (props_created && state_configured) {
return;
}
return;
}
&fruhdl)) == -1) {
return;
}
return;
}
if (rc != PICL_SUCCESS) {
return;
}
/* care for only events on chassis node */
return;
}
state_configured = 1;
return;
}
return;
}
if (wd_fd < 0) {
return;
}
}
if (polling_fd < 0) {
return;
}
}
switch (wd_get_chassis_type()) {
case WD_HOST: /* is host */
break;
case WD_STANDALONE: /* is satellite */
break;
default:
return;
}
(void) wd_create_add_props(); /* create and add properties */
props_created = 1;
/* read the hardware and initialize values */
(void) wd_set_init_state();
/* initialize wd-conf value */
if (spawn_threads == 0) {
/* threads are already created */
return;
}
/* start monitoring for the events */
"polling");
return;
}
/* thread used to pat watchdog */
"patting");
return;
}
spawn_threads = 0;
}
static void
wd_picl_register(void)
{
int rc = 0;
}
}
/* entry point (initialization) */
static void
wd_picl_init(void)
{
/* initialize the wd_conf path and name */
/* parse configuration file and set tunables */
/* if watchdog-enable is set to false dont intialize wd subsystem */
if (wd_enable == 0) {
return;
}
/* read watchdog related environment variables */
wd_get_env();
/* event handler for state change notifications from frutree */
}
static void
wd_picl_fini(void)
{
state_configured = 0; /* chassis state */
props_created = 0;
}
/*
* volatile function to read the timeout
*/
static int
{
/* update the buffer provided by user */
(void) mutex_lock(&data_lock);
}
(void) mutex_unlock(&data_lock);
return (PICL_SUCCESS);
}
/*
* volatile function to read the action
*/
static int
{
(void) mutex_lock(&data_lock);
switch (wd_data.wd1_action) {
break;
case WD_ACTION_NONE1:
case WD_ACTION_NONE2:
} else {
}
break;
}
switch (wd_data.wd2_action) {
case WD_ACTION_HARD_RESET:
break;
case WD_ACTION_NONE2:
break;
}
}
(void) mutex_unlock(&data_lock);
return (PICL_SUCCESS);
}
/*
* volatile function to write the action
* this function validates the user value before programming the
* action property. Properties can be modified only when watchdog
* is in disarmed state.
*/
static int
{
/* only super user can write this property */
return (PICL_PERMDENIED);
}
}
/* dont allow any write operations when watchdog is armed */
(void) mutex_lock(&data_lock);
(void) mutex_unlock(&data_lock);
return (PICL_PERMDENIED);
}
/* validate the values and store in internal cache */
switch (flag) {
case WD1:
else
wd_data.reboot_action = 0;
wd_data.reboot_action = 0;
} else {
}
break;
case WD2:
} else {
}
break;
}
(void) mutex_unlock(&data_lock);
return (rc);
}
/*
* volatile function to write the timeout
* this function validates the user value before programming the
* timeout property. Properties can be modified only when watchdog
* is in disarmed state.
*/
static int
{
/* only super user can write this property */
return (PICL_PERMDENIED);
}
/* dont allow any write operations when watchdog is armed */
(void) mutex_lock(&data_lock);
(void) mutex_unlock(&data_lock);
return (PICL_PERMDENIED);
}
(void) mutex_unlock(&data_lock);
}
/* validate the timeout values */
if (timeout < -1) {
return (PICL_INVALIDARG);
}
if (timeout > 0) {
switch (flag) {
case WD1:
if ((timeout % WD_L1_RESOLUTION) != 0) {
return (PICL_INVALIDARG);
}
return (PICL_INVALIDARG);
}
break;
case WD2:
if ((timeout % WD_L2_RESOLUTION) != 0) {
return (PICL_INVALIDARG);
}
/* 255 sec */
return (PICL_INVALIDARG);
}
}
}
/* update the internal cache */
(void) mutex_lock(&data_lock);
switch (flag) {
case WD1:
break;
case WD2:
break;
}
(void) mutex_unlock(&data_lock);
return (PICL_SUCCESS);
}