/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include "ipmi_impl.h" /* * Extracts bits between index h (high, inclusive) and l (low, exclusive) from * u, which must be an unsigned integer. */ #define BITX(u, h, l) (((u) >> (l)) & ((1LU << ((h) - (l) + 1LU)) - 1LU)) typedef struct ipmi_fru_read { uint8_t ifr_devid; uint8_t ifr_offset_lsb; uint8_t ifr_offset_msb; uint8_t ifr_count; } ipmi_fru_read_t; /* * returns: size of FRU inventory data in bytes, on success * -1, otherwise */ int ipmi_fru_read(ipmi_handle_t *ihp, ipmi_sdr_fru_locator_t *fru_loc, char **buf) { ipmi_cmd_t cmd, *resp; uint8_t count, devid; uint16_t sz, offset = 0; ipmi_fru_read_t cmd_data_in; devid = fru_loc->_devid_or_slaveaddr._logical._is_fl_devid; /* * First we issue a command to retrieve the size of the specified FRU's * inventory area */ cmd.ic_netfn = IPMI_NETFN_STORAGE; cmd.ic_cmd = IPMI_CMD_GET_FRU_INV_AREA; cmd.ic_data = &devid; cmd.ic_dlen = sizeof (uint8_t); cmd.ic_lun = 0; if ((resp = ipmi_send(ihp, &cmd)) == NULL) return (-1); if (resp->ic_dlen != 3) { (void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH, NULL); return (-1); } (void) memcpy(&sz, resp->ic_data, sizeof (uint16_t)); if ((*buf = malloc(sz)) == NULL) { (void) ipmi_set_error(ihp, EIPMI_NOMEM, NULL); return (-1); } while (offset < sz) { cmd_data_in.ifr_devid = devid; cmd_data_in.ifr_offset_lsb = BITX(offset, 7, 0); cmd_data_in.ifr_offset_msb = BITX(offset, 15, 8); if ((sz - offset) < 128) cmd_data_in.ifr_count = sz - offset; else cmd_data_in.ifr_count = 128; cmd.ic_netfn = IPMI_NETFN_STORAGE; cmd.ic_cmd = IPMI_CMD_READ_FRU_DATA; cmd.ic_data = &cmd_data_in; cmd.ic_dlen = sizeof (ipmi_fru_read_t); cmd.ic_lun = 0; if ((resp = ipmi_send(ihp, &cmd)) == NULL) return (-1); (void) memcpy(&count, resp->ic_data, sizeof (uint8_t)); if (count != cmd_data_in.ifr_count) { (void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH, NULL); return (-1); } (void) memcpy((*buf)+offset, (char *)(resp->ic_data)+1, count); offset += count; } return (sz); } /* * See Sect 12 of the IPMI Platform Management FRU Information Storage * Definition (v1.1). * * The FRU Product Info Area contains a number of fields which encode * both the type and length of various name fields into a single byte. * The byte is a bitfield broken down as follows: * * bits descr * ---- ----- * 7:6 encoding: * 11b = 8-bit ascii * 10b = 6-bit packed ascii * 5:0 length of data in bytes * * This function extracts the type and length and then copies the data into the * supplied buffer. If the type is 6-bit packed ASCII then it first converts * the string to an 8-bit ASCII string * * The function returns the length of the data. */ static int ipmi_fru_decode_string(uint8_t typelen, char *data, char *buf) { int i, j = 0, chunks, leftovers; uint8_t tmp, lo, type, len; type = typelen >> 6; len = BITX(typelen, 5, 0); if (len == 0) { *buf = '\0'; return (len); } /* * If the type is 8-bit ASCII, we can simply copy the string and return */ if (type == 0x3) { (void) strncpy(buf, data, len); *(buf+len) = '\0'; return (len); } else if (type == 0x1 || type == 0x0) { /* * Yuck - they either used BCD plus encoding, which we don't * currently handle, or they used an unspecified encoding type. * In these cases we'll set buf to an empty string. We still * need to return the length so that we can get to the next * record. */ *buf = '\0'; return (len); } /* * Otherwise, it's 6-bit packed ASCII, so we have to convert the * data first */ chunks = len / 3; leftovers = len % 3; /* * First we decode the 6-bit string in chunks of 3 bytes as far as * possible */ for (i = 0; i < chunks; i++) { tmp = BITX(*(data+j), 5, 0); *buf++ = (char)(tmp + 32); lo = BITX(*(data+j++), 7, 6); tmp = BITX(*(data+j), 3, 0); tmp = (tmp << 2) | lo; *buf++ = (char)(tmp + 32); lo = BITX(*(data+j++), 7, 4); tmp = BITX(*(data+j), 1, 0); tmp = (tmp << 4) | lo; *buf++ = (char)(tmp + 32); tmp = BITX(*(data+j++), 7, 2); *buf++ = (char)(tmp + 32); } switch (leftovers) { case 1: tmp = BITX(*(data+j), 5, 0); *buf++ = (char)(tmp + 32); break; case 2: tmp = BITX(*(data+j), 5, 0); *buf++ = (char)(tmp + 32); lo = BITX(*(data+j++), 7, 6); tmp = BITX(*(data+j), 3, 0); tmp = (tmp << 2) | lo; *buf++ = (char)(tmp + 32); break; } *buf = '\0'; return (len); } int ipmi_fru_parse_product(ipmi_handle_t *ihp, char *fru_area, ipmi_fru_prod_info_t *buf) { ipmi_fru_hdr_t fru_hdr; char *tmp; uint8_t len, typelen; (void) memcpy(&fru_hdr, fru_area, sizeof (ipmi_fru_hdr_t)); /* * We get the offset to the product info area from the FRU common * header which is at the start of the FRU inventory area. * * The product info area is optional, so if the offset is NULL, * indicating that it doesn't exist, then we return an error. */ if (!fru_hdr.ifh_product_info_off) { (void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL); return (-1); } tmp = fru_area + (fru_hdr.ifh_product_info_off * 8) + 3; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_manuf_name); tmp += len + 1; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_product_name); tmp += len + 1; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_part_number); tmp += len + 1; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_product_version); tmp += len + 1; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_product_serial); tmp += len + 1; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); (void) ipmi_fru_decode_string(typelen, tmp+1, buf->ifpi_asset_tag); return (0); } /* * The Board Info area is described in Sect 11 of the IPMI Platform Management * FRU Information Storage Definition (v1.1). */ int ipmi_fru_parse_board(ipmi_handle_t *ihp, char *fru_area, ipmi_fru_brd_info_t *buf) { ipmi_fru_hdr_t fru_hdr; char *tmp; uint8_t len, typelen; (void) memcpy(&fru_hdr, fru_area, sizeof (ipmi_fru_hdr_t)); /* * We get the offset to the board info area from the FRU common * header which is at the start of the FRU inventory area. * * The board info area is optional, so if the offset is NULL, * indicating that it doesn't exist, then we return an error. */ if (!fru_hdr.ifh_board_info_off) { (void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL); return (-1); } tmp = fru_area + (fru_hdr.ifh_board_info_off * 8) + 3; (void) memcpy(buf->ifbi_manuf_date, tmp, 3); tmp += 3; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifbi_manuf_name); tmp += len + 1; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifbi_board_name); tmp += len + 1; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifbi_product_serial); tmp += len + 1; (void) memcpy(&typelen, tmp, sizeof (uint8_t)); len = ipmi_fru_decode_string(typelen, tmp+1, buf->ifbi_part_number); return (0); }