root/sys/dev/ocs_fc/ocs_vpd.h
/*-
 * Copyright (c) 2017 Broadcom. All rights reserved.
 * The term "Broadcom" refers to Broadcom Limited and/or its subsidiaries.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. Neither the name of the copyright holder 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 HOLDER 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.
 */

/**
 * @file
 * OCS VPD parser
 */

#if !defined(__OCS_VPD_H__)
#define __OCS_VPD_H__

/**
 * @brief VPD buffer structure
 */

typedef struct {
        uint8_t *buffer;
        uint32_t length;
        uint32_t offset;
        uint8_t checksum;
        } vpdbuf_t;

/**
 * @brief return next VPD byte
 *
 * Returns next VPD byte and updates accumulated checksum
 *
 * @param vpd pointer to vpd buffer
 *
 * @return returns next byte for success, or a negative error code value for failure.
 *
 */

static inline int
vpdnext(vpdbuf_t *vpd)
{
        int rc = -1;
        if (vpd->offset < vpd->length) {
                rc = vpd->buffer[vpd->offset++];
                vpd->checksum += rc;
        }
        return rc;
}

/**
 * @brief return true if no more vpd buffer data
 *
 * return true if the vpd buffer data has been completely consumed
 *
 * @param vpd pointer to vpd buffer
 *
 * @return returns true if no more data
 *
 */
static inline int
vpddone(vpdbuf_t *vpd)
{
        return vpd->offset >= vpd->length;
}
/**
 * @brief return pointer to current VPD data location
 *
 * Returns a pointer to the current location in the VPD data
 *
 * @param vpd pointer to vpd buffer
 *
 * @return pointer to current VPD data location
 */

static inline uint8_t *
vpdref(vpdbuf_t *vpd)
{
        return &vpd->buffer[vpd->offset];
}

#define VPD_LARGE_RESOURCE_TYPE_ID_STRING_TAG   0x82
#define VPD_LARGE_RESOURCE_TYPE_R_TAG           0x90
#define VPD_LARGE_RESOURCE_TYPE_W_TAG           0x91
#define VPD_SMALL_RESOURCE_TYPE_END_TAG         0x78

/**
 * @brief find a VPD entry
 *
 * Finds a VPD entry given the two character code
 *
 * @param vpddata pointer to raw vpd data buffer
 * @param vpddata_length length of vpddata buffer in bytes
 * @param key key to look up

 * @return returns a pointer to the key location or NULL if not found or checksum error
 */

static inline uint8_t *
ocs_find_vpd(uint8_t *vpddata, uint32_t vpddata_length, const char *key)
{
        vpdbuf_t vpdbuf;
        uint8_t *pret = NULL;
        uint8_t c0 = key[0];
        uint8_t c1 = key[1];

        vpdbuf.buffer = (uint8_t*) vpddata;
        vpdbuf.length = vpddata_length;
        vpdbuf.offset = 0;
        vpdbuf.checksum = 0;

        while (!vpddone(&vpdbuf)) {
                int type = vpdnext(&vpdbuf);
                int len_lo;
                int len_hi;
                int len;
                int i;

                if (type == VPD_SMALL_RESOURCE_TYPE_END_TAG) {
                        break;
                }

                len_lo = vpdnext(&vpdbuf);
                len_hi = vpdnext(&vpdbuf);
                len = len_lo + (len_hi << 8);

                if ((type == VPD_LARGE_RESOURCE_TYPE_R_TAG) || (type == VPD_LARGE_RESOURCE_TYPE_W_TAG)) {
                        while (len > 0) {
                                int rc0;
                                int rc1;
                                int sublen;
                                uint8_t *pstart;

                                rc0 = vpdnext(&vpdbuf);
                                rc1 = vpdnext(&vpdbuf);

                                /* Mark this location */
                                pstart = vpdref(&vpdbuf);

                                sublen = vpdnext(&vpdbuf);

                                /* Adjust remaining len */
                                len -= (sublen + 3);

                                /* check for match with request */
                                if ((c0 == rc0) && (c1 == rc1)) {
                                        pret = pstart;
                                        for (i = 0; i < sublen; i++) {
                                                vpdnext(&vpdbuf);
                                        }
                                /* check for "RV" end */
                                } else if ('R' == rc0 && 'V' == rc1) {
                                        /* Read the checksum */
                                        for (i = 0; i < sublen; i++) {
                                                vpdnext(&vpdbuf);
                                        }

                                        /* The accumulated checksum should be zero here */
                                        if (vpdbuf.checksum != 0) {
                                                ocs_log_test(NULL, "checksum error\n");
                                                return NULL;
                                        }
                                }
                                else
                                        for (i = 0; i < sublen; i++) {
                                                vpdnext(&vpdbuf);
                                        }
                        }
                }

                for (i = 0; i < len; i++) {
                        vpdnext(&vpdbuf);
                }
        }

        return pret;
}
#endif