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