Home | History | Annotate | Download | only in disk-monitor
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     27 
     28 #include <ctype.h>
     29 #include <libipmi.h>
     30 #include <libnvpair.h>
     31 #include <libuutil.h>
     32 #include <limits.h>
     33 #include <stddef.h>
     34 #include <string.h>
     35 
     36 #include "diskmon_conf.h"
     37 #include "dm_platform.h"
     38 #include "util.h"
     39 
     40 /* For the purposes of disk capacity, a <X>B is 1000x, not 1024x */
     41 #define	ONE_KILOBYTE 1000.0
     42 #define	ONE_MEGABYTE (ONE_KILOBYTE * 1000)
     43 #define	ONE_GIGABYTE (ONE_MEGABYTE * 1000)
     44 #define	ONE_TERABYTE (ONE_GIGABYTE * 1000)
     45 #define	ONE_PETABYTE (ONE_TERABYTE * 1000)
     46 
     47 static ipmi_handle_t *g_ipmi_hdl;
     48 
     49 typedef enum {
     50 	IPMI_CACHE_SENSOR,
     51 	IPMI_CACHE_FRU
     52 } ipmi_cache_type_t;
     53 
     54 typedef struct ipmi_cache_entry {
     55 	ipmi_cache_type_t			ic_type;
     56 	uu_list_node_t				ic_node;
     57 	union {
     58 		ipmi_set_sensor_reading_t	ic_sensor;
     59 		ipmi_sunoem_fru_t		ic_fru;
     60 	} ic_data;
     61 } ipmi_cache_entry_t;
     62 
     63 static pthread_mutex_t g_ipmi_mtx = PTHREAD_MUTEX_INITIALIZER;
     64 static uu_list_pool_t *g_ipmi_cache_pool;
     65 static uu_list_t *g_ipmi_cache;
     66 
     67 /*
     68  * The textual strings that are used in the actions may be one of the
     69  * following forms:
     70  *
     71  * [1] `fru gid=<n> hdd=<m>'
     72  * [2] `sensor id=<x> assert=<y> deassert=<z>'
     73  *
     74  * The generic parser will take a string and spit out the first token
     75  * (e.g. `fru' or `sensor') and an nvlist that contains the key-value
     76  * pairs in the rest of the string.  The assumption is that there are
     77  * no embedded spaces or tabs in the keys or values.
     78  */
     79 
     80 static boolean_t
     81 isnumber(const char *str)
     82 {
     83 	boolean_t hex = B_FALSE;
     84 	int digits = 0;
     85 
     86 	if (strncasecmp(str, "0x", 2) == 0) {
     87 		hex = B_TRUE;
     88 		str += 2;
     89 	} else if (*str == '-' || *str == '+') {
     90 		str++;
     91 	}
     92 
     93 	while (*str != 0) {
     94 		if ((hex && !isxdigit(*str)) ||
     95 		    (!hex && !isdigit(*str))) {
     96 			return (B_FALSE);
     97 		}
     98 
     99 		str++;
    100 		digits++;
    101 	}
    102 
    103 	return ((digits == 0) ? B_FALSE : B_TRUE);
    104 }
    105 
    106 static void
    107 tolowerString(char *str)
    108 {
    109 	while (*str != 0) {
    110 		*str = tolower(*str);
    111 		str++;
    112 	}
    113 }
    114 
    115 static boolean_t
    116 parse_action_string(const char *actionString, char **cmdp, nvlist_t **propsp)
    117 {
    118 	char *action;
    119 	char *tok, *lasts, *eq;
    120 	int actionlen;
    121 	boolean_t rv = B_TRUE;
    122 
    123 	if (nvlist_alloc(propsp, NV_UNIQUE_NAME, 0) != 0)
    124 		return (B_FALSE);
    125 
    126 	actionlen = strlen(actionString) + 1;
    127 	action = dstrdup(actionString);
    128 
    129 	*cmdp = NULL;
    130 
    131 	if ((tok = strtok_r(action, " \t", &lasts)) != NULL) {
    132 
    133 		*cmdp = dstrdup(tok);
    134 
    135 		while (rv && (tok = strtok_r(NULL, " \t", &lasts)) != NULL) {
    136 
    137 			/* Look for a name=val construct */
    138 			if ((eq = strchr(tok, '=')) != NULL && eq[1] != 0) {
    139 
    140 				*eq = 0;
    141 				eq++;
    142 
    143 				/*
    144 				 * Convert token to lowercase to preserve
    145 				 * case-insensitivity, because nvlist doesn't
    146 				 * do case-insensitive lookups
    147 				 */
    148 				tolowerString(tok);
    149 
    150 				if (isnumber(eq)) {
    151 					/* Integer property */
    152 
    153 					if (nvlist_add_uint64(*propsp, tok,
    154 					    strtoull(eq, NULL, 0)) != 0)
    155 						rv = B_FALSE;
    156 				} else {
    157 					/* String property */
    158 
    159 					if (nvlist_add_string(*propsp, tok,
    160 					    eq) != 0)
    161 						rv = B_FALSE;
    162 				}
    163 			} else if (eq == NULL) {
    164 				/* Boolean property */
    165 				if (nvlist_add_boolean(*propsp, tok) != 0)
    166 					rv = B_FALSE;
    167 			} else /* Parse error (`X=' is invalid) */
    168 				rv = B_FALSE;
    169 		}
    170 	} else
    171 		rv = B_FALSE;
    172 
    173 	dfree(action, actionlen);
    174 	if (!rv) {
    175 		if (*cmdp) {
    176 			dstrfree(*cmdp);
    177 			*cmdp = NULL;
    178 		}
    179 		nvlist_free(*propsp);
    180 		*propsp = NULL;
    181 	}
    182 	return (rv);
    183 }
    184 
    185 static int
    186 platform_update_fru(nvlist_t *props, dm_fru_t *frup)
    187 {
    188 	uint64_t gid, hdd;
    189 	ipmi_sunoem_fru_t fru;
    190 	char *buf;
    191 	ipmi_cache_entry_t *entry;
    192 
    193 	if (nvlist_lookup_uint64(props, "gid", &gid) != 0 ||
    194 	    nvlist_lookup_uint64(props, "hdd", &hdd) != 0) {
    195 		return (-1);
    196 	}
    197 
    198 	fru.isf_type = (uint8_t)gid;
    199 	fru.isf_id = (uint8_t)hdd;
    200 
    201 	buf = (char *)dzmalloc(sizeof (fru.isf_data.disk.isf_capacity) + 1);
    202 
    203 	(void) memcpy(fru.isf_data.disk.isf_manufacturer, frup->manuf,
    204 	    MIN(sizeof (fru.isf_data.disk.isf_manufacturer),
    205 	    sizeof (frup->manuf)));
    206 	(void) memcpy(fru.isf_data.disk.isf_model, frup->model,
    207 	    MIN(sizeof (fru.isf_data.disk.isf_model), sizeof (frup->model)));
    208 	(void) memcpy(fru.isf_data.disk.isf_serial, frup->serial,
    209 	    MIN(sizeof (fru.isf_data.disk.isf_serial), sizeof (frup->serial)));
    210 	(void) memcpy(fru.isf_data.disk.isf_version, frup->rev,
    211 	    MIN(sizeof (fru.isf_data.disk.isf_version), sizeof (frup->rev)));
    212 	/*
    213 	 * Print the size of the disk to a temporary buffer whose size is
    214 	 * 1 more than the size of the buffer in the ipmi request data
    215 	 * structure, so we can get the full 8 characters (instead of 7 + NUL)
    216 	 */
    217 	(void) snprintf(buf, sizeof (fru.isf_data.disk.isf_capacity) + 1,
    218 	    "%.1f%s",
    219 	    frup->size_in_bytes >= ONE_PETABYTE ?
    220 	    (frup->size_in_bytes / ONE_PETABYTE) :
    221 	    (frup->size_in_bytes >= ONE_TERABYTE ?
    222 	    (frup->size_in_bytes / ONE_TERABYTE) :
    223 	    (frup->size_in_bytes >= ONE_GIGABYTE ?
    224 	    (frup->size_in_bytes / ONE_GIGABYTE) :
    225 	    (frup->size_in_bytes >= ONE_MEGABYTE ?
    226 	    (frup->size_in_bytes / ONE_MEGABYTE) :
    227 	    (frup->size_in_bytes / ONE_KILOBYTE)))),
    228 
    229 	    frup->size_in_bytes >= ONE_PETABYTE ? "PB" :
    230 	    (frup->size_in_bytes >= ONE_TERABYTE ? "TB" :
    231 	    (frup->size_in_bytes >= ONE_GIGABYTE ? "GB" :
    232 	    (frup->size_in_bytes >= ONE_MEGABYTE ? "MB" :
    233 	    "KB"))));
    234 	(void) memcpy(fru.isf_data.disk.isf_capacity, buf,
    235 	    sizeof (fru.isf_data.disk.isf_capacity));
    236 
    237 	dfree(buf, sizeof (fru.isf_data.disk.isf_capacity) + 1);
    238 
    239 	if (ipmi_sunoem_update_fru(g_ipmi_hdl, &fru) != 0)
    240 		return (-1);
    241 
    242 	/* find a cache entry or create one if necessary */
    243 	for (entry = uu_list_first(g_ipmi_cache); entry != NULL;
    244 	    entry = uu_list_next(g_ipmi_cache, entry)) {
    245 		if (entry->ic_type == IPMI_CACHE_FRU &&
    246 		    entry->ic_data.ic_fru.isf_type == gid &&
    247 		    entry->ic_data.ic_fru.isf_id == hdd)
    248 			break;
    249 	}
    250 
    251 	if (entry == NULL) {
    252 		entry = dzmalloc(sizeof (ipmi_cache_entry_t));
    253 		entry->ic_type = IPMI_CACHE_FRU;
    254 		(void) uu_list_insert_before(g_ipmi_cache, NULL, entry);
    255 	}
    256 
    257 	(void) memcpy(&entry->ic_data.ic_fru, &fru, sizeof (fru));
    258 
    259 	return (0);
    260 }
    261 
    262 static int
    263 platform_set_sensor(nvlist_t *props)
    264 {
    265 	uint64_t assertmask = 0, deassertmask = 0, sid;
    266 	boolean_t am_present, dam_present;
    267 	ipmi_set_sensor_reading_t sr, *sp;
    268 	ipmi_cache_entry_t *entry;
    269 	int ret;
    270 
    271 	/* We need at least 2 properties: `sid' and (`amask' || `dmask'): */
    272 	am_present = nvlist_lookup_uint64(props, "amask", &assertmask) == 0;
    273 	dam_present = nvlist_lookup_uint64(props, "dmask", &deassertmask) == 0;
    274 
    275 	if (nvlist_lookup_uint64(props, "sid", &sid) != 0 ||
    276 	    (!am_present && !dam_present)) {
    277 		return (-1);
    278 	}
    279 
    280 	if (sid > UINT8_MAX) {
    281 		log_warn("IPMI Plugin: Invalid sensor id `0x%llx'.\n",
    282 		    (longlong_t)sid);
    283 		return (-1);
    284 	} else if (assertmask > UINT16_MAX) {
    285 		log_warn("IPMI Plugin: Invalid assertion mask `0x%llx'.\n",
    286 		    (longlong_t)assertmask);
    287 		return (-1);
    288 	} else if (assertmask > UINT16_MAX) {
    289 		log_warn("IPMI Plugin: Invalid deassertion mask `0x%llx'.\n",
    290 		    (longlong_t)deassertmask);
    291 		return (-1);
    292 	}
    293 
    294 	(void) memset(&sr, '\0', sizeof (sr));
    295 	sr.iss_id = (uint8_t)sid;
    296 	if (am_present) {
    297 		sr.iss_assert_op = IPMI_SENSOR_OP_SET;
    298 		sr.iss_assert_state = (uint16_t)assertmask;
    299 	}
    300 	if (dam_present) {
    301 		sr.iss_deassrt_op = IPMI_SENSOR_OP_SET;
    302 		sr.iss_deassert_state = (uint16_t)deassertmask;
    303 	}
    304 
    305 	ret = ipmi_set_sensor_reading(g_ipmi_hdl, &sr);
    306 
    307 	/* find a cache entry or create one if necessary */
    308 	for (entry = uu_list_first(g_ipmi_cache); entry != NULL;
    309 	    entry = uu_list_next(g_ipmi_cache, entry)) {
    310 		if (entry->ic_type == IPMI_CACHE_SENSOR &&
    311 		    entry->ic_data.ic_sensor.iss_id == (uint8_t)sid)
    312 			break;
    313 	}
    314 
    315 	if (entry == NULL) {
    316 		entry = dzmalloc(sizeof (ipmi_cache_entry_t));
    317 		entry->ic_type = IPMI_CACHE_SENSOR;
    318 		(void) uu_list_insert_before(g_ipmi_cache, NULL, entry);
    319 		entry->ic_data.ic_sensor.iss_id = (uint8_t)sid;
    320 		entry->ic_data.ic_sensor.iss_assert_op = IPMI_SENSOR_OP_SET;
    321 		entry->ic_data.ic_sensor.iss_deassrt_op = IPMI_SENSOR_OP_SET;
    322 	}
    323 	sp = &entry->ic_data.ic_sensor;
    324 
    325 	if (am_present) {
    326 		sp->iss_assert_state |= assertmask;
    327 		sp->iss_deassert_state &= ~assertmask;
    328 	}
    329 	if (dam_present) {
    330 		sp->iss_deassert_state |= deassertmask;
    331 		sp->iss_assert_state &= ~deassertmask;
    332 	}
    333 
    334 	return (ret);
    335 }
    336 
    337 #define	PROTOCOL_SEPARATOR ':'
    338 
    339 static char *
    340 extract_protocol(const char *action)
    341 {
    342 	char *s = strchr(action, PROTOCOL_SEPARATOR);
    343 	char *proto = NULL;
    344 	int len;
    345 	int i = 0;
    346 
    347 	/* The protocol is the string before the separator, but in lower-case */
    348 	if (s) {
    349 		len = (uintptr_t)s - (uintptr_t)action;
    350 		proto = (char *)dmalloc(len + 1);
    351 		while (i < len) {
    352 			proto[i] = tolower(action[i]);
    353 			i++;
    354 		}
    355 		proto[len] = 0;
    356 	}
    357 
    358 	return (proto);
    359 }
    360 
    361 static char *
    362 extract_action(const char *action)
    363 {
    364 	/* The action is the string after the separator */
    365 	char *s = strchr(action, PROTOCOL_SEPARATOR);
    366 
    367 	return (s ? (s + 1) : NULL);
    368 }
    369 
    370 static int
    371 do_action(const char *action, dm_fru_t *fru)
    372 {
    373 	nvlist_t	*props;
    374 	char		*cmd;
    375 	int rv = -1;
    376 	char		*protocol = extract_protocol(action);
    377 	char		*actionp = extract_action(action);
    378 
    379 	if (strcmp(protocol, "ipmi") != 0) {
    380 		log_err("unknown protocol '%s'\n", protocol);
    381 		dstrfree(protocol);
    382 		return (-1);
    383 	}
    384 
    385 	dstrfree(protocol);
    386 
    387 	(void) pthread_mutex_lock(&g_ipmi_mtx);
    388 	if (parse_action_string(actionp, &cmd, &props)) {
    389 		if (strcmp(cmd, "fru") == 0) {
    390 			rv = platform_update_fru(props, fru);
    391 		} else if (strcmp(cmd, "state") == 0) {
    392 			rv = platform_set_sensor(props);
    393 		} else {
    394 			log_err("unknown platform action '%s'\n", cmd);
    395 		}
    396 		dstrfree(cmd);
    397 		nvlist_free(props);
    398 	}
    399 	(void) pthread_mutex_unlock(&g_ipmi_mtx);
    400 
    401 	return (rv);
    402 }
    403 
    404 int
    405 dm_platform_update_fru(const char *action, dm_fru_t *fru)
    406 {
    407 	return (do_action(action, fru));
    408 }
    409 
    410 int
    411 dm_platform_indicator_execute(const char *action)
    412 {
    413 	return (do_action(action, NULL));
    414 }
    415 
    416 int
    417 dm_platform_resync(void)
    418 {
    419 	ipmi_cache_entry_t *entry;
    420 	int rv = 0;
    421 
    422 	(void) pthread_mutex_lock(&g_ipmi_mtx);
    423 
    424 	/*
    425 	 * Called when the SP is reset, as the sensor/FRU state is not
    426 	 * maintained across reboots.  Note that we must update the FRU
    427 	 * information first, as certain sensor states prevent this from
    428 	 * working.
    429 	 */
    430 	for (entry = uu_list_first(g_ipmi_cache); entry != NULL;
    431 	    entry = uu_list_next(g_ipmi_cache, entry)) {
    432 		if (entry->ic_type == IPMI_CACHE_FRU)
    433 			rv |= ipmi_sunoem_update_fru(g_ipmi_hdl,
    434 			    &entry->ic_data.ic_fru);
    435 	}
    436 
    437 	for (entry = uu_list_first(g_ipmi_cache); entry != NULL;
    438 	    entry = uu_list_next(g_ipmi_cache, entry)) {
    439 		if (entry->ic_type == IPMI_CACHE_SENSOR)
    440 			rv |= ipmi_set_sensor_reading(g_ipmi_hdl,
    441 			    &entry->ic_data.ic_sensor);
    442 	}
    443 
    444 	(void) pthread_mutex_unlock(&g_ipmi_mtx);
    445 	return (rv);
    446 }
    447 
    448 int
    449 dm_platform_init(void)
    450 {
    451 	int err;
    452 	char *msg;
    453 
    454 	if ((g_ipmi_hdl = ipmi_open(&err, &msg)) == NULL) {
    455 		log_warn("Failed to load libipmi: %s\n", msg);
    456 		return (-1);
    457 	}
    458 
    459 	if ((g_ipmi_cache_pool = uu_list_pool_create(
    460 	    "ipmi_cache", sizeof (ipmi_cache_entry_t),
    461 	    offsetof(ipmi_cache_entry_t, ic_node), NULL, 0)) == NULL)
    462 		return (-1);
    463 
    464 	if ((g_ipmi_cache = uu_list_create(g_ipmi_cache_pool, NULL, 0))
    465 	    == NULL)
    466 		return (-1);
    467 
    468 	return (0);
    469 }
    470 
    471 void
    472 dm_platform_fini(void)
    473 {
    474 	if (g_ipmi_hdl)
    475 		ipmi_close(g_ipmi_hdl);
    476 	if (g_ipmi_cache) {
    477 		ipmi_cache_entry_t *entry;
    478 
    479 		while ((entry = uu_list_first(g_ipmi_cache)) != NULL) {
    480 			uu_list_remove(g_ipmi_cache, entry);
    481 			dfree(entry, sizeof (*entry));
    482 		}
    483 		uu_list_destroy(g_ipmi_cache);
    484 	}
    485 	if (g_ipmi_cache_pool)
    486 		uu_list_pool_destroy(g_ipmi_cache_pool);
    487 }
    488