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