/*
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <ctype.h>
#include <libipmi.h>
#include <libnvpair.h>
#include <libuutil.h>
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include "diskmon_conf.h"
#include "dm_platform.h"
#include "util.h"
/* For the purposes of disk capacity, a <X>B is 1000x, not 1024x */
#define ONE_KILOBYTE 1000.0
#define ONE_MEGABYTE (ONE_KILOBYTE * 1000)
#define ONE_GIGABYTE (ONE_MEGABYTE * 1000)
#define ONE_TERABYTE (ONE_GIGABYTE * 1000)
#define ONE_PETABYTE (ONE_TERABYTE * 1000)
static ipmi_handle_t *g_ipmi_hdl;
typedef enum {
IPMI_CACHE_SENSOR,
IPMI_CACHE_FRU
} ipmi_cache_type_t;
typedef struct ipmi_cache_entry {
ipmi_cache_type_t ic_type;
uu_list_node_t ic_node;
union {
ipmi_set_sensor_reading_t ic_sensor;
ipmi_sunoem_fru_t ic_fru;
} ic_data;
} ipmi_cache_entry_t;
static pthread_mutex_t g_ipmi_mtx = PTHREAD_MUTEX_INITIALIZER;
static uu_list_pool_t *g_ipmi_cache_pool;
static uu_list_t *g_ipmi_cache;
/*
* The textual strings that are used in the actions may be one of the
* following forms:
*
* [1] `fru gid=<n> hdd=<m>'
* [2] `sensor id=<x> assert=<y> deassert=<z>'
*
* The generic parser will take a string and spit out the first token
* (e.g. `fru' or `sensor') and an nvlist that contains the key-value
* pairs in the rest of the string. The assumption is that there are
* no embedded spaces or tabs in the keys or values.
*/
static boolean_t
isnumber(const char *str)
{
boolean_t hex = B_FALSE;
int digits = 0;
if (strncasecmp(str, "0x", 2) == 0) {
hex = B_TRUE;
str += 2;
} else if (*str == '-' || *str == '+') {
str++;
}
while (*str != 0) {
if ((hex && !isxdigit(*str)) ||
(!hex && !isdigit(*str))) {
return (B_FALSE);
}
str++;
digits++;
}
return ((digits == 0) ? B_FALSE : B_TRUE);
}
static void
tolowerString(char *str)
{
while (*str != 0) {
*str = tolower(*str);
str++;
}
}
static boolean_t
parse_action_string(const char *actionString, char **cmdp, nvlist_t **propsp)
{
char *action;
char *tok, *lasts, *eq;
int actionlen;
boolean_t rv = B_TRUE;
if (nvlist_alloc(propsp, NV_UNIQUE_NAME, 0) != 0)
return (B_FALSE);
actionlen = strlen(actionString) + 1;
action = dstrdup(actionString);
*cmdp = NULL;
if ((tok = strtok_r(action, " \t", &lasts)) != NULL) {
*cmdp = dstrdup(tok);
while (rv && (tok = strtok_r(NULL, " \t", &lasts)) != NULL) {
/* Look for a name=val construct */
if ((eq = strchr(tok, '=')) != NULL && eq[1] != 0) {
*eq = 0;
eq++;
/*
* Convert token to lowercase to preserve
* case-insensitivity, because nvlist doesn't
* do case-insensitive lookups
*/
tolowerString(tok);
if (isnumber(eq)) {
/* Integer property */
if (nvlist_add_uint64(*propsp, tok,
strtoull(eq, NULL, 0)) != 0)
rv = B_FALSE;
} else {
/* String property */
if (nvlist_add_string(*propsp, tok,
eq) != 0)
rv = B_FALSE;
}
} else if (eq == NULL) {
/* Boolean property */
if (nvlist_add_boolean(*propsp, tok) != 0)
rv = B_FALSE;
} else /* Parse error (`X=' is invalid) */
rv = B_FALSE;
}
} else
rv = B_FALSE;
dfree(action, actionlen);
if (!rv) {
if (*cmdp) {
dstrfree(*cmdp);
*cmdp = NULL;
}
nvlist_free(*propsp);
*propsp = NULL;
}
return (rv);
}
static int
platform_update_fru(nvlist_t *props, dm_fru_t *frup)
{
uint64_t gid, hdd;
ipmi_sunoem_fru_t fru;
char *buf;
ipmi_cache_entry_t *entry;
if (nvlist_lookup_uint64(props, "gid", &gid) != 0 ||
nvlist_lookup_uint64(props, "hdd", &hdd) != 0) {
return (-1);
}
fru.isf_type = (uint8_t)gid;
fru.isf_id = (uint8_t)hdd;
buf = (char *)dzmalloc(sizeof (fru.isf_data.disk.isf_capacity) + 1);
(void) memcpy(fru.isf_data.disk.isf_manufacturer, frup->manuf,
MIN(sizeof (fru.isf_data.disk.isf_manufacturer),
sizeof (frup->manuf)));
(void) memcpy(fru.isf_data.disk.isf_model, frup->model,
MIN(sizeof (fru.isf_data.disk.isf_model), sizeof (frup->model)));
(void) memcpy(fru.isf_data.disk.isf_serial, frup->serial,
MIN(sizeof (fru.isf_data.disk.isf_serial), sizeof (frup->serial)));
(void) memcpy(fru.isf_data.disk.isf_version, frup->rev,
MIN(sizeof (fru.isf_data.disk.isf_version), sizeof (frup->rev)));
/*
* Print the size of the disk to a temporary buffer whose size is
* 1 more than the size of the buffer in the ipmi request data
* structure, so we can get the full 8 characters (instead of 7 + NUL)
*/
(void) snprintf(buf, sizeof (fru.isf_data.disk.isf_capacity) + 1,
"%.1f%s",
frup->size_in_bytes >= ONE_PETABYTE ?
(frup->size_in_bytes / ONE_PETABYTE) :
(frup->size_in_bytes >= ONE_TERABYTE ?
(frup->size_in_bytes / ONE_TERABYTE) :
(frup->size_in_bytes >= ONE_GIGABYTE ?
(frup->size_in_bytes / ONE_GIGABYTE) :
(frup->size_in_bytes >= ONE_MEGABYTE ?
(frup->size_in_bytes / ONE_MEGABYTE) :
(frup->size_in_bytes / ONE_KILOBYTE)))),
frup->size_in_bytes >= ONE_PETABYTE ? "PB" :
(frup->size_in_bytes >= ONE_TERABYTE ? "TB" :
(frup->size_in_bytes >= ONE_GIGABYTE ? "GB" :
(frup->size_in_bytes >= ONE_MEGABYTE ? "MB" :
"KB"))));
(void) memcpy(fru.isf_data.disk.isf_capacity, buf,
sizeof (fru.isf_data.disk.isf_capacity));
dfree(buf, sizeof (fru.isf_data.disk.isf_capacity) + 1);
if (ipmi_sunoem_update_fru(g_ipmi_hdl, &fru) != 0)
return (-1);
/* find a cache entry or create one if necessary */
for (entry = uu_list_first(g_ipmi_cache); entry != NULL;
entry = uu_list_next(g_ipmi_cache, entry)) {
if (entry->ic_type == IPMI_CACHE_FRU &&
entry->ic_data.ic_fru.isf_type == gid &&
entry->ic_data.ic_fru.isf_id == hdd)
break;
}
if (entry == NULL) {
entry = dzmalloc(sizeof (ipmi_cache_entry_t));
entry->ic_type = IPMI_CACHE_FRU;
(void) uu_list_insert_before(g_ipmi_cache, NULL, entry);
}
(void) memcpy(&entry->ic_data.ic_fru, &fru, sizeof (fru));
return (0);
}
static int
platform_set_sensor(nvlist_t *props)
{
uint64_t assertmask = 0, deassertmask = 0, sid;
boolean_t am_present, dam_present;
ipmi_set_sensor_reading_t sr, *sp;
ipmi_cache_entry_t *entry;
int ret;
/* We need at least 2 properties: `sid' and (`amask' || `dmask'): */
am_present = nvlist_lookup_uint64(props, "amask", &assertmask) == 0;
dam_present = nvlist_lookup_uint64(props, "dmask", &deassertmask) == 0;
if (nvlist_lookup_uint64(props, "sid", &sid) != 0 ||
(!am_present && !dam_present)) {
return (-1);
}
if (sid > UINT8_MAX) {
log_warn("IPMI Plugin: Invalid sensor id `0x%llx'.\n",
(longlong_t)sid);
return (-1);
} else if (assertmask > UINT16_MAX) {
log_warn("IPMI Plugin: Invalid assertion mask `0x%llx'.\n",
(longlong_t)assertmask);
return (-1);
} else if (assertmask > UINT16_MAX) {
log_warn("IPMI Plugin: Invalid deassertion mask `0x%llx'.\n",
(longlong_t)deassertmask);
return (-1);
}
(void) memset(&sr, '\0', sizeof (sr));
sr.iss_id = (uint8_t)sid;
if (am_present) {
sr.iss_assert_op = IPMI_SENSOR_OP_SET;
sr.iss_assert_state = (uint16_t)assertmask;
}
if (dam_present) {
sr.iss_deassrt_op = IPMI_SENSOR_OP_SET;
sr.iss_deassert_state = (uint16_t)deassertmask;
}
ret = ipmi_set_sensor_reading(g_ipmi_hdl, &sr);
/* find a cache entry or create one if necessary */
for (entry = uu_list_first(g_ipmi_cache); entry != NULL;
entry = uu_list_next(g_ipmi_cache, entry)) {
if (entry->ic_type == IPMI_CACHE_SENSOR &&
entry->ic_data.ic_sensor.iss_id == (uint8_t)sid)
break;
}
if (entry == NULL) {
entry = dzmalloc(sizeof (ipmi_cache_entry_t));
entry->ic_type = IPMI_CACHE_SENSOR;
(void) uu_list_insert_before(g_ipmi_cache, NULL, entry);
entry->ic_data.ic_sensor.iss_id = (uint8_t)sid;
entry->ic_data.ic_sensor.iss_assert_op = IPMI_SENSOR_OP_SET;
entry->ic_data.ic_sensor.iss_deassrt_op = IPMI_SENSOR_OP_SET;
}
sp = &entry->ic_data.ic_sensor;
if (am_present) {
sp->iss_assert_state |= assertmask;
sp->iss_deassert_state &= ~assertmask;
}
if (dam_present) {
sp->iss_deassert_state |= deassertmask;
sp->iss_assert_state &= ~deassertmask;
}
return (ret);
}
#define PROTOCOL_SEPARATOR ':'
static char *
extract_protocol(const char *action)
{
char *s = strchr(action, PROTOCOL_SEPARATOR);
char *proto = NULL;
int len;
int i = 0;
/* The protocol is the string before the separator, but in lower-case */
if (s) {
len = (uintptr_t)s - (uintptr_t)action;
proto = (char *)dmalloc(len + 1);
while (i < len) {
proto[i] = tolower(action[i]);
i++;
}
proto[len] = 0;
}
return (proto);
}
static char *
extract_action(const char *action)
{
/* The action is the string after the separator */
char *s = strchr(action, PROTOCOL_SEPARATOR);
return (s ? (s + 1) : NULL);
}
static int
do_action(const char *action, dm_fru_t *fru)
{
nvlist_t *props;
char *cmd;
int rv = -1;
char *protocol = extract_protocol(action);
char *actionp = extract_action(action);
if (strcmp(protocol, "ipmi") != 0) {
log_err("unknown protocol '%s'\n", protocol);
dstrfree(protocol);
return (-1);
}
dstrfree(protocol);
(void) pthread_mutex_lock(&g_ipmi_mtx);
if (parse_action_string(actionp, &cmd, &props)) {
if (strcmp(cmd, "fru") == 0) {
rv = platform_update_fru(props, fru);
} else if (strcmp(cmd, "state") == 0) {
rv = platform_set_sensor(props);
} else {
log_err("unknown platform action '%s'\n", cmd);
}
dstrfree(cmd);
nvlist_free(props);
}
(void) pthread_mutex_unlock(&g_ipmi_mtx);
return (rv);
}
int
dm_platform_update_fru(const char *action, dm_fru_t *fru)
{
return (do_action(action, fru));
}
int
dm_platform_indicator_execute(const char *action)
{
return (do_action(action, NULL));
}
int
dm_platform_resync(void)
{
ipmi_cache_entry_t *entry;
int rv = 0;
(void) pthread_mutex_lock(&g_ipmi_mtx);
/*
* Called when the SP is reset, as the sensor/FRU state is not
* maintained across reboots. Note that we must update the FRU
* information first, as certain sensor states prevent this from
* working.
*/
for (entry = uu_list_first(g_ipmi_cache); entry != NULL;
entry = uu_list_next(g_ipmi_cache, entry)) {
if (entry->ic_type == IPMI_CACHE_FRU)
rv |= ipmi_sunoem_update_fru(g_ipmi_hdl,
&entry->ic_data.ic_fru);
}
for (entry = uu_list_first(g_ipmi_cache); entry != NULL;
entry = uu_list_next(g_ipmi_cache, entry)) {
if (entry->ic_type == IPMI_CACHE_SENSOR)
rv |= ipmi_set_sensor_reading(g_ipmi_hdl,
&entry->ic_data.ic_sensor);
}
(void) pthread_mutex_unlock(&g_ipmi_mtx);
return (rv);
}
int
dm_platform_init(void)
{
int err;
char *msg;
if ((g_ipmi_hdl = ipmi_open(&err, &msg, IPMI_TRANSPORT_BMC, NULL))
== NULL) {
log_warn("Failed to load libipmi: %s\n", msg);
return (-1);
}
if ((g_ipmi_cache_pool = uu_list_pool_create(
"ipmi_cache", sizeof (ipmi_cache_entry_t),
offsetof(ipmi_cache_entry_t, ic_node), NULL, 0)) == NULL)
return (-1);
if ((g_ipmi_cache = uu_list_create(g_ipmi_cache_pool, NULL, 0))
== NULL)
return (-1);
return (0);
}
void
dm_platform_fini(void)
{
if (g_ipmi_hdl)
ipmi_close(g_ipmi_hdl);
if (g_ipmi_cache) {
ipmi_cache_entry_t *entry;
while ((entry = uu_list_first(g_ipmi_cache)) != NULL) {
uu_list_remove(g_ipmi_cache, entry);
dfree(entry, sizeof (*entry));
}
uu_list_destroy(g_ipmi_cache);
}
if (g_ipmi_cache_pool)
uu_list_pool_destroy(g_ipmi_cache_pool);
}