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