xref: /illumos-gate/usr/src/cmd/fm/modules/common/disk-monitor/dm_platform.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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