a0147be547
Xilinx acquired Solarflare in 2019. Signed-off-by: Andrew Rybchenko <arybchenko@solarflare.com> Acked-by: James Fox <jamesfox@xilinx.com>
2562 lines
55 KiB
C
2562 lines
55 KiB
C
/* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* Copyright(c) 2019-2020 Xilinx, Inc.
|
|
* Copyright(c) 2012-2019 Solarflare Communications Inc.
|
|
*/
|
|
|
|
#include "efx.h"
|
|
#include "efx_impl.h"
|
|
|
|
#if EFX_OPTS_EF10()
|
|
|
|
#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 += EFX_P2ROUNDUP(uint32_t, 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, ¤t_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;
|
|
efx_nvram_info_t eni = { 0 };
|
|
|
|
if ((rc = efx_mcdi_nvram_info(enp, partn, &eni)) != 0)
|
|
goto fail1;
|
|
|
|
*sizep = eni.eni_partn_size;
|
|
|
|
return (0);
|
|
|
|
fail1:
|
|
EFSYS_PROBE1(fail1, efx_rc_t, rc);
|
|
|
|
return (rc);
|
|
}
|
|
|
|
__checkReturn efx_rc_t
|
|
ef10_nvram_partn_info(
|
|
__in efx_nic_t *enp,
|
|
__in uint32_t partn,
|
|
__out efx_nvram_info_t *enip)
|
|
{
|
|
efx_rc_t rc;
|
|
|
|
if ((rc = efx_mcdi_nvram_info(enp, partn, enip)) != 0)
|
|
goto fail1;
|
|
|
|
if (enip->eni_write_size == 0)
|
|
enip->eni_write_size = EF10_NVRAM_CHUNK;
|
|
|
|
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;
|
|
efx_nvram_info_t eni = { 0 };
|
|
uint32_t erase_size;
|
|
|
|
if ((rc = efx_mcdi_nvram_info(enp, partn, &eni)) != 0)
|
|
goto fail1;
|
|
|
|
erase_size = eni.eni_erase_size;
|
|
|
|
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;
|
|
efx_nvram_info_t eni = { 0 };
|
|
uint32_t write_size;
|
|
efx_rc_t rc;
|
|
|
|
if ((rc = efx_mcdi_nvram_info(enp, partn, &eni)) != 0)
|
|
goto fail1;
|
|
|
|
write_size = eni.eni_write_size;
|
|
|
|
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);
|
|
}
|
|
|
|
#define EF10_NVRAM_INITIAL_POLL_DELAY_US 10000
|
|
#define EF10_NVRAM_MAX_POLL_DELAY_US 1000000
|
|
#define EF10_NVRAM_POLL_RETRIES 100
|
|
|
|
__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;
|
|
uint32_t poll_delay_us = EF10_NVRAM_INITIAL_POLL_DELAY_US;
|
|
uint32_t poll_retry = 0;
|
|
uint32_t verify_result = MC_CMD_NVRAM_VERIFY_RC_UNKNOWN;
|
|
efx_rc_t rc;
|
|
|
|
rc = efx_mcdi_nvram_update_finish(enp, partn, reboot,
|
|
EFX_NVRAM_UPDATE_FLAGS_BACKGROUND, &verify_result);
|
|
|
|
/*
|
|
* NVRAM updates can take a long time (e.g. up to 1 minute for bundle
|
|
* images). Polling for NVRAM update completion ensures that other MCDI
|
|
* commands can be issued before the background NVRAM update completes.
|
|
*
|
|
* Without polling, other MCDI commands can only be issued before the
|
|
* NVRAM update completes if the MCDI transport and the firmware
|
|
* support the Asynchronous MCDI protocol extensions in SF-116575-PS.
|
|
*
|
|
* The initial call either completes the update synchronously, or
|
|
* returns RC_PENDING to indicate processing is continuing. In the
|
|
* latter case, we poll for at least 1 minute, at increasing intervals
|
|
* (10ms, 100ms, 1s).
|
|
*/
|
|
while (verify_result == MC_CMD_NVRAM_VERIFY_RC_PENDING) {
|
|
|
|
if (poll_retry > EF10_NVRAM_POLL_RETRIES) {
|
|
rc = ETIMEDOUT;
|
|
goto fail1;
|
|
}
|
|
poll_retry++;
|
|
|
|
EFSYS_SLEEP(poll_delay_us);
|
|
if (poll_delay_us < EF10_NVRAM_MAX_POLL_DELAY_US)
|
|
poll_delay_us *= 10;
|
|
|
|
/* Poll for completion of background NVRAM update. */
|
|
verify_result = MC_CMD_NVRAM_VERIFY_RC_UNKNOWN;
|
|
|
|
rc = efx_mcdi_nvram_update_finish(enp, partn, reboot,
|
|
EFX_NVRAM_UPDATE_FLAGS_POLL, &verify_result);
|
|
if (rc != 0) {
|
|
/* Poll failed, so assume NVRAM update failed. */
|
|
goto fail2;
|
|
}
|
|
}
|
|
|
|
if (verify_resultp != NULL)
|
|
*verify_resultp = verify_result;
|
|
|
|
return (0);
|
|
|
|
fail2:
|
|
EFSYS_PROBE(fail2);
|
|
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),
|
|
PARTN_MAP_ENTRY(BUNDLE, ALL, BUNDLE),
|
|
PARTN_MAP_ENTRY(BUNDLE_METADATA, ALL, BUNDLE_METADATA),
|
|
};
|
|
|
|
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)
|
|
{
|
|
efx_nvram_info_t eni = { 0 };
|
|
efx_rc_t rc;
|
|
|
|
if ((rc = ef10_nvram_partn_info(enp, partn, &eni)) != 0)
|
|
goto fail1;
|
|
|
|
if ((rc = ef10_nvram_partn_lock(enp, partn)) != 0)
|
|
goto fail2;
|
|
|
|
if (chunk_sizep != NULL)
|
|
*chunk_sizep = eni.eni_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 /* EFX_OPTS_EF10() */
|