xref: /freebsd/sys/dev/sfxge/common/efx_bootcfg.c (revision df0385e5549a400d6d5160926d6c19136570c344)
1e948693eSPhilip Paeps /*-
2718cf2ccSPedro F. Giffuni  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3718cf2ccSPedro F. Giffuni  *
4929c7febSAndrew Rybchenko  * Copyright (c) 2009-2016 Solarflare Communications Inc.
53c838a9fSAndrew Rybchenko  * All rights reserved.
6e948693eSPhilip Paeps  *
7e948693eSPhilip Paeps  * Redistribution and use in source and binary forms, with or without
83c838a9fSAndrew Rybchenko  * modification, are permitted provided that the following conditions are met:
9e948693eSPhilip Paeps  *
103c838a9fSAndrew Rybchenko  * 1. Redistributions of source code must retain the above copyright notice,
113c838a9fSAndrew Rybchenko  *    this list of conditions and the following disclaimer.
123c838a9fSAndrew Rybchenko  * 2. Redistributions in binary form must reproduce the above copyright notice,
133c838a9fSAndrew Rybchenko  *    this list of conditions and the following disclaimer in the documentation
143c838a9fSAndrew Rybchenko  *    and/or other materials provided with the distribution.
153c838a9fSAndrew Rybchenko  *
163c838a9fSAndrew Rybchenko  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
173c838a9fSAndrew Rybchenko  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
183c838a9fSAndrew Rybchenko  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
193c838a9fSAndrew Rybchenko  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
203c838a9fSAndrew Rybchenko  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
213c838a9fSAndrew Rybchenko  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
223c838a9fSAndrew Rybchenko  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
233c838a9fSAndrew Rybchenko  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
243c838a9fSAndrew Rybchenko  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
253c838a9fSAndrew Rybchenko  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
263c838a9fSAndrew Rybchenko  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
273c838a9fSAndrew Rybchenko  *
283c838a9fSAndrew Rybchenko  * The views and conclusions contained in the software and documentation are
293c838a9fSAndrew Rybchenko  * those of the authors and should not be interpreted as representing official
303c838a9fSAndrew Rybchenko  * policies, either expressed or implied, of the FreeBSD Project.
31e948693eSPhilip Paeps  */
32e948693eSPhilip Paeps 
335dee87d7SPhilip Paeps #include <sys/cdefs.h>
345dee87d7SPhilip Paeps __FBSDID("$FreeBSD$");
355dee87d7SPhilip Paeps 
36e948693eSPhilip Paeps #include "efx.h"
37e948693eSPhilip Paeps #include "efx_impl.h"
38e948693eSPhilip Paeps 
39e948693eSPhilip Paeps #if EFSYS_OPT_BOOTCFG
40e948693eSPhilip Paeps 
41e948693eSPhilip Paeps /*
42e948693eSPhilip Paeps  * Maximum size of BOOTCFG block across all nics as understood by SFCgPXE.
435081d55dSAndrew Rybchenko  * NOTE: This is larger than the Medford per-PF bootcfg sector.
44e948693eSPhilip Paeps  */
45e948693eSPhilip Paeps #define	BOOTCFG_MAX_SIZE 0x1000
46e948693eSPhilip Paeps 
47662c835bSAndrew Rybchenko /* Medford per-PF bootcfg sector */
48662c835bSAndrew Rybchenko #define	BOOTCFG_PER_PF   0x800
49662c835bSAndrew Rybchenko #define	BOOTCFG_PF_COUNT 16
50662c835bSAndrew Rybchenko 
5160cf15c5SAndrew Rybchenko #define	DHCP_END ((uint8_t)0xff)
5260cf15c5SAndrew Rybchenko #define	DHCP_PAD ((uint8_t)0)
53e948693eSPhilip Paeps 
545081d55dSAndrew Rybchenko 
55662c835bSAndrew Rybchenko /* Report the layout of bootcfg sectors in NVRAM partition. */
56662c835bSAndrew Rybchenko 	__checkReturn		efx_rc_t
57662c835bSAndrew Rybchenko efx_bootcfg_sector_info(
585081d55dSAndrew Rybchenko 	__in			efx_nic_t *enp,
59662c835bSAndrew Rybchenko 	__in			uint32_t pf,
60662c835bSAndrew Rybchenko 	__out_opt		uint32_t *sector_countp,
615081d55dSAndrew Rybchenko 	__out			size_t *offsetp,
625081d55dSAndrew Rybchenko 	__out			size_t *max_sizep)
635081d55dSAndrew Rybchenko {
64662c835bSAndrew Rybchenko 	uint32_t count;
655081d55dSAndrew Rybchenko 	size_t max_size;
665081d55dSAndrew Rybchenko 	size_t offset;
675081d55dSAndrew Rybchenko 	int rc;
685081d55dSAndrew Rybchenko 
695081d55dSAndrew Rybchenko 	switch (enp->en_family) {
705081d55dSAndrew Rybchenko #if EFSYS_OPT_SIENA
715081d55dSAndrew Rybchenko 	case EFX_FAMILY_SIENA:
725081d55dSAndrew Rybchenko 		max_size = BOOTCFG_MAX_SIZE;
735081d55dSAndrew Rybchenko 		offset = 0;
74662c835bSAndrew Rybchenko 		count = 1;
755081d55dSAndrew Rybchenko 		break;
765081d55dSAndrew Rybchenko #endif /* EFSYS_OPT_SIENA */
775081d55dSAndrew Rybchenko 
785081d55dSAndrew Rybchenko #if EFSYS_OPT_HUNTINGTON
795081d55dSAndrew Rybchenko 	case EFX_FAMILY_HUNTINGTON:
805081d55dSAndrew Rybchenko 		max_size = BOOTCFG_MAX_SIZE;
815081d55dSAndrew Rybchenko 		offset = 0;
82662c835bSAndrew Rybchenko 		count = 1;
835081d55dSAndrew Rybchenko 		break;
845081d55dSAndrew Rybchenko #endif /* EFSYS_OPT_HUNTINGTON */
855081d55dSAndrew Rybchenko 
865081d55dSAndrew Rybchenko #if EFSYS_OPT_MEDFORD
875081d55dSAndrew Rybchenko 	case EFX_FAMILY_MEDFORD: {
885081d55dSAndrew Rybchenko 		/* Shared partition (array indexed by PF) */
89662c835bSAndrew Rybchenko 		max_size = BOOTCFG_PER_PF;
90662c835bSAndrew Rybchenko 		count = BOOTCFG_PF_COUNT;
91662c835bSAndrew Rybchenko 		if (pf >= count) {
92662c835bSAndrew Rybchenko 			rc = EINVAL;
93662c835bSAndrew Rybchenko 			goto fail2;
94662c835bSAndrew Rybchenko 		}
95662c835bSAndrew Rybchenko 		offset = max_size * pf;
965081d55dSAndrew Rybchenko 		break;
975081d55dSAndrew Rybchenko 	}
985081d55dSAndrew Rybchenko #endif /* EFSYS_OPT_MEDFORD */
995081d55dSAndrew Rybchenko 
100*df0385e5SAndrew Rybchenko #if EFSYS_OPT_MEDFORD2
101*df0385e5SAndrew Rybchenko 	case EFX_FAMILY_MEDFORD2: {
102*df0385e5SAndrew Rybchenko 		/* Shared partition (array indexed by PF) */
103*df0385e5SAndrew Rybchenko 		max_size = BOOTCFG_PER_PF;
104*df0385e5SAndrew Rybchenko 		count = BOOTCFG_PF_COUNT;
105*df0385e5SAndrew Rybchenko 		if (pf >= count) {
106*df0385e5SAndrew Rybchenko 			rc = EINVAL;
107*df0385e5SAndrew Rybchenko 			goto fail3;
108*df0385e5SAndrew Rybchenko 		}
109*df0385e5SAndrew Rybchenko 		offset = max_size * pf;
110*df0385e5SAndrew Rybchenko 		break;
111*df0385e5SAndrew Rybchenko 	}
112*df0385e5SAndrew Rybchenko #endif /* EFSYS_OPT_MEDFORD2 */
113*df0385e5SAndrew Rybchenko 
1145081d55dSAndrew Rybchenko 	default:
1155081d55dSAndrew Rybchenko 		EFSYS_ASSERT(0);
1165081d55dSAndrew Rybchenko 		rc = ENOTSUP;
1175081d55dSAndrew Rybchenko 		goto fail1;
1185081d55dSAndrew Rybchenko 	}
1195081d55dSAndrew Rybchenko 	EFSYS_ASSERT3U(max_size, <=, BOOTCFG_MAX_SIZE);
1205081d55dSAndrew Rybchenko 
121662c835bSAndrew Rybchenko 	if (sector_countp != NULL)
122662c835bSAndrew Rybchenko 		*sector_countp = count;
1235081d55dSAndrew Rybchenko 	*offsetp = offset;
1245081d55dSAndrew Rybchenko 	*max_sizep = max_size;
1255081d55dSAndrew Rybchenko 
1265081d55dSAndrew Rybchenko 	return (0);
1275081d55dSAndrew Rybchenko 
128*df0385e5SAndrew Rybchenko #if EFSYS_OPT_MEDFORD2
129*df0385e5SAndrew Rybchenko fail3:
130*df0385e5SAndrew Rybchenko 	EFSYS_PROBE(fail3);
131*df0385e5SAndrew Rybchenko #endif
132662c835bSAndrew Rybchenko #if EFSYS_OPT_MEDFORD
133662c835bSAndrew Rybchenko fail2:
134662c835bSAndrew Rybchenko 	EFSYS_PROBE(fail2);
135662c835bSAndrew Rybchenko #endif
1365081d55dSAndrew Rybchenko fail1:
1375081d55dSAndrew Rybchenko 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
1385081d55dSAndrew Rybchenko 	return (rc);
1395081d55dSAndrew Rybchenko }
1405081d55dSAndrew Rybchenko 
1415081d55dSAndrew Rybchenko 
142e948693eSPhilip Paeps static	__checkReturn		uint8_t
143e948693eSPhilip Paeps efx_bootcfg_csum(
144e948693eSPhilip Paeps 	__in			efx_nic_t *enp,
145662c835bSAndrew Rybchenko 	__in_bcount(size)	uint8_t const *data,
146e948693eSPhilip Paeps 	__in			size_t size)
147e948693eSPhilip Paeps {
148e948693eSPhilip Paeps 	_NOTE(ARGUNUSED(enp))
149e948693eSPhilip Paeps 
150e948693eSPhilip Paeps 	unsigned int pos;
151e948693eSPhilip Paeps 	uint8_t checksum = 0;
152e948693eSPhilip Paeps 
153e948693eSPhilip Paeps 	for (pos = 0; pos < size; pos++)
154e948693eSPhilip Paeps 		checksum += data[pos];
155e948693eSPhilip Paeps 	return (checksum);
156e948693eSPhilip Paeps }
157e948693eSPhilip Paeps 
158460cb568SAndrew Rybchenko static	__checkReturn		efx_rc_t
159e948693eSPhilip Paeps efx_bootcfg_verify(
160e948693eSPhilip Paeps 	__in			efx_nic_t *enp,
161662c835bSAndrew Rybchenko 	__in_bcount(size)	uint8_t const *data,
162e948693eSPhilip Paeps 	__in			size_t size,
1633c838a9fSAndrew Rybchenko 	__out_opt		size_t *usedp)
164e948693eSPhilip Paeps {
165e948693eSPhilip Paeps 	size_t offset = 0;
166e948693eSPhilip Paeps 	size_t used = 0;
167460cb568SAndrew Rybchenko 	efx_rc_t rc;
168e948693eSPhilip Paeps 
169453130d9SPedro F. Giffuni 	/* Start parsing tags immediately after the checksum */
170e948693eSPhilip Paeps 	for (offset = 1; offset < size; ) {
171e948693eSPhilip Paeps 		uint8_t tag;
172e948693eSPhilip Paeps 		uint8_t length;
173e948693eSPhilip Paeps 
174e948693eSPhilip Paeps 		/* Consume tag */
175e948693eSPhilip Paeps 		tag = data[offset];
176e948693eSPhilip Paeps 		if (tag == DHCP_END) {
177e948693eSPhilip Paeps 			offset++;
178e948693eSPhilip Paeps 			used = offset;
179e948693eSPhilip Paeps 			break;
180e948693eSPhilip Paeps 		}
181e948693eSPhilip Paeps 		if (tag == DHCP_PAD) {
182e948693eSPhilip Paeps 			offset++;
183e948693eSPhilip Paeps 			continue;
184e948693eSPhilip Paeps 		}
185e948693eSPhilip Paeps 
186e948693eSPhilip Paeps 		/* Consume length */
187e948693eSPhilip Paeps 		if (offset + 1 >= size) {
188e948693eSPhilip Paeps 			rc = ENOSPC;
189e948693eSPhilip Paeps 			goto fail1;
190e948693eSPhilip Paeps 		}
191e948693eSPhilip Paeps 		length = data[offset + 1];
192e948693eSPhilip Paeps 
193e948693eSPhilip Paeps 		/* Consume *length */
194e948693eSPhilip Paeps 		if (offset + 1 + length >= size) {
195e948693eSPhilip Paeps 			rc = ENOSPC;
196e948693eSPhilip Paeps 			goto fail2;
197e948693eSPhilip Paeps 		}
198e948693eSPhilip Paeps 
199e948693eSPhilip Paeps 		offset += 2 + length;
200e948693eSPhilip Paeps 		used = offset;
201e948693eSPhilip Paeps 	}
202e948693eSPhilip Paeps 
203e948693eSPhilip Paeps 	/* Checksum the entire sector, including bytes after any DHCP_END */
204e948693eSPhilip Paeps 	if (efx_bootcfg_csum(enp, data, size) != 0) {
205e948693eSPhilip Paeps 		rc = EINVAL;
206e948693eSPhilip Paeps 		goto fail3;
207e948693eSPhilip Paeps 	}
208e948693eSPhilip Paeps 
209e948693eSPhilip Paeps 	if (usedp != NULL)
210e948693eSPhilip Paeps 		*usedp = used;
211e948693eSPhilip Paeps 
212e948693eSPhilip Paeps 	return (0);
213e948693eSPhilip Paeps 
214e948693eSPhilip Paeps fail3:
215e948693eSPhilip Paeps 	EFSYS_PROBE(fail3);
216e948693eSPhilip Paeps fail2:
217e948693eSPhilip Paeps 	EFSYS_PROBE(fail2);
218e948693eSPhilip Paeps fail1:
219460cb568SAndrew Rybchenko 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
220e948693eSPhilip Paeps 
221e948693eSPhilip Paeps 	return (rc);
222e948693eSPhilip Paeps }
223e948693eSPhilip Paeps 
224662c835bSAndrew Rybchenko /*
225662c835bSAndrew Rybchenko  * Copy bootcfg sector data to a target buffer which may differ in size.
226662c835bSAndrew Rybchenko  * Optionally corrects format errors in source buffer.
227662c835bSAndrew Rybchenko  */
228662c835bSAndrew Rybchenko 				efx_rc_t
229662c835bSAndrew Rybchenko efx_bootcfg_copy_sector(
230662c835bSAndrew Rybchenko 	__in			efx_nic_t *enp,
231662c835bSAndrew Rybchenko 	__inout_bcount(sector_length)
232662c835bSAndrew Rybchenko 				uint8_t *sector,
233662c835bSAndrew Rybchenko 	__in			size_t sector_length,
234662c835bSAndrew Rybchenko 	__out_bcount(data_size)	uint8_t *data,
235662c835bSAndrew Rybchenko 	__in			size_t data_size,
236662c835bSAndrew Rybchenko 	__in			boolean_t handle_format_errors)
237662c835bSAndrew Rybchenko {
238662c835bSAndrew Rybchenko 	size_t used_bytes;
239662c835bSAndrew Rybchenko 	efx_rc_t rc;
240662c835bSAndrew Rybchenko 
241662c835bSAndrew Rybchenko 	/* Verify that the area is correctly formatted and checksummed */
242662c835bSAndrew Rybchenko 	rc = efx_bootcfg_verify(enp, sector, sector_length,
243662c835bSAndrew Rybchenko 				    &used_bytes);
244662c835bSAndrew Rybchenko 
245662c835bSAndrew Rybchenko 	if (!handle_format_errors) {
246662c835bSAndrew Rybchenko 		if (rc != 0)
247662c835bSAndrew Rybchenko 			goto fail1;
248662c835bSAndrew Rybchenko 
249662c835bSAndrew Rybchenko 		if ((used_bytes < 2) ||
250662c835bSAndrew Rybchenko 		    (sector[used_bytes - 1] != DHCP_END)) {
251662c835bSAndrew Rybchenko 			/* Block too short, or DHCP_END missing */
252662c835bSAndrew Rybchenko 			rc = ENOENT;
253662c835bSAndrew Rybchenko 			goto fail2;
254662c835bSAndrew Rybchenko 		}
255662c835bSAndrew Rybchenko 	}
256662c835bSAndrew Rybchenko 
257662c835bSAndrew Rybchenko 	/* Synthesize empty format on verification failure */
258662c835bSAndrew Rybchenko 	if (rc != 0 || used_bytes == 0) {
259662c835bSAndrew Rybchenko 		sector[0] = 0;
260662c835bSAndrew Rybchenko 		sector[1] = DHCP_END;
261662c835bSAndrew Rybchenko 		used_bytes = 2;
262662c835bSAndrew Rybchenko 	}
263662c835bSAndrew Rybchenko 	EFSYS_ASSERT(used_bytes >= 2);	/* checksum and DHCP_END */
264662c835bSAndrew Rybchenko 	EFSYS_ASSERT(used_bytes <= sector_length);
265662c835bSAndrew Rybchenko 	EFSYS_ASSERT(sector_length >= 2);
266662c835bSAndrew Rybchenko 
267662c835bSAndrew Rybchenko 	/*
268662c835bSAndrew Rybchenko 	 * Legacy bootcfg sectors don't terminate with a DHCP_END character.
269662c835bSAndrew Rybchenko 	 * Modify the returned payload so it does.
270662c835bSAndrew Rybchenko 	 * Reinitialise the sector if there isn't room for the character.
271662c835bSAndrew Rybchenko 	 */
272662c835bSAndrew Rybchenko 	if (sector[used_bytes - 1] != DHCP_END) {
273662c835bSAndrew Rybchenko 		if (used_bytes >= sector_length) {
274662c835bSAndrew Rybchenko 			sector[0] = 0;
275662c835bSAndrew Rybchenko 			used_bytes = 1;
276662c835bSAndrew Rybchenko 		}
277662c835bSAndrew Rybchenko 		sector[used_bytes] = DHCP_END;
278662c835bSAndrew Rybchenko 		++used_bytes;
279662c835bSAndrew Rybchenko 	}
280662c835bSAndrew Rybchenko 
281662c835bSAndrew Rybchenko 	/*
282662c835bSAndrew Rybchenko 	 * Verify that the target buffer is large enough for the
283662c835bSAndrew Rybchenko 	 * entire used bootcfg area, then copy into the target buffer.
284662c835bSAndrew Rybchenko 	 */
285662c835bSAndrew Rybchenko 	if (used_bytes > data_size) {
286662c835bSAndrew Rybchenko 		rc = ENOSPC;
287662c835bSAndrew Rybchenko 		goto fail3;
288662c835bSAndrew Rybchenko 	}
289662c835bSAndrew Rybchenko 	memcpy(data, sector, used_bytes);
290662c835bSAndrew Rybchenko 
291662c835bSAndrew Rybchenko 	/* Zero out the unused portion of the target buffer */
292662c835bSAndrew Rybchenko 	if (used_bytes < data_size)
293662c835bSAndrew Rybchenko 		(void) memset(data + used_bytes, 0, data_size - used_bytes);
294662c835bSAndrew Rybchenko 
295662c835bSAndrew Rybchenko 	/*
296662c835bSAndrew Rybchenko 	 * The checksum includes trailing data after any DHCP_END character,
297662c835bSAndrew Rybchenko 	 * which we've just modified (by truncation or appending DHCP_END).
298662c835bSAndrew Rybchenko 	 */
299662c835bSAndrew Rybchenko 	data[0] -= efx_bootcfg_csum(enp, data, data_size);
300662c835bSAndrew Rybchenko 
301662c835bSAndrew Rybchenko 	return (0);
302662c835bSAndrew Rybchenko 
303662c835bSAndrew Rybchenko fail3:
304662c835bSAndrew Rybchenko 	EFSYS_PROBE(fail3);
305662c835bSAndrew Rybchenko fail2:
306662c835bSAndrew Rybchenko 	EFSYS_PROBE(fail2);
307662c835bSAndrew Rybchenko fail1:
308662c835bSAndrew Rybchenko 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
309662c835bSAndrew Rybchenko 
310662c835bSAndrew Rybchenko 	return (rc);
311662c835bSAndrew Rybchenko }
312662c835bSAndrew Rybchenko 
313460cb568SAndrew Rybchenko 				efx_rc_t
314e948693eSPhilip Paeps efx_bootcfg_read(
315e948693eSPhilip Paeps 	__in			efx_nic_t *enp,
3167e17c17dSAndrew Rybchenko 	__out_bcount(size)	uint8_t *data,
317e948693eSPhilip Paeps 	__in			size_t size)
318e948693eSPhilip Paeps {
319e948693eSPhilip Paeps 	uint8_t *payload = NULL;
320e948693eSPhilip Paeps 	size_t used_bytes;
3215081d55dSAndrew Rybchenko 	size_t partn_length;
322e948693eSPhilip Paeps 	size_t sector_length;
3235081d55dSAndrew Rybchenko 	size_t sector_offset;
324460cb568SAndrew Rybchenko 	efx_rc_t rc;
325662c835bSAndrew Rybchenko 	uint32_t sector_number;
326e948693eSPhilip Paeps 
327*df0385e5SAndrew Rybchenko #if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD || EFSYS_OPT_MEDFORD2
328662c835bSAndrew Rybchenko 	sector_number = enp->en_nic_cfg.enc_pf;
329662c835bSAndrew Rybchenko #else
330662c835bSAndrew Rybchenko 	sector_number = 0;
331662c835bSAndrew Rybchenko #endif
3325081d55dSAndrew Rybchenko 	rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length);
333e948693eSPhilip Paeps 	if (rc != 0)
334e948693eSPhilip Paeps 		goto fail1;
335e948693eSPhilip Paeps 
3365081d55dSAndrew Rybchenko 	/* The bootcfg sector may be stored in a (larger) shared partition */
337662c835bSAndrew Rybchenko 	rc = efx_bootcfg_sector_info(enp, sector_number,
338662c835bSAndrew Rybchenko 	    NULL, &sector_offset, &sector_length);
3395081d55dSAndrew Rybchenko 	if (rc != 0)
3405081d55dSAndrew Rybchenko 		goto fail2;
3415081d55dSAndrew Rybchenko 
3425081d55dSAndrew Rybchenko 	if (sector_length > BOOTCFG_MAX_SIZE)
3435081d55dSAndrew Rybchenko 		sector_length = BOOTCFG_MAX_SIZE;
3445081d55dSAndrew Rybchenko 
3455081d55dSAndrew Rybchenko 	if (sector_offset + sector_length > partn_length) {
3465081d55dSAndrew Rybchenko 		/* Partition is too small */
3475081d55dSAndrew Rybchenko 		rc = EFBIG;
3485081d55dSAndrew Rybchenko 		goto fail3;
3495081d55dSAndrew Rybchenko 	}
3505081d55dSAndrew Rybchenko 
351e948693eSPhilip Paeps 	/*
3525081d55dSAndrew Rybchenko 	 * We need to read the entire BOOTCFG sector to ensure we read all the
353e948693eSPhilip Paeps 	 * tags, because legacy bootcfg sectors are not guaranteed to end with
354e948693eSPhilip Paeps 	 * a DHCP_END character. If the user hasn't supplied a sufficiently
355e948693eSPhilip Paeps 	 * large buffer then use our own buffer.
356e948693eSPhilip Paeps 	 */
357e948693eSPhilip Paeps 	if (sector_length > size) {
358e948693eSPhilip Paeps 		EFSYS_KMEM_ALLOC(enp->en_esip, sector_length, payload);
359e948693eSPhilip Paeps 		if (payload == NULL) {
360e948693eSPhilip Paeps 			rc = ENOMEM;
3615081d55dSAndrew Rybchenko 			goto fail4;
362e948693eSPhilip Paeps 		}
363e948693eSPhilip Paeps 	} else
364e948693eSPhilip Paeps 		payload = (uint8_t *)data;
365e948693eSPhilip Paeps 
366e948693eSPhilip Paeps 	if ((rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
3675081d55dSAndrew Rybchenko 		goto fail5;
368e948693eSPhilip Paeps 
369662c835bSAndrew Rybchenko 	if ((rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
370662c835bSAndrew Rybchenko 	    sector_offset, (caddr_t)payload, sector_length)) != 0) {
371d5106d05SAndrew Rybchenko 		(void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
3725081d55dSAndrew Rybchenko 		goto fail6;
373662c835bSAndrew Rybchenko 	}
374662c835bSAndrew Rybchenko 
375d5106d05SAndrew Rybchenko 	if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
376662c835bSAndrew Rybchenko 		goto fail7;
377e948693eSPhilip Paeps 
378e948693eSPhilip Paeps 	/* Verify that the area is correctly formatted and checksummed */
3797e17c17dSAndrew Rybchenko 	rc = efx_bootcfg_verify(enp, payload, sector_length,
380e948693eSPhilip Paeps 	    &used_bytes);
381e948693eSPhilip Paeps 	if (rc != 0 || used_bytes == 0) {
3825e7bb158SAndrew Rybchenko 		payload[0] = (uint8_t)(~DHCP_END & 0xff);
383e948693eSPhilip Paeps 		payload[1] = DHCP_END;
384e948693eSPhilip Paeps 		used_bytes = 2;
385e948693eSPhilip Paeps 	}
386e948693eSPhilip Paeps 
387e948693eSPhilip Paeps 	EFSYS_ASSERT(used_bytes >= 2);	/* checksum and DHCP_END */
388e948693eSPhilip Paeps 	EFSYS_ASSERT(used_bytes <= sector_length);
389e948693eSPhilip Paeps 
390e948693eSPhilip Paeps 	/*
391e948693eSPhilip Paeps 	 * Legacy bootcfg sectors don't terminate with a DHCP_END character.
392e948693eSPhilip Paeps 	 * Modify the returned payload so it does. BOOTCFG_MAX_SIZE is by
393e948693eSPhilip Paeps 	 * definition large enough for any valid (per-port) bootcfg sector,
394e948693eSPhilip Paeps 	 * so reinitialise the sector if there isn't room for the character.
395e948693eSPhilip Paeps 	 */
396e948693eSPhilip Paeps 	if (payload[used_bytes - 1] != DHCP_END) {
397e948693eSPhilip Paeps 		if (used_bytes + 1 > sector_length) {
398e948693eSPhilip Paeps 			payload[0] = 0;
399e948693eSPhilip Paeps 			used_bytes = 1;
400e948693eSPhilip Paeps 		}
401e948693eSPhilip Paeps 
402e948693eSPhilip Paeps 		payload[used_bytes] = DHCP_END;
403e948693eSPhilip Paeps 		++used_bytes;
404e948693eSPhilip Paeps 	}
405e948693eSPhilip Paeps 
406e948693eSPhilip Paeps 	/*
407e948693eSPhilip Paeps 	 * Verify that the user supplied buffer is large enough for the
408e948693eSPhilip Paeps 	 * entire used bootcfg area, then copy into the user supplied buffer.
409e948693eSPhilip Paeps 	 */
410e948693eSPhilip Paeps 	if (used_bytes > size) {
411e948693eSPhilip Paeps 		rc = ENOSPC;
412662c835bSAndrew Rybchenko 		goto fail8;
413e948693eSPhilip Paeps 	}
414e948693eSPhilip Paeps 	if (sector_length > size) {
415e948693eSPhilip Paeps 		memcpy(data, payload, used_bytes);
416e948693eSPhilip Paeps 		EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
417e948693eSPhilip Paeps 	}
418e948693eSPhilip Paeps 
419e948693eSPhilip Paeps 	/* Zero out the unused portion of the user buffer */
420e948693eSPhilip Paeps 	if (used_bytes < size)
421e948693eSPhilip Paeps 		(void) memset(data + used_bytes, 0, size - used_bytes);
422e948693eSPhilip Paeps 
423e948693eSPhilip Paeps 	/*
424e948693eSPhilip Paeps 	 * The checksum includes trailing data after any DHCP_END character,
425e948693eSPhilip Paeps 	 * which we've just modified (by truncation or appending DHCP_END).
426e948693eSPhilip Paeps 	 */
427e948693eSPhilip Paeps 	data[0] -= efx_bootcfg_csum(enp, data, size);
428e948693eSPhilip Paeps 
429e948693eSPhilip Paeps 	return (0);
430e948693eSPhilip Paeps 
431662c835bSAndrew Rybchenko fail8:
432662c835bSAndrew Rybchenko 	EFSYS_PROBE(fail8);
4335081d55dSAndrew Rybchenko fail7:
4345081d55dSAndrew Rybchenko 	EFSYS_PROBE(fail7);
4355081d55dSAndrew Rybchenko fail6:
4365081d55dSAndrew Rybchenko 	EFSYS_PROBE(fail6);
437e948693eSPhilip Paeps fail5:
438e948693eSPhilip Paeps 	EFSYS_PROBE(fail5);
4395081d55dSAndrew Rybchenko 	if (sector_length > size)
4405081d55dSAndrew Rybchenko 		EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
441e948693eSPhilip Paeps fail4:
442e948693eSPhilip Paeps 	EFSYS_PROBE(fail4);
443e948693eSPhilip Paeps fail3:
444e948693eSPhilip Paeps 	EFSYS_PROBE(fail3);
445e948693eSPhilip Paeps fail2:
446e948693eSPhilip Paeps 	EFSYS_PROBE(fail2);
447e948693eSPhilip Paeps fail1:
448460cb568SAndrew Rybchenko 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
449e948693eSPhilip Paeps 
450e948693eSPhilip Paeps 	return (rc);
451e948693eSPhilip Paeps }
452e948693eSPhilip Paeps 
453460cb568SAndrew Rybchenko 				efx_rc_t
454e948693eSPhilip Paeps efx_bootcfg_write(
455e948693eSPhilip Paeps 	__in			efx_nic_t *enp,
4567e17c17dSAndrew Rybchenko 	__in_bcount(size)	uint8_t *data,
457e948693eSPhilip Paeps 	__in			size_t size)
458e948693eSPhilip Paeps {
4595081d55dSAndrew Rybchenko 	uint8_t *partn_data;
460e948693eSPhilip Paeps 	uint8_t checksum;
4615081d55dSAndrew Rybchenko 	size_t partn_length;
462e948693eSPhilip Paeps 	size_t sector_length;
4635081d55dSAndrew Rybchenko 	size_t sector_offset;
464e948693eSPhilip Paeps 	size_t used_bytes;
465460cb568SAndrew Rybchenko 	efx_rc_t rc;
466662c835bSAndrew Rybchenko 	uint32_t sector_number;
467662c835bSAndrew Rybchenko 
468*df0385e5SAndrew Rybchenko #if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD || EFSYS_OPT_MEDFORD2
469662c835bSAndrew Rybchenko 	sector_number = enp->en_nic_cfg.enc_pf;
470662c835bSAndrew Rybchenko #else
471662c835bSAndrew Rybchenko 	sector_number = 0;
472662c835bSAndrew Rybchenko #endif
473e948693eSPhilip Paeps 
4745081d55dSAndrew Rybchenko 	rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length);
475e948693eSPhilip Paeps 	if (rc != 0)
476e948693eSPhilip Paeps 		goto fail1;
477e948693eSPhilip Paeps 
4785081d55dSAndrew Rybchenko 	/* The bootcfg sector may be stored in a (larger) shared partition */
479662c835bSAndrew Rybchenko 	rc = efx_bootcfg_sector_info(enp, sector_number,
480662c835bSAndrew Rybchenko 	    NULL, &sector_offset, &sector_length);
4815081d55dSAndrew Rybchenko 	if (rc != 0)
4825081d55dSAndrew Rybchenko 		goto fail2;
4835081d55dSAndrew Rybchenko 
484e948693eSPhilip Paeps 	if (sector_length > BOOTCFG_MAX_SIZE)
485e948693eSPhilip Paeps 		sector_length = BOOTCFG_MAX_SIZE;
486e948693eSPhilip Paeps 
4875081d55dSAndrew Rybchenko 	if (sector_offset + sector_length > partn_length) {
4885081d55dSAndrew Rybchenko 		/* Partition is too small */
4895081d55dSAndrew Rybchenko 		rc = EFBIG;
4905081d55dSAndrew Rybchenko 		goto fail3;
4915081d55dSAndrew Rybchenko 	}
4925081d55dSAndrew Rybchenko 
493e948693eSPhilip Paeps 	if ((rc = efx_bootcfg_verify(enp, data, size, &used_bytes)) != 0)
4945081d55dSAndrew Rybchenko 		goto fail4;
495e948693eSPhilip Paeps 
496e948693eSPhilip Paeps 	/* The caller *must* terminate their block with a DHCP_END character */
4975081d55dSAndrew Rybchenko 	if ((used_bytes < 2) || ((uint8_t)data[used_bytes - 1] != DHCP_END)) {
4985081d55dSAndrew Rybchenko 		/* Block too short or DHCP_END missing */
499e948693eSPhilip Paeps 		rc = ENOENT;
5005081d55dSAndrew Rybchenko 		goto fail5;
501e948693eSPhilip Paeps 	}
502e948693eSPhilip Paeps 
503e948693eSPhilip Paeps 	/* Check that the hardware has support for this much data */
504e948693eSPhilip Paeps 	if (used_bytes > MIN(sector_length, BOOTCFG_MAX_SIZE)) {
505e948693eSPhilip Paeps 		rc = ENOSPC;
506e948693eSPhilip Paeps 		goto fail6;
507e948693eSPhilip Paeps 	}
508e948693eSPhilip Paeps 
5095081d55dSAndrew Rybchenko 	/*
5105081d55dSAndrew Rybchenko 	 * If the BOOTCFG sector is stored in a shared partition, then we must
5115081d55dSAndrew Rybchenko 	 * read the whole partition and insert the updated bootcfg sector at the
5125081d55dSAndrew Rybchenko 	 * correct offset.
5135081d55dSAndrew Rybchenko 	 */
5145081d55dSAndrew Rybchenko 	EFSYS_KMEM_ALLOC(enp->en_esip, partn_length, partn_data);
5155081d55dSAndrew Rybchenko 	if (partn_data == NULL) {
5165081d55dSAndrew Rybchenko 		rc = ENOMEM;
517e948693eSPhilip Paeps 		goto fail7;
5185081d55dSAndrew Rybchenko 	}
5195081d55dSAndrew Rybchenko 
5205081d55dSAndrew Rybchenko 	rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
5215081d55dSAndrew Rybchenko 	if (rc != 0)
5225081d55dSAndrew Rybchenko 		goto fail8;
5235081d55dSAndrew Rybchenko 
5245081d55dSAndrew Rybchenko 	/* Read the entire partition */
5255081d55dSAndrew Rybchenko 	rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG, 0,
5265081d55dSAndrew Rybchenko 				    (caddr_t)partn_data, partn_length);
5275081d55dSAndrew Rybchenko 	if (rc != 0)
5285081d55dSAndrew Rybchenko 		goto fail9;
529e948693eSPhilip Paeps 
530e948693eSPhilip Paeps 	/*
5315081d55dSAndrew Rybchenko 	 * Insert the BOOTCFG sector into the partition, Zero out all data after
5325081d55dSAndrew Rybchenko 	 * the DHCP_END tag, and adjust the checksum.
533e948693eSPhilip Paeps 	 */
5345081d55dSAndrew Rybchenko 	(void) memset(partn_data + sector_offset, 0x0, sector_length);
5355081d55dSAndrew Rybchenko 	(void) memcpy(partn_data + sector_offset, data, used_bytes);
5365081d55dSAndrew Rybchenko 
537e948693eSPhilip Paeps 	checksum = efx_bootcfg_csum(enp, data, used_bytes);
5385081d55dSAndrew Rybchenko 	partn_data[sector_offset] -= checksum;
539e948693eSPhilip Paeps 
5405081d55dSAndrew Rybchenko 	if ((rc = efx_nvram_erase(enp, EFX_NVRAM_BOOTROM_CFG)) != 0)
5415081d55dSAndrew Rybchenko 		goto fail10;
542e948693eSPhilip Paeps 
543e948693eSPhilip Paeps 	if ((rc = efx_nvram_write_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
544662c835bSAndrew Rybchenko 		    0, (caddr_t)partn_data, partn_length)) != 0)
5455081d55dSAndrew Rybchenko 		goto fail11;
546e948693eSPhilip Paeps 
547d5106d05SAndrew Rybchenko 	if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
548662c835bSAndrew Rybchenko 		goto fail12;
549e948693eSPhilip Paeps 
5505081d55dSAndrew Rybchenko 	EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data);
551e948693eSPhilip Paeps 
552e948693eSPhilip Paeps 	return (0);
553e948693eSPhilip Paeps 
554662c835bSAndrew Rybchenko fail12:
555662c835bSAndrew Rybchenko 	EFSYS_PROBE(fail12);
5565081d55dSAndrew Rybchenko fail11:
5575081d55dSAndrew Rybchenko 	EFSYS_PROBE(fail11);
5585081d55dSAndrew Rybchenko fail10:
5595081d55dSAndrew Rybchenko 	EFSYS_PROBE(fail10);
5605081d55dSAndrew Rybchenko fail9:
5615081d55dSAndrew Rybchenko 	EFSYS_PROBE(fail9);
562e948693eSPhilip Paeps 
563d5106d05SAndrew Rybchenko 	(void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
5645081d55dSAndrew Rybchenko fail8:
5655081d55dSAndrew Rybchenko 	EFSYS_PROBE(fail8);
5665081d55dSAndrew Rybchenko 
5675081d55dSAndrew Rybchenko 	EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data);
5685081d55dSAndrew Rybchenko fail7:
5695081d55dSAndrew Rybchenko 	EFSYS_PROBE(fail7);
5705081d55dSAndrew Rybchenko fail6:
5715081d55dSAndrew Rybchenko 	EFSYS_PROBE(fail6);
572e948693eSPhilip Paeps fail5:
573e948693eSPhilip Paeps 	EFSYS_PROBE(fail5);
574e948693eSPhilip Paeps fail4:
575e948693eSPhilip Paeps 	EFSYS_PROBE(fail4);
576e948693eSPhilip Paeps fail3:
577e948693eSPhilip Paeps 	EFSYS_PROBE(fail3);
578e948693eSPhilip Paeps fail2:
579e948693eSPhilip Paeps 	EFSYS_PROBE(fail2);
580e948693eSPhilip Paeps fail1:
581460cb568SAndrew Rybchenko 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
582e948693eSPhilip Paeps 
583e948693eSPhilip Paeps 	return (rc);
584e948693eSPhilip Paeps }
585e948693eSPhilip Paeps 
586e948693eSPhilip Paeps #endif	/* EFSYS_OPT_BOOTCFG */
587