/*
 * ifru  (was fruconfig.c)
 *
 * This tool reads the FRU configuration, and optionally sets the asset
 * tag field in the FRU data.  See IPMI v1.5 spec section 28.
 * It can use either the Intel /dev/imb driver or VALinux /dev/ipmikcs.
 *
 * Author:  Andy Cress  arcress at users.sourceforge.net
 *
 * Copyright (c) 2009 Kontron America, Inc.
 *
 * 10/28/02 Andy Cress - created
 * 12/11/02 Andy Cress v0.9  - disable write until checksum fixed.
 * 12/13/02 Andy Cress v0.91 - don't abort if cc=c9 in load_fru loop.
 * 01/27/03 Andy Cress v0.92 - more debug, do checksums,
 * 01/28/03 Andy Cress v1.0  do writes in small chunks, tested w SCB2 & STL2
 * 02/19/03 Andy Cress v1.1  also get System GUID
 * 03/10/03 Andy Cress v1.2  do better bounds checking on FRU size
 * 04/30/03 Andy Cress v1.3  Board Part# & Serial# reversed
 * 05/13/03 Andy Cress v1.4  Added Chassis fields
 * 06/19/03 Andy Cress v1.5  added errno.h (email from Travers Carter)
 * 05/03/04 Andy Cress v1.6  BladeCenter has no product area, only board area
 * 05/05/04 Andy Cress v1.7  call ipmi_close before exit,
 *                           added WIN32 compile options.
 * 11/01/04 Andy Cress v1.8  add -N / -R for remote nodes   
 * 12/10/04 Andy Cress v1.9  add gnu freeipmi interface
 * 01/13/05 Andy Cress v1.10 add logic to scan SDRs for all FRU devices,
 *                           and interpret them
 * 01/17/05 Andy Cress v1.11 decode SPD Manufacturer
 * 01/21/05 Andy Cress v1.12 format SystemGUID display
 * 02/03/05 Andy Cress v1.13 fixed fwords bit mask in load_fru,
 *                           decode DIMM size from SPD also.
 * 02/04/05 Andy Cress v1.14 decode FRU Board Mfg DateTime
 * 03/16/05 Andy Cress v1.15 show Asset Tag Length earlier
 * 05/24/05 Andy Cress v1.16 only do write_asset if successful show_fru
 * 06/20/05 Andy Cress v1.17 handle Device SDRs also for ATCA
 * 08/22/05 Andy Cress v1.18 allow setting Product Serial Number also (-s),
 *                           also add -b option to show only baseboard data.
 * 10/31/06 Andy Cress v1.25 handle 1-char asset/serial strings (avoid c1)
 */
/*M*
Copyright (c) 2009 Kontron America, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

  a.. Redistributions of source code must retain the above copyright notice, 
      this list of conditions and the following disclaimer. 
  b.. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution. 
  c.. Neither the name of Kontron nor the names of its contributors 
      may be used to endorse or promote products derived from this software 
      without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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.
 *M*/
#ifdef WIN32
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "getopt.h"
#elif defined(DOS)
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "getopt.h"
#else
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#if defined(HPUX)
/* getopt is defined in stdio.h */
#elif defined(MACOS)
/* getopt is defined in unistd.h */
#include <unistd.h>
#else
#include <errno.h>
#endif
#endif
#include <time.h>

#include "ipmicmd.h" 
#include "ipicmg.h" 
#include "oem_intel.h" 
#include "ifru.h" 

#define PICMG_CHILD   1
#define MIN_SDR_SZ  8
#ifndef URNLOOPS
#define URNLOOPS 1000
#endif
extern int get_BiosVersion(char *str);
extern int get_SystemGuid(uchar *guid);
extern void fmt_time(time_t etime, char *buf, int bufsz); /*see ievents.c*/
extern int get_LastError( void );  /* ipmilan.c */
extern int GetSDRRepositoryInfo(int *nret, int *fdev);  /*isensor.h*/
#ifdef METACOMMAND
extern int ipmi_kontronoem_main(void * intf, int  argc, char ** argv);
#endif

static char *progname  = "ifru";
static char *progver   = "3.00";
static int  vend_id = 0; 
static int  prod_id = 0; 
static char fdebug = 0;
static char fpicmg = 0;
static char fonlybase = 0;
static char fonlyhsc = 0;
static int  fwritefru = 0;
static int  fdevsdrs = 0;
static char fshowlen = 0;
static char ftestshow = 0;
// static char fgetfru  = 0;
static char fdoanyway = 0;
static char fprivset = 0;
static char fset_mc = 0;
static char fcanonical = 0;
static char foemkontron = 0;
static char fbasefru = 1;
static char fdump = 0;
static char frestore = 0;
static char fchild = 0;        /* =1 follow child MCs if picmg bladed*/
static char do_systeminfo = 1;
static char do_guid = 1;
static char bdelim = ':';
static uchar bmc_sa = BMC_SA;  /*defaults to 0x20*/
static uchar guid[17] = "";
static char *binfile = NULL;
static uchar *frubuf = NULL;
#define FIELD_LEN   20
#define SZ_PRODAREA   520 /* product area max is 8*32 + 3 = 259 mod 8 = 264 */
static int    sfru = 0;
static int    asset_offset = -1;
static int    asset_len = 0;
static int    sernum_offset = -1;
static int    sernum_len = 0;
static int    prodver_offset = -1;
static int    prodver_len = 0;
static int    chassis_offset = -1;
static int    chassis_len = 0;
static char   asset_tag[FIELD_LEN]  = {0};
static char   serial_num[FIELD_LEN] = {0};
static char   prod_ver[FIELD_LEN] = {0};
static char   chassis_name[FIELD_LEN] = {0};
static char   ps_prod[FIELD_LEN] = {0};
static int    maxprod = 0;
static int ctype;
static uchar g_bus = PUBLIC_BUS;
static uchar g_sa  = 0;
static uchar g_lun = BMC_LUN;
static uchar g_addrtype = ADDR_SMI;
static uchar g_fruid = 0;  /* default to fruid 0 */
static uchar g_frutype = 0; 
static uchar lastfru[3] = {0,0,0};
/* g_frutype values (detected), see also FruTypeString
 * --TAG----   FRU   IPMB
 * Baseboard = 0x0c  0x07 
 * PowerSply = 0x0a
 * PowerCage = 0x15
 * DIMM      = 0x20
 * HotSwapCt =       0x0f
 * ME        =       0x2e
 * SysInfo   =    0x40
 * Component =  *     *  (all others)
 */
#ifdef METACOMMAND
extern int verbose;  
extern int sdr_get_reservation(uchar *res_id, int fdev);
#else
static int verbose;  
static int sdr_get_reservation(uchar *res_id, int fdev) { return(-1); }
#endif

#define ERR_LENMAX   -7   /*same as LAN_ERR_BADLENGTH */
#define ERR_LENMIN   -10  /*same as LAN_ERR_TOO_SHORT */
#define ERR_OTHER    -13  /*same as LAN_ERR_OTHER     */

#define STRING_DATA_TYPE_BINARY         0x00
#define STRING_DATA_TYPE_BCD_PLUS       0x01
#define STRING_DATA_TYPE_SIX_BIT_ASCII  0x02
#define STRING_DATA_TYPE_LANG_DEPENDENT 0x03

#define FRUCHUNK_SZ   16
#define FRU_END         0xC1
#define FRU_EMPTY_FIELD 0xC0
#define FRU_TYPE_MASK   0xC0
#define FRU_LEN_MASK    0x3F

#define NUM_BOARD_FIELDS    6
#define NUM_PRODUCT_FIELDS  8
#define NUM_CHASSIS_FIELDS  3
#define MAX_CTYPE   26
char *ctypes[MAX_CTYPE] = { "", "Other", "Unknown", "Desktop", 
	"Low Profile Desktop", "Pizza Box", "Mini-Tower", "Tower", 
	"Portable", "Laptop", "Notebook", "Handheld", "Docking Station", 
	"All-in-One", "Sub-Notebook", "Space-saving", "Lunch Box", 
	"Main Server Chassis", "Expansion Chassis", "SubChassis", 
	"Bus Expansion Chassis", "Peripheral Chassis", 
	"RAID Chassis", /*0x17=23.*/ "Rack-Mount Chassis", 
	"New24" , "New25"};
	/* what about bladed chassies? */
char *ctype_hdr = 
"Chassis Type        "; 
char *chassis[NUM_CHASSIS_FIELDS] = {
"Chassis Part Number ", 
"Chassis Serial Num  ",
"Chassis OEM Field   " };
char *board[NUM_BOARD_FIELDS] = {
"Board Manufacturer  ",
"Board Product Name  ",
"Board Serial Number ",
"Board Part Number   ",
"Board FRU File ID   ",
"Board OEM Field     " };
char *product[NUM_PRODUCT_FIELDS] = {
"Product Manufacturer",
"Product Name        ",
"Product Part Number ",
"Product Version     ",
"Product Serial Num  ",  
"Product Asset Tag   ",
"Product FRU File ID ",
"Product OEM Field   " };
char *asset_hdr = 
"   Asset Tag Length ";

#if 0
typedef struct {
	uchar len :6;
	uchar type:2;
	} TYPE_LEN;  /*old, assumes lo-hi byte order*/
#else
typedef struct {
	uchar len;
	uchar type;
	} TYPE_LEN;
#endif

void
free_fru(uchar *pfrubuf)
{
   if (pfrubuf != NULL) {
      if (frubuf != NULL) free(frubuf);
      frubuf = NULL;
   }
   return;
}

int
load_fru(uchar sa, uchar frudev, uchar frutype, uchar **pfrubuf)
{
   int ret = 0;
   uchar indata[16];
   uchar resp[18];
   int sresp;
   uchar cc;
   int sz;
   char fwords;
   ushort fruoff = 0;
   int i, rv;
   int chunk;

   if (pfrubuf == NULL) return(ERR_BAD_PARAM);
   *pfrubuf = NULL;
   indata[0] = frudev;
   sresp = sizeof(resp);
   if (fdebug) printf("load_fru: sa=%02x, frudev=%02x, addrtype=%d\n",
			sa,frudev,g_addrtype);
   ret = ipmi_cmd_mc(GET_FRU_INV_AREA,indata,1,resp,&sresp,&cc,fdebug);
   if (fdebug) printf("load_fru: inv ret=%d, cc=%x, resp=%02x %02x %02x\n",
			ret,cc, resp[0], resp[1], resp[2]);
   if (ret != 0) return(ret);
   if (cc != 0) { ret = (cc & 0x00ff); return(ret); }

   sz = resp[0] + (resp[1] << 8);
   if (resp[2] & 0x01) { fwords = 1; sz = sz * 2; }
   else fwords = 0;

   frubuf = malloc(sz);
   if (frubuf == NULL) return(get_errno());
   *pfrubuf = frubuf;
   sfru = sz;
      
   /* Loop on READ_FRU_DATA */
   for (i = 0, chunk=FRUCHUNK_SZ; i < sz; i+=chunk)
   {
	if ((i+chunk) >= sz) chunk = sz - i;
	indata[0] = frudev;  /* FRU Device ID */
	if (fwords) {
	   indata[3] = chunk / 2;
	   fruoff = (i/2);
	} else {
	   indata[3] = (uchar)chunk;
	   fruoff = (ushort)i;
	}
        indata[1] = fruoff & 0x00FF;
        indata[2] = (fruoff & 0xFF00) >> 8;
	sresp = sizeof(resp);
        ret = ipmi_cmd_mc(READ_FRU_DATA,indata,4,resp,&sresp,&cc,fdebug);
	if (ret != 0) break;
	else if (cc != 0) {
		if (i == 0) ret = cc & 0x00ff; 
		if (fdebug) printf("read_fru[%d]: ret = %d cc = %x\n",i,ret,cc);
		break; 
	}
        memcpy(&frubuf[i],&resp[1],chunk);
   }

   if ((frudev == 0) && (sa == bmc_sa) && do_guid) 
   { /*main system fru, so get GUID*/
     sresp = sizeof(resp);
     rv = ipmi_cmd_mc(GET_SYSTEM_GUID,indata,0,resp,&sresp,&cc,fdebug);
     if (fdebug) printf("system_guid: ret = %d, cc = %x\n",rv,cc);
     if (rv == 0) rv = cc;
     if ((rv != 0) && !is_remote()) {  /* get UUID from SMBIOS */
        cc = 0; sresp = 16;
        rv = get_SystemGuid(resp); 
        if (fdebug) printf("get_SystemGuid: ret = %d\n",rv);
     }
     if (rv == 0 && cc == 0) {
	if (fdebug) {
	   printf("system guid (%d): ",sresp);
	   for (i=0; i<16; i++) printf("%02x ",resp[i]);
	   printf("\n");
	}
	memcpy(&guid,&resp,16);
   	guid[16] = 0;
     } else {
	printf("WARNING: GetSystemGuid error %d, %s\n",rv,decode_rv(rv));
	/*do not pass this error upstream*/
     }
   } /*endif*/
   return(ret);
}

static void decode_string(unsigned char type,
                          unsigned char language_code,
                          unsigned char *source,
                          int           slen,
                          char          *target,
                          int           tsize)
{
  static const char bcd_plus[] = "0123456789 -.:,_";
  unsigned char *s = &source[0];
  int len, i, j, k;
  union { uint32_t bits; char chars[4]; } u;
 
  if (slen == 0 || slen == 1) return;
  switch(type) {
     case STRING_DATA_TYPE_BINARY: /* 00: binary/unspecified */
        len = (slen*2); break; /* hex dump -> 2x length */
     case STRING_DATA_TYPE_SIX_BIT_ASCII:  /*type 2 6-bit ASCII*/
        /* 4 chars per group of 1-3 bytes */
        len = ((((slen+2)*4)/3) & ~3); break;
     case STRING_DATA_TYPE_LANG_DEPENDENT:  /* 03 language dependent */
     case STRING_DATA_TYPE_BCD_PLUS: /* 01b: BCD plus */
     default:  
	len = slen; break;
  }
  if (len >= tsize) len = tsize - 1;
  memset(target, 0, len);
  if (type == STRING_DATA_TYPE_BCD_PLUS) { /*type 1: BCD plus*/
     for (k=0; k<len; k++)
         target[k] = bcd_plus[(s[k] & 0x0f)];
     target[k] = '\0';
  } else if (type == STRING_DATA_TYPE_SIX_BIT_ASCII) { /*type 2: 6-bit ASCII*/
     for (i=j=0; i<slen; i+=3) {
            u.bits = 0;
            k = ((slen-i) < 3 ? (slen-i) : 3);
#if WORDS_BIGENDIAN
            u.chars[3] = s[i];
            u.chars[2] = (k > 1 ? s[i+1] : 0);
            u.chars[1] = (k > 2 ? s[i+2] : 0);
#define CHAR_IDX 3
#else
            memcpy((void *)&u.bits, &s[i], k);
#define CHAR_IDX 0
#endif
            for (k=0; k<4; k++) {
                    target[j++] = ((u.chars[CHAR_IDX] & 0x3f) + 0x20);
                    u.bits >>= 6;
            }
     }
     target[j] = '\0';
  } else if (type == STRING_DATA_TYPE_LANG_DEPENDENT) {  /*type 3*/
     if ((language_code == 0x00) || (language_code == 0x25) || 
	(language_code == 0x19)) {
       memcpy(target, source, len);
       target[len] = 0;
     } else {
       printf("Language 0x%x dependent decode not supported\n",language_code);
     }
  } else if (type == STRING_DATA_TYPE_BINARY) { /* 00: binary/unspecified */
     strncpy(target, buf2str(s, slen), len);
     target[len] = '\0';  /* add end-of-string char */
  } else {   /* other */
     printf("Unable to decode type 0x%.2x\n",type);
  }
  return;
}

uchar calc_cksum(uchar *pbuf,int len)
{
   int i; 
   uchar cksum;
   uchar sum = 0;
  
   for (i = 0; i < len; i++) sum += pbuf[i];
   cksum = 0 - sum; 
   return(cksum);
}

static void dumpbuf(uchar *pbuf,int sz)
{
   uchar line[17];
   uchar a;
   int i, j;

   line[0] = 0; line[16] = 0;
   j = 0;
   for (i = 0; i < sz; i++) {
	   if (i % 16 == 0) { j = 0; printf("%s\n  %04d: ",line,i); }
	   a = pbuf[i];
	   if (a < 0x20 || a > 0x7f) a = '.';
	   line[j++] = a;
	   printf("%02x ",pbuf[i]);
   }
   line[j] = 0;
   printf("%s\n",line);
   return;
} 

#define NSPDMFG 10
static struct { 
   uchar id;  char *str;
} spd_mfg[NSPDMFG] = {  /* see JEDEC JEP106 doc */
{ 0x02, "AMI" },
{ 0x04, "Fujitsu" },
{ 0x15, "Philips Semi" },
{ 0x1c, "Mitsubishi" },
{ 0x2c, "Micron" },
{ 0x89, "Intel" },
{ 0x98, "Kingston" },
{ 0xA8, "US Modular" },
{ 0xc1, "Infineon" },
{ 0xce, "Samsung" }
};

int ValidTL(uchar typelen)
{
   if (vend_id != VENDOR_INTEL) return 1;
   if ((typelen & 0xC0) == 0xC0) return 1;  /* validate C type */
   if ((typelen & 0x00) == 0x00) return 1;  /* validate 0 type too */
   else return 0;
}

#define SYS_FRUTYPE 0x40
char * FruTypeString(uchar frutype, uchar dev)
{
   char *pstr;
   switch (frutype) { 
            case 0x07:        /*IPMB*/
            case 0x0c: 	      /*FRU*/
			if (dev == 0) pstr = "Baseboard"; 
			else          pstr = "Board    "; 
			break; 
            case 0x0a:  pstr = "PowerSply"; break;  /*FRU*/
            case 0x15:  pstr = "PowerCage"; break;  /*FRU*/
            case 0x20:  pstr = "DIMM     "; break;  /*FRU*/
            case 0x0f:  pstr = "HotSwapCt"; break;  /*IPMB*/
            case 0x2e:  pstr = "ME       "; break;  /*IPMB*/
            case SYS_FRUTYPE:  pstr = "SysInfo  "; break;  /*SysInfo*/
            default:    pstr = "Component"; break; 
	}
   return(pstr);
}

static int ChkOverflow(int idx, int sz, uchar len) 
{
   if (idx >= sz) {
        printf("  ERROR - FRU Buffer Overflow (%d >= %d), last len=%d\n",
		idx,sz,len);
	return 1;   /*overflow error*/
   }
   return 0;
}

static void 
show_spd(uchar *spd, int lenspd, uchar frudev, uchar frutype)
{
   int sz;
   char *pstr;
   int sdcap, ranks, busw, sdw, totcap;
   uchar srev, mfgid, mrev1, mrev2, yr, wk;
   uchar isdcap, iranks, ibusw, isdw;
   uchar iparity, iser, ipart, irev;
   int i;
   char devstr[24];

   /* Sample SPD Headers: 
      80 08 07 0c 0a 01 48 00 (DDR SDRAM DIMM) 
      92 10 0b 01 03 1a 02 00 (DDR3 SDRAM DIMMs) */
   sz = spd[0];  /* sz should == lenspd */
   srev = spd[1];  /* SPD Rev: 8 for DDR, 10 for DDR3 SPD spec */
   if (fcanonical) devstr[0] = 0;  /*default is empty string*/
   else sprintf(devstr,"[%s,   %02x] ",FruTypeString(frutype,frudev),frudev);
   printf("%sMemory SPD Size     %c %d\n",
		devstr,bdelim,lenspd);
   switch (spd[2]) {
     case 0x02: pstr = "EDO"; break;
     case 0x04: pstr = "SDRAM"; break;
     case 0x05: pstr = "ROM"; break;
     case 0x06: pstr = "DDR SGRAM"; break;
     case 0x07: pstr = "DDR SDRAM"; break;
     case 0x08: pstr = "DDR2 SDRAM"; break;
     case 0x09: pstr = "DDR2 SDRAM FB"; break;
     case 0x0A: pstr = "DDR2 SDRAM FB PROBE"; break;
     case 0x0B: pstr = "DDR3 SDRAM"; break;
     default:   pstr = "DRAM"; break;  /*FPM DRAM or other*/
   }
   printf("%sMemory Type         %c %s\n", devstr,bdelim,pstr);
   if (srev < 0x10) {  /*used rev 0.8 of SPD spec*/
      printf("%sModule Density      %c %d MB per bank\n",
		devstr,bdelim, (spd[31] * 4));
      printf("%sModule Banks        %c %d banks\n", 
		devstr,bdelim,spd[5]);
      printf("%sModule Rows, Cols   %c %d rows, %d cols\n",
		devstr,bdelim, spd[3], spd[4]);
      iparity = spd[11];
      mfgid = spd[64];
   } else { /* use 1.0 SPD spec with DDR3 */
      /* SDRAM CAPACITY    = SPD byte 4 bits 3~0 */
      /* PRIMARY BUS WIDTH = SPD byte 8 bits 2~0 */
      /* SDRAM WIDTH       = SPD byte 7 bits 2~0 */
      /* RANKS             = SPD byte 7 bits 5~3 */
      isdcap = (spd[4] & 0x0f);
      iranks = (spd[7] & 0x38) >> 3;
      isdw   = (spd[7] & 0x07);
      ibusw  = (spd[8] & 0x07);
      iparity = (spd[8] & 0x38) >> 3;
      mfgid = spd[118];
      switch(isdcap) {
        case 0:  sdcap =   256; /*MB*/ break;
        case 1:  sdcap =   512; /*MB*/ break;
        case 2:  sdcap =  1024; /*MB*/ break;
        case 3:  sdcap =  2048; /*MB*/ break;
        case 4:  sdcap =  4096; /*MB*/ break;
        case 5:  sdcap =  8192; /*MB*/ break;
        case 6:  sdcap = 16384; /*MB*/ break;
        default: sdcap = 32768; /*MB*/ break;
      }
      switch(iranks) {
        case 0:  ranks =  1; break;
        case 1:  ranks =  2; break;
        case 2:  ranks =  3; break;
        case 3: 
        default: ranks =  4; break;
      }
      switch(isdw) {
        case 0:  sdw =  4; break;
        case 1:  sdw =  8; break;
        case 2:  sdw = 16; break;
        case 3: 
        default: sdw = 32; break;
      }
      switch(ibusw) {
        case 0:  busw =  8; break;
        case 1:  busw = 16; break;
        case 2:  busw = 32; break;
        case 3: 
        default: busw = 64; break;
      }
      totcap = sdcap / 8 * busw / sdw * ranks;
      printf("%sModule Density      %c %d Mbits\n",devstr,bdelim,sdcap);
      printf("%sModule Ranks        %c %d ranks\n",devstr,bdelim,ranks);
      printf("%sModule Capacity     %c %d MB\n",devstr,bdelim,totcap);
   }
   if (iparity == 0x00) pstr = "Non-parity";
   else /* usu 0x02 */  pstr = "ECC";
   printf("%sDIMM Config Type    %c %s\n", devstr,bdelim,pstr);
   for (i = 0; i < NSPDMFG; i++) 
	if (spd_mfg[i].id == mfgid) break;
   if (i == NSPDMFG) pstr = "";  /* not found, use null string */
   else pstr = spd_mfg[i].str;
   printf("%sManufacturer ID     %c %s (0x%02x)\n", devstr,bdelim,pstr,mfgid);
   if (srev < 0x10) {
      yr = spd[93];
      wk = spd[94];
      iser = 95;
      ipart = 73;
      irev = 91;
   } else { /* 1.0 SPD spec with DDR3 */
      yr = spd[120];
      wk = spd[121];
      iser = 122;
      ipart = 128;
      irev = 146;
   }
   mrev1 = spd[irev];  /* save this byte for later */
   mrev2 = spd[irev+1]; 
   spd[irev] = 0;      /*stringify part number */
   printf("%sManufacturer Part#  %c %s\n",
		devstr,bdelim,&spd[ipart]);
   printf("%sManufacturer Rev    %c %02x %02x\n",
		devstr,bdelim,mrev1,mrev2);
   printf("%sManufacturer Date   %c year=%02x week=%02x\n",
		devstr,bdelim, yr,wk);
   printf("%sAssembly Serial Num %c %02x%02x%02x%02x\n",
		devstr,bdelim,spd[iser],spd[iser+1],spd[iser+2],spd[iser+3]);
   spd[irev] = mrev1;  /* restore byte, so ok to repeat later */
   return;
}

void show_guid(uchar *pguid)
{
   char *s;
   int i;
	for (i=0; i<16; i++) {
	   if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) s = "-";
	   else s = "";
	   printf("%s%02x",s,pguid[i]);
	}
}

static char *volt_desc(uchar b)
{
   char *s;
   switch(b) {
   case 0x03: s = "5V"; break;
   case 0x02: s = "3.3V"; break;
   case 0x01: s = "-12V"; break;
   case 0x00: 
   default:   s = "12V"; break;
   }
   return(s);
}

static char *mgt_type(uchar b)
{
   char *s;
   switch(b) {
   case 0x01: s = "SysMgt_URL"; break;
   case 0x02: s = "SystemName"; break;
   case 0x03: s = "SysPingAddr"; break;
   case 0x04: s = "Compon_URL"; break;
   case 0x05: s = "ComponName"; break;
   case 0x06: s = "ComponPing"; break;
   case 0x07: 
   default:   s = "SysGUID"; break;
   }
   return(s);
}


static
void show_fru_multi(char *tag, int midx, uchar mtype, uchar *pdata, int dlen)
{
   int vend; 
   char mystr[256];
   char *s1; 
   int v1, v2, v3, v4, v5, v6, v7; 
   uchar b1, b2;

   if (fdebug) dumpbuf(pdata,dlen);  /*multi-record area dump*/
   sprintf(mystr,"%sMulti[%d] ",tag,midx);
   switch(mtype) {
	case 0x00:  /*Power Supply*/
		printf("%sPower Supply Record %c \n",mystr,bdelim);
		v1 = pdata[0] + ((pdata[1] & 0x0f) << 8);
		printf("\t Capacity  \t%c %d W\n",bdelim, v1);
		v2 = pdata[2] + (pdata[3] << 8);
		printf("\t Peak VA   \t%c %d VA\n",bdelim, v2);
		printf("\t Inrush Current\t%c %d A\n",bdelim, pdata[4]);
		printf("\t Inrush Interval\t%c %d ms\n",bdelim, pdata[5]);
		v3 = pdata[6] + (pdata[7] << 8);
		v4 = pdata[8] + (pdata[9] << 8);
		printf("\t Input Voltage Range1\t%c %d-%d V\n",
			bdelim,v3/100,v4/100);
		v3 = pdata[10] + (pdata[11] << 8);
		v4 = pdata[12] + (pdata[13] << 8);
		printf("\t Input Voltage Range2\t%c %d-%d V\n",
			bdelim,v3/100,v4/100);
		printf("\t Input Frequency Range\t%c %d-%d Hz\n",
			bdelim,pdata[14],pdata[15]);
		printf("\t AC Dropout Tolerance\t%c %d ms\n",bdelim, pdata[16]);
		b1 = pdata[17];
                b2 = (b1 & 0x01);
		if (b2) {  /*predictive fail*/
		    if ((b1 & 0x10) != 0) s1 = "DeassertFail ";
		    else s1 = "AssertFail ";
		} else {
		    if ((b1 & 0x10) != 0) s1 = "2pulses/rot ";
		    else s1 = "1pulse/rot ";
		}
		printf("\t Flags   \t%c %s%s%s%s%s\n",bdelim,
		  b2 ? "PredictFail " : "",
		  ((b1 & 0x02) == 0) ? "" : "PowerFactorCorrect ",
		  ((b1 & 0x04) == 0) ? "" : "AutoswitchVolt ",
		  ((b1 & 0x08) == 0) ? "" : "Hotswap ", s1);
		v5 = pdata[18] + ((pdata[19] & 0x0f) << 8);
		v6 = (pdata[19] & 0xf0) >> 4;
		printf("\t Peak Capacity \t%c %d W for %d s\n",bdelim, v5,v6);
		if (pdata[20] == 0) {
		   printf("\t Combined Capacity\t%c not specified\n",bdelim);
		} else {
		   b1 = pdata[20] & 0x0f;
		   b2 = (pdata[20] & 0xf0) >> 4;
		   v7 = pdata[21] + (pdata[22] << 8);
		   printf("\t Combined Capacity\t%c %d W (%s and %s)\n",
			bdelim, v7,volt_desc(b1),volt_desc(b2));
		}
		if (b2)   /*predictive fail*/
		   printf("\t Fan low threshold\t%c %d RPS\n",bdelim,pdata[23]);
		break;
	case 0x01:  /*DC Output*/
		b1 = pdata[0] & 0x0f;
		b2 = ((pdata[0] & 0x80) != 0);
		printf("%sDC Output       %c number %d\n",mystr,bdelim,b1);
		printf("\t Standby power \t%c %s\n", bdelim,
			(b2 ? "Yes" : "No"));
		v1 = pdata[1] + (pdata[2] << 8);
		printf("\t Nominal voltage \t%c %.2f V\n", bdelim, (double)v1 / 100);
		v2 = pdata[3] + (pdata[4] << 8);
		v3 = pdata[5] + (pdata[6] << 8);
		printf("\t Voltage deviation \t%c + %.2f V / - %.2f V\n", 
			bdelim, (double)v3/100, (double)v2/100);
		v4 = pdata[7] + (pdata[8] << 8);
		printf("\t Ripple and noise pk-pk \t%c %d mV\n", bdelim, v4);
		v5 = pdata[9] + (pdata[10] << 8);
		printf("\t Min current draw \t%c %.3f A\n", bdelim, (double)v5/1000);
		v6 = pdata[11] + (pdata[12] << 8);
		printf("\t Max current draw \t%c %.3f A\n", bdelim, (double)v6/1000);
		break;
	case 0x02:  /*DC Load*/
		b1 = pdata[0] & 0x0f;
		printf("%sDC Load         %c number %d\n",mystr,bdelim,b1);
		v1 = pdata[1] + (pdata[2] << 8);
		printf("\t Nominal voltage \t%c %.2f V\n", bdelim, (double)v1 / 100);
		v2 = pdata[3] + (pdata[4] << 8);
		v3 = pdata[5] + (pdata[6] << 8);
		printf("\t Min voltage allowed \t%c %.2f A\n", bdelim, (double)v2);
		printf("\t Max voltage allowed \t%c %.2f A\n", bdelim, (double)v3);
		v4 = pdata[7] + (pdata[8] << 8);
		printf("\t Ripple and noise pk-pk \t%c %d mV\n", bdelim, v4);
		v5 = pdata[9] + (pdata[10] << 8);
		printf("\t Min current load \t%c %.3f A\n", bdelim, (double)v5/1000);
		v6 = pdata[11] + (pdata[12] << 8);
		printf("\t Max current load \t%c %.3f A\n", bdelim, (double)v6/1000);
		break;
	case 0x03:  /*Management Access*/
		b1 = pdata[0];
		printf("%sManagemt Access %c %s ",mystr,bdelim,mgt_type(b1));
		memcpy(mystr,&pdata[1],dlen-1);
		mystr[dlen-1] = 0;
		printf("%s\n",mystr);
		break;
	case 0x04:  /*Base Compatibility*/
		vend = pdata[0] + (pdata[1] << 8) + (pdata[2] << 16);
		printf("%sBasic Compat    %c %06x\n",mystr,bdelim,vend);
		break;
	case 0x05:  /*Extended Compatibility*/
		vend = pdata[0] + (pdata[1] << 8) + (pdata[2] << 16);
		printf("%sExtended Compat %c %06x\n",mystr,bdelim,vend);
		break;
	case 0xC0:  /*OEM Extension*/
		vend = pdata[0] + (pdata[1] << 8) + (pdata[2] << 16);
		if (vend == OEM_PICMG) {
		   printf("%sOEM PICMG  %c \n", mystr,bdelim);
		   show_fru_picmg(pdata,dlen);
		} else 
		   printf("%sOEM Ext    %c %06x %02x\n",
			  mystr,bdelim, vend, pdata[3]);
		break;
	default:
		printf("%s %02x %c %02x\n", mystr,mtype,bdelim, pdata[0]);
		break;
   }
}

int
show_fru(uchar sa, uchar frudev, uchar frutype, uchar *pfrubuf)
{
   int ret = 0;
   int i, j, n, sz;
   uchar *pfru0;
   uchar *pfru;
   uchar lang;
   TYPE_LEN tl;
   char newstr[64];
   int iaoff, ialen, bdoff, bdlen; 
   int proff, prlen, choff, chlen;
   int moff, mlen;
   char devstr[24];
   char *pstr;

   if ((pfrubuf[0] & 0x80) == 0x80) {  /* 0x80 = type for DIMMs (SPD) */
      /* FRU Header: 80 08 07 0c 0a 01 48 00 (DIMM) */
      /* FRU Header: 92 10 0b 01 03 1a 02 00 (DDR3 DIMMs) */
      sz = pfrubuf[0];
      if (fdebug) {
	printf("DIMM SPD Body (size=%d/%d): ",sz,sfru);
	dumpbuf(pfrubuf,sfru);
      }
      show_spd(pfrubuf,sfru, frudev,frutype);
      return(ret);
   }

   pstr = FruTypeString(frutype,frudev);
   if (fcanonical) devstr[0] = 0;  /*default is empty string*/
   else sprintf(devstr,"[%s,%02x,%02x] ",pstr,sa,frudev);
   printf("%s%s FRU Size  %c %d\n",devstr,pstr,bdelim,sfru);

   /*
    * FRU header:
    *  0 = format_ver (01 is std, usu 0x80 if DIMM)
    *  1 = internal_use offset
    *  2 = chassis_info offset
    *  3 = board_info offset   (usu 6 fields)
    *  4 = product_info offset (usu 8 fields)
    *  5 = multirecord offset
    *  6 = pad (00)
    *  7 = header checksum (zero checksum)
    * FRU Header: 01 01 02 09 13 00 00 e0 (BMC)
    * FRU Header: 01 00 00 00 01 07 00 f7 (Power Cage)
    */
   pfru0 = &pfrubuf[0];  /*pointer to fru start*/
   pfru = &pfrubuf[0];
   sz = 8;  /*minimum for common header*/
   for (i = 1; i < 6; i++) 	/* walk thru offsets */
	if (pfrubuf[i] != 0) sz = pfrubuf[i] * 8;
   if (sz > 8) {   		/* if have at least one section */
	if (pfrubuf[5] != 0) j = 5 + pfrubuf[sz+2]; /*if multi-record area*/
	else j = pfrubuf[sz+1] * 8;   /* else add length of last section */
	sz += j;
   }

   /* Now, sz = size used, sfru = total available size */
   if (sz > sfru) {
      if (fdebug) {
	uchar hsum;
        printf("FRU Header: ");
        for (i = 0; i < 8; i++) printf("%02x ",pfrubuf[i]);
        printf("\n");
        hsum = calc_cksum(&pfrubuf[0],7);
	if (pfrubuf[7] != hsum)
           printf("FRU Header checksum mismatch (%x != %x)\n",pfrubuf[7],hsum);
      }
      printf("FRU size used=%d > available=%d\n",sz,sfru);
      if (fpicmg || fdoanyway) sz = sfru;  /*do it anyway*/
      else {
        printf("Please apply the correct FRU/SDR diskette\n");
	return(ERR_OTHER);
      }
   }
   /* internal area offset, length */
   iaoff = pfrubuf[1] * 8;  
   ialen = pfrubuf[iaoff + 1] * 8;
   /* chassis area offset, length */
   choff = pfrubuf[2] * 8;  
   chlen = pfrubuf[choff + 1] * 8;
   /* board area offset, length */
   bdoff = pfrubuf[3] * 8;  
   bdlen = pfrubuf[bdoff + 1] * 8;
   /* product area offset, length */
   proff = pfrubuf[4] * 8;  
   prlen = pfrubuf[proff + 1] * 8;
   /* multi-record area offset, length */
   moff = pfrubuf[5] * 8;  
   mlen = 0;
   if (moff > 0) {
      for (i = moff; i < sfru; ) {
         j = 5 + pfrubuf[i+2];
	 mlen += j;
         if (pfrubuf[i+1] & 0x80) break;
	 i += j;
      }
   }

   if (fdebug) {
      printf("FRU Header: ");
      for (i = 0; i < 8; i++) printf("%02x ",pfrubuf[i]);
      printf("\n");
      printf("FRU Body (size=%d/%d): ",sz,sfru);
      dumpbuf(pfrubuf,sfru);
      printf("header, len=%d, cksum0 = %02x, cksum1 = %02x\n",
                8,pfrubuf[7],calc_cksum(&pfrubuf[0],7));
      printf("internal off=%d, len=%d, cksum = %02x\n",
		iaoff,ialen,calc_cksum(&pfrubuf[iaoff],ialen-1));
      printf("chassis off=%d, len=%d, cksum = %02x\n",
		choff,chlen,calc_cksum(&pfrubuf[choff],chlen-1));
      printf("board off=%d, len=%d, cksum = %02x\n",
		bdoff,bdlen,calc_cksum(&pfrubuf[bdoff],bdlen-1));
      printf("prod  off=%d, len=%d, cksum = %02x\n",
		proff,prlen,calc_cksum(&pfrubuf[proff],prlen-1));
      /* Multi-record area */
      printf("multi off=%d, len=%d, fru sz=%d\n", moff,mlen,sz);  
   }  /*endif fdebug, show header*/

   if (choff != 0) {
      /* show Chassis area fields */
      pfru = &pfrubuf[choff];
      lang = 25;  /* English */
      ctype = pfru[2];  /*chassis type*/
      if (fdebug) printf("ctype=%x\n",ctype);
      if (ctype >= MAX_CTYPE) ctype = MAX_CTYPE - 1;
      printf("%s%s%c %s\n",devstr, ctype_hdr,bdelim,ctypes[ctype]);
      pfru += 3;  /* skip chassis header */
      tl.len  = 0;
      for (i = 0; i < NUM_CHASSIS_FIELDS; i++)
      {
         if (ChkOverflow((int)(pfru - pfru0),sfru,tl.len)) break;
         if (pfru[0] == FRU_END) break;  /*0xC1 = end of FRU area*/
         if (!ValidTL(pfru[0]))
	    printf("  ERROR - Invalid Type/Length %02x for %s\n",
			pfru[0],chassis[i]);
         tl.type = (pfru[0] & FRU_TYPE_MASK) >> 6;
         tl.len  = pfru[0] & FRU_LEN_MASK;
         if (i == 2) {  /* OEM field for chassis_name  */
	   chassis_offset = (int)(pfru - pfrubuf);
	   chassis_len    = tl.len;
	   if (fdebug) printf("chassis oem dtype=%d lang=%d len=%d\n",
				tl.type,lang,tl.len);
         }
         pfru++;
	 {
	   newstr[0] = 0;
	   decode_string(tl.type,lang,pfru,tl.len,newstr,sizeof(newstr));
	   printf("%s%s%c %s\n",devstr, chassis[i],bdelim,newstr);
         }
         pfru += tl.len;
      }
      if (fdebug) printf("num Chassis fields = %d\n",i);
   }

   if (bdoff != 0) {
      long nMin, nSec;
      time_t tsec;
      /* show Board area fields */
      pfru = &pfrubuf[bdoff];
      lang = pfru[2];
      /* Decode board mfg date-time (num minutes since 1/1/96) */
      nMin = pfru[3] + (pfru[4] << 8) + (pfru[5] << 16);
		  /* 820,454,400 sec from 1/1/70 to 1/1/96 */
      nSec = (nMin * 60) + 820454400;
      tsec = (time_t)(nSec & 0x0ffffffff);
      // fmt_time(tsec,newstr,sizeof(newstr)); 
      printf("%sBoard Mfg DateTime  %c %s",devstr,bdelim,ctime(&tsec));
      pfru += 6;  /* skip board header */
      tl.len  = 0;
      for (i = 0; i < NUM_BOARD_FIELDS; i++)
      {
         if (ChkOverflow((int)(pfru - pfru0),sfru,tl.len)) break;
         if (pfru[0] == FRU_END) break;  /*0xC1 = end*/
         if (!ValidTL(pfru[0]))
	    printf("  ERROR - Invalid Type/Length %02x for %s\n",
			pfru[0],board[i]);
         tl.type = (pfru[0] & FRU_TYPE_MASK) >> 6;
         tl.len  = pfru[0] & FRU_LEN_MASK;
         pfru++;
         {
	   newstr[0] = 0;
	   decode_string(tl.type,lang,pfru,tl.len,newstr,sizeof(newstr));
	   printf("%s%s%c %s\n",devstr, board[i],bdelim,newstr);
         }
         pfru += tl.len;
      }
      if (fdebug) printf("num Board fields = %d\n",i);
   }

   if (proff != 0) 
   {
      /* show Product area fields */
      pfru = &pfrubuf[proff];
      maxprod = pfru[1] * 8;
      lang = pfru[2];
      pfru += 3;  /* skip product header */
      tl.len  = 0;
      for (i = 0; i < NUM_PRODUCT_FIELDS; i++)
      {
         if (ChkOverflow((int)(pfru - pfru0),sfru,tl.len)) break;
         if (*pfru == FRU_END) {  /*0xC1 = end*/
            /* Wart for known Kontron 1-byte Product Version anomaly. */
            if (vend_id == VENDOR_KONTRON && i == 3) ; 
	    else break;
	 }
         if (*pfru == 0) *pfru = FRU_EMPTY_FIELD; /* fix a broken table */
         if (!ValidTL(pfru[0]))
	    printf("  ERROR - Invalid Type/Length %02x for %s\n",
			pfru[0],product[i]);
         tl.type = (pfru[0] & FRU_TYPE_MASK) >> 6;
         tl.len  = pfru[0] & FRU_LEN_MASK;
	 if ((frudev == 0) && (sa == bmc_sa)) {  /*baseboard FRU*/
           if (i == 5) {  /* asset tag */
	      asset_offset = (int)(pfru - pfrubuf);
	      asset_len    = tl.len;
	      if (fdebug) printf("asset off=%d dtype=%d lang=%d len=%d\n",
				 asset_offset,tl.type,lang,tl.len);
   	      if (fshowlen)  /* show asset tag length for main board */
		 printf("%s%c %d\n",asset_hdr,bdelim,asset_len);
           } else if (i == 4) {  /* serial number */
	      sernum_offset = (int)(pfru - pfrubuf);
	      sernum_len    = tl.len;
	      if (fdebug) printf("sernum dtype=%d lang=%d len=%d\n",
				 tl.type, lang, tl.len);
           } else if (i == 3) {  /* product version number */
	      prodver_offset = (int)(pfru - pfrubuf);
	      prodver_len    = tl.len;
	      if (fdebug) printf("prodver dtype=%d lang=%d len=%d\n",
				 tl.type, lang, tl.len);
	   }
	 }  /*if baseboard sa*/
         pfru++;
         {
	   newstr[0] = 0;
	   decode_string(tl.type,lang,pfru,tl.len,newstr,sizeof(newstr));
	   printf("%s%s%c %s\n",devstr, product[i],bdelim,newstr);
         }
         pfru += tl.len;
      }
      if (fdebug) 
	   printf("num Product fields = %d, last=%x, max = %d\n", 
		   i,*pfru,maxprod );
      if (*pfru == 0x00) *pfru = FRU_END;  /* insert end char if broken */
   }

   if (moff != 0) 
   {
      /* multi-record area may contain several record headers 
       * 0 = record type id
       * 1 = 0x02 or 0x82 if End-of-List
       * 2 = record len
       * 3 = record chksum
       * 4 = header chksum
       */
      pfru = &pfrubuf[moff];
      j = moff;
      for (i = 0; j < sz ; i++)
      {
         n = pfru[2];  /* len of this record */
	 show_fru_multi(devstr,i,pfru[0],&pfru[5],n);
         j += (5 + n);
         if (pfru[1] & 0x80) j = sz;  /*0x80 = last in list, break*/
         pfru += (5 + n);
      }
   }

   if ((frudev == 0) && (sa == bmc_sa) && do_guid) {
	printf("%sSystem GUID         %c ",devstr,bdelim);
	show_guid(guid);
	printf("\n");
   }
   return(ret);
}

int write_fru_data(uchar id, ushort offset, uchar *data, int dlen, char fdebug)
{
   int ret = -1;
   int chunk;
   ushort fruoff;
   uchar req[25];
   uchar resp[16];
   int sresp;
   uchar cc;
   int i, j;

   /* Write the buffer in small 16-byte (FRUCHUNK_SZ) chunks */
   req[0] = 0x00;  /* override FRU Device ID (fruid) */
   fruoff = offset;
   chunk = FRUCHUNK_SZ;
   for (i = 0; i < dlen; i += chunk) {
	req[1] = fruoff & 0x00ff;
	req[2] = (fruoff & 0xff00) >> 8;
	if ((i + chunk) > dlen) chunk = dlen - i;
	memcpy(&req[3],&data[i],chunk);
	if (fdebug) {
	   printf("write_fru_data[%d] (len=%d): ",i,chunk+3);
	   for (j = 0; j < chunk+3; j++) printf("%02x ",req[j]);
	   printf("\n");
	}
	sresp = sizeof(resp);
	ret = ipmi_cmd_mc(WRITE_FRU_DATA,req,(uchar)(chunk+3),resp,&sresp,
			&cc,fdebug);
	if ((ret == 0) && (cc != 0)) ret = cc & 0x00ff; 
	if (fdebug && ret == 0) 
		printf("write_fru_data[%d]: %d bytes written\n",i,resp[0]);
	if (ret != 0) break;
	fruoff += (ushort)chunk;
   }
   return(ret);
}

/* write_asset updates the FRU Product area only. */
int
write_asset(char *tag, char *sernum, char *prodver, int flag, uchar *pfrubuf)
{
   int ret = -1;
   uchar newdata[SZ_PRODAREA];
   int alen, clen;
   int snlen, verlen;
   uchar *pfru0;
   uchar *pfru;
   uchar *pnew;
   int i, j, m, n, newlen, max; 
   int chas_offset; 
   int prod_offset, prod_len; 
   int mult_offset, mult_len; 
   uchar chksum;
   char fdoasset  = 0;
   char fdosernum = 0;
   char fdoprodver = 0;
   char fdochassis = 0;

   if (flag == 0)  return ERR_OTHER;
   if (pfrubuf == NULL) pfrubuf = frubuf;
   if (pfrubuf == NULL) return ERR_OTHER;
   if ((flag & 0x01) != 0) {
      fdoasset = 1;
      alen = strlen_(tag);  /*new*/
   } else {
      alen = asset_len;  /*old*/
   }
   if ((flag & 0x02) != 0) {
      fdosernum = 1;
      snlen = strlen_(sernum); /*new*/
   } else {
      snlen = sernum_len;  /*old*/
   }
   if ((flag & 0x200) != 0) {
      fdochassis = 1;
      // clen = strlen(chassis_nm); /*new, ignore*/
      // if (clen <= 1) return ERR_LENMIN;
      clen = chassis_len; 
   } else {
      clen = chassis_len;  /*old*/
   }
   if ((flag & 0x08) != 0) {
      fdoprodver = 1;
      verlen = strlen_(prodver); /*new*/
   } else verlen = prodver_len;  /*old*/
   /* 
    * Abort if offset is negative or if it would clobber
    * fru header (8 bytes). 
    * Abort if asset tag is too big for fru buffer.
    */
   if (fdebug) {
       printf("write_asset: asset_off=%d asset_len=%d alen=%d  sFRU=%d\n",
		   asset_offset,asset_len,alen,sfru);
       printf("            sernum_off=%d sernm_len=%d snlen=%d maxprod=%d\n",
		   sernum_offset,sernum_len,snlen,maxprod);
       printf("            prodver=%d prodver_len=%d verlen=%d maxprod=%d\n",
		   prodver_offset,prodver_len,verlen,maxprod);
   }
   if (fdoasset && (alen <= 1)) return ERR_LENMIN;
   if (fdosernum && (snlen <= 1)) return ERR_LENMIN;
   if (asset_offset < 8) return ERR_LENMIN;
   if (asset_offset + alen > sfru) return ERR_LENMAX;
   if (sernum_offset < 8) return ERR_LENMIN;
   if (sernum_offset + snlen > sfru) return ERR_LENMAX;
   if (prodver_offset < 8) return ERR_LENMIN;
   if (prodver_offset + verlen > sfru) return ERR_LENMAX;
   
   pfru0 = &pfrubuf[0]; 
   chas_offset = pfrubuf[2] * 8;           // offset of chassis data
   prod_offset = pfrubuf[4] * 8;           // offset of product data
   prod_len = pfrubuf[prod_offset+1] * 8;      // length of product data 
   mult_offset = pfrubuf[5] * 8;  
   mult_len = 0;
   if (mult_offset > 0) {
      for (i = mult_offset; i < sfru; ) {
         mult_len += (5 + pfrubuf[i+2]);
         if (pfrubuf[i+1] & 0x80) break;
	 i = mult_len;
      }
   }
   /* Check if asset tag will fit in product data area of FRU. */
   if (fdebug) 
     printf("write_asset: fru[4,p]=[%02x,%02x] prod_off=%d plen=%d veroff=%d\n",
	pfrubuf[4],pfrubuf[prod_offset+1],prod_offset,prod_len, prodver_offset);
   /* asset comes after sernum, so this check works for both */
   if (prod_offset > prodver_offset) return ERR_LENMAX; // offsets wrong
   if (prod_len > sizeof(newdata)) return ERR_LENMAX; // product > buffer

   memset(newdata,0,prod_len);
   pnew = &newdata[0];
   /* Copy fru data from start to chassis OEM */
   pfru = &pfrubuf[chas_offset]; 

   /* Copy fru data before serial number */
   pfru = &pfrubuf[prod_offset]; 
   j = prodver_offset - prod_offset;
   memcpy(pnew,pfru,j);
   pfru += j; 
   pnew += j;
   if (fdebug) {
	printf("write_asset: fru[4,p]=[%02x,%02x] sernum_off=%d snlen=%d plen=%d\n",
		pfrubuf[4],pfrubuf[sernum_offset+1],sernum_offset,snlen,prod_len);
   }
   if (fdoprodver) {
      pnew[0] = (verlen | FRU_TYPE_MASK);  /*add type=3 to snlen*/
      memcpy(++pnew,prodver,verlen);
   } else {
      pnew[0] = (verlen | FRU_TYPE_MASK);  /*type/len byte*/
      memcpy(++pnew,&pfrubuf[prodver_offset+1],verlen);
   }
   j += prodver_len + 1;
   pfru += prodver_len + 1;
   pnew += verlen;  /*already ++pnew above*/

   /* Copy new or old serial number */
   if (fdebug) printf("pfrubuf[%ld]: %02x %02x %02x, j=%d, snlen=%d/%d\n",
		   (pfru-pfrubuf),pfru[0],pfru[1],pfru[2],j,snlen,sernum_len);
   if (fdosernum) {
      pnew[0] = (snlen | FRU_TYPE_MASK);  /*add type=3 to snlen*/
      memcpy(++pnew,sernum,snlen);
   } else {
      pnew[0] = (snlen | FRU_TYPE_MASK);  /*type/len byte*/
      memcpy(++pnew,&pfrubuf[sernum_offset+1],snlen);
   }
   /* increment past serial number, to asset tag */
   j += sernum_len + 1;
   pfru += sernum_len + 1;
   pnew += snlen;  /*already ++pnew above*/

   /* Copy new or old asset tag */
   if (fdebug) printf("pfrubuf[%ld]: %02x %02x %02x, j=%d, alen=%d/%d\n",
		   (pfru-pfrubuf),pfru[0],pfru[1],pfru[2],j,alen,asset_len);
   if (fdoasset) {
      pnew[0] = (alen | FRU_TYPE_MASK);  /*add type=3 to len*/
      memcpy(++pnew,tag,alen);
   } else {
      pnew[0] = (alen | FRU_TYPE_MASK);  /*type/len byte*/
      memcpy(++pnew,&pfrubuf[asset_offset+1],alen);
   }
   j += asset_len + 1;
   pfru += asset_len + 1;
   pnew += alen;

   /* copy trailing fru data from saved copy */
   n = (int)(pnew - newdata);  /* new num bytes used */
   m = (int)(pfru - pfru0);    /* old num bytes used */
   if (fdebug) printf("pfrubuf[%d]: %02x %02x %02x, j=%d, n=%d, plen=%d\n",
			m,pfru[0],pfru[1],pfru[2],j,n,prod_len);
   if (mult_offset > 0) {  /* do not expand if multi-record area there */
       max = prod_len - j;
   } else /* nothing else, can expand product area, up to sfru */
       max = sfru - (j + prod_offset); 
   if (fdebug) printf("pfrubuf[%ld]: %02x %02x %02x, j=%d n=%d remainder=%d\n",
		   (pfru-pfrubuf),pfru[0],pfru[1],pfru[2],j,n,max);
   if (max < 0) max = 0;
   for (i = 0; i < max; i++) {
     pnew[i] = pfru[i];
     if (pfru[i] == FRU_END) { i++; break; }
   }
   if (i == max) {  /*never found 0xC1 FRU_END*/
     pnew[0] = FRU_END;
     i = 1;
   } 

   newlen = n + i;
   if (fdebug) printf("newbuf[%d]: %02x %02x %02x, j=%d, newlen=%d\n",
		   n,pnew[0],pnew[1],pnew[2],j,newlen);
   /* round up to next 8-byte boundary */
   /* need one more byte for checksum, so if mod=0, adds 8 anyway */
   j = 8 - (newlen % 8);
   for (i = 0; i < j; i++) newdata[newlen++] = 0;

   if (newlen < prod_len) newlen = prod_len;  /* don't shrink product area */
   newdata[1] = newlen / 8;     // set length of product data 

   /* include new checksum (calc over Product area) */
   chksum = calc_cksum(&newdata[0],newlen-1);
   newdata[newlen-1] = chksum;

#ifdef NEEDED
   /* This caused a problem on Radisys systems, and is not needed, so skip it.*/
   if (mult_offset > 0) {
      /* add on any more data from multi-record area */
      pnew = &newdata[newlen];
      pfru = &pfrubuf[mult_offset];
      memcpy(pnew,pfru,mult_len);
      newlen += mult_len;
   }
#endif

   if (fdebug) {
	printf("old buffer (%d):",prod_len);
	dumpbuf(&pfrubuf[prod_offset],prod_len);
	printf("new buffer (%d):",newlen);
	dumpbuf(newdata,newlen);
   }
   if (prod_offset + newlen >= sfru) return ERR_LENMAX;
   if ((mult_offset > 0) && (newlen > prod_len)) return ERR_LENMAX;
#ifdef TEST
   newlen = 0;
#endif

   ret = write_fru_data(0, (ushort)prod_offset, newdata, newlen, fdebug);
   return(ret);
}

#define SDR_CHUNKSZ  16
#define LAST_REC   0xffff
#define SDR_STR_OFF  16

int get_sdr(ushort recid, ushort resid, ushort *recnext, 
		uchar *sdr, int *slen, uchar *pcc)
{
	uchar idata[6];
	uchar rdata[64];
	int sresp;
	ushort cmd;
	uchar cc = 0;
        int len = 0;
	int szsdr, thislen;
	int rc;
	
	memset(sdr,0,*slen); /*clear sdr */
	idata[0] = resid & 0x00ff;
	idata[1] = (resid & 0xff00) >> 8;
	idata[2] = recid & 0x00ff;
	idata[3] = (recid & 0xff00) >> 8;
	idata[4] = 0;  /*offset*/
	idata[5] = SDR_CHUNKSZ;  /*bytes to read*/
        if (fdevsdrs) cmd = GET_DEVICE_SDR;
        else cmd = GET_SDR;
	sresp = sizeof(rdata);
	rc = ipmi_cmd_mc(cmd, idata, 6, rdata, &sresp,&cc, fdebug);
	if (fdebug) printf("get_sdr[%x] ret = %d cc = %x sresp = %d\n",
				recid,rc,cc,sresp);
	if (rc == 0 && cc == 0xCA) {  /*cannot read SDR_CHUNKSZ bytes*/
	   idata[5] = 8;  /*bytes to read*/
	   sresp = sizeof(rdata);
	   rc = ipmi_cmd_mc(cmd, idata, 6, rdata, &sresp,&cc, fdebug);
	   if (fdebug) printf("get_sdr[%x] ret = %d cc = %x sresp = %d\n",
				recid,rc,cc,sresp);
	}
	*pcc = cc;
	if (rc == 0 && cc == 0) {
	   *recnext = rdata[0] + (rdata[1] << 8);
           if (sresp < 2) len = 0;
	   else len = sresp-2;
	   if (len > *slen) len = *slen;
	   memcpy(sdr,&rdata[2],len);
           szsdr = sdr[4] + 5;  /*get actual SDR size*/
	   if (szsdr > *slen) szsdr = *slen;
           /* if an SDR locator record, get the rest of it. */
	   if (sdr[3] == 0x11 || sdr[3] == 0x12)
	   if (szsdr > SDR_CHUNKSZ) {
		ushort resid2;
		rc = sdr_get_reservation((uchar *)&resid2,fdevsdrs);
		if (fdebug) printf("2nd sdr_get_reservation ret=%d\n",rc);
		thislen = szsdr - SDR_CHUNKSZ;
		if (thislen > SDR_CHUNKSZ) thislen = SDR_CHUNKSZ;
		idata[0] = resid2 & 0x00ff;
		idata[1] = (resid2 & 0xff00) >> 8;
		idata[2] = recid & 0x00ff;
		idata[3] = (recid & 0xff00) >> 8;
		idata[4] = SDR_CHUNKSZ;  /*offset*/
		idata[5] = (uchar)thislen;  /*bytes to read*/
		sresp = sizeof(rdata);
		rc = ipmi_cmd_mc(cmd, idata, 6, rdata, &sresp,&cc, fdebug);
		if (fdebug) printf("get_sdr[%x] 2nd ret=%d cc=%x sresp=%d\n",
				recid,rc,cc,sresp);
		if (rc == 0 && cc == 0) {
		   if (sresp < 2) sresp = 0;
		   else sresp -= 2;
		   if (sresp > thislen) sresp = thislen;
	   	   memcpy(&sdr[len],&rdata[2],sresp);
		   len += sresp;
		} else rc = 0;  /*ok because got first part*/
	   }  /*endif got first chunk, need more*/
	   *slen = len;
	} else *slen = 0;  
	return(rc);
}

void show_loadfru_error(uchar sa, uchar fruid, int ret)
{
   if (ret == 0) return;
   switch(ret) {
      case 0x081: printf("\tFRU(%x,%x) device busy\n",sa,fruid); break;
      case 0x0C3: printf("\tFRU(%x,%x) timeout, not found\n",sa,fruid); break;
      case 0x0CB: printf("\tFRU(%x,%x) not present\n",sa,fruid); break;
      default:    printf("load_fru(%x,%x) error = %d (0x%x)\n",
			sa,fruid,ret,ret);
		  break;
   }
   return;
}

int get_show_fru(ushort recid, uchar *sdr, int sdrlen)
{
   int ret = 0;
   int ilen;
   char idstr[32];
   uchar sa, fruid =0;
   uchar frutype = 0;
   char fgetfru = 0;
   char fisbase = 0;
   uchar *pfru;

   if (sdrlen > SDR_STR_OFF) {
	ilen = sdrlen - SDR_STR_OFF;
	if (ilen >= sizeof(idstr)) ilen = sizeof(idstr) - 1;
	memcpy(idstr,&sdr[SDR_STR_OFF],ilen);
	idstr[ilen] = 0;
   } else idstr[0] = 0;
   sa = sdr[5];  /* usu 0x20 for bmc_sa */
   if (fbasefru) fisbase = 1;

	    /* Get its FRU data */
	    if ((sdr[3] == 0x11) && (sdr[7] & 0x80)) {  /* FRU SDRs */
	      /* It is a logical FRU device */
	      if (fcanonical)
	         printf("SDR[%04x] FRU  %c %s\n", recid, bdelim, idstr);
	      else
	         printf("SDR[%04x] FRU  %02x %02x %02x %02x %s\n", recid, 
			sdr[5],sdr[6],sdr[12],sdr[13],idstr);
	      fruid = sdr[6];
	      if (sdrlen > 12) frutype = sdr[12];
              if (sa == bmc_sa && fruid == 0) { /*dont repeat base fru */
		frutype = 0x0c;
		fisbase = 1;
                if (fbasefru) fgetfru = 1;
              } else
                switch(frutype)   /*FRU entity id*/
                {
                  case 0x0a:   /*Power Supply*/
                  case 0x20:   /*DIMM*/
                  case 0x15:   /*Power Cage*/
                  default:
                        fgetfru = 1;
                        break;
                }
            } else if (sdr[3] == 0x12) {  /* IPMB SDRs (DLRs for MCs) */
	        if (fcanonical)
	           printf("SDR[%04x] IPMB %c %s\n", recid, bdelim, idstr);
		else
                   printf("SDR[%04x] IPMB %02x %02x %02x %02x %s\n", recid,
                        sdr[5],sdr[6],sdr[12],sdr[13],idstr);
                fruid = 0;  /*every MC must have fruid 0*/
	      if (sdrlen > 12) frutype = sdr[12];
                if (sa == bmc_sa && fruid == 0) { /*dont repeat base fru */
		   fisbase = 1;
		   frutype = 0x07;
                   if (fdebug) printf("do bmc_sa %02x once\n",sa);
		   g_frutype = frutype;
                   if (fbasefru) fgetfru = 1;
		} else if (frutype == 0x2e) { /*skip ME*/
                   if (fdebug) printf("skipping ME sa %02x, %02x\n",sa,fruid);
                } else if (sa == 0x28) { /*do nothing for Bridge Ctlr sa=0x28*/
                   if (fdebug) printf("skipping IPMB sa %02x, %02x\n",sa,fruid);
                } else if (sa == 0xC0) { /* HotSwap Backplane (sa=0xC0) */
                   /* Note: Loading sa 0xC0 over ipmi lan gives a timeout
                    * error, but it works locally. */
                   fgetfru = 1;
                } else  { /* other misc sa,fruid */
                   fgetfru = 1;
                }
            }
	
	    /* Check if matches previous FRU, if so don't repeat */
	    if ((sa == lastfru[0]) && (fruid == lastfru[1]) )
		fgetfru = 0;  

            if (fgetfru) {
                uchar adrtype;
                adrtype = g_addrtype;
                if (fdebug) printf("set_mc %02x:%02x:%02x type=%d fruid=%02x\n",
				g_bus,sa,g_lun,adrtype,fruid);
                ipmi_set_mc(g_bus, sa, g_lun,adrtype);
                ret = load_fru(sa,fruid,frutype,&pfru);
                if (ret != 0) {
		   show_loadfru_error(sa,fruid,ret);
                } else {
                   ret = show_fru(sa,fruid,frutype,pfru);
                   if (ret != 0) printf("show_fru error = %d\n",ret);
                   if (sa == bmc_sa && fruid == 0) fbasefru = 0;
                }
		free_fru(pfru);
		pfru = NULL;
                ipmi_restore_mc();
		lastfru[0] = sa;
		lastfru[1] = fruid;
		lastfru[2] = frutype;
            }
   return(ret);
}

/*
 * test_show_fru
 *
 * Reads the FRU buffer dump from a file to test decoding the FRU.
 * This file uses the format output from 'ipmiutil fru -x'.
 *
[Component,20,00] Component FRU Size  : 4096
FRU Header: 01 0a 00 01 20 00 00 d4
FRU Body (size=256/4096):
  0000: 01 0a 00 01 20 00 00 d4 01 09 00 c0 3b 7e 83 64 .... .......;~.d
 *
 */
static int test_show_fru(char *infile)
{
   int rv = -1;
   FILE *fp;
   int len, i, idx, sz, off;
   char buff[256];
   uchar sa = 0x20; 
   uchar fruid =0;
   uchar frutype = 0;
   uchar *pfru = NULL;
   char *p1;
   char *p2;

   fp = fopen(infile,"r");
   if (fp == NULL) {
	printf("Cannot open file %s\n",infile);
	return(ERR_FILE_OPEN);
   } else {
	if (fdebug) printf("decoding text file with FRU buffer bytes\n");
	idx = 0;
	off = 0;
	sz = sizeof(buff);
        while (fgets(buff, 255, fp)) {
   	       len = strlen_(buff);
	       if (fdebug) printf("fgets[%d]: %s",idx,buff); /*has '\n'*/
	       if (idx == 0) {   /* the FRU Size line*/
		   p1 = strstr(buff,"FRU Size");
		   if (p1 == NULL) continue; 
		   p1 = strchr(buff,',');
		   if (p1 == NULL) continue; 
		   p1++;
		   p2 = strchr(p1,',');
		   if (p2 == NULL) p2 = &buff[len];
 		   *p2 = 0;  /*stringify*/
		   sa = htoi(p1);
		   p1 = p2 + 1;
		   p2 = strchr(p1,']');
		   if (p2 == NULL) p2 = &buff[len];
 		   *p2 = 0;  /*stringify*/
		   fruid = htoi(p1);
		   frutype = 0;
		   if (fdebug) printf("read[%d] sa=%x fruid=%x\n",idx,sa,fruid);
		   idx++;
	       } else if (idx == 1) {   /* the FRU Header line*/
		   p1 = strstr(buff,"FRU Header");
		   // if (p1 == NULL) continue;  
		   if (fdebug) printf("read[%d] p1=%p\n",idx,p1);
		   idx++;
	       } else if (idx == 2) {   /* the FRU Bodu line*/
		   p1 = strstr(buff,"FRU Body");
		   if (p1 == NULL) continue;
		   p1 = strchr(buff,'=');
		   if (p1 == NULL) continue;
		   ++p1;
		   p2 = strchr(p1,'/');
		   if (p2 == NULL) p2 = &buff[len];
 		   *p2 = 0;  /*stringify*/
		   sz = atoi(p1);
		   p1 = p2 + 1;
		   p2 = strchr(p1,')');
		   if (p2 == NULL) p2 = &buff[len];
 		   *p2 = 0;  /*stringify*/
		   sfru = atoi(p1);  /*global sfru*/
		   pfru = malloc(sfru);
		   if (fdebug) 
			printf("read[%d] sz=%d/%d pfru=%p\n",idx,sz,sfru,pfru);
		   idx++;
	       } else {  /*idx==3, this has the FRU buffer data */
		   p1 = strchr(buff,':');
		   if (p1 == NULL) continue;
		   p1 += 2;
		   for (i = 0; i < 16; i++) {
		      pfru[off+i] = htoi(&p1[i*3]);
   	           }
		   off += 16;
		   if (fdebug) printf("read[%d] offset=%d\n",idx,off);
	       } /*endif */
	       if (off >= sz) { rv = 0; break; }
        } /*end while*/
                   
	fclose(fp);
        if (rv == 0) {
	   if (pfru == NULL) {
		printf("Invalid input file format (mode %d)\n",idx); 
		return(ERR_BAD_FORMAT);
	   }
           rv = show_fru(sa,fruid,frutype,pfru);
           if (rv != 0) printf("show_fru error = %d\n",rv);
	}
        free_fru(pfru);
   }
   return(rv);
}

#ifdef ALONE
#ifdef WIN32
int __cdecl
#else
int
#endif
main(int  argc, char **argv)
#else
/* METACOMMAND */
int i_fru(int argc, char **argv)
#endif
{
   int ret, rv;
   int c;
   uchar DevRecord[16];
   ushort recid;
   ushort nextid;
   ushort rsvid;
   uchar sdr[48];
   char biosver[80];
   uchar cc;
   uchar sa;
   //uchar fruid = 0;
   //uchar frutype = 0;
   int len, i, j;
   char *s1;
   uchar *pfru = NULL;
   FILE *fp;
   int ipass, npass, nsdrs;
   char *tag;
   char do_reserve = 1;
   char devstr[32];

   printf("%s: version %s\n",progname,progver);
          
   parse_lan_options('V',"4",0);  /*default to admin privilege*/
   while ( (c = getopt( argc, argv,"a:bcd:efhkl:m:n:i:p:r:s:t:v:xyzT:V:J:EYF:P:N:R:U:Z:?")) != EOF )
      switch(c) {
          case 'x': fdebug = 1;  break;
          case 'z': fdebug = 3;  break; /*do more LAN debug detail*/
          case 'b': fonlybase = 1; 
		    g_frutype = 0x07;  break;
          case 'c': fcanonical = 1; bdelim = BDELIM; break;
          case 'd': fdump = 1;      /*dump fru to a file*/
                binfile = optarg;
                break;
          case 'e': fchild = 1;  break;  /*extra child MCs if bladed*/
          case 'f': fchild = 1;  break;  /*follow child MCs if bladed*/
          case 'h': fonlyhsc = 1;     /* show HSC FRU, same as -m00c000s */
		    g_frutype = 0x0f;   break;
          case 'k': foemkontron = 1;  break;
          case 'n': 
		fwritefru |= 0x200; 
		if (optarg) {
                   len = strlen_(optarg);
                   if (len >= FIELD_LEN) len = FIELD_LEN - 1;
                   strncpy(chassis_name,optarg,len);
                   if (len == 1) {  /* add a space */
                       chassis_name[1] = ' ';
                       chassis_name[2] = 0;
                   }
                }
		break;
          case 'p':  /*Power Supply Product */
		// fwritefru |= 0x10; 
		if (optarg) {
                   len = strlen_(optarg);
                   if (len >= FIELD_LEN) len = FIELD_LEN - 1;
                   strncpy(ps_prod,optarg,len);
                   if (len == 1) {  /* add a space */
                       ps_prod[1] = ' ';
                       ps_prod[2] = 0;
                   }
                }
		break;
          case 'r': frestore = 1;   /*restore fru from a file*/
                fwritefru = 0x100;
                binfile = optarg;
                break;
          case 't': ftestshow = 1;  /*test show_fru from a file*/
                binfile = optarg;
                break;
          case 'a':
		fwritefru |= 0x01;
		if (optarg) {
                   len = strlen_(optarg);
                   if (len >= FIELD_LEN) len = FIELD_LEN - 1;
                   strncpy(asset_tag,optarg,len);
                   if (len == 1) {  /* add a space */
                       asset_tag[1] = ' ';
                       asset_tag[2] = 0;
                   }
                }
		break;
          case 's':
		fwritefru |= 0x02; 
		if (optarg) { 
                   len = strlen_(optarg);
                   if (len >= FIELD_LEN) len = FIELD_LEN - 1;
                   strncpy(serial_num,optarg,len);
                   if (len == 1) {  /* add a space */
                       serial_num[1] = ' ';
                       serial_num[2] = 0;
                   }
                }
		break;
          case 'v':
		fwritefru |= 0x08; 
		if (optarg) { 
                   len = strlen_(optarg);
                   if (len >= FIELD_LEN) len = FIELD_LEN - 1;
                   strncpy(prod_ver,optarg,len);
                   if (len == 1) {  /* add a space */
                       prod_ver[1] = ' ';
                       prod_ver[2] = 0;
                   }
                }
		break;
          case 'm': /* specific IPMB MC, 3-byte address, e.g. "409600" */
                    g_bus = htoi(&optarg[0]);  /*bus/channel*/
                    g_sa  = htoi(&optarg[2]);  /*device slave address*/
                    g_lun = htoi(&optarg[4]);  /*LUN*/
                    g_addrtype = ADDR_IPMB;
		    if (optarg[6] == 's') {
		             g_addrtype = ADDR_SMI;  s1 = "SMI";
		    } else { g_addrtype = ADDR_IPMB; s1 = "IPMB"; }
		    fset_mc = 1;
		    printf("set MC at %s bus=%x sa=%x lun=%x\n",
		            s1,g_bus,g_sa,g_lun);
                    break;
          case 'l': /* set local IPMB MC sa (same as -Z but sets bmc_sa) */
                    sa  = htoi(&optarg[0]);  /*device slave address*/
		    ipmi_set_mymc(g_bus, sa, g_lun,ADDR_IPMB);
		    bmc_sa = sa;
                    break;
          case 'i': fonlybase = 1;   /*specify a fru id*/
		    if (strncmp(optarg,"0x",2) == 0) g_fruid = htoi(&optarg[2]);
		    else g_fruid = htoi(optarg); 
		    printf("Using FRU ID 0x%02x\n",g_fruid);
                    break;
          case 'y': fdoanyway = 1;  break;
          case 'V':    /* priv level */
		    fprivset = 1;
          case 'N':    /* nodename */
          case 'U':    /* remote username */
          case 'P':    /* remote password */
          case 'R':    /* remote password */
          case 'E':    /* get password from IPMI_PASSWORD environment var */
          case 'F':    /* force driver type */
          case 'T':    /* auth type */
          case 'J':    /* cipher suite */ 
          case 'Y':    /* prompt for remote password */
          case 'Z':    /* set local MC address */
                parse_lan_options(c,optarg,fdebug);
                break;
          default:
                printf("Usage: %s [-bceikmtvx -a asset_tag -s ser_num -NUPREFTVY]\n",
			 progname);
		printf("   -a tag   Sets the Product Asset Tag\n");
		printf("   -b       Only show Baseboard FRU data\n");
		printf("   -c       show canonical, delimited output\n");
		printf("   -d file  Dump the binary FRU data to a file\n");
		printf("   -e       walk Every child FRU, for blade MCs\n");
		printf("   -i 00    Get a specific FRU ID\n");
		printf("   -k       Kontron setsn, setmfgdate\n");
                printf("   -m002000 specific MC (bus 00,sa 20,lun 00)\n");
		printf("   -s snum  Sets the Product Serial Number\n");
		printf("   -v pver  Sets the Product Version Number\n");
		printf("   -x       Display extra debug messages\n");
		print_lan_opt_usage(0);
		ret = ERR_USAGE;
		goto do_exit; 
      }

   if (ftestshow) {
       ret = test_show_fru(binfile);
       goto do_exit;
   }

   if (is_remote() && (!fprivset)) {
       /* If priv not specified, not writing, and only targetted FRU, 
          can default to request user privilege. */
       if (!fwritefru && fonlybase) parse_lan_options('V',"2",0);
   }
 
   ret = ipmi_getdeviceid( DevRecord, sizeof(DevRecord),fdebug);
   if (ret == 0) {
      uchar ipmi_maj, ipmi_min;
      ipmi_maj = DevRecord[4] & 0x0f;
      ipmi_min = DevRecord[4] >> 4;
      vend_id = DevRecord[6] + (DevRecord[7] << 8) + (DevRecord[8] << 16);
      prod_id = DevRecord[9] + (DevRecord[10] << 8);
      show_devid( DevRecord[2],  DevRecord[3], ipmi_maj, ipmi_min);
      if (ipmi_maj < 2) do_systeminfo = 0;
      if ((DevRecord[1] & 0x80) == 0x80) fdevsdrs = 1;
      if (vend_id == VENDOR_NEC) fdevsdrs = 0;
      else if (vend_id == VENDOR_SUN) {
	  do_guid = 0;       /*Sun doesn't support GUID*/
	  do_systeminfo = 0; /*Sun doesn't support system info*/
      } else if (vend_id == VENDOR_INTEL) {
	 if (is_thurley(vend_id,prod_id) || is_romley(vend_id,prod_id)) 
		set_max_kcs_loops(URNLOOPS); /*longer for cmds (default 300)*/
      }
   } else { 
	goto do_exit; 
   }

   ret = ipmi_getpicmg( DevRecord, sizeof(DevRecord),fdebug);
   if (ret == 0)  fpicmg = 1;
   if (!fpicmg) fdevsdrs = 0; /* override, use SDR repository for FRU */
   if (fdebug) printf("bmc_sa = %02x, fdevsdrs = %d\n",bmc_sa,fdevsdrs);

   if (fset_mc) {
       /* target a specific MC via IPMB (usu a picmg blade) */
       if (fdebug) printf("set_mc: %02x:%02x:%02x type=%d\n",
		          g_bus,g_sa,g_lun,g_addrtype);
       ipmi_set_mc(g_bus,g_sa,g_lun,g_addrtype);
       fonlybase = 1;   /*only show this MC*/
   } else {
       g_sa  = bmc_sa;  /* BMC_SA = 0x20 */
   }
   if (g_frutype == 0) {
       g_frutype = 0x01;   /* other = "Component" */
   }

   if (foemkontron) {
#ifdef METACOMMAND
       for (i = 0; i < optind; i++) { argv++; argc--; }
       if (fdebug) verbose = 1;
       ret = ipmi_kontronoem_main(NULL,argc,argv);
#else
       ret = LAN_ERR_NOTSUPPORT;
#endif
       goto do_exit;
   }
   npass = 1;

   if (fonlybase || fonlyhsc) fbasefru = 1;
   else {
     /* find FRU devices from SDRs */
     if (fpicmg && fdevsdrs) { 
	/* npass = 2 will get both SdrRep & DevSdr passes on CMM */
        npass = 2;
        g_addrtype = ADDR_IPMB;
     }

     nsdrs = 0;
     for (ipass = 0; ipass < npass; ipass++)
     {
       ret = GetSDRRepositoryInfo(&j,&fdevsdrs);
       if (fdebug) printf("GetSDRRepositoryInfo: ret=%x nSDRs=%d fdevsdrs=%d\n",
			   ret,j,fdevsdrs);
       if (j == 0) {
          /* this is an error, probably because fdevsdrs is wrong.*/
          fdevsdrs = (fdevsdrs ^ 1);
          if (fdebug) printf("nsdrs=0, retrying with fdevsdrs=%d\n",fdevsdrs);
          j = 60; /*try some default num SDRs*/
       }
       nsdrs = j;
       if (fdevsdrs) tag="Device SDRs";
       else tag="SDR Repository";
       printf("--- Scanning %s for %d SDRs ---\n",tag,nsdrs);

       /* loop thru SDRs to find FRU devices */
       recid = 0;
       while (recid != LAST_REC)
       {
	  if (do_reserve) {
             /* reserve the SDR repository */
             ret = sdr_get_reservation((uchar *)&rsvid,fdevsdrs);
             if (fdebug) printf("sdr_get_reservation ret=%d\n",ret);
	     if (ret == 0) do_reserve = 0;
	  }

	  len = sizeof(sdr); /*sizeof(sdr); get 32 sdr bytes*/
	  ret = get_sdr(recid,rsvid,&nextid,sdr,&len,&cc);
	  if ((ret != 0) || (cc != 0)) {
	    printf("SDR[%04x] error %d ccode = %x\n",recid,ret,cc);
	    if ((cc == 0xC5) || (cc == 0x83)) ; /*do not stop (ARC)*/
	    else break;  /*stop if errors*/
	  }
          if (len >= MIN_SDR_SZ) {
	     if ((sdr[3] == 0x11) || (sdr[3] == 0x12)) /*SDR FRU or IPMB type*/
	        ret = get_show_fru(recid, sdr,len);
	     do_reserve = 1;
          }  /*endif get_show_fru */
#ifdef PICMG_CHILD
           /*
            * Special logic for blade child MCs in PICMG ATCA systems 
            * if fchild, try all child MCs within the chassis.
            * SDR type 12 capabilities bits (sdrdata[8]):
            *    80 = Chassis Device
            *    40 = Bridge
            *    20 = IPMB Event Generator
            *    10 = IPMB Event Receiver
            *    08 = FRU Device
            *    04 = SEL Device
            *    02 = SDR Repository Device
            *    01 = Sensor Device
            *    But all child MCs use Device SDRs anyway.
            */
           if (fpicmg && fchild && (sdr[3] == 0x12)) { /* PICMG MC DLR */
              ushort  _recid, _recnext; 
	      int _sz;
              uchar _sdrdata[MAX_SDR_SIZE];
              int devsdrs_save;
              uchar cc;

              /* save the BMC globals, use IPMB MC */
              devsdrs_save  = fdevsdrs;
              fdevsdrs = 1;   /* use Device SDRs for the children*/
              if (fdebug)
                printf(" --- IPMB MC (sa=%02x cap=%02x id=%02x devsdrs=%d):\n",
                       sdr[5],sdr[8],sdr[12],fdevsdrs);
              ipmi_set_mc(PICMG_SLAVE_BUS,sdr[5],sdr[6],g_addrtype);

              _sz = 16;
              ret = ipmi_cmd_mc(GET_DEVICE_ID,NULL,0,_sdrdata,&_sz,&cc,fdebug);
              if (ret == 0 && cc == 0) {
                /* get a new SDR Reservation ID */
	        ret = sdr_get_reservation((uchar *)&rsvid,fdevsdrs);
		if (fdebug) printf("sdr_get_reservation ret=%d\n",ret);
                /* Get the SDRs from the IPMB MC */
                _recid = 0;
                while (_recid != 0xffff) 
                {
                  _sz = sizeof(_sdrdata);
		  ret = get_sdr(_recid,rsvid,&_recnext,_sdrdata,&_sz,&cc);
		  if (fdebug) printf("get_sdr(%x) rv=%d cc=%x rlen=%d\n",
					_recid,ret,cc,_sz);
  	          if (ret != 0) {
  	             printf("%04x get_sdr error %d, rlen=%d\n",_recid,ret,_sz);
                     break;
                  }
                  else if (_sz >= MIN_SDR_SZ) {
		     if ((_sdrdata[3] == 0x11) || (_sdrdata[3] == 0x12)) 
			ret = get_show_fru(_recid, _sdrdata, _sz);
		  }
                  if (_recnext == _recid) _recid = 0xffff;
                  else _recid = _recnext;
                } /*end while*/
              } /*endif ret==0*/

              /* restore BMC globals */
              fdevsdrs = devsdrs_save;
              ipmi_restore_mc();
	      do_reserve = 1; /* get a new SDR Reservation ID */
           }  /*endif fpicmg && fchild*/
#endif

           recid = nextid;
        } /*end while sdrs*/
        if (npass > 1) {   /*npass==2 for PICMG child*/
           /* Switch fdevsdrs from Device to Repository (or vice-versa) */
           if (fdevsdrs == 0) fdevsdrs = 1;
           else fdevsdrs = 0;
        }
     } /*end ipass loop*/
   } /*endif not fonlybase*/
 
   /* load the FRU data for Baseboard (address 0x20) */
   printf("\n");
   sa = g_sa;  /* bmc_sa = BMC_SA = 0x20 */
   if (fonlyhsc) { sa = 0xC0;  g_addrtype = ADDR_SMI; 
      ipmi_set_mc(g_bus,sa,g_lun,g_addrtype); 
   }
   if (g_addrtype == ADDR_IPMB) 
      ipmi_set_mc(g_bus,sa,g_lun,g_addrtype);

   if (fbasefru) {
      /* get and display the Baseboard FRU data */
      ret = load_fru(sa,g_fruid,g_frutype,&pfru);
      if (ret != 0) {
	 show_loadfru_error(sa,g_fruid,ret);
	 free_fru(pfru);
	 pfru = NULL;
 	 goto do_exit;
      }
      ret = show_fru(sa,g_fruid,g_frutype,pfru);
      if (ret != 0) printf("show_fru error = %d\n",ret);
   }

   if (fcanonical) devstr[0] = 0;  /*default is empty string*/
   else sprintf(devstr,"[%s,%02x,%02x] ", /*was by g_frutype*/
		FruTypeString(SYS_FRUTYPE,g_fruid),sa,g_fruid);

   if (!is_remote()) {
	i = get_BiosVersion(biosver);
	if (i == 0) 
		printf("%sBIOS Version        %c %s\n",devstr,bdelim,biosver);
   }

   if (do_systeminfo) {
      char infostr[64];
      int len;
      len = sizeof(infostr);
      rv = get_system_info(1,infostr,&len);  /*Firmware Version*/
      if (rv == 0) {
         len = sizeof(infostr);
         rv = get_system_info(2,infostr,&len);
         if (rv == 0) printf("%sSystem Name         %c %s\n",devstr,bdelim,infostr);
         len = sizeof(infostr);
         rv = get_system_info(3,infostr,&len);
         if (rv == 0) printf("%sPri Operating System%c %s\n",devstr,bdelim,infostr);
         len = sizeof(infostr);
         rv = get_system_info(4,infostr,&len);
         if (rv == 0) printf("%sSec Operating System%c %s\n",devstr,bdelim,infostr);
      } else {
         if (fdebug && (rv == 0xC1)) /*only supported on later IPMI 2.0 fw */
            printf("GetSystemInfo not supported on this platform\n");
      }
   }

   if (fdump && ret == 0) {
      /* Dump FRU to a binary file */
      fp = fopen(binfile,"w");
      if (fp == NULL) {
         ret = get_LastError();
         printf("Cannot open file %s, error %d\n",binfile,ret);
      } else {
         printf("Writing FRU size %d to %s  ...\n",sfru,binfile);
         len = (int)fwrite(frubuf, 1, sfru, fp);
         fclose(fp);
         if (len <= 0) {
            ret = get_LastError();
            printf("Error %d writing file %s\n",ret,binfile);
         } else ret = 0;
      }
      goto do_exit;
   }
   else if (frestore) {
      uchar cksum;
      /* Restore FRU from a binary file */
      fp = fopen(binfile,"r");
      if (fp == NULL) {
	 ret = get_LastError();
         printf("Cannot open file %s, error %d\n",binfile,ret);
      } else {
	 ret = 0;
	 /* sfru and frubuf were set from load_fru above. */
	 len = (int)fread(frubuf, 1, sfru, fp);
	 if (len <= 0) { 
	     ret = get_LastError();
             printf("Error %d reading file %s\n",ret,binfile);
	     sfru = 0;	/*for safety*/
	 }
         fclose(fp);
         if (fdebug) {
	    printf("FRU buffer from file (%d):",sfru);
	    dumpbuf(frubuf,sfru);
         }
	 /* Do some validation of the FRU buffer header */
	 cksum = calc_cksum(&frubuf[0],7);
         if (fdebug) 
	     printf("header, len=8, cksum0 = %02x, cksum1 = %02x\n",
		    frubuf[7],cksum); 
	 if (frubuf[7] != cksum) {
	    printf("Not a valid FRU file\n");
	    ret = ERR_BAD_FORMAT;
            free_fru(frubuf);
	 }
	 if (ret == 0) {  /*successfully read data*/
            printf("Writing FRU size %d from %s  ...\n",sfru,binfile);
	    ret = write_fru_data(g_fruid, 0, frubuf, sfru, fdebug);
            free_fru(frubuf);
            if (ret != 0) printf("write_fru error %d (0x%02x)\n",ret,ret);
	    else {  /* successful, show new data */
               ret = load_fru(sa,g_fruid,g_frutype,&pfru);
	       if (ret != 0) show_loadfru_error(sa,g_fruid,ret);
               else ret = show_fru(sa,g_fruid,g_frutype,pfru);
               free_fru(pfru);
	       pfru = NULL;
	    }
	 } 
      }
   }  /*end-else frestore */
   else if ((fwritefru != 0) && ret == 0) {
	if (fbasefru == 0) {
	   /* if not fbasefru, must reload fru because freed in get_show_fru */
	   ret = load_fru(sa,g_fruid,g_frutype,&pfru);
	   if (ret != 0) {
	      show_loadfru_error(sa,g_fruid,ret);
	      free_fru(pfru);
	      pfru = NULL;
	      goto do_exit;
	   }
	   if (fdebug) printf("Baseboard FRU buffer reloaded (%d):",sfru);
	}
	printf("\nWriting new product data (%s,%s,%s) ...\n",
		prod_ver,serial_num,asset_tag);
        ret = write_asset(asset_tag,serial_num,prod_ver,fwritefru,pfru);
        free_fru(pfru);
        if (ret != 0) printf("write_asset error %d (0x%02x)\n",ret,ret);
	else {  /* successful, show new data */
           ret = load_fru(sa,g_fruid,g_frutype,&pfru);
	   if (ret != 0) show_loadfru_error(sa,g_fruid,ret);
           else ret = show_fru(sa,g_fruid,g_frutype,pfru);
           free_fru(pfru);
	}
	pfru = NULL;
   }
   else 
	free_fru(pfru);

do_exit:
   ipmi_close_();
   // show_outcome(progname,ret);  
   return(ret);
}

/* end ifru.c */
