xref: /illumos-gate/usr/src/lib/libipmi/common/ipmi_fru.c (revision 7dacfc4494f6d14358974ef2830b5cd8c66a84de)
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 <libipmi.h>
29 #include <string.h>
30 
31 #include "ipmi_impl.h"
32 
33 /*
34  * Extracts bits between index h (high, inclusive) and l (low, exclusive) from
35  * u, which must be an unsigned integer.
36  */
37 #define	BITX(u, h, l)	(((u) >> (l)) & ((1LU << ((h) - (l) + 1LU)) - 1LU))
38 
39 typedef struct ipmi_fru_read
40 {
41 	uint8_t		ifr_devid;
42 	uint8_t		ifr_offset_lsb;
43 	uint8_t		ifr_offset_msb;
44 	uint8_t		ifr_count;
45 } ipmi_fru_read_t;
46 
47 /*
48  * returns: size of FRU inventory data in bytes, on success
49  *          -1, otherwise
50  */
51 int
52 ipmi_fru_read(ipmi_handle_t *ihp, ipmi_sdr_fru_locator_t *fru_loc, char **buf)
53 {
54 	ipmi_cmd_t cmd, *resp;
55 	uint8_t count, devid;
56 	uint16_t sz, offset = 0;
57 	ipmi_fru_read_t cmd_data_in;
58 
59 	devid = fru_loc->_devid_or_slaveaddr._logical._is_fl_devid;
60 	/*
61 	 * First we issue a command to retrieve the size of the specified FRU's
62 	 * inventory area
63 	 */
64 	cmd.ic_netfn = IPMI_NETFN_STORAGE;
65 	cmd.ic_cmd = IPMI_CMD_GET_FRU_INV_AREA;
66 	cmd.ic_data = &devid;
67 	cmd.ic_dlen = sizeof (uint8_t);
68 	cmd.ic_lun = 0;
69 
70 	if ((resp = ipmi_send(ihp, &cmd)) == NULL)
71 		return (-1);
72 
73 	if (resp->ic_dlen != 3) {
74 		(void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH, NULL);
75 		return (-1);
76 	}
77 
78 	(void) memcpy(&sz, resp->ic_data, sizeof (uint16_t));
79 	if ((*buf = malloc(sz)) == NULL) {
80 		(void) ipmi_set_error(ihp, EIPMI_NOMEM, NULL);
81 		return (-1);
82 	}
83 
84 	while (offset < sz) {
85 		cmd_data_in.ifr_devid = devid;
86 		cmd_data_in.ifr_offset_lsb = BITX(offset, 7, 0);
87 		cmd_data_in.ifr_offset_msb = BITX(offset, 15, 8);
88 		if ((sz - offset) < 128)
89 			cmd_data_in.ifr_count = sz - offset;
90 		else
91 			cmd_data_in.ifr_count = 128;
92 
93 		cmd.ic_netfn = IPMI_NETFN_STORAGE;
94 		cmd.ic_cmd = IPMI_CMD_READ_FRU_DATA;
95 		cmd.ic_data = &cmd_data_in;
96 		cmd.ic_dlen = sizeof (ipmi_fru_read_t);
97 		cmd.ic_lun = 0;
98 
99 		if ((resp = ipmi_send(ihp, &cmd)) == NULL)
100 			return (-1);
101 
102 		(void) memcpy(&count, resp->ic_data, sizeof (uint8_t));
103 		if (count != cmd_data_in.ifr_count) {
104 			(void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH,
105 			    NULL);
106 			return (-1);
107 		}
108 		(void) memcpy((*buf)+offset, (char *)(resp->ic_data)+1, count);
109 		offset += count;
110 	}
111 	return (sz);
112 }
113 
114 /*
115  * See Sect 12 of the IPMI Platform Management FRU Information Storage
116  * Definition (v1.1).
117  *
118  * The FRU Product Info Area contains a number of fields which encode
119  * both the type and length of various name fields into a single byte.
120  * The byte is a bitfield broken down as follows:
121  *
122  *   bits	descr
123  *   ----	-----
124  *   7:6	encoding:
125  *		11b = 8-bit ascii
126  *              10b = 6-bit packed ascii
127  *   5:0	length of data in bytes
128  *
129  * This function extracts the type and length and then copies the data into the
130  * supplied buffer.  If the type is 6-bit packed ASCII then it first converts
131  * the string to an 8-bit ASCII string
132  *
133  * The function returns the length of the data.
134  */
135 static int
136 ipmi_fru_decode_string(uint8_t typelen, char *data, char *buf)
137 {
138 	int i, j = 0, chunks, leftovers;
139 	uint8_t tmp, lo, type, len;
140 
141 	type = typelen >> 6;
142 	len = BITX(typelen, 5, 0);
143 
144 	if (len == 0) {
145 		*buf = '\0';
146 		return (len);
147 	}
148 	/*
149 	 * If the type is 8-bit ASCII, we can simply copy the string and return
150 	 */
151 	if (type == 0x3) {
152 		(void) strncpy(buf, data, len);
153 		*(buf+len) = '\0';
154 		return (len);
155 	} else if (type == 0x1 || type == 0x0) {
156 		/*
157 		 * Yuck - they either used BCD plus encoding, which we don't
158 		 * currently handle, or they used an unspecified encoding type.
159 		 * In these cases we'll set buf to an empty string.  We still
160 		 * need to return the length so that we can get to the next
161 		 * record.
162 		 */
163 		*buf = '\0';
164 		return (len);
165 	}
166 
167 	/*
168 	 * Otherwise, it's 6-bit packed ASCII, so we have to convert the
169 	 * data first
170 	 */
171 	chunks = len / 3;
172 	leftovers = len % 3;
173 
174 	/*
175 	 * First we decode the 6-bit string in chunks of 3 bytes as far as
176 	 * possible
177 	 */
178 	for (i = 0; i < chunks; i++) {
179 		tmp = BITX(*(data+j), 5, 0);
180 		*buf++ = (char)(tmp + 32);
181 
182 		lo = BITX(*(data+j++), 7, 6);
183 		tmp = BITX(*(data+j), 3, 0);
184 		tmp = (tmp << 2) | lo;
185 		*buf++ = (char)(tmp + 32);
186 
187 		lo = BITX(*(data+j++), 7, 4);
188 		tmp = BITX(*(data+j), 1, 0);
189 		tmp = (tmp << 4) | lo;
190 		*buf++ = (char)(tmp + 32);
191 
192 		tmp = BITX(*(data+j++), 7, 2);
193 		*buf++ = (char)(tmp + 32);
194 	}
195 	switch (leftovers) {
196 		case 1:
197 			tmp = BITX(*(data+j), 5, 0);
198 			*buf++ = (char)(tmp + 32);
199 			break;
200 		case 2:
201 			tmp = BITX(*(data+j), 5, 0);
202 			*buf++ = (char)(tmp + 32);
203 
204 			lo = BITX(*(data+j++), 7, 6);
205 			tmp = BITX(*(data+j), 3, 0);
206 			tmp = (tmp << 2) | lo;
207 			*buf++ = (char)(tmp + 32);
208 			break;
209 	}
210 	*buf = '\0';
211 	return (len);
212 }
213 
214 int
215 ipmi_fru_parse_product(ipmi_handle_t *ihp, char *fru_area,
216     ipmi_fru_prod_info_t *buf)
217 {
218 	ipmi_fru_hdr_t fru_hdr;
219 	char *tmp;
220 	uint8_t len, typelen;
221 
222 	(void) memcpy(&fru_hdr, fru_area, sizeof (ipmi_fru_hdr_t));
223 
224 	/*
225 	 * We get the offset to the product info area from the FRU common
226 	 * header which is at the start of the FRU inventory area.
227 	 *
228 	 * The product info area is optional, so if the offset is NULL,
229 	 * indicating that it doesn't exist, then we return an error.
230 	 */
231 	if (!fru_hdr.ifh_product_info_off) {
232 		(void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL);
233 		return (-1);
234 	}
235 
236 	tmp = fru_area + (fru_hdr.ifh_product_info_off * 8) + 3;
237 
238 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
239 	len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_manuf_name);
240 	tmp += len + 1;
241 
242 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
243 	len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_product_name);
244 	tmp += len + 1;
245 
246 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
247 	len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_part_number);
248 	tmp += len + 1;
249 
250 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
251 	len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_product_version);
252 	tmp += len + 1;
253 
254 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
255 	len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_product_serial);
256 	tmp += len + 1;
257 
258 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
259 	(void) ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_asset_tag);
260 
261 	return (0);
262 }
263 
264 
265 /*
266  * The Board Info area is described in Sect 11 of the IPMI Platform Management
267  * FRU Information Storage Definition (v1.1).
268  */
269 int
270 ipmi_fru_parse_board(ipmi_handle_t *ihp, char *fru_area,
271     ipmi_fru_brd_info_t *buf)
272 {
273 	ipmi_fru_hdr_t fru_hdr;
274 	char *tmp;
275 	uint8_t len, typelen;
276 
277 	(void) memcpy(&fru_hdr, fru_area, sizeof (ipmi_fru_hdr_t));
278 
279 	/*
280 	 * We get the offset to the board info area from the FRU common
281 	 * header which is at the start of the FRU inventory area.
282 	 *
283 	 * The board info area is optional, so if the offset is NULL,
284 	 * indicating that it doesn't exist, then we return an error.
285 	 */
286 	if (!fru_hdr.ifh_board_info_off) {
287 		(void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL);
288 		return (-1);
289 	}
290 	tmp = fru_area + (fru_hdr.ifh_board_info_off * 8) + 3;
291 
292 	(void) memcpy(buf->ifbi_manuf_date, tmp, 3);
293 	tmp += 3;
294 
295 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
296 	len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifbi_manuf_name);
297 	tmp += len + 1;
298 
299 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
300 	len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifbi_board_name);
301 	tmp += len + 1;
302 
303 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
304 	len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifbi_product_serial);
305 	tmp += len + 1;
306 
307 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
308 	len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifbi_part_number);
309 
310 	return (0);
311 }
312