piclwatchdog.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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
* 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 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 <sys/types.h>
#include <sys/stat.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"
#pragma init(wd_picl_register) /* init section */
/* debug variables */
static int wd_debug = 0;
static hrtime_t start1, end1;
static int count = 0;
typedef struct { /* used to keep track of time taken for last 5 pats */
int res_seq;
int req_seq;
int64_t time;
} wd_time_t;
#define NUMBER_OF_READINGS 5
static wd_time_t time1[NUMBER_OF_READINGS];
/* global declarations */
static int wd_fd = -1; /* fd used to send watchdog commands */
static int polling_fd = -1; /* polling thread that snoops for events */
static int wd_enable = 1;
static int state_configured = 0; /* chassis state */
static int props_created = 0;
static int wd_pat_thr_priority = -1;
static pid_t pid = -1; /* PID that owns watchdog services */
static cond_t patting_cv;
static mutex_t data_lock;
static mutex_t patting_lock;
static int32_t pat_time = 0;
static thread_t polling_thr_tid;
static thread_t patting_thr_tid;
static wd_data_t wd_data;
static char wd_conf[MAXPATHLEN];
#define NULLREAD (int (*)(ptree_rarg_t *, void *))0
#define NULLWRITE (int (*)(ptree_warg_t *, const void *))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 char *strtok_r(char *s1, const char *s2, char **lasts);
extern int wd_get_chassis_type();
static picld_plugin_reg_t wd_reg_info = {
PICLD_PLUGIN_VERSION_1,
PICLD_PLUGIN_CRITICAL,
"SUNW_picl_watchdog",
wd_picl_init,
wd_picl_fini,
};
/*
* This function parses wd.conf file to set the tunables
* tunables at present: patting thread priority, pat time, wd_enable
*/
static void
wd_parse_config_file(char *wd_conf)
{
FILE *fp;
char buf[WD_CONF_MAXSIZE];
char *token, *last, *value;
if ((fp = fopen(wd_conf, "r")) == NULL) {
return;
}
while (fgets(buf, sizeof (buf), fp) != NULL) {
if (buf[0] == '\0' || buf[0] == '#') {
continue;
}
token = last = value = NULL;
value = (char *)strtok_r((char *)buf, WD_DELIMETER, &last);
if (last) {
token = (char *)strtok_r(last, WD_DELIMETER, &last);
} else {
continue;
}
if (value == NULL || token == NULL) {
continue;
}
if (strcmp(token, WD_PAT_THREAD_PRIORITY) == 0) {
wd_pat_thr_priority = strtol(value,
(char **)NULL, 10);
} else if (strcmp(token, WD_PATTING_TIME) == 0) {
errno = 0;
pat_time = strtol(value, (char **)NULL, 10);
if (errno != 0) {
pat_time = 0;
}
} else if (strcmp(token, WD_ENABLE) == 0) {
if (strcmp(value, "false") == 0) {
wd_enable = 0;
}
} else { /* unknown token */
continue;
}
}
(void) fclose(fp);
}
/*
* read the SMC watchdog registers
*/
static int
wd_get_reg_dump(uint8_t buffer[])
{
int rc = 0, i;
sc_reqmsg_t req_pkt;
sc_rspmsg_t rsp_pkt;
/* initialize the request packet */
(void) smc_init_smc_msg(&req_pkt, SMC_GET_WATCHDOG_TIMER,
DEFAULT_SEQN, 0);
/* make a call to smc library to send cmd */
if ((rc = smc_send_msg(DEFAULT_FD, &req_pkt, &rsp_pkt,
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
WD_DEBUG1(WD_PICL_GET_ERR, rc);
return (PICL_FAILURE);
}
/* read 8 bytes */
bzero(buffer, WD_REGISTER_LEN);
for (i = 0; i < WD_REGISTER_LEN; i++) {
buffer[i] = rsp_pkt.data[i];
}
return (PICL_SUCCESS);
}
/*
* get the HEALTHY# line state
* Return -1 for Error
* 0 for HEALTHY# down
* 1 for HEALTHY# up
*/
static int
wd_get_healthy_status()
{
sc_reqmsg_t req_pkt;
sc_rspmsg_t rsp_pkt;
/* initialize the request packet */
(void) smc_init_smc_msg(&req_pkt, SMC_GET_EXECUTION_STATE,
DEFAULT_SEQN, 0);
/* make a call to smc library to send cmd */
if (smc_send_msg(DEFAULT_FD, &req_pkt, &rsp_pkt,
WD_POLL_TIMEOUT) != SMC_SUCCESS) {
return (-1);
}
return ((rsp_pkt.data[0] & IS_HEALTHY) ? WD_HEALTHY_UP :
WD_HEALTHY_DOWN);
}
/*ARGSUSED*/
static void
event_completion_handler(char *ename, void *earg, size_t size)
{
free(ename);
free(earg);
}
/*
* posts picl-state-change event if there is change in watchdog-timer state
*/
static picl_errno_t
post_wd_state_event(picl_nodehdl_t nodeh, char *state)
{
nvlist_t *nvl;
size_t nvl_size;
char *pack_buf = NULL;
picl_errno_t rc;
char *ename = PICLEVENT_STATE_CHANGE, *evname = NULL;
if (state == NULL) {
return (PICL_FAILURE);
}
if ((evname = strdup(ename)) == NULL) {
return (PICL_NOSPACE);
}
if ((rc = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, NULL)) != 0) {
free(evname);
syslog(LOG_ERR, WD_NVLIST_ERR, rc);
return (PICL_FAILURE);
}
if ((rc = nvlist_add_uint64(nvl, PICLEVENTARG_NODEHANDLE,
nodeh)) != 0) {
nvlist_free(nvl);
free(evname);
syslog(LOG_ERR, WD_NVLIST_ERR, rc);
return (PICL_FAILURE);
}
if ((rc = nvlist_add_string(nvl, PICLEVENTARG_STATE,
state)) != 0) {
nvlist_free(nvl);
free(evname);
syslog(LOG_ERR, WD_NVLIST_ERR, rc);
return (PICL_FAILURE);
}
if ((rc = nvlist_pack(nvl, &pack_buf, &nvl_size, NV_ENCODE_NATIVE,
NULL)) != 0) {
nvlist_free(nvl);
free(evname);
syslog(LOG_ERR, WD_NVLIST_ERR, rc);
return (PICL_FAILURE);
}
if ((rc = ptree_post_event(evname, pack_buf, nvl_size,
event_completion_handler)) != PICL_SUCCESS) {
free(pack_buf);
free(evname);
nvlist_free(nvl);
return (rc);
}
nvlist_free(nvl);
return (PICL_SUCCESS);
}
/*
* Updates the State value in picl tree and posts a state-change event
*/
static void
wd_picl_update_state(int level, uint8_t stat)
{
picl_errno_t rc;
char state[PICL_PROPNAMELEN_MAX];
switch (stat) {
case WD_ARMED:
(void) strncpy(state, PICL_PROPVAL_WD_STATE_ARMED,
sizeof (state));
break;
case WD_DISARMED:
(void) strncpy(state, PICL_PROPVAL_WD_STATE_DISARMED,
sizeof (state));
break;
case WD_EXPIRED:
(void) strncpy(state, PICL_PROPVAL_WD_STATE_EXPIRED,
sizeof (state));
break;
default:
return;
}
(void) mutex_lock(&data_lock);
switch (level) {
case WD1:
wd_data.wd1_run_state = stat;
break;
case WD2:
wd_data.wd2_run_state = stat;
break;
case WD1_2:
wd_data.wd1_run_state = stat;
wd_data.wd2_run_state = stat;
break;
default:
return;
}
(void) mutex_unlock(&data_lock);
if (!state_configured) {
return;
}
switch (level) {
case WD1:
if ((rc = post_wd_state_event(wd_data.wd1_nodehdl,
state)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_PICL_POST_EVENT_ERR, rc);
}
break;
case WD2:
if ((rc = post_wd_state_event(wd_data.wd2_nodehdl,
state)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_PICL_POST_EVENT_ERR, rc);
}
break;
case WD1_2:
if ((rc = post_wd_state_event(wd_data.wd1_nodehdl,
state)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_PICL_POST_EVENT_ERR, rc);
}
if ((rc = post_wd_state_event(wd_data.wd2_nodehdl,
state)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_PICL_POST_EVENT_ERR, rc);
}
break;
}
}
/*
* Sends a command to SMC to reset the watchdog-timers
*/
static int
wd_pat()
{
int rc = 0;
static uint8_t seq = 1;
sc_reqmsg_t req_pkt;
sc_rspmsg_t rsp_pkt;
if (seq < WD_MAX_SEQN) {
req_pkt.hdr.msg_id = seq++;
} else {
seq = 1;
req_pkt.hdr.msg_id = seq;
}
if (wd_debug & WD_TIME_DEBUG) {
start1 = gethrtime();
}
/* initialize the request packet */
(void) smc_init_smc_msg(&req_pkt, SMC_RESET_WATCHDOG_TIMER,
DEFAULT_SEQN, 0);
/* make a call to smc library to send cmd */
if ((rc = smc_send_msg(wd_fd, &req_pkt, &rsp_pkt,
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
syslog(LOG_CRIT, WD_PICL_PAT_ERR, rc);
return (PICL_FAILURE);
}
if (wd_debug & WD_TIME_DEBUG) {
end1 = gethrtime();
time1[count].res_seq = SC_MSG_ID(&rsp_pkt);
time1[count].req_seq = SC_MSG_ID(&req_pkt);
time1[count].time = (end1 - start1);
if (count < (NUMBER_OF_READINGS - 1)) {
count++;
} else {
count = 0;
}
}
return (PICL_SUCCESS);
}
/* used to set the new values for watchdog and start the watchdog */
static int
wd_start(uchar_t action_1, uchar_t action_2,
uchar_t timeout_2, uchar_t *timeout_1, uint8_t patting_option)
{
int rc = 0;
sc_reqmsg_t req_pkt;
sc_rspmsg_t rsp_pkt;
if (timeout_1 == NULL) {
return (PICL_FAILURE);
}
req_pkt.data[0] = WD_USEFLAG_OS;
req_pkt.data[1] = action_1 | action_2; /* actions */
req_pkt.data[2] = timeout_2; /* wd timeout 2 */
req_pkt.data[3] = WD_XPR_FLG_CLR_OS; /* expiration flags */
req_pkt.data[4] = timeout_1[1]; /* LSB for wd timeout 1 */
req_pkt.data[5] = timeout_1[0]; /* MSB for wd timeout 1 */
if (patting_option == ENABLE_AUTO_PAT) {
req_pkt.data[0] |= WD_ENABLE_AUTO_PAT;
}
/* initialize the request packet */
(void) smc_init_smc_msg(&req_pkt, SMC_SET_WATCHDOG_TIMER,
DEFAULT_SEQN, WD_SET_CMD_DATA_LEN);
/* make a call to smc library to send cmd */
if ((rc = smc_send_msg(wd_fd, &req_pkt, &rsp_pkt,
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
WD_DEBUG1(WD_PICL_START_ERR, rc);
return (PICL_FAILURE);
}
/* reset the watchdog timer */
(void) smc_init_smc_msg(&req_pkt, SMC_RESET_WATCHDOG_TIMER,
DEFAULT_SEQN, 0);
if ((rc = smc_send_msg(wd_fd, &req_pkt, &rsp_pkt,
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
WD_DEBUG1(WD_PICL_START_ERR, rc);
return (PICL_FAILURE);
}
return (PICL_SUCCESS);
}
/*
* Validates timeout and action fields and arms the watchdog-timers
*/
static int
wd_arm(uint8_t patting_option)
{
int rc;
uint16_t wd_time1;
uint8_t wd_time2, wd1_action, wd2_action;
uint8_t timeout1[2];
if (wd_data.wd1_timeout >= 0) {
wd_time1 = wd_data.wd1_timeout/WD_L1_RESOLUTION;
} else {
wd_time1 = 0;
}
if (wd_data.wd2_timeout >= 0) {
wd_time2 = wd_data.wd2_timeout/WD_L2_RESOLUTION;
} else {
wd_time2 = 0;
}
timeout1[0] = wd_time1 >> 8; /* MSB */
timeout1[1] = wd_time1 & 0x00ff; /* LSB */
/* check the HELATHY# status if action is alarm */
if (wd_data.wd1_action == WD_ACTION_HEALTHY_DOWN_HOST ||
wd_data.wd1_action == WD_ACTION_HEALTHY_DOWN_SAT) {
rc = wd_get_healthy_status();
if (rc == WD_HEALTHY_DOWN) {
WD_DEBUG0(WD_HEALTHY_ERR);
return (PICL_FAILURE);
} else if (rc == -1) {
syslog(LOG_ERR, WD_GET_HEALTH_ERR);
return (PICL_FAILURE);
}
}
if (wd_data.wd1_timeout == -1) {
wd1_action = WD_ACTION_NONE2;
} else {
wd1_action = wd_data.wd1_action;
}
if (wd_data.wd2_timeout == -1) {
wd2_action = WD_ACTION_NONE2;
} else {
wd2_action = wd_data.wd2_action;
}
rc = wd_start(wd1_action, wd2_action,
wd_time2, timeout1, patting_option);
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 *
wd_patting_thread(void *args)
{
time_t sec;
pcinfo_t pci;
long nano_sec;
timestruc_t to;
long sleep_time;
struct timeval tp;
int err, state;
for (;;) {
(void) mutex_lock(&patting_lock);
while (wd_data.wd_pat_state == WD_NORESET) {
(void) cond_wait(&patting_cv, &patting_lock);
}
(void) mutex_unlock(&patting_lock);
/* reset pat-time to zero */
pat_time = 0; /* tunable */
wd_parse_config_file(wd_conf);
if (wd_pat_thr_priority < 0 || wd_pat_thr_priority > 59) {
wd_pat_thr_priority = WD_DEFAULT_THREAD_PRIORITY;
}
/* change the priority of thread to realtime class */
(void) strncpy(pci.pc_clname, "RT", sizeof (pci.pc_clname));
if (priocntl(P_LWPID, P_MYID, PC_GETCID, (caddr_t)&pci) != -1) {
pcparms_t pcp;
rtparms_t *rtp = (rtparms_t *)pcp.pc_clparms;
rtp->rt_pri = wd_pat_thr_priority;
rtp->rt_tqsecs = 0;
rtp->rt_tqnsecs = RT_TQDEF;
pcp.pc_cid = pci.pc_cid;
if (priocntl(P_LWPID, P_MYID, PC_SETPARMS,
(caddr_t)&pcp) != 0) {
syslog(LOG_ERR, WD_PICL_RT_THRD_FAIL);
}
} else {
syslog(LOG_ERR, WD_PICL_RT_THRD_NO_PERM_ERR);
}
switch (wd_data.wd1_timeout) {
case 0:
if (wd_arm(DISABLE_AUTO_PAT) == PICL_SUCCESS) {
wd_picl_update_state(WD1, WD_ARMED);
if (wd_data.wd2_timeout >= 0) {
wd_picl_update_state(WD2, WD_ARMED);
}
} else {
syslog(LOG_ERR, WD_PICL_START_ERR,
PICL_FAILURE);
}
/* no need to pat */
(void) mutex_lock(&patting_lock);
wd_data.wd_pat_state = WD_NORESET;
(void) mutex_unlock(&patting_lock);
continue;
case -1:
if (wd_data.wd2_timeout < 0) {
(void) mutex_lock(&patting_lock);
wd_data.wd_pat_state = WD_NORESET;
(void) mutex_unlock(&patting_lock);
continue;
}
if (wd_arm(DISABLE_AUTO_PAT) == PICL_SUCCESS) {
wd_picl_update_state(WD2, WD_ARMED);
} else {
syslog(LOG_ERR, WD_PICL_START_ERR,
PICL_FAILURE);
}
/* no need to pat */
(void) mutex_lock(&patting_lock);
wd_data.wd_pat_state = WD_NORESET;
(void) mutex_unlock(&patting_lock);
continue;
default:
break;
}
if (pat_time == 0) {
if (wd_data.wd1_timeout > WD_PAT_TIME) {
pat_time = WD_PAT_TIME;
} else {
pat_time = wd_data.wd1_timeout - 80;
}
}
if (pat_time <= 0) {
WD_DEBUG0(WD_PICL_PAT_TIME_ERR);
(void) mutex_lock(&patting_lock);
wd_data.wd_pat_state = WD_NORESET;
(void) mutex_unlock(&patting_lock);
continue;
}
sleep_time = wd_data.wd1_timeout - pat_time;
if (wd_data.wd1_timeout <= 0 || sleep_time <= 0) {
WD_DEBUG0(WD_PICL_ARM_PAT_ERR);
(void) mutex_lock(&patting_lock);
wd_data.wd_pat_state = WD_NORESET;
(void) mutex_unlock(&patting_lock);
continue;
} else {
wd_picl_update_state(WD1, WD_ARMED);
}
if (wd_data.wd2_timeout >= 0) {
wd_picl_update_state(WD2, WD_ARMED);
}
sec = sleep_time/1000;
nano_sec = (sleep_time - (sec * 1000)) * 1000000;
if (wd_arm(ENABLE_AUTO_PAT) != PICL_SUCCESS) {
wd_picl_update_state(WD1_2, WD_DISARMED);
(void) mutex_lock(&patting_lock);
wd_data.wd_pat_state = WD_NORESET;
(void) mutex_unlock(&patting_lock);
syslog(LOG_ERR, WD_PICL_START_ERR, PICL_FAILURE);
continue;
}
do /* pat the watchdog until expiry or user disarm */
{
(void) mutex_lock(&patting_lock);
state = wd_data.wd_pat_state;
if (state == WD_NORESET) {
(void) mutex_unlock(&patting_lock);
break;
}
(void) gettimeofday(&tp, NULL);
to.tv_sec = tp.tv_sec + sec;
if ((nano_sec + (tp.tv_usec * 1000)) >= 1000000000) {
to.tv_sec += 1;
to.tv_nsec = (nano_sec +
(tp.tv_usec * 1000)) - 1000000000;
} else {
to.tv_nsec = nano_sec + (tp.tv_usec * 1000);
}
err = cond_timedwait(&patting_cv, &patting_lock, &to);
(void) mutex_unlock(&patting_lock);
if (err == ETIME) { /* woke up from sleep */
(void) wd_pat();
}
} while (state == WD_RESET);
}
/*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
is_owner_alive()
{
char strpid[50];
struct stat buf;
if (pid == -1) {
return (-1);
}
/* check if the file exists or not */
(void) snprintf(strpid, sizeof (pid), "/proc/%ld/status", pid);
errno = 0;
if (stat(strpid, &buf) == 0) {
return (1);
}
if (errno == ENOENT) {
return (0);
} else {
syslog(LOG_ERR, WD_GET_OWN_FAILED, errno);
}
return (-1);
}
/*
* Sends a cmd to SMC to stop watchdog timers
*/
static int
wd_stop()
{
int rc = 0;
sc_reqmsg_t req_pkt;
sc_rspmsg_t rsp_pkt;
uint8_t buffer[8];
if (wd_get_reg_dump(buffer) != 0) {
return (PICL_FAILURE);
}
/* clear the expiration flags */
buffer[3] = 0xff; /* expiration flags */
(void) memcpy(SC_MSG_DATA(&req_pkt), buffer,
WD_SET_CMD_DATA_LEN);
/* initialize the request packet */
(void) smc_init_smc_msg(&req_pkt, SMC_SET_WATCHDOG_TIMER,
DEFAULT_SEQN, WD_SET_CMD_DATA_LEN);
/* make a call to smc library to send cmd */
if ((rc = smc_send_msg(wd_fd, &req_pkt, &rsp_pkt,
WD_POLL_TIMEOUT)) != SMC_SUCCESS) {
syslog(LOG_ERR, WD_PICL_STOP_ERR, rc);
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
wd_worker_function(uint8_t flag, pid_t proc_id)
{
int rc = PICL_SUCCESS;
int wd1_state, wd2_state;
(void) mutex_lock(&data_lock);
wd1_state = wd_data.wd1_run_state;
wd2_state = wd_data.wd2_run_state;
(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) {
WD_DEBUG0(WD_PICL_WD1_RUNNING_ERR);
rc = PICL_FAILURE;
break;
}
if (wd2_state != WD_DISARMED) {
WD_DEBUG0(WD_PICL_WD2_RUNNING_ERR);
rc = PICL_FAILURE;
break;
}
/* check the HELATHY# status if action is alarm */
if (wd_data.wd1_timeout >= 0) {
if (wd_data.wd1_action == WD_ACTION_HEALTHY_DOWN_HOST ||
wd_data.wd1_action == WD_ACTION_HEALTHY_DOWN_SAT) {
rc = wd_get_healthy_status();
if (rc == WD_HEALTHY_DOWN) {
WD_DEBUG0(WD_HEALTHY_ERR);
return (PICL_FAILURE);
} else if (rc == -1) {
syslog(LOG_ERR, WD_GET_HEALTH_ERR);
return (PICL_FAILURE);
} else {
rc = PICL_SUCCESS;
}
}
}
/* signal the patting thread */
(void) mutex_lock(&patting_lock);
wd_data.wd_pat_state = WD_RESET;
(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
*/
if (proc_id != pid) {
switch (is_owner_alive()) {
case -1:
if ((wd1_state != WD_DISARMED) ||
(wd2_state != WD_DISARMED)) {
break;
}
/* watchdog is already disarmed */
WD_DEBUG0(WD_PICL_NO_WD_ERR);
return (PICL_FAILURE);
case 1:
/* owner is still alive, deny the operation */
WD_DEBUG0(WD_PICL_PERM_DENIED);
return (PICL_PERMDENIED);
default:
break;
}
}
/* watchdog is running */
if ((rc = wd_stop()) == PICL_SUCCESS) {
wd_picl_update_state(WD1_2, WD_DISARMED);
(void) mutex_lock(&patting_lock);
wd_data.wd_pat_state = WD_NORESET;
(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 (wd1_state == WD_DISARMED && wd2_state == WD_DISARMED) {
if ((rc = wd_arm(DISABLE_AUTO_PAT)) == PICL_SUCCESS) {
if (wd_data.wd1_timeout >= 0) {
wd_picl_update_state(WD1, WD_ARMED);
}
if (wd_data.wd2_timeout >= 0) {
wd_picl_update_state(WD2, WD_ARMED);
}
} else {
return (rc);
}
} else { /* WD is running */
if (wd1_state != WD_ARMED) {
WD_DEBUG0(WD_PICL_NO_WD_ERR);
return (PICL_INVALIDARG);
}
/* check if OS is patting the watchdog or not */
(void) mutex_lock(&patting_lock);
if (wd_data.wd_pat_state == WD_RESET) {
WD_DEBUG0(WD_PICL_TRY_PAT_ERR);
(void) mutex_unlock(&patting_lock);
return (PICL_INVALIDARG);
}
/* check if the process owns the WD services */
if (proc_id != pid) {
WD_DEBUG0(WD_PICL_PERM_DENIED);
return (PICL_PERMDENIED);
}
rc = wd_pat();
}
break;
default:
rc = PICL_INVALIDARG;
break;
} /* switch */
return (rc);
}
/*ARGSUSED*/
static int
wd_write_op(ptree_warg_t *parg, const void *buf)
{
int rc = PICL_INVALIDARG;
uint8_t flag;
/* only after state is configured */
if (!state_configured) {
if (parg->cred.dc_pid != getpid()) {
WD_DEBUG0(WD_PICL_STATE_INVALID);
return (PICL_PERMDENIED);
}
}
/* only super user can write this property */
if (parg->cred.dc_euid != SUPER_USER) {
WD_DEBUG0(WD_NO_ROOT_PERM);
return (PICL_PERMDENIED);
}
if (strcmp((char *)buf, PICL_PROPVAL_WD_OP_ARM) == 0) {
flag = USER_ARMED_WD;
rc = PICL_SUCCESS;
}
if (strcmp((char *)buf, PICL_PROPVAL_WD_OP_DISARM) == 0) {
flag = USER_DISARMED_WD;
rc = PICL_SUCCESS;
}
/* for debug purpose only */
if (strcmp((char *)buf, WD_ARM_PAT) == 0) {
flag = USER_ARMED_PAT_WD;
rc = PICL_SUCCESS;
}
if (rc == PICL_SUCCESS) {
rc = wd_worker_function(flag, parg->cred.dc_pid);
} else {
rc = PICL_INVALIDARG;
}
if (rc == PICL_SUCCESS) {
switch (flag) {
case USER_ARMED_PAT_WD:
case USER_ARMED_WD:
/* get the process id of client */
if (parg->cred.dc_pid != getpid()) {
pid = parg->cred.dc_pid;
} 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
wd1_read_status(ptree_rarg_t *parg, void *buf)
{
int rc = PICL_SUCCESS;
(void) mutex_lock(&data_lock);
switch (wd_data.wd1_run_state) {
case WD_EXPIRED:
(void) strncpy((char *)buf, PICL_PROPVAL_WD_STATE_EXPIRED,
PICL_PROPNAMELEN_MAX);
break;
case WD_DISARMED:
(void) strncpy((char *)buf, PICL_PROPVAL_WD_STATE_DISARMED,
PICL_PROPNAMELEN_MAX);
break;
case WD_ARMED:
(void) strncpy((char *)buf, PICL_PROPVAL_WD_STATE_ARMED,
PICL_PROPNAMELEN_MAX);
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
wd_get_wd2_status(int *present_status)
{
int rc;
uchar_t buffer[WD_REGISTER_LEN];
bzero(buffer, WD_REGISTER_LEN);
(void) mutex_lock(&data_lock);
*present_status = wd_data.wd2_run_state;
if (wd_data.wd2_run_state != WD_ARMED) {
/* we already have the latest state */
(void) mutex_unlock(&data_lock);
return (PICL_SUCCESS);
}
(void) mutex_unlock(&data_lock);
/* read watchdog registers */
if ((rc = wd_get_reg_dump(buffer)) != 0) {
return (rc);
}
if (buffer[0] & WD_WD_RUNNING) {
*present_status = WD_ARMED;
return (PICL_SUCCESS);
}
if (buffer[3] != 0) {
(void) mutex_lock(&data_lock);
*present_status = wd_data.wd2_run_state = WD_EXPIRED;
(void) mutex_unlock(&data_lock);
}
return (PICL_SUCCESS);
}
/* volatile call back function to read the watchdog L2 status */
/*ARGSUSED*/
static int
wd2_read_status(ptree_rarg_t *parg, void *buf)
{
int present_status, rc;
if ((rc = wd_get_wd2_status(&present_status)) !=
PICL_SUCCESS) {
return (rc);
}
/* copy the present state in user buffer */
switch (present_status) {
case WD_ARMED:
(void) strncpy((char *)buf, PICL_PROPVAL_WD_STATE_ARMED,
PICL_PROPNAMELEN_MAX);
break;
case WD_EXPIRED:
(void) strncpy((char *)buf, PICL_PROPVAL_WD_STATE_EXPIRED,
PICL_PROPNAMELEN_MAX);
break;
case WD_DISARMED:
(void) strncpy((char *)buf, PICL_PROPVAL_WD_STATE_DISARMED,
PICL_PROPNAMELEN_MAX);
break;
}
return (PICL_SUCCESS);
}
/* this thread listens for watchdog expiry events */
/*ARGSUSED*/
static void *
wd_polling(void *args)
{
uint8_t stat;
int poll_retval;
struct pollfd fds;
sc_rspmsg_t rsp_pkt;
int i;
fds.fd = polling_fd;
fds.events = POLLIN | POLLPRI;
fds.revents = 0;
for (;;) {
poll_retval = poll(&fds, 1, -1);
if (props_created == 0)
continue;
switch (poll_retval) {
case 0:
break;
case -1:
syslog(LOG_ERR, WD_PICL_POLL_ERR);
break;
default:
/* something happened */
if ((read(polling_fd, &rsp_pkt,
sizeof (sc_rspmsg_t))) < 0) {
syslog(LOG_ERR, WD_PICL_SMC_READ_ERR);
break;
}
if (rsp_pkt.hdr.cmd == SMC_EXPIRED_WATCHDOG_NOTIF) {
(void) mutex_lock(&data_lock);
stat = wd_data.wd1_run_state;
(void) mutex_unlock(&data_lock);
if (stat != WD_ARMED) {
continue;
}
wd_picl_update_state(WD1, WD_EXPIRED);
(void) mutex_lock(&patting_lock);
wd_data.wd_pat_state = WD_NORESET;
(void) cond_signal(&patting_cv);
(void) mutex_unlock(&patting_lock);
syslog(LOG_WARNING, WD_WD1_EXPIRED);
if (wd_debug & WD_TIME_DEBUG) {
syslog(LOG_ERR, " latest count : %d", count);
for (i = 0; i < NUMBER_OF_READINGS; i++) {
syslog(LOG_ERR, "i = %d, req_seq = %d,"
"res_seq = %d, time = %lld nsec",
i, time1[i].req_seq,
time1[i].res_seq,
time1[i].time);
}
}
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
wd_get_status(wd_state_t *state)
{
picl_errno_t rc;
uchar_t buffer[WD_REGISTER_LEN];
bzero(buffer, WD_REGISTER_LEN);
/* read watchdog registers */
if ((rc = wd_get_reg_dump(buffer)) != 0) {
return (rc);
}
/* get action */
state->action1 = buffer[1] & 0xF0; /* most significant 4 bits */
if (state->action1 == 0x0) {
state->action1 = WD_ACTION_NONE1;
}
state->action2 = buffer[1] & 0x0F; /* least significant 4 bits */
if (state->action2 == 0x0) {
state->action2 = WD_ACTION_NONE2;
}
state->timeout2 = buffer[2];
state->timeout1[0] = buffer[5]; /* MSB */
state->timeout1[1] = buffer[4]; /* LSB */
state->present_t1[0] = buffer[7]; /* MSB */
state->present_t1[1] = buffer[6]; /* LSB */
if (buffer[0] & WD_WD_RUNNING) {
state->present_state = WD_ARMED;
return (PICL_SUCCESS);
}
if (buffer[3] != 0) {
state->present_state = WD_EXPIRED;
return (PICL_SUCCESS);
} else {
state->present_state = WD_DISARMED;
return (PICL_SUCCESS);
}
}
/* read the smc hardware and intialize the internal state */
static void
wd_set_init_state()
{
wd_state_t state;
uint16_t tmp1, tmp2, wd_time1;
if (wd_get_status(&state) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_PICL_GET_STAT_ERR);
/* defualt state is expired ??? */
state.present_state = WD_EXPIRED;
}
switch (state.present_state) {
case WD_EXPIRED:
case WD_DISARMED:
if (state.present_state == WD_EXPIRED)
wd_picl_update_state(WD1_2, WD_EXPIRED);
else
wd_picl_update_state(WD1_2, WD_DISARMED);
wd_data.wd_pat_state = WD_NORESET;
wd_data.wd1_action = state.action1;
wd_data.wd2_action = state.action2;
tmp1 = state.timeout1[0] << 8;
tmp2 = state.timeout1[1];
wd_time1 = tmp1 | tmp2;
wd_data.wd1_timeout = wd_time1 * WD_L1_RESOLUTION;
wd_data.wd2_timeout = state.timeout2 * WD_L2_RESOLUTION;
break;
case WD_ARMED:
/*
* get the present values and restart the
* watchdog from os level and continue to pat
*/
wd_picl_update_state(WD1_2, WD_ARMED);
wd_data.wd_pat_state = WD_RESET;
wd_data.wd1_action = (state.action1 << 4);
wd_data.wd2_action = state.action2;
tmp1 = state.timeout1[0] << 8;
tmp2 = state.timeout1[1];
wd_time1 = tmp1 | tmp2;
wd_data.wd1_timeout = wd_time1 * WD_L1_RESOLUTION;
wd_data.wd2_timeout = state.timeout2 * WD_L2_RESOLUTION;
(void) wd_stop();
}
}
/*
* wrapper for ptree interface to create property
*/
static int
wd_create_property(
int ptype, /* PICL property type */
int pmode, /* PICL access mode */
size_t psize, /* size of PICL property */
char *pname, /* property name */
int (*readfn)(ptree_rarg_t *, void *),
int (*writefn)(ptree_warg_t *, const void *),
picl_nodehdl_t nodeh, /* node for property */
picl_prophdl_t *propp, /* pointer to prop_handle */
void *vbuf) /* initial value */
{
picl_errno_t rc;
ptree_propinfo_t propinfo;
rc = ptree_init_propinfo(&propinfo, PTREE_PROPINFO_VERSION,
ptype, pmode, psize, pname, readfn, writefn);
if (rc != PICL_SUCCESS) {
syslog(LOG_ERR, WD_PICL_PROP_INIT_ERR, rc);
return (rc);
}
rc = ptree_create_and_add_prop(nodeh, &propinfo, vbuf, propp);
if (rc != PICL_SUCCESS) {
return (rc);
}
return (PICL_SUCCESS);
}
/* Create and add Watchdog properties */
static void
wd_create_add_props()
{
int rc;
picl_nodehdl_t rooth, sysmgmt_h, platformh;
int32_t timeout1 = 0;
int32_t timeout2 = 0;
char buf[PICL_WD_PROPVAL_MAX];
/* get picl root node handle */
if ((rc = ptree_get_root(&rooth)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 1, rc);
return;
}
/* get picl platform node handle */
if ((rc = ptree_get_node_by_path(PLATFORM_PATH,
&platformh)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 2, rc);
return;
}
/* get the picl sysmgmt node handle */
if ((rc = ptree_find_node(platformh, PICL_PROP_NAME,
PICL_PTYPE_CHARSTRING,
PICL_NODE_SYSMGMT, strlen(PICL_NODE_SYSMGMT),
&sysmgmt_h)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 3, rc);
return;
}
/* start creating the watchdog nodes and properties */
if ((rc = ptree_create_and_add_node(sysmgmt_h, PICL_NODE_WD_CONTROLLER,
PICL_CLASS_WATCHDOG_CONTROLLER,
&(wd_data.wd_ctrl_nodehdl))) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 4, rc);
return;
}
/* Add wd-op property to watchdog controller node */
(void) strncpy(buf, "", sizeof (buf));
if ((rc = wd_create_property(PICL_PTYPE_CHARSTRING,
PICL_WRITE + PICL_VOLATILE,
PICL_PROPNAMELEN_MAX, PICL_PROP_WATCHDOG_OPERATION,
NULL, wd_write_op,
wd_data.wd_ctrl_nodehdl,
&(wd_data.wd_ops_hdl),
(void *)buf)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 5, rc);
return;
}
/* create L1 node and add to controller */
if ((rc = ptree_create_and_add_node(wd_data.wd_ctrl_nodehdl,
PICL_NODE_WD_L1, PICL_CLASS_WATCHDOG_TIMER,
&(wd_data.wd1_nodehdl))) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 6, rc);
return;
}
/* create L2 node and add to controller */
if ((rc = ptree_create_and_add_node(wd_data.wd_ctrl_nodehdl,
PICL_NODE_WD_L2, PICL_CLASS_WATCHDOG_TIMER,
&(wd_data.wd2_nodehdl))) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 7, rc);
return;
}
/* create watchdog properties */
/* create state property here */
(void) strncpy(buf, PICL_PROPVAL_WD_STATE_DISARMED,
sizeof (buf));
if ((rc = wd_create_property(PICL_PTYPE_CHARSTRING,
PICL_READ + PICL_VOLATILE, PICL_PROPNAMELEN_MAX,
PICL_PROP_STATE, wd1_read_status, NULLWRITE,
wd_data.wd1_nodehdl,
&(wd_data.wd1_state_hdl), (void *)buf)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 8, rc);
return;
}
if ((rc = wd_create_property(PICL_PTYPE_CHARSTRING,
PICL_READ + PICL_VOLATILE, PICL_PROPNAMELEN_MAX,
PICL_PROP_STATE, wd2_read_status, NULLWRITE,
wd_data.wd2_nodehdl,
&(wd_data.wd2_state_hdl), (void *)buf)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 9, rc);
return;
}
/* create timeout property here */
if ((rc = wd_create_property(PICL_PTYPE_UNSIGNED_INT,
PICL_READ + PICL_WRITE + PICL_VOLATILE,
sizeof (timeout1), PICL_PROP_WATCHDOG_TIMEOUT,
wd_read_timeout, wd_write_timeout, wd_data.wd1_nodehdl,
&(wd_data.wd1_timeout_hdl), (void *)&(timeout1))) !=
PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 10, rc);
return;
}
if ((rc = wd_create_property(PICL_PTYPE_UNSIGNED_INT,
PICL_READ + PICL_WRITE + PICL_VOLATILE,
sizeof (wd_data.wd2_timeout), PICL_PROP_WATCHDOG_TIMEOUT,
wd_read_timeout, wd_write_timeout, wd_data.wd2_nodehdl,
&(wd_data.wd2_timeout_hdl), (void *)&(timeout2))) !=
PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 11, rc);
return;
}
/* create wd_action property here */
(void) strncpy(buf, PICL_PROPVAL_WD_ACTION_NONE,
sizeof (buf));
if ((rc = wd_create_property(PICL_PTYPE_CHARSTRING,
PICL_READ + PICL_WRITE + PICL_VOLATILE,
PICL_PROPNAMELEN_MAX, PICL_PROP_WATCHDOG_ACTION,
wd_read_action, wd_write_action,
wd_data.wd1_nodehdl, &(wd_data.wd1_action_hdl),
(void *)buf)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 12, rc);
return;
}
if ((rc = wd_create_property(PICL_PTYPE_CHARSTRING,
PICL_READ + PICL_WRITE + PICL_VOLATILE,
PICL_PROPNAMELEN_MAX, PICL_PROP_WATCHDOG_ACTION,
wd_read_action, wd_write_action,
wd_data.wd2_nodehdl, &(wd_data.wd2_action_hdl),
(void *)buf)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_NODE_INIT_ERR, 13, rc);
return;
}
}
static int
wd_ioctl(int fd, int cmd, int len, char *buf)
{
int rtnval;
struct strioctl sioc;
sioc.ic_cmd = cmd;
sioc.ic_timout = 60;
sioc.ic_len = len;
sioc.ic_dp = buf;
rtnval = ioctl(fd, I_STR, &sioc);
return (rtnval);
}
static int
wd_open(int attr)
{
int cc;
sc_cmdspec_t wd_cmdspec;
if ((wd_fd = open(SMC_NODE, attr)) < 0) {
return (-1);
}
/* get exclusive access for set and reset commands of watchdog */
wd_cmdspec.args[0] = SMC_SET_WATCHDOG_TIMER;
wd_cmdspec.args[1] = SMC_RESET_WATCHDOG_TIMER;
wd_cmdspec.attribute = SC_ATTR_EXCLUSIVE;
cc = wd_ioctl(wd_fd, SCIOC_MSG_SPEC, 3,
(char *)&wd_cmdspec);
if (cc < 0) {
syslog(LOG_ERR, WD_PICL_EXCLUSIVE_ACCESS_ERR);
return (-1);
}
return (wd_fd);
}
static int
wd_open_pollfd(int attr)
{
int cc;
sc_cmdspec_t wd_cmdspec;
if ((polling_fd = open(SMC_NODE, attr)) < 0) {
return (-1);
}
/* request for watchdog expiry notification */
wd_cmdspec.args[0] = SMC_EXPIRED_WATCHDOG_NOTIF;
wd_cmdspec.attribute = SC_ATTR_EXCLUSIVE;
cc = wd_ioctl(polling_fd, SCIOC_MSG_SPEC, 2,
(char *)&wd_cmdspec);
if (cc < 0) {
syslog(LOG_ERR, WD_PICL_SET_ATTR_FAILED);
return (-1);
}
return (polling_fd);
}
/* read the ENVIRONMENT variables and initialize tunables */
static void
wd_get_env()
{
char *val;
int intval = 0;
/* read frutree debug flag value */
if (val = getenv(WATCHDOG_DEBUG)) {
errno = 0;
intval = strtol(val, (char **)NULL, 0);
if (errno == 0) {
wd_debug = intval;
}
}
}
/*
* PTREE Entry Points
*/
/* picl-state-change event handler */
/*ARGSUSED*/
static void
wd_state_change_evhandler(const char *ename, const void *earg,
size_t size, void *cookie)
{
char *value;
picl_errno_t rc;
nvlist_t *nvlp;
picl_nodehdl_t fruhdl;
static int spawn_threads = 1;
char name[PICL_PROPNAMELEN_MAX];
if (strcmp(ename, PICLEVENT_STATE_CHANGE)) {
return;
}
/* neglect all events if wd props are already created */
if (props_created && state_configured) {
return;
}
if (nvlist_unpack((char *)earg, size, &nvlp, NULL)) {
return;
}
if ((nvlist_lookup_uint64(nvlp, PICLEVENTARG_NODEHANDLE,
&fruhdl)) == -1) {
nvlist_free(nvlp);
return;
}
if (nvlist_lookup_string(nvlp, PICLEVENTARG_STATE, &value)) {
nvlist_free(nvlp);
return;
}
rc = ptree_get_propval_by_name(fruhdl, PICL_PROP_NAME,
(void *)name, sizeof (name));
if (rc != PICL_SUCCESS) {
nvlist_free(nvlp);
return;
}
/* care for only events on chassis node */
if (strcmp(name, PICL_NODE_CHASSIS) != 0) {
nvlist_free(nvlp);
return;
}
if (strcmp(value, PICLEVENTARGVAL_CONFIGURED) == 0) {
state_configured = 1;
nvlist_free(nvlp);
return;
}
if (strcmp(value, PICLEVENTARGVAL_CONFIGURING) != 0) {
nvlist_free(nvlp);
return;
}
if (wd_fd < 0) {
if ((wd_fd = wd_open(O_RDWR)) < 0) {
syslog(LOG_CRIT, WD_PICL_SMC_OPEN_ERR);
nvlist_free(nvlp);
return;
}
}
if (polling_fd < 0) {
if ((polling_fd = wd_open_pollfd(O_RDWR)) < 0) {
syslog(LOG_CRIT, WD_PICL_SMC_OPEN_ERR);
nvlist_free(nvlp);
return;
}
}
switch (wd_get_chassis_type()) {
case WD_HOST: /* is host */
wd_data.is_host = B_TRUE;
break;
case WD_STANDALONE: /* is satellite */
wd_data.is_host = B_FALSE;
break;
default:
nvlist_free(nvlp);
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 */
(void) snprintf(wd_conf, sizeof (wd_conf), "%s/%s",
PICL_CONFIG_DIR, WD_CONF_FILE);
if (spawn_threads == 0) {
/* threads are already created */
nvlist_free(nvlp);
return;
}
/* start monitoring for the events */
if (thr_create(NULL, NULL, wd_polling,
NULL, THR_BOUND, &polling_thr_tid) != 0) {
syslog(LOG_ERR, WD_PICL_THREAD_CREATE_FAILED,
"polling");
nvlist_free(nvlp);
return;
}
/* thread used to pat watchdog */
if (thr_create(NULL, NULL, wd_patting_thread,
NULL, THR_BOUND, &patting_thr_tid) != 0) {
syslog(LOG_ERR, WD_PICL_THREAD_CREATE_FAILED,
"patting");
nvlist_free(nvlp);
return;
}
spawn_threads = 0;
nvlist_free(nvlp);
}
static void
wd_picl_register(void)
{
int rc = 0;
if ((rc = picld_plugin_register(&wd_reg_info)) != PICL_SUCCESS) {
syslog(LOG_ERR, WD_PICL_REG_ERR, rc);
}
}
/* entry point (initialization) */
static void
wd_picl_init(void)
{
/* initialize the wd_conf path and name */
(void) snprintf(wd_conf, sizeof (wd_conf), "%s/%s",
PICL_CONFIG_DIR, WD_CONF_FILE);
/* parse configuration file and set tunables */
wd_parse_config_file(wd_conf);
/* 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 */
(void) ptree_register_handler(PICLEVENT_STATE_CHANGE,
wd_state_change_evhandler, NULL);
}
static void
wd_picl_fini(void)
{
(void) ptree_unregister_handler(PICLEVENT_STATE_CHANGE,
wd_state_change_evhandler, NULL);
state_configured = 0; /* chassis state */
props_created = 0;
(void) ptree_delete_node(wd_data.wd_ctrl_nodehdl);
(void) ptree_destroy_node(wd_data.wd_ctrl_nodehdl);
}
/*
* volatile function to read the timeout
*/
static int
wd_read_timeout(ptree_rarg_t *parg, void *buf)
{
/* update the buffer provided by user */
(void) mutex_lock(&data_lock);
if (parg->proph == wd_data.wd1_timeout_hdl) {
*(int32_t *)buf = wd_data.wd1_timeout;
} else if (parg->proph == wd_data.wd2_timeout_hdl) {
*(int32_t *)buf = wd_data.wd2_timeout;
}
(void) mutex_unlock(&data_lock);
return (PICL_SUCCESS);
}
/*
* volatile function to read the action
*/
static int
wd_read_action(ptree_rarg_t *parg, void *buf)
{
(void) mutex_lock(&data_lock);
if (parg->proph == wd_data.wd1_action_hdl) {
switch (wd_data.wd1_action) {
case WD_ACTION_HEALTHY_DOWN_HOST:
case WD_ACTION_HEALTHY_DOWN_SAT:
(void) strcpy((char *)buf,
PICL_PROPVAL_WD_ACTION_ALARM);
break;
case WD_ACTION_NONE1:
case WD_ACTION_NONE2:
if (wd_data.reboot_action == 1) {
(void) strcpy((char *)buf,
PICL_PROPVAL_WD_ACTION_REBOOT);
} else {
(void) strcpy((char *)buf,
PICL_PROPVAL_WD_ACTION_NONE);
}
break;
}
} else if (parg->proph == wd_data.wd2_action_hdl) {
switch (wd_data.wd2_action) {
case WD_ACTION_HARD_RESET:
(void) strcpy((char *)buf,
PICL_PROPVAL_WD_ACTION_RESET);
break;
case WD_ACTION_NONE2:
(void) strcpy((char *)buf, PICL_PROPVAL_WD_ACTION_NONE);
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
wd_write_action(ptree_warg_t *parg, const void *buf)
{
int flag = 0x0;
picl_errno_t rc = PICL_SUCCESS;
char wd_action[PICL_WD_PROPVAL_MAX];
/* only super user can write this property */
if (parg->cred.dc_euid != SUPER_USER) {
return (PICL_PERMDENIED);
}
if (parg->proph == wd_data.wd1_action_hdl) {
flag = WD1;
} else if (parg->proph == wd_data.wd2_action_hdl) {
flag = WD2;
}
/* dont allow any write operations when watchdog is armed */
(void) mutex_lock(&data_lock);
if (wd_data.wd1_run_state != WD_DISARMED ||
wd_data.wd2_run_state != WD_DISARMED) {
(void) mutex_unlock(&data_lock);
return (PICL_PERMDENIED);
}
/* validate the values and store in internal cache */
(void) strcpy(wd_action, (char *)buf);
switch (flag) {
case WD1:
if (strcmp(wd_action, PICL_PROPVAL_WD_ACTION_ALARM) == 0) {
if (wd_data.is_host)
wd_data.wd1_action = WD_ACTION_HEALTHY_DOWN_HOST;
else
wd_data.wd1_action = WD_ACTION_HEALTHY_DOWN_SAT;
wd_data.reboot_action = 0;
} else if (strcmp(wd_action, PICL_PROPVAL_WD_ACTION_NONE) == 0) {
wd_data.wd1_action = WD_ACTION_NONE1;
wd_data.reboot_action = 0;
} else if (strcmp(wd_action, PICL_PROPVAL_WD_ACTION_REBOOT) == 0) {
wd_data.wd1_action = WD_ACTION_NONE1;
wd_data.reboot_action = 1;
} else {
rc = PICL_INVALIDARG;
}
break;
case WD2:
if (strcmp(wd_action, PICL_PROPVAL_WD_ACTION_RESET) == 0) {
wd_data.wd2_action = WD_ACTION_HARD_RESET;
} else if (strcmp(wd_action, PICL_PROPVAL_WD_ACTION_NONE) == 0) {
wd_data.wd2_action = WD_ACTION_NONE2;
} else {
rc = PICL_INVALIDARG;
}
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
wd_write_timeout(ptree_warg_t *parg, const void *buf)
{
int32_t timeout;
int flag = 0x0;
/* only super user can write this property */
if (parg->cred.dc_euid != SUPER_USER) {
return (PICL_PERMDENIED);
}
/* dont allow any write operations when watchdog is armed */
(void) mutex_lock(&data_lock);
if (wd_data.wd1_run_state != WD_DISARMED ||
wd_data.wd2_run_state != WD_DISARMED) {
(void) mutex_unlock(&data_lock);
return (PICL_PERMDENIED);
}
(void) mutex_unlock(&data_lock);
if (parg->proph == wd_data.wd1_timeout_hdl) {
flag = WD1;
} else if (parg->proph == wd_data.wd2_timeout_hdl) {
flag = WD2;
}
/* validate the timeout values */
timeout = *(int32_t *)buf;
if (timeout < -1) {
return (PICL_INVALIDARG);
}
if (timeout > 0) {
switch (flag) {
case WD1:
if ((timeout % WD_L1_RESOLUTION) != 0) {
return (PICL_INVALIDARG);
}
if ((timeout/WD_L1_RESOLUTION) > WD_MAX_L1) {
return (PICL_INVALIDARG);
}
break;
case WD2:
if ((timeout % WD_L2_RESOLUTION) != 0) {
return (PICL_INVALIDARG);
}
if ((timeout/WD_L2_RESOLUTION) > WD_MAX_L2) {
/* 255 sec */
return (PICL_INVALIDARG);
}
}
}
/* update the internal cache */
(void) mutex_lock(&data_lock);
switch (flag) {
case WD1:
wd_data.wd1_timeout = timeout;
break;
case WD2:
wd_data.wd2_timeout = timeout;
break;
}
(void) mutex_unlock(&data_lock);
return (PICL_SUCCESS);
}