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 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 /*
26  * Copyright (c) 2017, Joyent, Inc.
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 /*
40  * The default and minimum size in bytes that will be used when reading
41  * the FRU inventory area.
42  */
43 #define	DEF_CHUNK_SZ	128
44 #define	MIN_CHUNK_SZ	16
45 
46 typedef struct ipmi_fru_read
47 {
48 	uint8_t		ifr_devid;
49 	uint8_t		ifr_offset_lsb;
50 	uint8_t		ifr_offset_msb;
51 	uint8_t		ifr_count;
52 } ipmi_fru_read_t;
53 
54 /*
55  * returns: size of FRU inventory data in bytes, on success
56  *          -1, otherwise
57  */
58 int
ipmi_fru_read(ipmi_handle_t * ihp,ipmi_sdr_fru_locator_t * fru_loc,char ** buf)59 ipmi_fru_read(ipmi_handle_t *ihp, ipmi_sdr_fru_locator_t *fru_loc, char **buf)
60 {
61 	ipmi_cmd_t cmd, *resp;
62 	int ierrno;
63 	uint8_t count, devid, chunksz;
64 	uint16_t sz, offset = 0;
65 	ipmi_fru_read_t cmd_data_in;
66 	char *tmp;
67 
68 	devid = fru_loc->_devid_or_slaveaddr._logical._is_fl_devid;
69 	/*
70 	 * First we issue a command to retrieve the size of the specified FRU's
71 	 * inventory area
72 	 */
73 	cmd.ic_netfn = IPMI_NETFN_STORAGE;
74 	cmd.ic_cmd = IPMI_CMD_GET_FRU_INV_AREA;
75 	cmd.ic_data = &devid;
76 	cmd.ic_dlen = sizeof (uint8_t);
77 	cmd.ic_lun = 0;
78 
79 	if ((resp = ipmi_send(ihp, &cmd)) == NULL)
80 		return (-1);
81 
82 	if (resp->ic_dlen != 3) {
83 		(void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH, NULL);
84 		return (-1);
85 	}
86 
87 	(void) memcpy(&sz, resp->ic_data, sizeof (uint16_t));
88 	if ((tmp = malloc(sz)) == NULL) {
89 		(void) ipmi_set_error(ihp, EIPMI_NOMEM, NULL);
90 		return (-1);
91 	}
92 
93 	chunksz = DEF_CHUNK_SZ;
94 	while (offset < sz) {
95 		cmd_data_in.ifr_devid = devid;
96 		cmd_data_in.ifr_offset_lsb = BITX(offset, 7, 0);
97 		cmd_data_in.ifr_offset_msb = BITX(offset, 15, 8);
98 		if ((sz - offset) < chunksz)
99 			cmd_data_in.ifr_count = sz - offset;
100 		else
101 			cmd_data_in.ifr_count = chunksz;
102 
103 		cmd.ic_netfn = IPMI_NETFN_STORAGE;
104 		cmd.ic_cmd = IPMI_CMD_READ_FRU_DATA;
105 		cmd.ic_data = &cmd_data_in;
106 		cmd.ic_dlen = sizeof (ipmi_fru_read_t);
107 		cmd.ic_lun = 0;
108 
109 		/*
110 		 * The FRU area must be read in chunks as its total size will
111 		 * be larger than what would fit in a single message.  The
112 		 * maximum size of a message can vary between platforms so
113 		 * if while attempting to read a chunk we receive an error code
114 		 * indicating that the requested chunk size is invalid, we will
115 		 * perform a reverse exponential backoff of the chunk size until
116 		 * either the read succeeds or we hit bottom, at which point
117 		 * we'll fail the operation.
118 		 */
119 		if ((resp = ipmi_send(ihp, &cmd)) == NULL) {
120 			ierrno = ipmi_errno(ihp);
121 			if (chunksz > MIN_CHUNK_SZ &&
122 			    (ierrno == EIPMI_DATA_LENGTH_EXCEEDED ||
123 			    ierrno == EIPMI_INVALID_REQUEST)) {
124 				chunksz = chunksz >> 1;
125 				continue;
126 			}
127 			free(tmp);
128 			return (-1);
129 		}
130 
131 		(void) memcpy(&count, resp->ic_data, sizeof (uint8_t));
132 		if (count != cmd_data_in.ifr_count) {
133 			(void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH,
134 			    NULL);
135 			free(tmp);
136 			return (-1);
137 		}
138 		(void) memcpy(tmp+offset, (char *)(resp->ic_data)+1, count);
139 		offset += count;
140 	}
141 	*buf = tmp;
142 	return (sz);
143 }
144 
145 int
ipmi_fru_parse_product(ipmi_handle_t * ihp,char * fru_area,ipmi_fru_prod_info_t * buf)146 ipmi_fru_parse_product(ipmi_handle_t *ihp, char *fru_area,
147     ipmi_fru_prod_info_t *buf)
148 {
149 	ipmi_fru_hdr_t fru_hdr;
150 	char *tmp;
151 	uint8_t len, typelen;
152 
153 	(void) memcpy(&fru_hdr, fru_area, sizeof (ipmi_fru_hdr_t));
154 
155 	/*
156 	 * We get the offset to the product info area from the FRU common
157 	 * header which is at the start of the FRU inventory area.
158 	 *
159 	 * The product info area is optional, so if the offset is NULL,
160 	 * indicating that it doesn't exist, then we return an error.
161 	 */
162 	if (!fru_hdr.ifh_product_info_off) {
163 		(void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL);
164 		return (-1);
165 	}
166 
167 	tmp = fru_area + (fru_hdr.ifh_product_info_off * 8) + 3;
168 
169 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
170 	len = BITX(typelen, 5, 0);
171 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifpi_manuf_name);
172 	tmp += len + 1;
173 
174 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
175 	len = BITX(typelen, 5, 0);
176 	ipmi_decode_string((typelen >> 6), len, tmp+1,
177 	    buf->ifpi_product_name);
178 	tmp += len + 1;
179 
180 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
181 	len = BITX(typelen, 5, 0);
182 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifpi_part_number);
183 	tmp += len + 1;
184 
185 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
186 	len = BITX(typelen, 5, 0);
187 	ipmi_decode_string((typelen >> 6), len, tmp+1,
188 	    buf->ifpi_product_version);
189 	tmp += len + 1;
190 
191 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
192 	len = BITX(typelen, 5, 0);
193 	ipmi_decode_string((typelen >> 6), len, tmp+1,
194 	    buf->ifpi_product_serial);
195 	tmp += len + 1;
196 
197 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
198 	len = BITX(typelen, 5, 0);
199 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifpi_asset_tag);
200 
201 	return (0);
202 }
203 
204 
205 /*
206  * The Board Info area is described in Sect 11 of the IPMI Platform Management
207  * FRU Information Storage Definition (v1.1).
208  */
209 int
ipmi_fru_parse_board(ipmi_handle_t * ihp,char * fru_area,ipmi_fru_brd_info_t * buf)210 ipmi_fru_parse_board(ipmi_handle_t *ihp, char *fru_area,
211     ipmi_fru_brd_info_t *buf)
212 {
213 	ipmi_fru_hdr_t fru_hdr;
214 	char *tmp;
215 	uint8_t len, typelen;
216 
217 	(void) memcpy(&fru_hdr, fru_area, sizeof (ipmi_fru_hdr_t));
218 
219 	/*
220 	 * We get the offset to the board info area from the FRU common
221 	 * header which is at the start of the FRU inventory area.
222 	 *
223 	 * The board info area is optional, so if the offset is NULL,
224 	 * indicating that it doesn't exist, then we return an error.
225 	 */
226 	if (!fru_hdr.ifh_board_info_off) {
227 		(void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL);
228 		return (-1);
229 	}
230 	tmp = fru_area + (fru_hdr.ifh_board_info_off * 8) + 3;
231 
232 	(void) memcpy(buf->ifbi_manuf_date, tmp, 3);
233 	tmp += 3;
234 
235 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
236 	len = BITX(typelen, 5, 0);
237 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifbi_manuf_name);
238 	tmp += len + 1;
239 
240 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
241 	len = BITX(typelen, 5, 0);
242 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifbi_board_name);
243 	tmp += len + 1;
244 
245 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
246 	len = BITX(typelen, 5, 0);
247 	ipmi_decode_string((typelen >> 6), len, tmp+1,
248 	    buf->ifbi_product_serial);
249 	tmp += len + 1;
250 
251 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
252 	len = BITX(typelen, 5, 0);
253 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifbi_part_number);
254 
255 	return (0);
256 }
257