diff --git a/sys/dev/sfxge/common/hunt_impl.h b/sys/dev/sfxge/common/hunt_impl.h index 6d0435f83666..a2be07438cc6 100644 --- a/sys/dev/sfxge/common/hunt_impl.h +++ b/sys/dev/sfxge/common/hunt_impl.h @@ -295,8 +295,8 @@ hunt_mcdi_macaddr_change_supported( extern __checkReturn int hunt_nvram_buf_read_tlv( __in efx_nic_t *enp, - __in_bcount(partn_size) caddr_t partn_data, - __in size_t partn_size, + __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); @@ -326,6 +326,15 @@ hunt_nvram_partn_write_tlv( __in_bcount(size) caddr_t data, __in size_t size); +extern __checkReturn int +hunt_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); + extern __checkReturn int hunt_nvram_partn_size( __in efx_nic_t *enp, diff --git a/sys/dev/sfxge/common/hunt_nvram.c b/sys/dev/sfxge/common/hunt_nvram.c index 3e130f4a51b0..1816900eb017 100644 --- a/sys/dev/sfxge/common/hunt_nvram.c +++ b/sys/dev/sfxge/common/hunt_nvram.c @@ -571,13 +571,19 @@ efx_nvram_tlv_validate( return (rc); } -/* Read and validate an entire TLV formatted partition */ -static __checkReturn int -hunt_nvram_read_tlv_partition( - __in efx_nic_t *enp, - __in uint32_t partn, - __in_bcount(partn_size) caddr_t partn_data, - __in size_t partn_size) +/* + * 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 int +hunt_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; @@ -589,20 +595,20 @@ hunt_nvram_read_tlv_partition( EFX_STATIC_ASSERT(sizeof (*header) <= HUNTINGTON_NVRAM_CHUNK); - if ((partn_data == NULL) || (partn_size == 0)) { + if ((seg_data == NULL) || (max_seg_size == 0)) { rc = EINVAL; goto fail1; } - /* Read initial chunk of partition */ - if ((rc = hunt_nvram_partn_read(enp, partn, 0, partn_data, + /* Read initial chunk of the segment, starting at offset */ + if ((rc = hunt_nvram_partn_read(enp, partn, seg_offset, seg_data, HUNTINGTON_NVRAM_CHUNK)) != 0) { goto fail2; } - /* The partition header must be the first item (at offset zero) */ - if ((rc = tlv_init_cursor_from_size(&cursor, partn_data, - partn_size)) != 0) { + /* A PARTITION_HEADER tag must be the first item at the given offset */ + if ((rc = tlv_init_cursor_from_size(&cursor, seg_data, + max_seg_size)) != 0) { rc = EFAULT; goto fail3; } @@ -612,23 +618,23 @@ hunt_nvram_read_tlv_partition( } header = (struct tlv_partition_header *)tlv_item(&cursor); - /* Check TLV partition length (includes the END tag) */ + /* Check TLV segment length (includes the END tag) */ total_length = __LE_TO_CPU_32(header->total_length); - if (total_length > partn_size) { + if (total_length > max_seg_size) { rc = EFBIG; goto fail5; } - /* Read the remaining partition content */ + /* Read the remaining segment content */ if (total_length > HUNTINGTON_NVRAM_CHUNK) { if ((rc = hunt_nvram_partn_read(enp, partn, - HUNTINGTON_NVRAM_CHUNK, - partn_data + HUNTINGTON_NVRAM_CHUNK, + seg_offset + HUNTINGTON_NVRAM_CHUNK, + seg_data + HUNTINGTON_NVRAM_CHUNK, total_length - HUNTINGTON_NVRAM_CHUNK)) != 0) goto fail6; } - /* Check partition ends with PARTITION_TRAILER and END tags */ + /* Check segment ends with PARTITION_TRAILER and END tags */ if ((rc = tlv_find(&cursor, TLV_TAG_PARTITION_TRAILER)) != 0) { rc = EINVAL; goto fail7; @@ -644,7 +650,7 @@ hunt_nvram_read_tlv_partition( goto fail9; } - /* Check data read from partition is consistent */ + /* Check data read from segment is consistent */ if (trailer->generation != header->generation) { /* * The partition data may have been modified between successive @@ -656,10 +662,10 @@ hunt_nvram_read_tlv_partition( goto fail10; } - /* Verify partition checksum */ + /* Verify segment checksum */ cksum = 0; for (pos = 0; (size_t)pos < total_length; pos += sizeof (uint32_t)) { - cksum += *((uint32_t *)(partn_data + pos)); + cksum += *((uint32_t *)(seg_data + pos)); } if (cksum != 0) { rc = EINVAL; @@ -696,13 +702,13 @@ hunt_nvram_read_tlv_partition( /* * Read a single TLV item from a host memory - * buffer containing a TLV formatted partition. + * buffer containing a TLV formatted segment. */ __checkReturn int hunt_nvram_buf_read_tlv( __in efx_nic_t *enp, - __in_bcount(partn_size) caddr_t partn_data, - __in size_t partn_size, + __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) @@ -713,14 +719,14 @@ hunt_nvram_buf_read_tlv( caddr_t value; int rc; - if ((partn_data == NULL) || (partn_size == 0)) { + if ((seg_data == NULL) || (max_seg_size == 0)) { rc = EINVAL; goto fail1; } - /* Find requested TLV tag in partition data */ - if ((rc = tlv_init_cursor_from_size(&cursor, partn_data, - partn_size)) != 0) { + /* Find requested TLV tag in segment data */ + if ((rc = tlv_init_cursor_from_size(&cursor, seg_data, + max_seg_size)) != 0) { rc = EFAULT; goto fail2; } @@ -760,18 +766,16 @@ hunt_nvram_buf_read_tlv( return (rc); } - - -/* Read a single TLV item from a TLV formatted partition */ +/* Read a single TLV item from the first segment in a TLV formatted partition */ __checkReturn int hunt_nvram_partn_read_tlv( - __in efx_nic_t *enp, - __in uint32_t partn, - __in uint32_t tag, - __deref_out_bcount_opt(*sizep) caddr_t *datap, - __out size_t *sizep) + __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 partn_data = NULL; + caddr_t seg_data = NULL; size_t partn_size = 0; size_t length; caddr_t data; @@ -787,39 +791,39 @@ hunt_nvram_partn_read_tlv( goto fail2; } - EFSYS_KMEM_ALLOC(enp->en_esip, partn_size, partn_data); - if (partn_data == NULL) { + EFSYS_KMEM_ALLOC(enp->en_esip, partn_size, seg_data); + if (seg_data == NULL) { rc = ENOMEM; goto fail3; } /* - * Read the entire TLV partition. Retry until consistent partition - * contents are returned. Inconsistent data may be read if: - * a) the partition contents are invalid + * 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 { - rc = hunt_nvram_read_tlv_partition(enp, partn, - partn_data, partn_size); + rc = hunt_nvram_read_tlv_segment(enp, partn, 0, + seg_data, partn_size); } while ((rc == EAGAIN) && (--retry > 0)); if (rc != 0) { - /* Failed to obtain consistent partition data */ + /* Failed to obtain consistent segment data */ goto fail4; } - if ((rc = hunt_nvram_buf_read_tlv(enp, partn_data, partn_size, + if ((rc = hunt_nvram_buf_read_tlv(enp, seg_data, partn_size, tag, &data, &length)) != 0) goto fail5; - EFSYS_KMEM_FREE(enp->en_esip, partn_size, partn_data); + EFSYS_KMEM_FREE(enp->en_esip, partn_size, seg_data); - *datap = data; - *sizep = length; + *seg_datap = data; + *seg_sizep = length; return (0); @@ -828,7 +832,137 @@ hunt_nvram_partn_read_tlv( fail4: EFSYS_PROBE(fail4); - EFSYS_KMEM_FREE(enp->en_esip, partn_size, partn_data); + EFSYS_KMEM_FREE(enp->en_esip, partn_size, seg_data); +fail3: + EFSYS_PROBE(fail3); +fail2: + EFSYS_PROBE(fail2); +fail1: + EFSYS_PROBE1(fail1, int, rc); + + return (rc); +} + +/* Compute the size of a segment. */ + static __checkReturn int +hunt_nvram_buf_segment_size( + __in caddr_t seg_data, + __in size_t max_seg_size, + __out size_t *seg_sizep) +{ + int rc; + tlv_cursor_t cursor; + struct tlv_partition_header *header; + struct tlv_partition_trailer *trailer; + 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, 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; + } + trailer = (struct tlv_partition_trailer *)tlv_item(&cursor); + + 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: @@ -841,12 +975,12 @@ hunt_nvram_partn_read_tlv( /* * Add or update a single TLV item in a host memory buffer containing a TLV - * formatted partition. + * formatted segment. Historically partitions consisted of only one segment. */ - __checkReturn int + __checkReturn int hunt_nvram_buf_write_tlv( - __inout_bcount(partn_size) caddr_t partn_data, - __in size_t partn_size, + __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, @@ -860,9 +994,9 @@ hunt_nvram_buf_write_tlv( int pos; int rc; - /* The partition header must be the first item (at offset zero) */ - if ((rc = tlv_init_cursor_from_size(&cursor, partn_data, - partn_size)) != 0) { + /* A PARTITION_HEADER tag must be the first item (at offset zero) */ + if ((rc = tlv_init_cursor_from_size(&cursor, seg_data, + max_seg_size)) != 0) { rc = EFAULT; goto fail1; } @@ -901,7 +1035,10 @@ hunt_nvram_buf_write_tlv( /* Update PARTITION_HEADER and PARTITION_TRAILER fields */ *total_lengthp = tlv_block_length_used(&cursor); - EFSYS_ASSERT3U(*total_lengthp, <=, partn_size); + 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); @@ -912,12 +1049,14 @@ hunt_nvram_buf_write_tlv( trailer->checksum = 0; cksum = 0; for (pos = 0; (size_t)pos < *total_lengthp; pos += sizeof (uint32_t)) { - cksum += *((uint32_t *)(partn_data + pos)); + cksum += *((uint32_t *)(seg_data + pos)); } trailer->checksum = ~cksum + 1; return (0); +fail7: + EFSYS_PROBE(fail7); fail6: EFSYS_PROBE(fail6); fail5: @@ -934,7 +1073,11 @@ hunt_nvram_buf_write_tlv( return (rc); } -/* Add or update a single TLV item in a TLV formatted partition */ +/* + * 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 int hunt_nvram_partn_write_tlv( __in efx_nic_t *enp, @@ -943,10 +1086,114 @@ hunt_nvram_partn_write_tlv( __in_bcount(size) caddr_t data, __in size_t size) { - size_t partn_size; - caddr_t partn_data; - size_t total_length; + return hunt_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 int +hunt_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) +{ int rc; + int 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 propogate the error. + */ + status = hunt_nvram_read_tlv_segment(enp, partn, *partn_offsetp, + *seg_datap, *src_remain_lenp); + if (status != 0) + return (EINVAL); + + status = hunt_nvram_buf_segment_size(*seg_datap, + *src_remain_lenp, &original_segment_size); + if (status != 0) + return (EINVAL); + + if (write) { + /* Update the contents of the segment in the buffer */ + if ((rc = hunt_nvram_buf_write_tlv(*seg_datap, + *dest_remain_lenp, tag, data, size, + &modified_segment_size)) != 0) + goto fail1; + *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); + +fail1: + EFSYS_PROBE1(fail1, int, 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 int +hunt_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; + int 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); @@ -960,27 +1207,49 @@ hunt_nvram_partn_write_tlv( goto fail2; } + remaining_original_length = partn_size; + remaining_modified_length = partn_size; + segment_data = partn_data; + /* Lock the partition */ if ((rc = hunt_nvram_partn_lock(enp, partn)) != 0) goto fail3; - /* Read the partition contents (no need to retry when locked). */ - if ((rc = hunt_nvram_read_tlv_partition(enp, partn, - partn_data, partn_size)) != 0) { - /* Failed to obtain consistent partition data */ - goto fail4; + /* Iterate over each (potential) segment to update it. */ + do { + boolean_t write = all_segments || current_offset == 0; + + rc = hunt_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 + * hunt_nvram_buf_write_tlv returning ENOSPC. + */ + if (total_length > partn_size) { + rc = ENOSPC; + goto fail5; } - /* Update the contents in memory */ - if ((rc = hunt_nvram_buf_write_tlv(partn_data, partn_size, - tag, data, size, &total_length)) != 0) - goto fail5; - - /* Erase the whole partition */ + /* Erase the whole partition in NVRAM */ if ((rc = hunt_nvram_partn_erase(enp, partn, 0, partn_size)) != 0) goto fail6; - /* Write new partition contents to NVRAM */ + /* Write new partition contents from the buffer to NVRAM */ if ((rc = hunt_nvram_partn_write(enp, partn, 0, partn_data, total_length)) != 0) goto fail7; @@ -1014,6 +1283,10 @@ hunt_nvram_partn_write_tlv( 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 int hunt_nvram_partn_size( __in efx_nic_t *enp, @@ -1171,10 +1444,11 @@ hunt_nvram_partn_set_version( size = sizeof (partn_version) - (2 * sizeof (uint32_t)); - if ((rc = hunt_nvram_partn_write_tlv(enp, + /* Write the version number to all segments in the partition */ + if ((rc = hunt_nvram_partn_write_segment_tlv(enp, NVRAM_PARTITION_TYPE_DYNAMIC_CONFIG, TLV_TAG_PARTITION_VERSION(partn), - (caddr_t)&partn_version.version_w, size)) != 0) + (caddr_t)&partn_version.version_w, size, B_TRUE)) != 0) goto fail1; return (0); diff --git a/sys/dev/sfxge/common/hunt_vpd.c b/sys/dev/sfxge/common/hunt_vpd.c index 536ebbcf06f3..bc814cf99a68 100644 --- a/sys/dev/sfxge/common/hunt_vpd.c +++ b/sys/dev/sfxge/common/hunt_vpd.c @@ -396,11 +396,11 @@ hunt_vpd_write( if ((rc = efx_vpd_hunk_length(data, size, &vpd_length)) != 0) goto fail1; - /* Store new dynamic VPD in DYNAMIC_CONFIG partition */ - if ((rc = hunt_nvram_partn_write_tlv(enp, + /* Store new dynamic VPD in all segments in DYNAMIC_CONFIG partition */ + if ((rc = hunt_nvram_partn_write_segment_tlv(enp, NVRAM_PARTITION_TYPE_DYNAMIC_CONFIG, TLV_TAG_PF_DYNAMIC_VPD(pci_pf), - data, vpd_length)) != 0) { + data, vpd_length, B_TRUE)) != 0) { goto fail2; }