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