root/usr/src/cmd/picl/plugins/sun4u/lib/fruaccess/cvrt_spd_data.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "fru_access_impl.h"

static uchar_t                  sp_sec_hdr[] = SP_SEC_HDR;
static uchar_t                  sp_seg_hdr[] = SP_SEG_HDR;
static uchar_t                  sp_seg_body[] = SP_DATA;

/*
 * function to return section header for simulated SPD fruid
 *
 * parameters:
 *      sec_hdr         buffer to receive section header
 *      sec_hdr_len     size of buffer sec_hdr
 * return value:
 *      size of returned data (0 if sec_hdr_len too small)
 */
size_t
get_sp_sec_hdr(void *sec_hdr, size_t sec_hdr_len)
{
        if (sec_hdr_len < sizeof (sp_sec_hdr))
                return (0);
        (void) memcpy(sec_hdr, sp_sec_hdr, sizeof (sp_sec_hdr));
        return (sizeof (sp_sec_hdr));
}

/*
 * function to return segment header for simulated SPD fruid
 *
 * parameters:
 *      seg_hdr         buffer to receive segment header
 *      seg_hdr_len     size of buffer seg_hdr
 * return value:
 *      size of returned data (0 if seg_hdr_len too small)
 */
size_t
get_sp_seg_hdr(void *seg_hdr, size_t seg_hdr_len)
{
        if (seg_hdr_len < sizeof (sp_seg_hdr))
                return (0);
        (void) memcpy(seg_hdr, sp_seg_hdr, sizeof (sp_seg_hdr));
        return (sizeof (sp_seg_hdr));
}

/*
 * Function to convert SPD data into SPD fruid segment.
 * The segment comprises two tagged records: DIMM_Capacity and SPD_R.
 *
 * DIMM_Capacity is a text string showing the total usable size of the
 * DIMM (i.e. not including error correction bits). This record is derived
 * from module row density and number of rows.
 *
 * SPD_R contains the entire SPD data area from the DIMM. It is slightly
 * massaged to make it easier to display:
 * bytes  0 -  63 are presented as is
 * bytes 64 -  71 (JEDEC code) are compressed into 2 bytes, matching the
 *                format used in ManR
 * bytes 72 -  92 are copied as is (to bytes 66 - 86)
 * byte  93       year of manufacture is expanded to a 2 byte (big endian)
 *                field which includes the century (to bytes 87 - 88)
 * bytes 94 - 127 are copied as is (to bytes 89 - 122)
 *
 * parameters:
 *      spd_data        pointer to SPD data
 *      spd_data_len    length of supplied SPD data
 *      sp_seg_ptr      pointer to receive address of converted data
 *      sp_seg_len      pointer for size of converted data
 * return value:
 *      0       - success
 *      NZ      - error code
 */
int
cvrt_dim_data(const char *spd_data, size_t spd_data_len, uchar_t **sp_seg_ptr,
    size_t *sp_seg_len)
{
        int             c;
        ushort_t        year;
        int             capacity;
        spd_data_t      *spd;
        uint32_t        sum;

        if (spd_data_len < sizeof (spd_data_t))
                return (EINVAL);

        spd = (spd_data_t *)spd_data;
        *sp_seg_ptr = malloc(sizeof (sp_seg_body));

        if (*sp_seg_ptr == NULL)
                return (ENOMEM);

        /* set up template for SP seg */
        (void) memcpy(*sp_seg_ptr, sp_seg_body, sizeof (sp_seg_body));

        year = spd->manu_year;

        if (year < 80)
                year += 2000;
        else
                year += 1900;

        /*
         * move first 64 bytes of SPD data into SPD-R record
         */
        (void) memcpy(*sp_seg_ptr + SPD_R_OFF, spd_data, 64);

        /*
         * re-write full data width as big endian
         */
        (*sp_seg_ptr + SPD_R_OFF + DATA_WIDTH)[0] = spd->ms_data_width;
        (*sp_seg_ptr + SPD_R_OFF + DATA_WIDTH)[1] = spd->ls_data_width;

        /*
         * construct Sun compressed encoding for JEDEC code
         */
        for (c = 0; c < sizeof (spd->jedec) - 1; c++) {
                if (spd->jedec[c] != 0x7F)
                        break;
        }

        (*sp_seg_ptr)[SPD_R_OFF + MANUF_ID] = (uchar_t)c;
        (*sp_seg_ptr)[SPD_R_OFF + MANUF_ID + 1] = (uchar_t)spd->jedec[c];

        /*
         * move other fields in place
         */
        (void) memcpy(*sp_seg_ptr + SPD_R_OFF + MANUF_LOC,
            &spd->manu_loc, MANUF_YEAR - MANUF_LOC);

        (*sp_seg_ptr + SPD_R_OFF + MANUF_YEAR)[0] = (uchar_t)(year >> 8);
        (*sp_seg_ptr + SPD_R_OFF + MANUF_YEAR)[1] = (uchar_t)year;

        (void) memcpy(*sp_seg_ptr + SPD_R_OFF + MANUF_WEEK,
            &spd->manu_week, SPD_R_LEN - MANUF_WEEK);

        /*
         * calculate the capacity and insert into capacity record
         */
        if ((spd->spd_rev >> 4) > 1) {
                (void) snprintf((char *)(*sp_seg_ptr + DIMM_CAP_OFF), 8,
                    "ver %x.%x", spd->spd_rev >> 4, spd->spd_rev & 0x0f);
        } else if ((spd->memory_type != SPDMEM_SDRAM) &&
            (spd->memory_type != SPDMEM_SDRAM_DDR) &&
            (spd->memory_type != SPDMEM_DDR2_SDRAM)) {
                /*
                 * can't handle this memory type
                 */
                ((char *)(*sp_seg_ptr))[DIMM_CAP_OFF] = '\0';
        } else if ((((spd->ms_data_width << 8) | spd->ls_data_width) == 72) &&
            ((spd->n_rows & 0xf0) == 0) && ((spd->n_cols & 0xf0) == 0)) {
                /*
                 * OK it's 72-bits wide with equal width banks
                 */
                char m_or_g = 'G';
                capacity = spd->mod_row_density;
                if (((spd->memory_type == SPDMEM_DDR2_SDRAM) &&
                    (capacity > 16)) ||
                    (capacity > 4)) {
                        capacity *= 4;
                        m_or_g = 'M';
                }
                c = spd->n_mod_rows;
                if (spd->memory_type == SPDMEM_DDR2_SDRAM) {
                        c &= 7;
                        c++;
                }
                capacity *= c;
                if ((m_or_g == 'M') && (capacity >= 1024)) {
                        capacity /= 1024;
                        m_or_g = 'G';
                }
                (void) snprintf((char *)(*sp_seg_ptr + DIMM_CAP_OFF), 8,
                    "%d %cB", capacity, m_or_g);
        } else {
                ((char *)(*sp_seg_ptr))[DIMM_CAP_OFF] = '\0';
        }

        /*
         * finally, set the checksum
         */
        sum = compute_crc32(*sp_seg_ptr, sizeof (sp_seg_body) - 5);
        for (c = 0; c < 4; c++) {
                (*sp_seg_ptr + sizeof (sp_seg_body) - 4)[c] =
                    ((char *)(&sum))[c];
        }
        *sp_seg_len = sizeof (sp_seg_body);
        return (0);
}

/*
 * get_spd_data - reads raw data from container
 * parameters:
 *      fd              file descriptor for SPD device
 *      ctr_offset      container offset
 *      ctr_len         container size
 *      spd_data        buffer to receive SPD data (length ctr_len)
 * return value:
 *      0       - success
 *      NZ      - error code
 */
int
get_spd_data(int fd, char *spd_data, size_t ctr_len, off_t ctr_offset)
{
        if (ctr_len < sizeof (spd_data_t))
                return (EINVAL);

        (void) memset(spd_data, 0, ctr_len);

        if (pread(fd, spd_data, sizeof (spd_data_t), ctr_offset) !=
            sizeof (spd_data_t))
                return (EIO);
        return (0);
}