1 /* SPDX-License-Identifier: BSD-3-Clause
3 * Copyright (c) 2009-2018 Solarflare Communications Inc.
13 * Maximum size of BOOTCFG block across all nics as understood by SFCgPXE.
14 * NOTE: This is larger than the Medford per-PF bootcfg sector.
16 #define BOOTCFG_MAX_SIZE 0x1000
18 /* Medford per-PF bootcfg sector */
19 #define BOOTCFG_PER_PF 0x800
20 #define BOOTCFG_PF_COUNT 16
22 #define DHCP_END ((uint8_t)0xff)
23 #define DHCP_PAD ((uint8_t)0)
26 /* Report the layout of bootcfg sectors in NVRAM partition. */
27 __checkReturn efx_rc_t
28 efx_bootcfg_sector_info(
31 __out_opt uint32_t *sector_countp,
32 __out size_t *offsetp,
33 __out size_t *max_sizep)
40 switch (enp->en_family) {
42 case EFX_FAMILY_SIENA:
43 max_size = BOOTCFG_MAX_SIZE;
47 #endif /* EFSYS_OPT_SIENA */
49 #if EFSYS_OPT_HUNTINGTON
50 case EFX_FAMILY_HUNTINGTON:
51 max_size = BOOTCFG_MAX_SIZE;
55 #endif /* EFSYS_OPT_HUNTINGTON */
58 case EFX_FAMILY_MEDFORD: {
59 /* Shared partition (array indexed by PF) */
60 max_size = BOOTCFG_PER_PF;
61 count = BOOTCFG_PF_COUNT;
66 offset = max_size * pf;
69 #endif /* EFSYS_OPT_MEDFORD */
76 EFSYS_ASSERT3U(max_size, <=, BOOTCFG_MAX_SIZE);
78 if (sector_countp != NULL)
79 *sector_countp = count;
81 *max_sizep = max_size;
90 EFSYS_PROBE1(fail1, efx_rc_t, rc);
95 static __checkReturn uint8_t
98 __in_bcount(size) uint8_t const *data,
101 _NOTE(ARGUNUSED(enp))
104 uint8_t checksum = 0;
106 for (pos = 0; pos < size; pos++)
107 checksum += data[pos];
111 static __checkReturn efx_rc_t
114 __in_bcount(size) uint8_t const *data,
116 __out_opt size_t *usedp)
122 /* Start parsing tags immediately after the checksum */
123 for (offset = 1; offset < size; ) {
129 if (tag == DHCP_END) {
134 if (tag == DHCP_PAD) {
140 if (offset + 1 >= size) {
144 length = data[offset + 1];
146 /* Consume *length */
147 if (offset + 1 + length >= size) {
152 offset += 2 + length;
156 /* Checksum the entire sector, including bytes after any DHCP_END */
157 if (efx_bootcfg_csum(enp, data, size) != 0) {
172 EFSYS_PROBE1(fail1, efx_rc_t, rc);
178 * Copy bootcfg sector data to a target buffer which may differ in size.
179 * Optionally corrects format errors in source buffer.
182 efx_bootcfg_copy_sector(
184 __inout_bcount(sector_length)
186 __in size_t sector_length,
187 __out_bcount(data_size) uint8_t *data,
188 __in size_t data_size,
189 __in boolean_t handle_format_errors)
194 /* Verify that the area is correctly formatted and checksummed */
195 rc = efx_bootcfg_verify(enp, sector, sector_length,
198 if (!handle_format_errors) {
202 if ((used_bytes < 2) ||
203 (sector[used_bytes - 1] != DHCP_END)) {
204 /* Block too short, or DHCP_END missing */
210 /* Synthesize empty format on verification failure */
211 if (rc != 0 || used_bytes == 0) {
213 sector[1] = DHCP_END;
216 EFSYS_ASSERT(used_bytes >= 2); /* checksum and DHCP_END */
217 EFSYS_ASSERT(used_bytes <= sector_length);
218 EFSYS_ASSERT(sector_length >= 2);
221 * Legacy bootcfg sectors don't terminate with a DHCP_END character.
222 * Modify the returned payload so it does.
223 * Reinitialise the sector if there isn't room for the character.
225 if (sector[used_bytes - 1] != DHCP_END) {
226 if (used_bytes >= sector_length) {
230 sector[used_bytes] = DHCP_END;
235 * Verify that the target buffer is large enough for the
236 * entire used bootcfg area, then copy into the target buffer.
238 if (used_bytes > data_size) {
242 memcpy(data, sector, used_bytes);
244 /* Zero out the unused portion of the target buffer */
245 if (used_bytes < data_size)
246 (void) memset(data + used_bytes, 0, data_size - used_bytes);
249 * The checksum includes trailing data after any DHCP_END character,
250 * which we've just modified (by truncation or appending DHCP_END).
252 data[0] -= efx_bootcfg_csum(enp, data, data_size);
261 EFSYS_PROBE1(fail1, efx_rc_t, rc);
269 __out_bcount(size) uint8_t *data,
272 uint8_t *payload = NULL;
275 size_t sector_length;
276 size_t sector_offset;
278 uint32_t sector_number;
280 #if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD
281 sector_number = enp->en_nic_cfg.enc_pf;
285 rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length);
289 /* The bootcfg sector may be stored in a (larger) shared partition */
290 rc = efx_bootcfg_sector_info(enp, sector_number,
291 NULL, §or_offset, §or_length);
295 if (sector_length > BOOTCFG_MAX_SIZE)
296 sector_length = BOOTCFG_MAX_SIZE;
298 if (sector_offset + sector_length > partn_length) {
299 /* Partition is too small */
305 * We need to read the entire BOOTCFG sector to ensure we read all the
306 * tags, because legacy bootcfg sectors are not guaranteed to end with
307 * a DHCP_END character. If the user hasn't supplied a sufficiently
308 * large buffer then use our own buffer.
310 if (sector_length > size) {
311 EFSYS_KMEM_ALLOC(enp->en_esip, sector_length, payload);
312 if (payload == NULL) {
317 payload = (uint8_t *)data;
319 if ((rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
322 if ((rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
323 sector_offset, (caddr_t)payload, sector_length)) != 0) {
324 (void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
328 if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
331 /* Verify that the area is correctly formatted and checksummed */
332 rc = efx_bootcfg_verify(enp, payload, sector_length,
334 if (rc != 0 || used_bytes == 0) {
335 payload[0] = (uint8_t)(~DHCP_END & 0xff);
336 payload[1] = DHCP_END;
340 EFSYS_ASSERT(used_bytes >= 2); /* checksum and DHCP_END */
341 EFSYS_ASSERT(used_bytes <= sector_length);
344 * Legacy bootcfg sectors don't terminate with a DHCP_END character.
345 * Modify the returned payload so it does. BOOTCFG_MAX_SIZE is by
346 * definition large enough for any valid (per-port) bootcfg sector,
347 * so reinitialise the sector if there isn't room for the character.
349 if (payload[used_bytes - 1] != DHCP_END) {
350 if (used_bytes + 1 > sector_length) {
355 payload[used_bytes] = DHCP_END;
360 * Verify that the user supplied buffer is large enough for the
361 * entire used bootcfg area, then copy into the user supplied buffer.
363 if (used_bytes > size) {
367 if (sector_length > size) {
368 memcpy(data, payload, used_bytes);
369 EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
372 /* Zero out the unused portion of the user buffer */
373 if (used_bytes < size)
374 (void) memset(data + used_bytes, 0, size - used_bytes);
377 * The checksum includes trailing data after any DHCP_END character,
378 * which we've just modified (by truncation or appending DHCP_END).
380 data[0] -= efx_bootcfg_csum(enp, data, size);
392 if (sector_length > size)
393 EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
401 EFSYS_PROBE1(fail1, efx_rc_t, rc);
409 __in_bcount(size) uint8_t *data,
415 size_t sector_length;
416 size_t sector_offset;
419 uint32_t sector_number;
421 #if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD
422 sector_number = enp->en_nic_cfg.enc_pf;
427 rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length);
431 /* The bootcfg sector may be stored in a (larger) shared partition */
432 rc = efx_bootcfg_sector_info(enp, sector_number,
433 NULL, §or_offset, §or_length);
437 if (sector_length > BOOTCFG_MAX_SIZE)
438 sector_length = BOOTCFG_MAX_SIZE;
440 if (sector_offset + sector_length > partn_length) {
441 /* Partition is too small */
446 if ((rc = efx_bootcfg_verify(enp, data, size, &used_bytes)) != 0)
449 /* The caller *must* terminate their block with a DHCP_END character */
450 if ((used_bytes < 2) || ((uint8_t)data[used_bytes - 1] != DHCP_END)) {
451 /* Block too short or DHCP_END missing */
456 /* Check that the hardware has support for this much data */
457 if (used_bytes > MIN(sector_length, BOOTCFG_MAX_SIZE)) {
463 * If the BOOTCFG sector is stored in a shared partition, then we must
464 * read the whole partition and insert the updated bootcfg sector at the
467 EFSYS_KMEM_ALLOC(enp->en_esip, partn_length, partn_data);
468 if (partn_data == NULL) {
473 rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
477 /* Read the entire partition */
478 rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG, 0,
479 (caddr_t)partn_data, partn_length);
484 * Insert the BOOTCFG sector into the partition, Zero out all data after
485 * the DHCP_END tag, and adjust the checksum.
487 (void) memset(partn_data + sector_offset, 0x0, sector_length);
488 (void) memcpy(partn_data + sector_offset, data, used_bytes);
490 checksum = efx_bootcfg_csum(enp, data, used_bytes);
491 partn_data[sector_offset] -= checksum;
493 if ((rc = efx_nvram_erase(enp, EFX_NVRAM_BOOTROM_CFG)) != 0)
496 if ((rc = efx_nvram_write_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
497 0, (caddr_t)partn_data, partn_length)) != 0)
500 if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
503 EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data);
516 (void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
520 EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data);
534 EFSYS_PROBE1(fail1, efx_rc_t, rc);
539 #endif /* EFSYS_OPT_BOOTCFG */