14557a2a1Srobj /*
24557a2a1Srobj  * CDDL HEADER START
34557a2a1Srobj  *
44557a2a1Srobj  * The contents of this file are subject to the terms of the
54557a2a1Srobj  * Common Development and Distribution License (the "License").
64557a2a1Srobj  * You may not use this file except in compliance with the License.
74557a2a1Srobj  *
84557a2a1Srobj  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
94557a2a1Srobj  * or http://www.opensolaris.org/os/licensing.
104557a2a1Srobj  * See the License for the specific language governing permissions
114557a2a1Srobj  * and limitations under the License.
124557a2a1Srobj  *
134557a2a1Srobj  * When distributing Covered Code, include this CDDL HEADER in each
144557a2a1Srobj  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
154557a2a1Srobj  * If applicable, add the following below this CDDL HEADER, with the
164557a2a1Srobj  * fields enclosed by brackets "[]" replaced with your own identifying
174557a2a1Srobj  * information: Portions Copyright [yyyy] [name of copyright owner]
184557a2a1Srobj  *
194557a2a1Srobj  * CDDL HEADER END
204557a2a1Srobj  */
214557a2a1Srobj /*
222eeaed14Srobj  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
234557a2a1Srobj  * Use is subject to license terms.
244557a2a1Srobj  */
25*b10f758dSRob Johnston /*
26*b10f758dSRob Johnston  * Copyright (c) 2017, Joyent, Inc.
27*b10f758dSRob Johnston  */
284557a2a1Srobj #include <libipmi.h>
294557a2a1Srobj #include <string.h>
304557a2a1Srobj 
314557a2a1Srobj #include "ipmi_impl.h"
324557a2a1Srobj 
334557a2a1Srobj /*
344557a2a1Srobj  * Extracts bits between index h (high, inclusive) and l (low, exclusive) from
354557a2a1Srobj  * u, which must be an unsigned integer.
364557a2a1Srobj  */
374557a2a1Srobj #define	BITX(u, h, l)	(((u) >> (l)) & ((1LU << ((h) - (l) + 1LU)) - 1LU))
384557a2a1Srobj 
39*b10f758dSRob Johnston /*
40*b10f758dSRob Johnston  * The default and minimum size in bytes that will be used when reading
41*b10f758dSRob Johnston  * the FRU inventory area.
42*b10f758dSRob Johnston  */
43*b10f758dSRob Johnston #define	DEF_CHUNK_SZ	128
44*b10f758dSRob Johnston #define	MIN_CHUNK_SZ	16
45*b10f758dSRob Johnston 
464557a2a1Srobj typedef struct ipmi_fru_read
474557a2a1Srobj {
484557a2a1Srobj 	uint8_t		ifr_devid;
494557a2a1Srobj 	uint8_t		ifr_offset_lsb;
504557a2a1Srobj 	uint8_t		ifr_offset_msb;
514557a2a1Srobj 	uint8_t		ifr_count;
524557a2a1Srobj } ipmi_fru_read_t;
534557a2a1Srobj 
544557a2a1Srobj /*
554557a2a1Srobj  * returns: size of FRU inventory data in bytes, on success
564557a2a1Srobj  *          -1, otherwise
574557a2a1Srobj  */
584557a2a1Srobj int
ipmi_fru_read(ipmi_handle_t * ihp,ipmi_sdr_fru_locator_t * fru_loc,char ** buf)594557a2a1Srobj ipmi_fru_read(ipmi_handle_t *ihp, ipmi_sdr_fru_locator_t *fru_loc, char **buf)
604557a2a1Srobj {
614557a2a1Srobj 	ipmi_cmd_t cmd, *resp;
62*b10f758dSRob Johnston 	int ierrno;
63*b10f758dSRob Johnston 	uint8_t count, devid, chunksz;
644557a2a1Srobj 	uint16_t sz, offset = 0;
654557a2a1Srobj 	ipmi_fru_read_t cmd_data_in;
662cb5535aSrobj 	char *tmp;
674557a2a1Srobj 
684557a2a1Srobj 	devid = fru_loc->_devid_or_slaveaddr._logical._is_fl_devid;
694557a2a1Srobj 	/*
704557a2a1Srobj 	 * First we issue a command to retrieve the size of the specified FRU's
714557a2a1Srobj 	 * inventory area
724557a2a1Srobj 	 */
734557a2a1Srobj 	cmd.ic_netfn = IPMI_NETFN_STORAGE;
744557a2a1Srobj 	cmd.ic_cmd = IPMI_CMD_GET_FRU_INV_AREA;
754557a2a1Srobj 	cmd.ic_data = &devid;
764557a2a1Srobj 	cmd.ic_dlen = sizeof (uint8_t);
774557a2a1Srobj 	cmd.ic_lun = 0;
784557a2a1Srobj 
794557a2a1Srobj 	if ((resp = ipmi_send(ihp, &cmd)) == NULL)
804557a2a1Srobj 		return (-1);
814557a2a1Srobj 
824557a2a1Srobj 	if (resp->ic_dlen != 3) {
834557a2a1Srobj 		(void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH, NULL);
844557a2a1Srobj 		return (-1);
854557a2a1Srobj 	}
864557a2a1Srobj 
874557a2a1Srobj 	(void) memcpy(&sz, resp->ic_data, sizeof (uint16_t));
882cb5535aSrobj 	if ((tmp = malloc(sz)) == NULL) {
894557a2a1Srobj 		(void) ipmi_set_error(ihp, EIPMI_NOMEM, NULL);
904557a2a1Srobj 		return (-1);
914557a2a1Srobj 	}
924557a2a1Srobj 
93*b10f758dSRob Johnston 	chunksz = DEF_CHUNK_SZ;
944557a2a1Srobj 	while (offset < sz) {
954557a2a1Srobj 		cmd_data_in.ifr_devid = devid;
964557a2a1Srobj 		cmd_data_in.ifr_offset_lsb = BITX(offset, 7, 0);
974557a2a1Srobj 		cmd_data_in.ifr_offset_msb = BITX(offset, 15, 8);
98*b10f758dSRob Johnston 		if ((sz - offset) < chunksz)
994557a2a1Srobj 			cmd_data_in.ifr_count = sz - offset;
1004557a2a1Srobj 		else
101*b10f758dSRob Johnston 			cmd_data_in.ifr_count = chunksz;
1024557a2a1Srobj 
1034557a2a1Srobj 		cmd.ic_netfn = IPMI_NETFN_STORAGE;
1044557a2a1Srobj 		cmd.ic_cmd = IPMI_CMD_READ_FRU_DATA;
1054557a2a1Srobj 		cmd.ic_data = &cmd_data_in;
1064557a2a1Srobj 		cmd.ic_dlen = sizeof (ipmi_fru_read_t);
1074557a2a1Srobj 		cmd.ic_lun = 0;
1084557a2a1Srobj 
109*b10f758dSRob Johnston 		/*
110*b10f758dSRob Johnston 		 * The FRU area must be read in chunks as its total size will
111*b10f758dSRob Johnston 		 * be larger than what would fit in a single message.  The
112*b10f758dSRob Johnston 		 * maximum size of a message can vary between platforms so
113*b10f758dSRob Johnston 		 * if while attempting to read a chunk we receive an error code
114*b10f758dSRob Johnston 		 * indicating that the requested chunk size is invalid, we will
115*b10f758dSRob Johnston 		 * perform a reverse exponential backoff of the chunk size until
116*b10f758dSRob Johnston 		 * either the read succeeds or we hit bottom, at which point
117*b10f758dSRob Johnston 		 * we'll fail the operation.
118*b10f758dSRob Johnston 		 */
1192cb5535aSrobj 		if ((resp = ipmi_send(ihp, &cmd)) == NULL) {
120*b10f758dSRob Johnston 			ierrno = ipmi_errno(ihp);
121*b10f758dSRob Johnston 			if (chunksz > MIN_CHUNK_SZ &&
122*b10f758dSRob Johnston 			    (ierrno == EIPMI_DATA_LENGTH_EXCEEDED ||
123*b10f758dSRob Johnston 			    ierrno == EIPMI_INVALID_REQUEST)) {
124*b10f758dSRob Johnston 				chunksz = chunksz >> 1;
125*b10f758dSRob Johnston 				continue;
126*b10f758dSRob Johnston 			}
1272cb5535aSrobj 			free(tmp);
1284557a2a1Srobj 			return (-1);
1292cb5535aSrobj 		}
1304557a2a1Srobj 
1314557a2a1Srobj 		(void) memcpy(&count, resp->ic_data, sizeof (uint8_t));
1324557a2a1Srobj 		if (count != cmd_data_in.ifr_count) {
1334557a2a1Srobj 			(void) ipmi_set_error(ihp, EIPMI_BAD_RESPONSE_LENGTH,
1344557a2a1Srobj 			    NULL);
1352cb5535aSrobj 			free(tmp);
1364557a2a1Srobj 			return (-1);
1374557a2a1Srobj 		}
1382cb5535aSrobj 		(void) memcpy(tmp+offset, (char *)(resp->ic_data)+1, count);
1394557a2a1Srobj 		offset += count;
1404557a2a1Srobj 	}
1412cb5535aSrobj 	*buf = tmp;
1424557a2a1Srobj 	return (sz);
1434557a2a1Srobj }
1444557a2a1Srobj 
1454557a2a1Srobj int
ipmi_fru_parse_product(ipmi_handle_t * ihp,char * fru_area,ipmi_fru_prod_info_t * buf)1464557a2a1Srobj ipmi_fru_parse_product(ipmi_handle_t *ihp, char *fru_area,
1474557a2a1Srobj     ipmi_fru_prod_info_t *buf)
1484557a2a1Srobj {
1494557a2a1Srobj 	ipmi_fru_hdr_t fru_hdr;
1504557a2a1Srobj 	char *tmp;
1514557a2a1Srobj 	uint8_t len, typelen;
1524557a2a1Srobj 
1534557a2a1Srobj 	(void) memcpy(&fru_hdr, fru_area, sizeof (ipmi_fru_hdr_t));
1544557a2a1Srobj 
1554557a2a1Srobj 	/*
1564557a2a1Srobj 	 * We get the offset to the product info area from the FRU common
1574557a2a1Srobj 	 * header which is at the start of the FRU inventory area.
1584557a2a1Srobj 	 *
1594557a2a1Srobj 	 * The product info area is optional, so if the offset is NULL,
1604557a2a1Srobj 	 * indicating that it doesn't exist, then we return an error.
1614557a2a1Srobj 	 */
1624557a2a1Srobj 	if (!fru_hdr.ifh_product_info_off) {
1634557a2a1Srobj 		(void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL);
1644557a2a1Srobj 		return (-1);
1654557a2a1Srobj 	}
1664557a2a1Srobj 
1674557a2a1Srobj 	tmp = fru_area + (fru_hdr.ifh_product_info_off * 8) + 3;
1684557a2a1Srobj 
1694557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
1702cb5535aSrobj 	len = BITX(typelen, 5, 0);
1712eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifpi_manuf_name);
1724557a2a1Srobj 	tmp += len + 1;
1734557a2a1Srobj 
1744557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
1752cb5535aSrobj 	len = BITX(typelen, 5, 0);
1762eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1,
1772eeaed14Srobj 	    buf->ifpi_product_name);
1784557a2a1Srobj 	tmp += len + 1;
1794557a2a1Srobj 
1804557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
1812cb5535aSrobj 	len = BITX(typelen, 5, 0);
1822eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifpi_part_number);
1834557a2a1Srobj 	tmp += len + 1;
1844557a2a1Srobj 
1854557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
1862cb5535aSrobj 	len = BITX(typelen, 5, 0);
1872eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1,
1882eeaed14Srobj 	    buf->ifpi_product_version);
1894557a2a1Srobj 	tmp += len + 1;
1904557a2a1Srobj 
1914557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
1922cb5535aSrobj 	len = BITX(typelen, 5, 0);
1932eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1,
1942eeaed14Srobj 	    buf->ifpi_product_serial);
1954557a2a1Srobj 	tmp += len + 1;
1964557a2a1Srobj 
1974557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
1982cb5535aSrobj 	len = BITX(typelen, 5, 0);
1992eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifpi_asset_tag);
2004557a2a1Srobj 
2014557a2a1Srobj 	return (0);
2024557a2a1Srobj }
2034557a2a1Srobj 
2044557a2a1Srobj 
2054557a2a1Srobj /*
2064557a2a1Srobj  * The Board Info area is described in Sect 11 of the IPMI Platform Management
2074557a2a1Srobj  * FRU Information Storage Definition (v1.1).
2084557a2a1Srobj  */
2094557a2a1Srobj int
ipmi_fru_parse_board(ipmi_handle_t * ihp,char * fru_area,ipmi_fru_brd_info_t * buf)2104557a2a1Srobj ipmi_fru_parse_board(ipmi_handle_t *ihp, char *fru_area,
2114557a2a1Srobj     ipmi_fru_brd_info_t *buf)
2124557a2a1Srobj {
2134557a2a1Srobj 	ipmi_fru_hdr_t fru_hdr;
2144557a2a1Srobj 	char *tmp;
2154557a2a1Srobj 	uint8_t len, typelen;
2164557a2a1Srobj 
2174557a2a1Srobj 	(void) memcpy(&fru_hdr, fru_area, sizeof (ipmi_fru_hdr_t));
2184557a2a1Srobj 
2194557a2a1Srobj 	/*
2204557a2a1Srobj 	 * We get the offset to the board info area from the FRU common
2214557a2a1Srobj 	 * header which is at the start of the FRU inventory area.
2224557a2a1Srobj 	 *
2234557a2a1Srobj 	 * The board info area is optional, so if the offset is NULL,
2244557a2a1Srobj 	 * indicating that it doesn't exist, then we return an error.
2254557a2a1Srobj 	 */
2264557a2a1Srobj 	if (!fru_hdr.ifh_board_info_off) {
2274557a2a1Srobj 		(void) ipmi_set_error(ihp, EIPMI_NOT_PRESENT, NULL);
2284557a2a1Srobj 		return (-1);
2294557a2a1Srobj 	}
2304557a2a1Srobj 	tmp = fru_area + (fru_hdr.ifh_board_info_off * 8) + 3;
2314557a2a1Srobj 
2324557a2a1Srobj 	(void) memcpy(buf->ifbi_manuf_date, tmp, 3);
2334557a2a1Srobj 	tmp += 3;
2344557a2a1Srobj 
2354557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
2362cb5535aSrobj 	len = BITX(typelen, 5, 0);
2372eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifbi_manuf_name);
2384557a2a1Srobj 	tmp += len + 1;
2394557a2a1Srobj 
2404557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
2412cb5535aSrobj 	len = BITX(typelen, 5, 0);
2422eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifbi_board_name);
2434557a2a1Srobj 	tmp += len + 1;
2444557a2a1Srobj 
2454557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
2462cb5535aSrobj 	len = BITX(typelen, 5, 0);
2472eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1,
2482eeaed14Srobj 	    buf->ifbi_product_serial);
2494557a2a1Srobj 	tmp += len + 1;
2504557a2a1Srobj 
2514557a2a1Srobj 	(void) memcpy(&typelen, tmp, sizeof (uint8_t));
2522cb5535aSrobj 	len = BITX(typelen, 5, 0);
2532eeaed14Srobj 	ipmi_decode_string((typelen >> 6), len, tmp+1, buf->ifbi_part_number);
2544557a2a1Srobj 
2554557a2a1Srobj 	return (0);
2564557a2a1Srobj }
257