freebsd-dev/sys/dev/sfxge/common/ef10_nvram.c
Andrew Rybchenko e919b7ec20 sfxge(4): generalise EF10 NVRAM buffer interface
The SFN driver's PartitionControl WMI object requires an API to parse
and filter partition data in TLV format, particularly for the Dynamic
Config partition. The ef10_nvram_buffer functions provide this
functionality but are tied to use with license partition only.
Modify functions so they are applicable to all TLV partitions and add
functions to support in-place tag modification.

Submitted by:   Richard Houldsworth <rhouldsworth at solarflare.com>
Sponsored by:   Solarflare Communications, Inc.
Differential Revision:  https://reviews.freebsd.org/D18256
2018-11-30 07:05:00 +00:00

2517 lines
54 KiB
C

/*-
* Copyright (c) 2012-2016 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
* and/or other materials provided with the distribution.
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "efx.h"
#include "efx_impl.h"
#if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD || EFSYS_OPT_MEDFORD2
#if EFSYS_OPT_VPD || EFSYS_OPT_NVRAM
#include "ef10_tlv_layout.h"
/* Cursor for TLV partition format */
typedef struct tlv_cursor_s {
uint32_t *block; /* Base of data block */
uint32_t *current; /* Cursor position */
uint32_t *end; /* End tag position */
uint32_t *limit; /* Last dword of data block */
} tlv_cursor_t;
typedef struct nvram_partition_s {
uint16_t type;
uint8_t chip_select;
uint8_t flags;
/*
* The full length of the NVRAM partition.
* This is different from tlv_partition_header.total_length,
* which can be smaller.
*/
uint32_t length;
uint32_t erase_size;
uint32_t *data;
tlv_cursor_t tlv_cursor;
} nvram_partition_t;
static __checkReturn efx_rc_t
tlv_validate_state(
__inout tlv_cursor_t *cursor);
static void
tlv_init_block(
__out uint32_t *block)
{
*block = __CPU_TO_LE_32(TLV_TAG_END);
}
static uint32_t
tlv_tag(
__in tlv_cursor_t *cursor)
{
uint32_t dword, tag;
dword = cursor->current[0];
tag = __LE_TO_CPU_32(dword);
return (tag);
}
static size_t
tlv_length(
__in tlv_cursor_t *cursor)
{
uint32_t dword, length;
if (tlv_tag(cursor) == TLV_TAG_END)
return (0);
dword = cursor->current[1];
length = __LE_TO_CPU_32(dword);
return ((size_t)length);
}
static uint8_t *
tlv_value(
__in tlv_cursor_t *cursor)
{
if (tlv_tag(cursor) == TLV_TAG_END)
return (NULL);
return ((uint8_t *)(&cursor->current[2]));
}
static uint8_t *
tlv_item(
__in tlv_cursor_t *cursor)
{
if (tlv_tag(cursor) == TLV_TAG_END)
return (NULL);
return ((uint8_t *)cursor->current);
}
/*
* 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) \
(1 + 1 + (((length) + sizeof (uint32_t) - 1) / sizeof (uint32_t)))
static uint32_t *
tlv_next_item_ptr(
__in tlv_cursor_t *cursor)
{
uint32_t length;
length = tlv_length(cursor);
return (cursor->current + TLV_DWORD_COUNT(length));
}
static __checkReturn efx_rc_t
tlv_advance(
__inout tlv_cursor_t *cursor)
{
efx_rc_t rc;
if ((rc = tlv_validate_state(cursor)) != 0)
goto fail1;
if (cursor->current == cursor->end) {
/* No more tags after END tag */
cursor->current = NULL;
rc = ENOENT;
goto fail2;
}
/* Advance to next item and validate */
cursor->current = tlv_next_item_ptr(cursor);
if ((rc = tlv_validate_state(cursor)) != 0)
goto fail3;
return (0);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
static efx_rc_t
tlv_rewind(
__in tlv_cursor_t *cursor)
{
efx_rc_t rc;
cursor->current = cursor->block;
if ((rc = tlv_validate_state(cursor)) != 0)
goto fail1;
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
static efx_rc_t
tlv_find(
__inout tlv_cursor_t *cursor,
__in uint32_t tag)
{
efx_rc_t rc;
rc = tlv_rewind(cursor);
while (rc == 0) {
if (tlv_tag(cursor) == tag)
break;
rc = tlv_advance(cursor);
}
return (rc);
}
static __checkReturn efx_rc_t
tlv_validate_state(
__inout tlv_cursor_t *cursor)
{
efx_rc_t rc;
/* Check cursor position */
if (cursor->current < cursor->block) {
rc = EINVAL;
goto fail1;
}
if (cursor->current > cursor->limit) {
rc = EINVAL;
goto fail2;
}
if (tlv_tag(cursor) != TLV_TAG_END) {
/* Check current item has space for tag and length */
if (cursor->current > (cursor->limit - 1)) {
cursor->current = NULL;
rc = EFAULT;
goto fail3;
}
/* Check we have value data for current item and an END tag */
if (tlv_next_item_ptr(cursor) > cursor->limit) {
cursor->current = NULL;
rc = EFAULT;
goto fail4;
}
}
return (0);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
static efx_rc_t
tlv_init_cursor(
__out tlv_cursor_t *cursor,
__in uint32_t *block,
__in uint32_t *limit,
__in uint32_t *current)
{
cursor->block = block;
cursor->limit = limit;
cursor->current = current;
cursor->end = NULL;
return (tlv_validate_state(cursor));
}
static __checkReturn efx_rc_t
tlv_init_cursor_from_size(
__out tlv_cursor_t *cursor,
__in_bcount(size)
uint8_t *block,
__in size_t size)
{
uint32_t *limit;
limit = (uint32_t *)(block + size - sizeof (uint32_t));
return (tlv_init_cursor(cursor, (uint32_t *)block,
limit, (uint32_t *)block));
}
static __checkReturn efx_rc_t
tlv_init_cursor_at_offset(
__out tlv_cursor_t *cursor,
__in_bcount(size)
uint8_t *block,
__in size_t size,
__in size_t offset)
{
uint32_t *limit;
uint32_t *current;
limit = (uint32_t *)(block + size - sizeof (uint32_t));
current = (uint32_t *)(block + offset);
return (tlv_init_cursor(cursor, (uint32_t *)block, limit, current));
}
static __checkReturn efx_rc_t
tlv_require_end(
__inout tlv_cursor_t *cursor)
{
uint32_t *pos;
efx_rc_t rc;
if (cursor->end == NULL) {
pos = cursor->current;
if ((rc = tlv_find(cursor, TLV_TAG_END)) != 0)
goto fail1;
cursor->end = cursor->current;
cursor->current = pos;
}
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
static size_t
tlv_block_length_used(
__inout tlv_cursor_t *cursor)
{
efx_rc_t rc;
if ((rc = tlv_validate_state(cursor)) != 0)
goto fail1;
if ((rc = tlv_require_end(cursor)) != 0)
goto fail2;
/* Return space used (including the END tag) */
return (cursor->end + 1 - cursor->block) * sizeof (uint32_t);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (0);
}
static uint32_t *
tlv_last_segment_end(
__in tlv_cursor_t *cursor)
{
tlv_cursor_t segment_cursor;
uint32_t *last_segment_end = cursor->block;
uint32_t *segment_start = cursor->block;
/*
* 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 (;;) {
if (tlv_init_cursor(&segment_cursor, segment_start,
cursor->limit, segment_start) != 0)
break;
if (tlv_require_end(&segment_cursor) != 0)
break;
last_segment_end = segment_cursor.end;
segment_start = segment_cursor.end + 1;
}
return (last_segment_end);
}
static uint32_t *
tlv_write(
__in tlv_cursor_t *cursor,
__in uint32_t tag,
__in_bcount(size) uint8_t *data,
__in size_t size)
{
uint32_t len = size;
uint32_t *ptr;
ptr = cursor->current;
*ptr++ = __CPU_TO_LE_32(tag);
*ptr++ = __CPU_TO_LE_32(len);
if (len > 0) {
ptr[(len - 1) / sizeof (uint32_t)] = 0;
memcpy(ptr, data, len);
ptr += P2ROUNDUP(len, sizeof (uint32_t)) / sizeof (*ptr);
}
return (ptr);
}
static __checkReturn efx_rc_t
tlv_insert(
__inout tlv_cursor_t *cursor,
__in uint32_t tag,
__in_bcount(size)
uint8_t *data,
__in size_t size)
{
unsigned int delta;
uint32_t *last_segment_end;
efx_rc_t rc;
if ((rc = tlv_validate_state(cursor)) != 0)
goto fail1;
if ((rc = tlv_require_end(cursor)) != 0)
goto fail2;
if (tag == TLV_TAG_END) {
rc = EINVAL;
goto fail3;
}
last_segment_end = tlv_last_segment_end(cursor);
delta = TLV_DWORD_COUNT(size);
if (last_segment_end + 1 + delta > cursor->limit) {
rc = ENOSPC;
goto fail4;
}
/* Move data up: new space at cursor->current */
memmove(cursor->current + delta, cursor->current,
(last_segment_end + 1 - cursor->current) * sizeof (uint32_t));
/* Adjust the end pointer */
cursor->end += delta;
/* Write new TLV item */
tlv_write(cursor, tag, data, size);
return (0);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
static __checkReturn efx_rc_t
tlv_delete(
__inout tlv_cursor_t *cursor)
{
unsigned int delta;
uint32_t *last_segment_end;
efx_rc_t rc;
if ((rc = tlv_validate_state(cursor)) != 0)
goto fail1;
if (tlv_tag(cursor) == TLV_TAG_END) {
rc = EINVAL;
goto fail2;
}
delta = TLV_DWORD_COUNT(tlv_length(cursor));
if ((rc = tlv_require_end(cursor)) != 0)
goto fail3;
last_segment_end = tlv_last_segment_end(cursor);
/* Shuffle things down, destroying the item at cursor->current */
memmove(cursor->current, cursor->current + delta,
(last_segment_end + 1 - cursor->current) * sizeof (uint32_t));
/* Zero the new space at the end of the TLV chain */
memset(last_segment_end + 1 - delta, 0, delta * sizeof (uint32_t));
/* Adjust the end pointer */
cursor->end -= delta;
return (0);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
static __checkReturn efx_rc_t
tlv_modify(
__inout tlv_cursor_t *cursor,
__in uint32_t tag,
__in_bcount(size)
uint8_t *data,
__in size_t size)
{
uint32_t *pos;
unsigned int old_ndwords;
unsigned int new_ndwords;
unsigned int delta;
uint32_t *last_segment_end;
efx_rc_t rc;
if ((rc = tlv_validate_state(cursor)) != 0)
goto fail1;
if (tlv_tag(cursor) == TLV_TAG_END) {
rc = EINVAL;
goto fail2;
}
if (tlv_tag(cursor) != tag) {
rc = EINVAL;
goto fail3;
}
old_ndwords = TLV_DWORD_COUNT(tlv_length(cursor));
new_ndwords = TLV_DWORD_COUNT(size);
if ((rc = tlv_require_end(cursor)) != 0)
goto fail4;
last_segment_end = tlv_last_segment_end(cursor);
if (new_ndwords > old_ndwords) {
/* Expand space used for TLV item */
delta = new_ndwords - old_ndwords;
pos = cursor->current + old_ndwords;
if (last_segment_end + 1 + delta > cursor->limit) {
rc = ENOSPC;
goto fail5;
}
/* Move up: new space at (cursor->current + old_ndwords) */
memmove(pos + delta, pos,
(last_segment_end + 1 - pos) * sizeof (uint32_t));
/* Adjust the end pointer */
cursor->end += delta;
} else if (new_ndwords < old_ndwords) {
/* Shrink space used for TLV item */
delta = old_ndwords - new_ndwords;
pos = cursor->current + new_ndwords;
/* Move down: remove words at (cursor->current + new_ndwords) */
memmove(pos, pos + delta,
(last_segment_end + 1 - pos) * sizeof (uint32_t));
/* Zero the new space at the end of the TLV chain */
memset(last_segment_end + 1 - delta, 0,
delta * sizeof (uint32_t));
/* Adjust the end pointer */
cursor->end -= delta;
}
/* Write new data */
tlv_write(cursor, tag, data, size);
return (0);
fail5:
EFSYS_PROBE(fail5);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
static uint32_t checksum_tlv_partition(
__in nvram_partition_t *partition)
{
tlv_cursor_t *cursor;
uint32_t *ptr;
uint32_t *end;
uint32_t csum;
size_t len;
cursor = &partition->tlv_cursor;
len = tlv_block_length_used(cursor);
EFSYS_ASSERT3U((len & 3), ==, 0);
csum = 0;
ptr = partition->data;
end = &ptr[len >> 2];
while (ptr < end)
csum += __LE_TO_CPU_32(*ptr++);
return (csum);
}
static __checkReturn efx_rc_t
tlv_update_partition_len_and_cks(
__in tlv_cursor_t *cursor)
{
efx_rc_t rc;
nvram_partition_t partition;
struct tlv_partition_header *header;
struct tlv_partition_trailer *trailer;
size_t new_len;
/*
* 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.
*/
partition.data = cursor->block;
memcpy(&partition.tlv_cursor, cursor, sizeof (*cursor));
header = (struct tlv_partition_header *)partition.data;
/* Sanity check. */
if (__LE_TO_CPU_32(header->tag) != TLV_TAG_PARTITION_HEADER) {
rc = EFAULT;
goto fail1;
}
new_len = tlv_block_length_used(&partition.tlv_cursor);
if (new_len == 0) {
rc = EFAULT;
goto fail2;
}
header->total_length = __CPU_TO_LE_32(new_len);
/* Ensure the modified partition always has a new generation count. */
header->generation = __CPU_TO_LE_32(
__LE_TO_CPU_32(header->generation) + 1);
trailer = (struct tlv_partition_trailer *)((uint8_t *)header +
new_len - sizeof (*trailer) - sizeof (uint32_t));
trailer->generation = header->generation;
trailer->checksum = __CPU_TO_LE_32(
__LE_TO_CPU_32(trailer->checksum) -
checksum_tlv_partition(&partition));
return (0);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
/* Validate buffer contents (before writing to flash) */
__checkReturn efx_rc_t
ef10_nvram_buffer_validate(
__in uint32_t partn,
__in_bcount(partn_size) caddr_t partn_data,
__in size_t partn_size)
{
tlv_cursor_t cursor;
struct tlv_partition_header *header;
struct tlv_partition_trailer *trailer;
size_t total_length;
uint32_t cksum;
int pos;
efx_rc_t rc;
EFX_STATIC_ASSERT(sizeof (*header) <= EF10_NVRAM_CHUNK);
if ((partn_data == NULL) || (partn_size == 0)) {
rc = EINVAL;
goto fail1;
}
/* The partition header must be the first item (at offset zero) */
if ((rc = tlv_init_cursor_from_size(&cursor, (uint8_t *)partn_data,
partn_size)) != 0) {
rc = EFAULT;
goto fail2;
}
if (tlv_tag(&cursor) != TLV_TAG_PARTITION_HEADER) {
rc = EINVAL;
goto fail3;
}
header = (struct tlv_partition_header *)tlv_item(&cursor);
/* Check TLV partition length (includes the END tag) */
total_length = __LE_TO_CPU_32(header->total_length);
if (total_length > partn_size) {
rc = EFBIG;
goto fail4;
}
/* Check partition header matches partn */
if (__LE_TO_CPU_16(header->type_id) != partn) {
rc = EINVAL;
goto fail5;
}
/* Check partition ends with PARTITION_TRAILER and END tags */
if ((rc = tlv_find(&cursor, TLV_TAG_PARTITION_TRAILER)) != 0) {
rc = EINVAL;
goto fail6;
}
trailer = (struct tlv_partition_trailer *)tlv_item(&cursor);
if ((rc = tlv_advance(&cursor)) != 0) {
rc = EINVAL;
goto fail7;
}
if (tlv_tag(&cursor) != TLV_TAG_END) {
rc = EINVAL;
goto fail8;
}
/* Check generation counts are consistent */
if (trailer->generation != header->generation) {
rc = EINVAL;
goto fail9;
}
/* Verify partition checksum */
cksum = 0;
for (pos = 0; (size_t)pos < total_length; pos += sizeof (uint32_t)) {
cksum += *((uint32_t *)(partn_data + pos));
}
if (cksum != 0) {
rc = EINVAL;
goto fail10;
}
return (0);
fail10:
EFSYS_PROBE(fail10);
fail9:
EFSYS_PROBE(fail9);
fail8:
EFSYS_PROBE(fail8);
fail7:
EFSYS_PROBE(fail7);
fail6:
EFSYS_PROBE(fail6);
fail5:
EFSYS_PROBE(fail5);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
void
ef10_nvram_buffer_init(
__out_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size)
{
uint32_t *buf = (uint32_t *)bufferp;
memset(buf, 0xff, buffer_size);
tlv_init_block(buf);
}
__checkReturn efx_rc_t
ef10_nvram_buffer_create(
__in uint32_t partn_type,
__out_bcount(partn_size)
caddr_t partn_data,
__in size_t partn_size)
{
uint32_t *buf = (uint32_t *)partn_data;
efx_rc_t rc;
tlv_cursor_t cursor;
struct tlv_partition_header header;
struct tlv_partition_trailer trailer;
unsigned int min_buf_size = sizeof (struct tlv_partition_header) +
sizeof (struct tlv_partition_trailer);
if (partn_size < min_buf_size) {
rc = EINVAL;
goto fail1;
}
ef10_nvram_buffer_init(partn_data, partn_size);
if ((rc = tlv_init_cursor(&cursor, buf,
(uint32_t *)((uint8_t *)buf + partn_size),
buf)) != 0) {
goto fail2;
}
header.tag = __CPU_TO_LE_32(TLV_TAG_PARTITION_HEADER);
header.length = __CPU_TO_LE_32(sizeof (header) - 8);
header.type_id = __CPU_TO_LE_16(partn_type);
header.preset = 0;
header.generation = __CPU_TO_LE_32(1);
header.total_length = 0; /* This will be fixed below. */
if ((rc = tlv_insert(
&cursor, TLV_TAG_PARTITION_HEADER,
(uint8_t *)&header.type_id, sizeof (header) - 8)) != 0)
goto fail3;
if ((rc = tlv_advance(&cursor)) != 0)
goto fail4;
trailer.tag = __CPU_TO_LE_32(TLV_TAG_PARTITION_TRAILER);
trailer.length = __CPU_TO_LE_32(sizeof (trailer) - 8);
trailer.generation = header.generation;
trailer.checksum = 0; /* This will be fixed below. */
if ((rc = tlv_insert(&cursor, TLV_TAG_PARTITION_TRAILER,
(uint8_t *)&trailer.generation, sizeof (trailer) - 8)) != 0)
goto fail5;
if ((rc = tlv_update_partition_len_and_cks(&cursor)) != 0)
goto fail6;
/* Check that the partition is valid. */
if ((rc = ef10_nvram_buffer_validate(partn_type,
partn_data, partn_size)) != 0)
goto fail7;
return (0);
fail7:
EFSYS_PROBE(fail7);
fail6:
EFSYS_PROBE(fail6);
fail5:
EFSYS_PROBE(fail5);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
static uint32_t
byte_offset(
__in uint32_t *position,
__in uint32_t *base)
{
return (uint32_t)((uint8_t *)position - (uint8_t *)base);
}
__checkReturn efx_rc_t
ef10_nvram_buffer_find_item_start(
__in_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size,
__out uint32_t *startp)
{
/* Read past partition header to find start address of the first key */
tlv_cursor_t cursor;
efx_rc_t rc;
/* A PARTITION_HEADER tag must be the first item (at offset zero) */
if ((rc = tlv_init_cursor_from_size(&cursor, (uint8_t *)bufferp,
buffer_size)) != 0) {
rc = EFAULT;
goto fail1;
}
if (tlv_tag(&cursor) != TLV_TAG_PARTITION_HEADER) {
rc = EINVAL;
goto fail2;
}
if ((rc = tlv_advance(&cursor)) != 0) {
rc = EINVAL;
goto fail3;
}
*startp = byte_offset(cursor.current, cursor.block);
if ((rc = tlv_require_end(&cursor)) != 0)
goto fail4;
return (0);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_buffer_find_end(
__in_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size,
__in uint32_t offset,
__out uint32_t *endp)
{
/* Read to end of partition */
tlv_cursor_t cursor;
efx_rc_t rc;
uint32_t *segment_used;
_NOTE(ARGUNUSED(offset))
if ((rc = tlv_init_cursor_from_size(&cursor, (uint8_t *)bufferp,
buffer_size)) != 0) {
rc = EFAULT;
goto fail1;
}
segment_used = cursor.block;
/*
* 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 used space including that end tag.
*/
while (tlv_tag(&cursor) == TLV_TAG_PARTITION_HEADER) {
if (tlv_require_end(&cursor) != 0) {
if (segment_used == cursor.block) {
/*
* First segment is corrupt, so there is
* no valid data in partition.
*/
rc = EINVAL;
goto fail2;
}
break;
}
segment_used = cursor.end + 1;
cursor.current = segment_used;
}
/* Return space used (including the END tag) */
*endp = (segment_used - cursor.block) * sizeof (uint32_t);
return (0);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn __success(return != B_FALSE) boolean_t
ef10_nvram_buffer_find_item(
__in_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size,
__in uint32_t offset,
__out uint32_t *startp,
__out uint32_t *lengthp)
{
/* Find TLV at offset and return key start and length */
tlv_cursor_t cursor;
uint8_t *key;
uint32_t tag;
if (tlv_init_cursor_at_offset(&cursor, (uint8_t *)bufferp,
buffer_size, offset) != 0) {
return (B_FALSE);
}
while ((key = tlv_item(&cursor)) != NULL) {
tag = tlv_tag(&cursor);
if (tag == TLV_TAG_PARTITION_HEADER ||
tag == TLV_TAG_PARTITION_TRAILER) {
if (tlv_advance(&cursor) != 0) {
break;
}
continue;
}
*startp = byte_offset(cursor.current, cursor.block);
*lengthp = byte_offset(tlv_next_item_ptr(&cursor),
cursor.current);
return (B_TRUE);
}
return (B_FALSE);
}
__checkReturn efx_rc_t
ef10_nvram_buffer_peek_item(
__in_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size,
__in uint32_t offset,
__out uint32_t *tagp,
__out uint32_t *lengthp,
__out uint32_t *value_offsetp)
{
efx_rc_t rc;
tlv_cursor_t cursor;
uint32_t tag;
if ((rc = tlv_init_cursor_at_offset(&cursor, (uint8_t *)bufferp,
buffer_size, offset)) != 0) {
goto fail1;
}
tag = tlv_tag(&cursor);
*tagp = tag;
if (tag == TLV_TAG_END) {
/*
* To allow stepping over the END tag, report the full tag
* length and a zero length value.
*/
*lengthp = sizeof (tag);
*value_offsetp = sizeof (tag);
} else {
*lengthp = byte_offset(tlv_next_item_ptr(&cursor),
cursor.current);
*value_offsetp = byte_offset((uint32_t *)tlv_value(&cursor),
cursor.current);
}
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_buffer_get_item(
__in_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size,
__in uint32_t offset,
__in uint32_t length,
__out uint32_t *tagp,
__out_bcount_part(value_max_size, *lengthp)
caddr_t valuep,
__in size_t value_max_size,
__out uint32_t *lengthp)
{
efx_rc_t rc;
tlv_cursor_t cursor;
uint32_t value_length;
if (buffer_size < (offset + length)) {
rc = ENOSPC;
goto fail1;
}
if ((rc = tlv_init_cursor_at_offset(&cursor, (uint8_t *)bufferp,
buffer_size, offset)) != 0) {
goto fail2;
}
value_length = tlv_length(&cursor);
if (value_max_size < value_length) {
rc = ENOSPC;
goto fail3;
}
memcpy(valuep, tlv_value(&cursor), value_length);
*tagp = tlv_tag(&cursor);
*lengthp = value_length;
return (0);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_buffer_insert_item(
__in_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size,
__in uint32_t offset,
__in uint32_t tag,
__in_bcount(length) caddr_t valuep,
__in uint32_t length,
__out uint32_t *lengthp)
{
efx_rc_t rc;
tlv_cursor_t cursor;
if ((rc = tlv_init_cursor_at_offset(&cursor, (uint8_t *)bufferp,
buffer_size, offset)) != 0) {
goto fail1;
}
rc = tlv_insert(&cursor, tag, (uint8_t *)valuep, length);
if (rc != 0)
goto fail2;
*lengthp = byte_offset(tlv_next_item_ptr(&cursor),
cursor.current);
return (0);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_buffer_modify_item(
__in_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size,
__in uint32_t offset,
__in uint32_t tag,
__in_bcount(length) caddr_t valuep,
__in uint32_t length,
__out uint32_t *lengthp)
{
efx_rc_t rc;
tlv_cursor_t cursor;
if ((rc = tlv_init_cursor_at_offset(&cursor, (uint8_t *)bufferp,
buffer_size, offset)) != 0) {
goto fail1;
}
rc = tlv_modify(&cursor, tag, (uint8_t *)valuep, length);
if (rc != 0) {
goto fail2;
}
*lengthp = byte_offset(tlv_next_item_ptr(&cursor),
cursor.current);
return (0);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_buffer_delete_item(
__in_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size,
__in uint32_t offset,
__in uint32_t length,
__in uint32_t end)
{
efx_rc_t rc;
tlv_cursor_t cursor;
_NOTE(ARGUNUSED(length, end))
if ((rc = tlv_init_cursor_at_offset(&cursor, (uint8_t *)bufferp,
buffer_size, offset)) != 0) {
goto fail1;
}
if ((rc = tlv_delete(&cursor)) != 0)
goto fail2;
return (0);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_buffer_finish(
__in_bcount(buffer_size)
caddr_t bufferp,
__in size_t buffer_size)
{
efx_rc_t rc;
tlv_cursor_t cursor;
if ((rc = tlv_init_cursor_from_size(&cursor, (uint8_t *)bufferp,
buffer_size)) != 0) {
rc = EFAULT;
goto fail1;
}
if ((rc = tlv_require_end(&cursor)) != 0)
goto fail2;
if ((rc = tlv_update_partition_len_and_cks(&cursor)) != 0)
goto fail3;
return (0);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
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
ef10_nvram_read_tlv_segment(
__in efx_nic_t *enp,
__in uint32_t partn,
__in size_t seg_offset,
__in_bcount(max_seg_size) caddr_t seg_data,
__in size_t max_seg_size)
{
tlv_cursor_t cursor;
struct tlv_partition_header *header;
struct tlv_partition_trailer *trailer;
size_t total_length;
uint32_t cksum;
int pos;
efx_rc_t rc;
EFX_STATIC_ASSERT(sizeof (*header) <= EF10_NVRAM_CHUNK);
if ((seg_data == NULL) || (max_seg_size == 0)) {
rc = EINVAL;
goto fail1;
}
/* Read initial chunk of the segment, starting at offset */
if ((rc = ef10_nvram_partn_read_mode(enp, partn, seg_offset, seg_data,
EF10_NVRAM_CHUNK,
MC_CMD_NVRAM_READ_IN_V2_TARGET_CURRENT)) != 0) {
goto fail2;
}
/* A PARTITION_HEADER tag must be the first item at the given offset */
if ((rc = tlv_init_cursor_from_size(&cursor, (uint8_t *)seg_data,
max_seg_size)) != 0) {
rc = EFAULT;
goto fail3;
}
if (tlv_tag(&cursor) != TLV_TAG_PARTITION_HEADER) {
rc = EINVAL;
goto fail4;
}
header = (struct tlv_partition_header *)tlv_item(&cursor);
/* Check TLV segment length (includes the END tag) */
total_length = __LE_TO_CPU_32(header->total_length);
if (total_length > max_seg_size) {
rc = EFBIG;
goto fail5;
}
/* Read the remaining segment content */
if (total_length > EF10_NVRAM_CHUNK) {
if ((rc = ef10_nvram_partn_read_mode(enp, partn,
seg_offset + EF10_NVRAM_CHUNK,
seg_data + EF10_NVRAM_CHUNK,
total_length - EF10_NVRAM_CHUNK,
MC_CMD_NVRAM_READ_IN_V2_TARGET_CURRENT)) != 0)
goto fail6;
}
/* Check segment ends with PARTITION_TRAILER and END tags */
if ((rc = tlv_find(&cursor, TLV_TAG_PARTITION_TRAILER)) != 0) {
rc = EINVAL;
goto fail7;
}
trailer = (struct tlv_partition_trailer *)tlv_item(&cursor);
if ((rc = tlv_advance(&cursor)) != 0) {
rc = EINVAL;
goto fail8;
}
if (tlv_tag(&cursor) != TLV_TAG_END) {
rc = EINVAL;
goto fail9;
}
/* Check data read from segment is consistent */
if (trailer->generation != header->generation) {
/*
* 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.
*/
rc = EAGAIN;
goto fail10;
}
/* Verify segment checksum */
cksum = 0;
for (pos = 0; (size_t)pos < total_length; pos += sizeof (uint32_t)) {
cksum += *((uint32_t *)(seg_data + pos));
}
if (cksum != 0) {
rc = EINVAL;
goto fail11;
}
return (0);
fail11:
EFSYS_PROBE(fail11);
fail10:
EFSYS_PROBE(fail10);
fail9:
EFSYS_PROBE(fail9);
fail8:
EFSYS_PROBE(fail8);
fail7:
EFSYS_PROBE(fail7);
fail6:
EFSYS_PROBE(fail6);
fail5:
EFSYS_PROBE(fail5);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
/*
* Read a single TLV item from a host memory
* buffer containing a TLV formatted segment.
*/
__checkReturn efx_rc_t
ef10_nvram_buf_read_tlv(
__in efx_nic_t *enp,
__in_bcount(max_seg_size) caddr_t seg_data,
__in size_t max_seg_size,
__in uint32_t tag,
__deref_out_bcount_opt(*sizep) caddr_t *datap,
__out size_t *sizep)
{
tlv_cursor_t cursor;
caddr_t data;
size_t length;
caddr_t value;
efx_rc_t rc;
_NOTE(ARGUNUSED(enp))
if ((seg_data == NULL) || (max_seg_size == 0)) {
rc = EINVAL;
goto fail1;
}
/* Find requested TLV tag in segment data */
if ((rc = tlv_init_cursor_from_size(&cursor, (uint8_t *)seg_data,
max_seg_size)) != 0) {
rc = EFAULT;
goto fail2;
}
if ((rc = tlv_find(&cursor, tag)) != 0) {
rc = ENOENT;
goto fail3;
}
value = (caddr_t)tlv_value(&cursor);
length = tlv_length(&cursor);
if (length == 0)
data = NULL;
else {
/* Copy out data from TLV item */
EFSYS_KMEM_ALLOC(enp->en_esip, length, data);
if (data == NULL) {
rc = ENOMEM;
goto fail4;
}
memcpy(data, value, length);
}
*datap = data;
*sizep = length;
return (0);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
/* Read a single TLV item from the first segment in a TLV formatted partition */
__checkReturn efx_rc_t
ef10_nvram_partn_read_tlv(
__in efx_nic_t *enp,
__in uint32_t partn,
__in uint32_t tag,
__deref_out_bcount_opt(*seg_sizep) caddr_t *seg_datap,
__out size_t *seg_sizep)
{
caddr_t seg_data = NULL;
size_t partn_size = 0;
size_t length;
caddr_t data;
int retry;
efx_rc_t rc;
/* Allocate sufficient memory for the entire partition */
if ((rc = ef10_nvram_partn_size(enp, partn, &partn_size)) != 0)
goto fail1;
if (partn_size == 0) {
rc = ENOENT;
goto fail2;
}
EFSYS_KMEM_ALLOC(enp->en_esip, partn_size, seg_data);
if (seg_data == NULL) {
rc = ENOMEM;
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 = ef10_nvram_read_tlv_segment(enp, partn, 0,
seg_data, partn_size)) != 0)
--retry;
} while ((rc == EAGAIN) && (retry > 0));
if (rc != 0) {
/* Failed to obtain consistent segment data */
if (rc == EAGAIN)
rc = EIO;
goto fail4;
}
if ((rc = ef10_nvram_buf_read_tlv(enp, seg_data, partn_size,
tag, &data, &length)) != 0)
goto fail5;
EFSYS_KMEM_FREE(enp->en_esip, partn_size, seg_data);
*seg_datap = data;
*seg_sizep = length;
return (0);
fail5:
EFSYS_PROBE(fail5);
fail4:
EFSYS_PROBE(fail4);
EFSYS_KMEM_FREE(enp->en_esip, partn_size, seg_data);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
/* Compute the size of a segment. */
static __checkReturn efx_rc_t
ef10_nvram_buf_segment_size(
__in caddr_t seg_data,
__in size_t max_seg_size,
__out size_t *seg_sizep)
{
efx_rc_t rc;
tlv_cursor_t cursor;
struct tlv_partition_header *header;
uint32_t cksum;
int pos;
uint32_t *end_tag_position;
uint32_t segment_length;
/* A PARTITION_HEADER tag must be the first item at the given offset */
if ((rc = tlv_init_cursor_from_size(&cursor, (uint8_t *)seg_data,
max_seg_size)) != 0) {
rc = EFAULT;
goto fail1;
}
if (tlv_tag(&cursor) != TLV_TAG_PARTITION_HEADER) {
rc = EINVAL;
goto fail2;
}
header = (struct tlv_partition_header *)tlv_item(&cursor);
/* Check TLV segment length (includes the END tag) */
*seg_sizep = __LE_TO_CPU_32(header->total_length);
if (*seg_sizep > max_seg_size) {
rc = EFBIG;
goto fail3;
}
/* Check segment ends with PARTITION_TRAILER and END tags */
if ((rc = tlv_find(&cursor, TLV_TAG_PARTITION_TRAILER)) != 0) {
rc = EINVAL;
goto fail4;
}
if ((rc = tlv_advance(&cursor)) != 0) {
rc = EINVAL;
goto fail5;
}
if (tlv_tag(&cursor) != TLV_TAG_END) {
rc = EINVAL;
goto fail6;
}
end_tag_position = cursor.current;
/* Verify segment checksum */
cksum = 0;
for (pos = 0; (size_t)pos < *seg_sizep; pos += sizeof (uint32_t)) {
cksum += *((uint32_t *)(seg_data + pos));
}
if (cksum != 0) {
rc = EINVAL;
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.
*/
segment_length = tlv_block_length_used(&cursor);
if (segment_length > max_seg_size) {
rc = EINVAL;
goto fail8;
}
if (segment_length != *seg_sizep) {
rc = EINVAL;
goto fail9;
}
/* Skip over the first HEADER tag. */
rc = tlv_rewind(&cursor);
rc = tlv_advance(&cursor);
while (rc == 0) {
if (tlv_tag(&cursor) == TLV_TAG_END) {
/* Check that the END tag is the one found earlier. */
if (cursor.current != end_tag_position)
goto fail10;
break;
}
/* Check for duplicate HEADER tags before the END tag. */
if (tlv_tag(&cursor) == TLV_TAG_PARTITION_HEADER) {
rc = EINVAL;
goto fail11;
}
rc = tlv_advance(&cursor);
}
if (rc != 0)
goto fail12;
return (0);
fail12:
EFSYS_PROBE(fail12);
fail11:
EFSYS_PROBE(fail11);
fail10:
EFSYS_PROBE(fail10);
fail9:
EFSYS_PROBE(fail9);
fail8:
EFSYS_PROBE(fail8);
fail7:
EFSYS_PROBE(fail7);
fail6:
EFSYS_PROBE(fail6);
fail5:
EFSYS_PROBE(fail5);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
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.
*/
__checkReturn efx_rc_t
ef10_nvram_buf_write_tlv(
__inout_bcount(max_seg_size) caddr_t seg_data,
__in size_t max_seg_size,
__in uint32_t tag,
__in_bcount(tag_size) caddr_t tag_data,
__in size_t tag_size,
__out size_t *total_lengthp)
{
tlv_cursor_t cursor;
struct tlv_partition_header *header;
struct tlv_partition_trailer *trailer;
uint32_t generation;
uint32_t cksum;
int pos;
efx_rc_t rc;
/* A PARTITION_HEADER tag must be the first item (at offset zero) */
if ((rc = tlv_init_cursor_from_size(&cursor, (uint8_t *)seg_data,
max_seg_size)) != 0) {
rc = EFAULT;
goto fail1;
}
if (tlv_tag(&cursor) != TLV_TAG_PARTITION_HEADER) {
rc = EINVAL;
goto fail2;
}
header = (struct tlv_partition_header *)tlv_item(&cursor);
/* Update the TLV chain to contain the new data */
if ((rc = tlv_find(&cursor, tag)) == 0) {
/* Modify existing TLV item */
if ((rc = tlv_modify(&cursor, tag,
(uint8_t *)tag_data, tag_size)) != 0)
goto fail3;
} else {
/* Insert a new TLV item before the PARTITION_TRAILER */
rc = tlv_find(&cursor, TLV_TAG_PARTITION_TRAILER);
if (rc != 0) {
rc = EINVAL;
goto fail4;
}
if ((rc = tlv_insert(&cursor, tag,
(uint8_t *)tag_data, tag_size)) != 0) {
rc = EINVAL;
goto fail5;
}
}
/* Find the trailer tag */
if ((rc = tlv_find(&cursor, TLV_TAG_PARTITION_TRAILER)) != 0) {
rc = EINVAL;
goto fail6;
}
trailer = (struct tlv_partition_trailer *)tlv_item(&cursor);
/* Update PARTITION_HEADER and PARTITION_TRAILER fields */
*total_lengthp = tlv_block_length_used(&cursor);
if (*total_lengthp > max_seg_size) {
rc = ENOSPC;
goto fail7;
}
generation = __LE_TO_CPU_32(header->generation) + 1;
header->total_length = __CPU_TO_LE_32(*total_lengthp);
header->generation = __CPU_TO_LE_32(generation);
trailer->generation = __CPU_TO_LE_32(generation);
/* Recompute PARTITION_TRAILER checksum */
trailer->checksum = 0;
cksum = 0;
for (pos = 0; (size_t)pos < *total_lengthp; pos += sizeof (uint32_t)) {
cksum += *((uint32_t *)(seg_data + pos));
}
trailer->checksum = ~cksum + 1;
return (0);
fail7:
EFSYS_PROBE(fail7);
fail6:
EFSYS_PROBE(fail6);
fail5:
EFSYS_PROBE(fail5);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
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.
*/
__checkReturn efx_rc_t
ef10_nvram_partn_write_tlv(
__in efx_nic_t *enp,
__in uint32_t partn,
__in uint32_t tag,
__in_bcount(size) caddr_t data,
__in size_t size)
{
return ef10_nvram_partn_write_segment_tlv(enp, partn, tag, data,
size, B_FALSE);
}
/*
* 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
ef10_nvram_segment_write_tlv(
__in efx_nic_t *enp,
__in uint32_t partn,
__in uint32_t tag,
__in_bcount(size) caddr_t data,
__in size_t size,
__inout caddr_t *seg_datap,
__inout size_t *partn_offsetp,
__inout size_t *src_remain_lenp,
__inout size_t *dest_remain_lenp,
__in boolean_t write)
{
efx_rc_t rc;
efx_rc_t status;
size_t original_segment_size;
size_t modified_segment_size;
/*
* 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 propagate the error.
*/
status = ef10_nvram_read_tlv_segment(enp, partn, *partn_offsetp,
*seg_datap, *src_remain_lenp);
if (status != 0) {
rc = EINVAL;
goto fail1;
}
status = ef10_nvram_buf_segment_size(*seg_datap,
*src_remain_lenp, &original_segment_size);
if (status != 0) {
rc = EINVAL;
goto fail2;
}
if (write) {
/* Update the contents of the segment in the buffer */
if ((rc = ef10_nvram_buf_write_tlv(*seg_datap,
*dest_remain_lenp, tag, data, size,
&modified_segment_size)) != 0) {
goto fail3;
}
*dest_remain_lenp -= modified_segment_size;
*seg_datap += modified_segment_size;
} else {
/*
* We won't modify this segment, but still need to update the
* remaining lengths and pointers.
*/
*dest_remain_lenp -= original_segment_size;
*seg_datap += original_segment_size;
}
*partn_offsetp += original_segment_size;
*src_remain_lenp -= original_segment_size;
return (0);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
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.
*/
__checkReturn efx_rc_t
ef10_nvram_partn_write_segment_tlv(
__in efx_nic_t *enp,
__in uint32_t partn,
__in uint32_t tag,
__in_bcount(size) caddr_t data,
__in size_t size,
__in boolean_t all_segments)
{
size_t partn_size = 0;
caddr_t partn_data;
size_t total_length = 0;
efx_rc_t rc;
size_t current_offset = 0;
size_t remaining_original_length;
size_t remaining_modified_length;
caddr_t segment_data;
EFSYS_ASSERT3U(partn, ==, NVRAM_PARTITION_TYPE_DYNAMIC_CONFIG);
/* Allocate sufficient memory for the entire partition */
if ((rc = ef10_nvram_partn_size(enp, partn, &partn_size)) != 0)
goto fail1;
EFSYS_KMEM_ALLOC(enp->en_esip, partn_size, partn_data);
if (partn_data == NULL) {
rc = ENOMEM;
goto fail2;
}
remaining_original_length = partn_size;
remaining_modified_length = partn_size;
segment_data = partn_data;
/* Lock the partition */
if ((rc = ef10_nvram_partn_lock(enp, partn)) != 0)
goto fail3;
/* Iterate over each (potential) segment to update it. */
do {
boolean_t write = all_segments || current_offset == 0;
rc = ef10_nvram_segment_write_tlv(enp, partn, tag, data, size,
&segment_data, &current_offset, &remaining_original_length,
&remaining_modified_length, write);
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);
total_length = segment_data - partn_data;
/*
* 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) {
rc = ENOSPC;
goto fail5;
}
/* Erase the whole partition in NVRAM */
if ((rc = ef10_nvram_partn_erase(enp, partn, 0, partn_size)) != 0)
goto fail6;
/* Write new partition contents from the buffer to NVRAM */
if ((rc = ef10_nvram_partn_write(enp, partn, 0, partn_data,
total_length)) != 0)
goto fail7;
/* Unlock the partition */
(void) ef10_nvram_partn_unlock(enp, partn, NULL);
EFSYS_KMEM_FREE(enp->en_esip, partn_size, partn_data);
return (0);
fail7:
EFSYS_PROBE(fail7);
fail6:
EFSYS_PROBE(fail6);
fail5:
EFSYS_PROBE(fail5);
fail4:
EFSYS_PROBE(fail4);
(void) ef10_nvram_partn_unlock(enp, partn, NULL);
fail3:
EFSYS_PROBE(fail3);
EFSYS_KMEM_FREE(enp->en_esip, partn_size, partn_data);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
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.
*/
__checkReturn efx_rc_t
ef10_nvram_partn_size(
__in efx_nic_t *enp,
__in uint32_t partn,
__out size_t *sizep)
{
efx_rc_t rc;
if ((rc = efx_mcdi_nvram_info(enp, partn, sizep,
NULL, NULL, NULL)) != 0)
goto fail1;
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_partn_lock(
__in efx_nic_t *enp,
__in uint32_t partn)
{
efx_rc_t rc;
if ((rc = efx_mcdi_nvram_update_start(enp, partn)) != 0)
goto fail1;
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_partn_read_mode(
__in efx_nic_t *enp,
__in uint32_t partn,
__in unsigned int offset,
__out_bcount(size) caddr_t data,
__in size_t size,
__in uint32_t mode)
{
size_t chunk;
efx_rc_t rc;
while (size > 0) {
chunk = MIN(size, EF10_NVRAM_CHUNK);
if ((rc = efx_mcdi_nvram_read(enp, partn, offset,
data, chunk, mode)) != 0) {
goto fail1;
}
size -= chunk;
data += chunk;
offset += chunk;
}
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_partn_read(
__in efx_nic_t *enp,
__in uint32_t partn,
__in unsigned int offset,
__out_bcount(size) caddr_t data,
__in size_t size)
{
/*
* An A/B partition has two data stores (current and backup).
* Read requests which come in through the EFX API expect to read the
* current, active store of an A/B partition. For non A/B partitions,
* there is only a single store and so the mode param is ignored.
*/
return ef10_nvram_partn_read_mode(enp, partn, offset, data, size,
MC_CMD_NVRAM_READ_IN_V2_TARGET_CURRENT);
}
__checkReturn efx_rc_t
ef10_nvram_partn_read_backup(
__in efx_nic_t *enp,
__in uint32_t partn,
__in unsigned int offset,
__out_bcount(size) caddr_t data,
__in size_t size)
{
/*
* An A/B partition has two data stores (current and backup).
* Read the backup store of an A/B partition (i.e. the store currently
* being written to if the partition is locked).
*
* This is needed when comparing the existing partition content to avoid
* unnecessary writes, or to read back what has been written to check
* that the writes have succeeded.
*/
return ef10_nvram_partn_read_mode(enp, partn, offset, data, size,
MC_CMD_NVRAM_READ_IN_V2_TARGET_BACKUP);
}
__checkReturn efx_rc_t
ef10_nvram_partn_erase(
__in efx_nic_t *enp,
__in uint32_t partn,
__in unsigned int offset,
__in size_t size)
{
efx_rc_t rc;
uint32_t erase_size;
if ((rc = efx_mcdi_nvram_info(enp, partn, NULL, NULL,
&erase_size, NULL)) != 0)
goto fail1;
if (erase_size == 0) {
if ((rc = efx_mcdi_nvram_erase(enp, partn, offset, size)) != 0)
goto fail2;
} else {
if (size % erase_size != 0) {
rc = EINVAL;
goto fail3;
}
while (size > 0) {
if ((rc = efx_mcdi_nvram_erase(enp, partn, offset,
erase_size)) != 0)
goto fail4;
offset += erase_size;
size -= erase_size;
}
}
return (0);
fail4:
EFSYS_PROBE(fail4);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_partn_write(
__in efx_nic_t *enp,
__in uint32_t partn,
__in unsigned int offset,
__in_bcount(size) caddr_t data,
__in size_t size)
{
size_t chunk;
uint32_t write_size;
efx_rc_t rc;
if ((rc = efx_mcdi_nvram_info(enp, partn, NULL, NULL,
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) {
rc = EINVAL;
goto fail2;
}
} else {
write_size = EF10_NVRAM_CHUNK;
}
while (size > 0) {
chunk = MIN(size, write_size);
if ((rc = efx_mcdi_nvram_write(enp, partn, offset,
data, chunk)) != 0) {
goto fail3;
}
size -= chunk;
data += chunk;
offset += chunk;
}
return (0);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_partn_unlock(
__in efx_nic_t *enp,
__in uint32_t partn,
__out_opt uint32_t *verify_resultp)
{
boolean_t reboot = B_FALSE;
efx_rc_t rc;
if (verify_resultp != NULL)
*verify_resultp = MC_CMD_NVRAM_VERIFY_RC_UNKNOWN;
rc = efx_mcdi_nvram_update_finish(enp, partn, reboot, verify_resultp);
if (rc != 0)
goto fail1;
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_partn_set_version(
__in efx_nic_t *enp,
__in uint32_t partn,
__in_ecount(4) uint16_t version[4])
{
struct tlv_partition_version partn_version;
size_t size;
efx_rc_t rc;
/* Add or modify partition version TLV item */
partn_version.version_w = __CPU_TO_LE_16(version[0]);
partn_version.version_x = __CPU_TO_LE_16(version[1]);
partn_version.version_y = __CPU_TO_LE_16(version[2]);
partn_version.version_z = __CPU_TO_LE_16(version[3]);
size = sizeof (partn_version) - (2 * sizeof (uint32_t));
/* Write the version number to all segments in the partition */
if ((rc = ef10_nvram_partn_write_segment_tlv(enp,
NVRAM_PARTITION_TYPE_DYNAMIC_CONFIG,
TLV_TAG_PARTITION_VERSION(partn),
(caddr_t)&partn_version.version_w, size, B_TRUE)) != 0)
goto fail1;
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
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_mask;
efx_nvram_type_t nvtype;
} ef10_parttbl_entry_t;
/* Port mask values */
#define PORT_1 (1u << 1)
#define PORT_2 (1u << 2)
#define PORT_3 (1u << 3)
#define PORT_4 (1u << 4)
#define PORT_ALL (0xffffffffu)
#define PARTN_MAP_ENTRY(partn, port_mask, nvtype) \
{ (NVRAM_PARTITION_TYPE_##partn), (PORT_##port_mask), (EFX_NVRAM_##nvtype) }
/* Translate EFX NVRAM types to firmware partition types */
static ef10_parttbl_entry_t hunt_parttbl[] = {
/* partn ports nvtype */
PARTN_MAP_ENTRY(MC_FIRMWARE, ALL, MC_FIRMWARE),
PARTN_MAP_ENTRY(MC_FIRMWARE_BACKUP, ALL, MC_GOLDEN),
PARTN_MAP_ENTRY(EXPANSION_ROM, ALL, BOOTROM),
PARTN_MAP_ENTRY(EXPROM_CONFIG_PORT0, 1, BOOTROM_CFG),
PARTN_MAP_ENTRY(EXPROM_CONFIG_PORT1, 2, BOOTROM_CFG),
PARTN_MAP_ENTRY(EXPROM_CONFIG_PORT2, 3, BOOTROM_CFG),
PARTN_MAP_ENTRY(EXPROM_CONFIG_PORT3, 4, BOOTROM_CFG),
PARTN_MAP_ENTRY(DYNAMIC_CONFIG, ALL, DYNAMIC_CFG),
PARTN_MAP_ENTRY(FPGA, ALL, FPGA),
PARTN_MAP_ENTRY(FPGA_BACKUP, ALL, FPGA_BACKUP),
PARTN_MAP_ENTRY(LICENSE, ALL, LICENSE),
};
static ef10_parttbl_entry_t medford_parttbl[] = {
/* partn ports nvtype */
PARTN_MAP_ENTRY(MC_FIRMWARE, ALL, MC_FIRMWARE),
PARTN_MAP_ENTRY(MC_FIRMWARE_BACKUP, ALL, MC_GOLDEN),
PARTN_MAP_ENTRY(EXPANSION_ROM, ALL, BOOTROM),
PARTN_MAP_ENTRY(EXPROM_CONFIG, ALL, BOOTROM_CFG),
PARTN_MAP_ENTRY(DYNAMIC_CONFIG, ALL, DYNAMIC_CFG),
PARTN_MAP_ENTRY(FPGA, ALL, FPGA),
PARTN_MAP_ENTRY(FPGA_BACKUP, ALL, FPGA_BACKUP),
PARTN_MAP_ENTRY(LICENSE, ALL, LICENSE),
PARTN_MAP_ENTRY(EXPANSION_UEFI, ALL, UEFIROM),
PARTN_MAP_ENTRY(MUM_FIRMWARE, ALL, MUM_FIRMWARE),
};
static ef10_parttbl_entry_t medford2_parttbl[] = {
/* partn ports nvtype */
PARTN_MAP_ENTRY(MC_FIRMWARE, ALL, MC_FIRMWARE),
PARTN_MAP_ENTRY(MC_FIRMWARE_BACKUP, ALL, MC_GOLDEN),
PARTN_MAP_ENTRY(EXPANSION_ROM, ALL, BOOTROM),
PARTN_MAP_ENTRY(EXPROM_CONFIG, ALL, BOOTROM_CFG),
PARTN_MAP_ENTRY(DYNAMIC_CONFIG, ALL, DYNAMIC_CFG),
PARTN_MAP_ENTRY(FPGA, ALL, FPGA),
PARTN_MAP_ENTRY(FPGA_BACKUP, ALL, FPGA_BACKUP),
PARTN_MAP_ENTRY(LICENSE, ALL, LICENSE),
PARTN_MAP_ENTRY(EXPANSION_UEFI, ALL, UEFIROM),
PARTN_MAP_ENTRY(MUM_FIRMWARE, ALL, MUM_FIRMWARE),
PARTN_MAP_ENTRY(DYNCONFIG_DEFAULTS, ALL, DYNCONFIG_DEFAULTS),
PARTN_MAP_ENTRY(ROMCONFIG_DEFAULTS, ALL, ROMCONFIG_DEFAULTS),
};
static __checkReturn efx_rc_t
ef10_parttbl_get(
__in efx_nic_t *enp,
__out ef10_parttbl_entry_t **parttblp,
__out size_t *parttbl_rowsp)
{
switch (enp->en_family) {
case EFX_FAMILY_HUNTINGTON:
*parttblp = hunt_parttbl;
*parttbl_rowsp = EFX_ARRAY_SIZE(hunt_parttbl);
break;
case EFX_FAMILY_MEDFORD:
*parttblp = medford_parttbl;
*parttbl_rowsp = EFX_ARRAY_SIZE(medford_parttbl);
break;
case EFX_FAMILY_MEDFORD2:
*parttblp = medford2_parttbl;
*parttbl_rowsp = EFX_ARRAY_SIZE(medford2_parttbl);
break;
default:
EFSYS_ASSERT(B_FALSE);
return (EINVAL);
}
return (0);
}
__checkReturn efx_rc_t
ef10_nvram_type_to_partn(
__in efx_nic_t *enp,
__in efx_nvram_type_t type,
__out uint32_t *partnp)
{
efx_mcdi_iface_t *emip = &(enp->en_mcdi.em_emip);
ef10_parttbl_entry_t *parttbl = NULL;
size_t parttbl_rows = 0;
unsigned int i;
EFSYS_ASSERT3U(type, !=, EFX_NVRAM_INVALID);
EFSYS_ASSERT3U(type, <, EFX_NVRAM_NTYPES);
EFSYS_ASSERT(partnp != NULL);
if (ef10_parttbl_get(enp, &parttbl, &parttbl_rows) == 0) {
for (i = 0; i < parttbl_rows; i++) {
ef10_parttbl_entry_t *entry = &parttbl[i];
if ((entry->nvtype == type) &&
(entry->port_mask & (1u << emip->emi_port))) {
*partnp = entry->partn;
return (0);
}
}
}
return (ENOTSUP);
}
#if EFSYS_OPT_DIAG
static __checkReturn efx_rc_t
ef10_nvram_partn_to_type(
__in efx_nic_t *enp,
__in uint32_t partn,
__out efx_nvram_type_t *typep)
{
efx_mcdi_iface_t *emip = &(enp->en_mcdi.em_emip);
ef10_parttbl_entry_t *parttbl = NULL;
size_t parttbl_rows = 0;
unsigned int i;
EFSYS_ASSERT(typep != NULL);
if (ef10_parttbl_get(enp, &parttbl, &parttbl_rows) == 0) {
for (i = 0; i < parttbl_rows; i++) {
ef10_parttbl_entry_t *entry = &parttbl[i];
if ((entry->partn == partn) &&
(entry->port_mask & (1u << emip->emi_port))) {
*typep = entry->nvtype;
return (0);
}
}
}
return (ENOTSUP);
}
__checkReturn efx_rc_t
ef10_nvram_test(
__in efx_nic_t *enp)
{
efx_nvram_type_t type;
unsigned int npartns = 0;
uint32_t *partns = NULL;
size_t size;
unsigned int i;
efx_rc_t rc;
/* Read available partitions from NVRAM partition map */
size = MC_CMD_NVRAM_PARTITIONS_OUT_TYPE_ID_MAXNUM * sizeof (uint32_t);
EFSYS_KMEM_ALLOC(enp->en_esip, size, partns);
if (partns == NULL) {
rc = ENOMEM;
goto fail1;
}
if ((rc = efx_mcdi_nvram_partitions(enp, (caddr_t)partns, size,
&npartns)) != 0) {
goto fail2;
}
for (i = 0; i < npartns; i++) {
/* Check if the partition is supported for this port */
if ((rc = ef10_nvram_partn_to_type(enp, partns[i], &type)) != 0)
continue;
if ((rc = efx_mcdi_nvram_test(enp, partns[i])) != 0)
goto fail3;
}
EFSYS_KMEM_FREE(enp->en_esip, size, partns);
return (0);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
EFSYS_KMEM_FREE(enp->en_esip, size, partns);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
#endif /* EFSYS_OPT_DIAG */
__checkReturn efx_rc_t
ef10_nvram_partn_get_version(
__in efx_nic_t *enp,
__in uint32_t partn,
__out uint32_t *subtypep,
__out_ecount(4) uint16_t version[4])
{
efx_rc_t rc;
/* FIXME: get highest partn version from all ports */
/* FIXME: return partn description if available */
if ((rc = efx_mcdi_nvram_metadata(enp, partn, subtypep,
version, NULL, 0)) != 0)
goto fail1;
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_partn_rw_start(
__in efx_nic_t *enp,
__in uint32_t partn,
__out size_t *chunk_sizep)
{
uint32_t write_size = 0;
efx_rc_t rc;
if ((rc = efx_mcdi_nvram_info(enp, partn, NULL, NULL,
NULL, &write_size)) != 0)
goto fail1;
if ((rc = ef10_nvram_partn_lock(enp, partn)) != 0)
goto fail2;
if (chunk_sizep != NULL) {
if (write_size == 0)
*chunk_sizep = EF10_NVRAM_CHUNK;
else
*chunk_sizep = write_size;
}
return (0);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
__checkReturn efx_rc_t
ef10_nvram_partn_rw_finish(
__in efx_nic_t *enp,
__in uint32_t partn,
__out_opt uint32_t *verify_resultp)
{
efx_rc_t rc;
if ((rc = ef10_nvram_partn_unlock(enp, partn, verify_resultp)) != 0)
goto fail1;
return (0);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
#endif /* EFSYS_OPT_NVRAM */
#endif /* EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD || EFSYS_OPT_MEDFORD2 */