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