/*
* fru_spd_decode.c
*
* IPMI code for handling FRU with SPD data
*
* Author: Novell Inc.
* Pat Campbell plc@novell.com
*
* Copyright 2005 Novell Inc.
*
* Corey Minyard: Removed the SPD-specific function calls and only
* support the new FRU interface.
*
* Copyright 2005 Montavista Software, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <limits.h>
#include <OpenIPMI/ipmiif.h>
#include <OpenIPMI/ipmi_fru.h>
#include <OpenIPMI/ipmi_err.h>
#include <OpenIPMI/internal/ipmi_fru.h>
#include <OpenIPMI/internal/ipmi_malloc.h>
#include "manfid.h"
/*
* From PC SDRAM Serial Presence Detect (SPD) Specification
* Revision 1.2A December, 1997 and
* http://www.simmtester.com/page/news/showpubnews.asp?num=101
*/
/* Used to decode byte 2, Memory Type, of SPD data */
static const struct valstr spd_memtype_vals[] =
{
{0x02, "EDO"},
{0x04, "SDRAM"},
{0x07, "DDR"},
{0x00, NULL},
};
/* Used to decode byte 11, Module Configuration Type, of SPD data */
static const struct valstr spd_config_vals[] =
{
{0x00, "None"},
{0x01, "Parity"},
{0x02, "ECC"},
{0x00, NULL},
};
/* Used to decode byte 8, Module Interface Signal Levels, of SPD data */
static const struct valstr spd_voltage_vals[] =
{
{0x00, "5.0V TTL"},
{0x01, "LVTTL"},
{0x02, "HSTL 1.5V"},
{0x03, "SSTL 3.3V"},
{0x04, "SSTL 2.5V"},
{0x00, NULL},
};
typedef struct i_SPDInfo
{
int size;
const char *memoryType;
const char *voltageInterface;
const char *errorDetection;
const char *manufacturer;
char partNumber[19];
unsigned char rawData[128];
} SPD_info_t;
static void
fru_node_destroy (ipmi_fru_node_t *node)
{
ipmi_fru_t *fru = i_ipmi_fru_node_get_data(node);
ipmi_fru_deref(fru);
}
static int
set_fru_str_info(const char **name,
enum ipmi_fru_data_type_e *dtype,
char **data,
unsigned int *data_len,
const char *iname,
const char *idata)
{
int len = 0;
if (name)
*name = iname;
if (dtype)
*dtype = IPMI_FRU_DATA_ASCII;
if (data) {
char *d;
len = strlen(idata) + 1;
d = ipmi_mem_alloc(len);
if (!d)
return ENOMEM;
strcpy(d, idata);
*data = d;
}
if (data_len) {
if (!len)
len = strlen(idata) + 1;
*data_len = len;
}
return 0;
}
static int
fru_node_get_field (ipmi_fru_node_t *pnode,
unsigned int index,
const char **name,
enum ipmi_fru_data_type_e *dtype,
int *intval,
time_t *time,
double *floatval,
char **data,
unsigned int *data_len,
ipmi_fru_node_t **sub_node)
{
ipmi_fru_t *fru = i_ipmi_fru_node_get_data(pnode);
SPD_info_t *spd_info = i_ipmi_fru_get_rec_data(fru);
int rv;
switch (index) {
case 0:
if (name)
*name = "size";
if (intval)
*intval = spd_info->size;
if (dtype)
*dtype = IPMI_FRU_DATA_INT;
rv = 0;
break;
case 1:
rv = set_fru_str_info(name, dtype, data, data_len, "memory_type",
spd_info->memoryType);
break;
case 2:
rv = set_fru_str_info(name, dtype, data, data_len, "voltage_interface",
spd_info->voltageInterface);
break;
case 3:
rv = set_fru_str_info(name, dtype, data, data_len, "error_detection",
spd_info->errorDetection);
break;
case 4:
rv = set_fru_str_info(name, dtype, data, data_len, "manufacturer",
spd_info->manufacturer);
break;
case 5:
rv = set_fru_str_info(name, dtype, data, data_len, "part_number",
spd_info->partNumber);
break;
default:
rv = EINVAL;
}
return rv;
}
static int
fru_get_root_node (ipmi_fru_t *fru, const char **name, ipmi_fru_node_t **rnode)
{
ipmi_fru_node_t *node;
if (name)
*name = "SPD FRU";
if (rnode) {
node = i_ipmi_fru_node_alloc(fru);
if (!node)
return ENOMEM;
i_ipmi_fru_node_set_data(node, fru);
i_ipmi_fru_node_set_get_field(node, fru_node_get_field);
i_ipmi_fru_node_set_destructor(node, fru_node_destroy);
ipmi_fru_ref(fru);
*rnode = node;
}
return 0;
}
static void
fru_cleanup_recs (ipmi_fru_t *fru)
{
SPD_info_t *spd_info = (SPD_info_t *) i_ipmi_fru_get_rec_data (fru);
if (!spd_info)
return;
ipmi_mem_free(spd_info);
}
static const char *
val2str(uint16_t val, const struct valstr *vs)
{
int i = 0;
while (vs[i].str != NULL) {
if (vs[i].val == val)
return vs[i].str;
i++;
}
return NULL;
}
static void
loadInfo(SPD_info_t *spd_info, unsigned char *spd_data)
{
int i;
memcpy(spd_info->rawData, spd_data, 128);
spd_info->size = spd_data[5] * (spd_data[31] << 2);
spd_info->memoryType = val2str(spd_data[2], spd_memtype_vals);
spd_info->voltageInterface = val2str(spd_data[8], spd_voltage_vals);
spd_info->errorDetection = val2str(spd_data[11], spd_config_vals);
/* handle jedec table bank continuation values */
spd_info->manufacturer = NULL;
if (spd_data[64] != 0x7f)
spd_info->manufacturer = val2str (spd_data[64], jedec_id1_vals);
else if (spd_data[65] != 0x7f)
spd_info->manufacturer = val2str (spd_data[65], jedec_id2_vals);
else if (spd_data[66] != 0x7f)
spd_info->manufacturer = val2str (spd_data[66], jedec_id3_vals);
else if (spd_data[67] != 0x7f)
spd_info->manufacturer = val2str (spd_data[67], jedec_id4_vals);
else if (spd_data[68] != 0x7f)
spd_info->manufacturer = val2str (spd_data[68], jedec_id5_vals);
else
spd_info->manufacturer = val2str (spd_data[69], jedec_id6_vals);
if (spd_info->manufacturer == NULL)
spd_info->manufacturer = "Unknown";
if (spd_data[73]) {
for (i=0; i<18; i++) {
/* Some strings seem to use 0xff for filler. */
if (spd_data[73+i] == 0xff)
break;
spd_info->partNumber[i] = spd_data[73+i];
}
spd_info->partNumber[i] = '\0';
} else {
strcpy(spd_info->partNumber, "Unknown");
}
}
static int
process_fru_spd_info(ipmi_fru_t *fru)
{
unsigned char *data = i_ipmi_fru_get_data_ptr(fru);
SPD_info_t *spd_info;
/*
* We are here because FRU checksum failed
* ipmitool uses dev_type and dev_type_modifier
* to determine if it is an SPD. ipmiutil uses
* first byte of 0x80, which is what we will use
* to start with
*/
if (data[0] == 0x80) {
i_ipmi_fru_set_op_get_root_node(fru, fru_get_root_node);
spd_info = ipmi_mem_alloc(sizeof (*spd_info));
if (!spd_info)
return ENOMEM;
memset(spd_info, 0, sizeof(*spd_info));
loadInfo(spd_info, data);
i_ipmi_fru_set_rec_data(fru, spd_info);
i_ipmi_fru_set_op_cleanup_recs(fru, fru_cleanup_recs);
return 0;
}
return EBADF;
}
/************************************************************************
*
* Init/shutdown
*
************************************************************************/
static int spd_initialized;
int
i_ipmi_fru_spd_decoder_init (void)
{
int rv;
if (spd_initialized)
return 0;
rv = i_ipmi_fru_register_decoder (process_fru_spd_info);
if (!rv)
spd_initialized = 1;
return rv;
}
void
i_ipmi_fru_spd_decoder_shutdown (void)
{
if (!spd_initialized)
return;
i_ipmi_fru_deregister_decoder (process_fru_spd_info);
spd_initialized = 0;
}