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