ef10_nvram.c revision 49ef7e0638c8b771d8a136eae78b1c0f99acc8e0
/*
* Copyright (c) 2012-2015 Solarflare Communications 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:
*
* 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
*
* 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.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of the FreeBSD Project.
*/
#include "efx.h"
#include "efx_impl.h"
#if EFSYS_OPT_VPD || EFSYS_OPT_NVRAM
#include "ef10_tlv_layout.h"
/* Cursor for TLV partition format */
typedef struct tlv_cursor_s {
} tlv_cursor_t;
typedef struct nvram_partition_s {
/*
* The full length of the NVRAM partition.
* This is different from tlv_partition_header.total_length,
* which can be smaller.
*/
static __checkReturn efx_rc_t
static void
{
}
static uint32_t
{
return (tag);
}
static size_t
{
return (0);
}
static uint8_t *
{
return (NULL);
}
static uint8_t *
{
return (NULL);
}
/*
* TLV item DWORD length is tag + length + value (rounded up to DWORD)
* equivalent to tlv_n_words_for_len in mc-comms tlv.c
*/
#define TLV_DWORD_COUNT(length) \
static uint32_t *
{
}
static __checkReturn efx_rc_t
{
goto fail1;
/* No more tags after END tag */
goto fail2;
}
/* Advance to next item and validate */
goto fail3;
return (0);
return (rc);
}
static efx_rc_t
{
goto fail1;
return (0);
return (rc);
}
static efx_rc_t
{
while (rc == 0) {
break;
}
return (rc);
}
static __checkReturn efx_rc_t
{
/* Check cursor position */
goto fail1;
}
goto fail2;
}
/* Check current item has space for tag and length */
goto fail3;
}
/* Check we have value data for current item and another tag */
goto fail4;
}
}
return (0);
return (rc);
}
static efx_rc_t
{
return (tlv_validate_state(cursor));
}
static __checkReturn efx_rc_t
{
}
static __checkReturn efx_rc_t
{
}
static __checkReturn efx_rc_t
{
goto fail1;
}
return (0);
return (rc);
}
static size_t
{
goto fail1;
goto fail2;
/* Return space used (including the END tag) */
return (0);
}
static uint32_t *
{
/*
* Go through each segment and check that it has an end tag. If there
* is no end tag then the previous segment was the last valid one,
* so return the pointer to its end tag.
*/
for (;;) {
break;
if (tlv_require_end(&segment_cursor) != 0)
break;
}
return (last_segment_end);
}
static void
{
if (len > 0) {
}
}
static __checkReturn efx_rc_t
{
unsigned int delta;
goto fail1;
goto fail2;
if (tag == TLV_TAG_END) {
goto fail3;
}
goto fail4;
}
/* Move data up: new space at cursor->current */
/* Adjust the end pointer */
/* Write new TLV item */
return (0);
return (rc);
}
static __checkReturn efx_rc_t
{
unsigned int delta;
goto fail1;
goto fail2;
}
goto fail3;
/* Shuffle things down, destroying the item at cursor->current */
/* Zero the new space at the end of the TLV chain */
/* Adjust the end pointer */
return (0);
return (rc);
}
static __checkReturn efx_rc_t
{
unsigned int old_ndwords;
unsigned int new_ndwords;
unsigned int delta;
goto fail1;
goto fail2;
}
goto fail3;
}
goto fail4;
if (new_ndwords > old_ndwords) {
/* Expand space used for TLV item */
goto fail5;
}
/* Move up: new space at (cursor->current + old_ndwords) */
/* Adjust the end pointer */
} else if (new_ndwords < old_ndwords) {
/* Shrink space used for TLV item */
/* Move down: remove words at (cursor->current + new_ndwords) */
/* Zero the new space at the end of the TLV chain */
/* Adjust the end pointer */
}
/* Write new data */
return (0);
return (rc);
}
static uint32_t checksum_tlv_partition(
{
csum = 0;
return (csum);
}
static __checkReturn efx_rc_t
{
struct tlv_partition_header *header;
struct tlv_partition_trailer *trailer;
/*
* We just modified the partition, so the total length may not be
* valid. Don't use tlv_find(), which performs some sanity checks
* that may fail here.
*/
/* Sanity check. */
goto fail1;
}
if (new_len == 0) {
goto fail2;
}
/* Ensure the modified partition always has a new generation count. */
return (0);
return (rc);
}
/* Validate buffer contents (before writing to flash) */
{
struct tlv_partition_header *header;
struct tlv_partition_trailer *trailer;
int pos;
goto fail1;
}
/* The partition header must be the first item (at offset zero) */
partn_size)) != 0) {
goto fail2;
}
goto fail3;
}
/* Check TLV partition length (includes the END tag) */
if (total_length > partn_size) {
goto fail4;
}
/* Check partition ends with PARTITION_TRAILER and END tags */
goto fail5;
}
goto fail6;
}
goto fail7;
}
/* Check generation counts are consistent */
goto fail8;
}
/* Verify partition checksum */
cksum = 0;
}
if (cksum != 0) {
goto fail9;
}
return (0);
return (rc);
}
{
struct tlv_partition_header header;
struct tlv_partition_trailer trailer;
unsigned min_buf_size = sizeof (struct tlv_partition_header) +
sizeof (struct tlv_partition_trailer);
if (partn_size < min_buf_size) {
goto fail1;
}
buf)) != 0) {
goto fail2;
}
if ((rc = tlv_insert(
goto fail3;
goto fail4;
goto fail5;
goto fail6;
/* Check that the partition is valid. */
partn_data, partn_size)) != 0)
goto fail7;
return (0);
return (rc);
}
static uint32_t
{
}
{
// Read past partition header to find start address of the first key
/* A PARTITION_HEADER tag must be the first item (at offset zero) */
buffer_size)) != 0) {
goto fail1;
}
goto fail2;
}
goto fail3;
}
goto fail4;
return (0);
return (rc);
}
{
// Read to end of partition
buffer_size)) != 0) {
goto fail1;
}
goto fail2;
return (0);
return (rc);
}
{
// Find TLV at offset and return key start and length
buffer_size, offset) != 0) {
return (B_FALSE);
}
if (tag == TLV_TAG_PARTITION_HEADER ||
tag == TLV_TAG_PARTITION_TRAILER) {
if (tlv_advance(&cursor) != 0) {
break;
}
continue;
}
return (B_TRUE);
}
return (B_FALSE);
}
{
if (item_max_size < length) {
goto fail1;
}
buffer_size, offset)) != 0) {
goto fail2;
}
if (length < item_length) {
goto fail3;
}
*lengthp = item_length;
return (0);
return (rc);
}
{
buffer_size, offset)) != 0) {
goto fail1;
}
if (rc != 0) {
goto fail2;
}
return (0);
return (rc);
}
{
buffer_size, offset)) != 0) {
goto fail1;
}
goto fail2;
return (0);
return (rc);
}
{
buffer_size)) != 0) {
goto fail1;
}
goto fail2;
goto fail3;
return (0);
return (rc);
}
/*
* Read and validate a segment from a partition. A segment is a complete
* tlv chain between PARTITION_HEADER and PARTITION_END tags. There may
* be multiple segments in a partition, so seg_offset allows segments
* beyond the first to be read.
*/
static __checkReturn efx_rc_t
{
struct tlv_partition_header *header;
struct tlv_partition_trailer *trailer;
int pos;
goto fail1;
}
/* Read initial chunk of the segment, starting at offset */
MC_CMD_NVRAM_READ_IN_V2_TARGET_CURRENT)) != 0) {
goto fail2;
}
/* A PARTITION_HEADER tag must be the first item at the given offset */
max_seg_size)) != 0) {
goto fail3;
}
goto fail4;
}
/* Check TLV segment length (includes the END tag) */
if (total_length > max_seg_size) {
goto fail5;
}
/* Read the remaining segment content */
if (total_length > EF10_NVRAM_CHUNK) {
goto fail6;
}
/* Check segment ends with PARTITION_TRAILER and END tags */
goto fail7;
}
goto fail8;
}
goto fail9;
}
/* Check data read from segment is consistent */
/*
* The partition data may have been modified between successive
* MCDI NVRAM_READ requests by the MC or another PCI function.
*
* The caller must retry to obtain consistent partition data.
*/
goto fail10;
}
/* Verify segment checksum */
cksum = 0;
}
if (cksum != 0) {
goto fail11;
}
return (0);
return (rc);
}
/*
* Read a single TLV item from a host memory
* buffer containing a TLV formatted segment.
*/
{
goto fail1;
}
/* Find requested TLV tag in segment data */
max_seg_size)) != 0) {
goto fail2;
}
goto fail3;
}
if (length == 0)
else {
/* Copy out data from TLV item */
goto fail4;
}
}
return (0);
return (rc);
}
/* Read a single TLV item from the first segment in a TLV formatted partition */
{
size_t partn_size = 0;
int retry;
/* Allocate sufficient memory for the entire partition */
goto fail1;
if (partn_size == 0) {
goto fail2;
}
goto fail3;
}
/*
* Read the first segment in a TLV partition. Retry until consistent
* segment contents are returned. Inconsistent data may be read if:
* a) the segment contents are invalid
* b) the MC has rebooted while we were reading the partition
* c) the partition has been modified while we were reading it
* Limit retry attempts to ensure forward progress.
*/
retry = 10;
do {
if (rc != 0) {
/* Failed to obtain consistent segment data */
goto fail4;
}
goto fail5;
return (0);
return (rc);
}
/* Compute the size of a segment. */
static __checkReturn efx_rc_t
{
struct tlv_partition_header *header;
int pos;
/* A PARTITION_HEADER tag must be the first item at the given offset */
max_seg_size)) != 0) {
goto fail1;
}
goto fail2;
}
/* Check TLV segment length (includes the END tag) */
if (*seg_sizep > max_seg_size) {
goto fail3;
}
/* Check segment ends with PARTITION_TRAILER and END tags */
goto fail4;
}
goto fail5;
}
goto fail6;
}
/* Verify segment checksum */
cksum = 0;
}
if (cksum != 0) {
goto fail7;
}
/*
* Calculate total length from HEADER to END tags and compare to
* max_seg_size and the total_length field in the HEADER tag.
*/
if (segment_length > max_seg_size) {
goto fail8;
}
if (segment_length != *seg_sizep) {
goto fail9;
}
/* Skip over the first HEADER tag. */
while (rc == 0) {
/* Check that the END tag is the one found earlier. */
goto fail10;
break;
}
/* Check for duplicate HEADER tags before the END tag. */
goto fail11;
}
}
if (rc != 0)
goto fail12;
return (0);
return (rc);
}
/*
* Add or update a single TLV item in a host memory buffer containing a TLV
* formatted segment. Historically partitions consisted of only one segment.
*/
{
struct tlv_partition_header *header;
struct tlv_partition_trailer *trailer;
int pos;
/* A PARTITION_HEADER tag must be the first item (at offset zero) */
max_seg_size)) != 0) {
goto fail1;
}
goto fail2;
}
/* Update the TLV chain to contain the new data */
/* Modify existing TLV item */
goto fail3;
} else {
/* Insert a new TLV item before the PARTITION_TRAILER */
if (rc != 0) {
goto fail4;
}
goto fail5;
}
}
/* Find the trailer tag */
goto fail6;
}
/* Update PARTITION_HEADER and PARTITION_TRAILER fields */
if (*total_lengthp > max_seg_size) {
goto fail7;
}
/* Recompute PARTITION_TRAILER checksum */
cksum = 0;
}
return (0);
return (rc);
}
/*
* Add or update a single TLV item in the first segment of a TLV formatted
* dynamic config partition. The first segment is the current active
* configuration.
*/
{
}
/*
* Read a segment from nvram at the given offset into a buffer (segment_data)
* and optionally write a new tag to it.
*/
static __checkReturn efx_rc_t
{
/*
* Read the segment from NVRAM into the segment_data buffer and validate
* it, returning if it does not validate. This is not a failure unless
* this is the first segment in a partition. In this case the caller
* must propogate the error.
*/
*seg_datap, *src_remain_lenp);
if (status != 0)
return (EINVAL);
if (status != 0)
return (EINVAL);
if (write) {
/* Update the contents of the segment in the buffer */
&modified_segment_size)) != 0)
goto fail1;
} else {
/*
* We won't modify this segment, but still need to update the
* remaining lengths and pointers.
*/
}
return (0);
return (rc);
}
/*
* Add or update a single TLV item in either the first segment or in all
* segments in a TLV formatted dynamic config partition. Dynamic config
* partitions on boards that support RFID are divided into a number of segments,
* each formatted like a partition, with header, trailer and end tags. The first
* segment is the current active configuration.
*
* The segments are initialised by manftest and each contain a different
* configuration e.g. firmware variant. The firmware can be instructed
* via RFID to copy a segment to replace the first segment, hence changing the
* active configuration. This allows ops to change the configuration of a board
* prior to shipment using RFID.
*
* Changes to the dynamic config may need to be written to all segments (e.g.
* firmware versions) or just the first segment (changes to the active
* configuration). See SF-111324-SW "The use of RFID in Solarflare Products".
* If only the first segment is written the code still needs to be aware of the
* possible presence of subsequent segments as writing to a segment may cause
* its size to increase, which would overwrite the subsequent segments and
* invalidate them.
*/
{
size_t partn_size = 0;
size_t total_length = 0;
size_t current_offset = 0;
/* Allocate sufficient memory for the entire partition */
goto fail1;
if (partn_data == NULL) {
goto fail2;
}
/* Lock the partition */
goto fail3;
/* Iterate over each (potential) segment to update it. */
do {
if (rc != 0) {
if (current_offset == 0) {
/*
* If no data has been read then the first
* segment is invalid, which is an error.
*/
goto fail4;
}
break;
}
} while (current_offset < partn_size);
/*
* We've run out of space. This should actually be dealt with by
* ef10_nvram_buf_write_tlv returning ENOSPC.
*/
if (total_length > partn_size) {
goto fail5;
}
/* Erase the whole partition in NVRAM */
goto fail6;
/* Write new partition contents from the buffer to NVRAM */
total_length)) != 0)
goto fail7;
/* Unlock the partition */
return (0);
return (rc);
}
/*
* Get the size of a NVRAM partition. This is the total size allocated in nvram,
* not the data used by the segments in the partition.
*/
{
goto fail1;
return (0);
return (rc);
}
{
goto fail1;
return (0);
return (rc);
}
{
while (size > 0) {
goto fail1;
}
}
return (0);
return (rc);
}
{
/*
* Read requests which come in through the EFX API expect to
* read the current, active partition.
*/
}
{
&erase_size, NULL)) != 0)
goto fail1;
if (erase_size == 0) {
goto fail2;
} else {
if (size % erase_size != 0) {
goto fail3;
}
while (size > 0) {
erase_size)) != 0)
goto fail4;
offset += erase_size;
size -= erase_size;
}
}
return (0);
return (rc);
}
{
NULL, &write_size)) != 0)
goto fail1;
if (write_size != 0) {
/*
* Check that the size is a multiple of the write chunk size if
* the write chunk size is available.
*/
if (size % write_size != 0) {
goto fail2;
}
} else {
}
while (size > 0) {
goto fail3;
}
}
return (0);
return (rc);
}
void
{
goto fail1;
return;
}
{
struct tlv_partition_version partn_version;
/* Add or modify partition version TLV item */
/* Write the version number to all segments in the partition */
goto fail1;
return (0);
return (rc);
}
#endif /* EFSYS_OPT_VPD || EFSYS_OPT_NVRAM */
#if EFSYS_OPT_NVRAM
typedef struct ef10_parttbl_entry_s {
unsigned int partn;
unsigned int port;
/* Translate EFX NVRAM types to firmware partition types */
static ef10_parttbl_entry_t hunt_parttbl[] = {
};
static ef10_parttbl_entry_t medford_parttbl[] = {
};
static __checkReturn efx_rc_t
{
case EFX_FAMILY_HUNTINGTON:
*parttblp = hunt_parttbl;
break;
case EFX_FAMILY_MEDFORD:
break;
default:
return (EINVAL);
}
return (0);
}
{
size_t parttbl_rows = 0;
unsigned int i;
for (i = 0; i < parttbl_rows; i++) {
return (0);
}
}
}
return (ENOTSUP);
}
#if EFSYS_OPT_DIAG
static __checkReturn efx_rc_t
{
size_t parttbl_rows = 0;
unsigned int i;
for (i = 0; i < parttbl_rows; i++) {
return (0);
}
}
}
return (ENOTSUP);
}
{
unsigned int npartns = 0;
unsigned int i;
/* Read available partitions from NVRAM partition map */
goto fail1;
}
&npartns)) != 0) {
goto fail2;
}
for (i = 0; i < npartns; i++) {
/* Check if the partition is supported for this port */
continue;
goto fail3;
}
return (0);
return (rc);
}
#endif /* EFSYS_OPT_DIAG */
{
/* FIXME: get highest partn version from all ports */
/* FIXME: return partn description if available */
goto fail1;
return (0);
return (rc);
}
{
goto fail1;
if (chunk_sizep != NULL)
return (0);
return (rc);
}
void
{
}
#endif /* EFSYS_OPT_NVRAM */
#endif /* EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD */