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 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 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 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