1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2009-2016 Solarflare Communications Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 *    this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 *    this list of conditions and the following disclaimer in the documentation
14 *    and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
18 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 * The views and conclusions contained in the software and documentation are
29 * those of the authors and should not be interpreted as representing official
30 * policies, either expressed or implied, of the FreeBSD Project.
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD$");
35
36#include "efx.h"
37#include "efx_impl.h"
38
39#if EFSYS_OPT_BOOTCFG
40
41/*
42 * Maximum size of BOOTCFG block across all nics as understood by SFCgPXE.
43 * NOTE: This is larger than the Medford per-PF bootcfg sector.
44 */
45#define	BOOTCFG_MAX_SIZE 0x1000
46
47/* Medford per-PF bootcfg sector */
48#define	BOOTCFG_PER_PF   0x800
49#define	BOOTCFG_PF_COUNT 16
50
51#define	DHCP_END ((uint8_t)0xff)
52#define	DHCP_PAD ((uint8_t)0)
53
54
55/* Report the layout of bootcfg sectors in NVRAM partition. */
56	__checkReturn		efx_rc_t
57efx_bootcfg_sector_info(
58	__in			efx_nic_t *enp,
59	__in			uint32_t pf,
60	__out_opt		uint32_t *sector_countp,
61	__out			size_t *offsetp,
62	__out			size_t *max_sizep)
63{
64	uint32_t count;
65	size_t max_size;
66	size_t offset;
67	int rc;
68
69	switch (enp->en_family) {
70#if EFSYS_OPT_SIENA
71	case EFX_FAMILY_SIENA:
72		max_size = BOOTCFG_MAX_SIZE;
73		offset = 0;
74		count = 1;
75		break;
76#endif /* EFSYS_OPT_SIENA */
77
78#if EFSYS_OPT_HUNTINGTON
79	case EFX_FAMILY_HUNTINGTON:
80		max_size = BOOTCFG_MAX_SIZE;
81		offset = 0;
82		count = 1;
83		break;
84#endif /* EFSYS_OPT_HUNTINGTON */
85
86#if EFSYS_OPT_MEDFORD
87	case EFX_FAMILY_MEDFORD: {
88		/* Shared partition (array indexed by PF) */
89		max_size = BOOTCFG_PER_PF;
90		count = BOOTCFG_PF_COUNT;
91		if (pf >= count) {
92			rc = EINVAL;
93			goto fail2;
94		}
95		offset = max_size * pf;
96		break;
97	}
98#endif /* EFSYS_OPT_MEDFORD */
99
100	default:
101		EFSYS_ASSERT(0);
102		rc = ENOTSUP;
103		goto fail1;
104	}
105	EFSYS_ASSERT3U(max_size, <=, BOOTCFG_MAX_SIZE);
106
107	if (sector_countp != NULL)
108		*sector_countp = count;
109	*offsetp = offset;
110	*max_sizep = max_size;
111
112	return (0);
113
114#if EFSYS_OPT_MEDFORD
115fail2:
116	EFSYS_PROBE(fail2);
117#endif
118fail1:
119	EFSYS_PROBE1(fail1, efx_rc_t, rc);
120	return (rc);
121}
122
123
124static	__checkReturn		uint8_t
125efx_bootcfg_csum(
126	__in			efx_nic_t *enp,
127	__in_bcount(size)	uint8_t const *data,
128	__in			size_t size)
129{
130	_NOTE(ARGUNUSED(enp))
131
132	unsigned int pos;
133	uint8_t checksum = 0;
134
135	for (pos = 0; pos < size; pos++)
136		checksum += data[pos];
137	return (checksum);
138}
139
140static	__checkReturn		efx_rc_t
141efx_bootcfg_verify(
142	__in			efx_nic_t *enp,
143	__in_bcount(size)	uint8_t const *data,
144	__in			size_t size,
145	__out_opt		size_t *usedp)
146{
147	size_t offset = 0;
148	size_t used = 0;
149	efx_rc_t rc;
150
151	/* Start parsing tags immediately after the checksum */
152	for (offset = 1; offset < size; ) {
153		uint8_t tag;
154		uint8_t length;
155
156		/* Consume tag */
157		tag = data[offset];
158		if (tag == DHCP_END) {
159			offset++;
160			used = offset;
161			break;
162		}
163		if (tag == DHCP_PAD) {
164			offset++;
165			continue;
166		}
167
168		/* Consume length */
169		if (offset + 1 >= size) {
170			rc = ENOSPC;
171			goto fail1;
172		}
173		length = data[offset + 1];
174
175		/* Consume *length */
176		if (offset + 1 + length >= size) {
177			rc = ENOSPC;
178			goto fail2;
179		}
180
181		offset += 2 + length;
182		used = offset;
183	}
184
185	/* Checksum the entire sector, including bytes after any DHCP_END */
186	if (efx_bootcfg_csum(enp, data, size) != 0) {
187		rc = EINVAL;
188		goto fail3;
189	}
190
191	if (usedp != NULL)
192		*usedp = used;
193
194	return (0);
195
196fail3:
197	EFSYS_PROBE(fail3);
198fail2:
199	EFSYS_PROBE(fail2);
200fail1:
201	EFSYS_PROBE1(fail1, efx_rc_t, rc);
202
203	return (rc);
204}
205
206/*
207 * Copy bootcfg sector data to a target buffer which may differ in size.
208 * Optionally corrects format errors in source buffer.
209 */
210				efx_rc_t
211efx_bootcfg_copy_sector(
212	__in			efx_nic_t *enp,
213	__inout_bcount(sector_length)
214				uint8_t *sector,
215	__in			size_t sector_length,
216	__out_bcount(data_size)	uint8_t *data,
217	__in			size_t data_size,
218	__in			boolean_t handle_format_errors)
219{
220	size_t used_bytes;
221	efx_rc_t rc;
222
223	/* Verify that the area is correctly formatted and checksummed */
224	rc = efx_bootcfg_verify(enp, sector, sector_length,
225				    &used_bytes);
226
227	if (!handle_format_errors) {
228		if (rc != 0)
229			goto fail1;
230
231		if ((used_bytes < 2) ||
232		    (sector[used_bytes - 1] != DHCP_END)) {
233			/* Block too short, or DHCP_END missing */
234			rc = ENOENT;
235			goto fail2;
236		}
237	}
238
239	/* Synthesize empty format on verification failure */
240	if (rc != 0 || used_bytes == 0) {
241		sector[0] = 0;
242		sector[1] = DHCP_END;
243		used_bytes = 2;
244	}
245	EFSYS_ASSERT(used_bytes >= 2);	/* checksum and DHCP_END */
246	EFSYS_ASSERT(used_bytes <= sector_length);
247	EFSYS_ASSERT(sector_length >= 2);
248
249	/*
250	 * Legacy bootcfg sectors don't terminate with a DHCP_END character.
251	 * Modify the returned payload so it does.
252	 * Reinitialise the sector if there isn't room for the character.
253	 */
254	if (sector[used_bytes - 1] != DHCP_END) {
255		if (used_bytes >= sector_length) {
256			sector[0] = 0;
257			used_bytes = 1;
258		}
259		sector[used_bytes] = DHCP_END;
260		++used_bytes;
261	}
262
263	/*
264	 * Verify that the target buffer is large enough for the
265	 * entire used bootcfg area, then copy into the target buffer.
266	 */
267	if (used_bytes > data_size) {
268		rc = ENOSPC;
269		goto fail3;
270	}
271	memcpy(data, sector, used_bytes);
272
273	/* Zero out the unused portion of the target buffer */
274	if (used_bytes < data_size)
275		(void) memset(data + used_bytes, 0, data_size - used_bytes);
276
277	/*
278	 * The checksum includes trailing data after any DHCP_END character,
279	 * which we've just modified (by truncation or appending DHCP_END).
280	 */
281	data[0] -= efx_bootcfg_csum(enp, data, data_size);
282
283	return (0);
284
285fail3:
286	EFSYS_PROBE(fail3);
287fail2:
288	EFSYS_PROBE(fail2);
289fail1:
290	EFSYS_PROBE1(fail1, efx_rc_t, rc);
291
292	return (rc);
293}
294
295				efx_rc_t
296efx_bootcfg_read(
297	__in			efx_nic_t *enp,
298	__out_bcount(size)	uint8_t *data,
299	__in			size_t size)
300{
301	uint8_t *payload = NULL;
302	size_t used_bytes;
303	size_t partn_length;
304	size_t sector_length;
305	size_t sector_offset;
306	efx_rc_t rc;
307	uint32_t sector_number;
308
309#if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD
310	sector_number = enp->en_nic_cfg.enc_pf;
311#else
312	sector_number = 0;
313#endif
314	rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length);
315	if (rc != 0)
316		goto fail1;
317
318	/* The bootcfg sector may be stored in a (larger) shared partition */
319	rc = efx_bootcfg_sector_info(enp, sector_number,
320	    NULL, &sector_offset, &sector_length);
321	if (rc != 0)
322		goto fail2;
323
324	if (sector_length > BOOTCFG_MAX_SIZE)
325		sector_length = BOOTCFG_MAX_SIZE;
326
327	if (sector_offset + sector_length > partn_length) {
328		/* Partition is too small */
329		rc = EFBIG;
330		goto fail3;
331	}
332
333	/*
334	 * We need to read the entire BOOTCFG sector to ensure we read all the
335	 * tags, because legacy bootcfg sectors are not guaranteed to end with
336	 * a DHCP_END character. If the user hasn't supplied a sufficiently
337	 * large buffer then use our own buffer.
338	 */
339	if (sector_length > size) {
340		EFSYS_KMEM_ALLOC(enp->en_esip, sector_length, payload);
341		if (payload == NULL) {
342			rc = ENOMEM;
343			goto fail4;
344		}
345	} else
346		payload = (uint8_t *)data;
347
348	if ((rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
349		goto fail5;
350
351	if ((rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
352	    sector_offset, (caddr_t)payload, sector_length)) != 0) {
353		(void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG);
354		goto fail6;
355	}
356
357	if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG)) != 0)
358		goto fail7;
359
360	/* Verify that the area is correctly formatted and checksummed */
361	rc = efx_bootcfg_verify(enp, payload, sector_length,
362	    &used_bytes);
363	if (rc != 0 || used_bytes == 0) {
364		payload[0] = (uint8_t)(~DHCP_END & 0xff);
365		payload[1] = DHCP_END;
366		used_bytes = 2;
367	}
368
369	EFSYS_ASSERT(used_bytes >= 2);	/* checksum and DHCP_END */
370	EFSYS_ASSERT(used_bytes <= sector_length);
371
372	/*
373	 * Legacy bootcfg sectors don't terminate with a DHCP_END character.
374	 * Modify the returned payload so it does. BOOTCFG_MAX_SIZE is by
375	 * definition large enough for any valid (per-port) bootcfg sector,
376	 * so reinitialise the sector if there isn't room for the character.
377	 */
378	if (payload[used_bytes - 1] != DHCP_END) {
379		if (used_bytes + 1 > sector_length) {
380			payload[0] = 0;
381			used_bytes = 1;
382		}
383
384		payload[used_bytes] = DHCP_END;
385		++used_bytes;
386	}
387
388	/*
389	 * Verify that the user supplied buffer is large enough for the
390	 * entire used bootcfg area, then copy into the user supplied buffer.
391	 */
392	if (used_bytes > size) {
393		rc = ENOSPC;
394		goto fail8;
395	}
396	if (sector_length > size) {
397		memcpy(data, payload, used_bytes);
398		EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
399	}
400
401	/* Zero out the unused portion of the user buffer */
402	if (used_bytes < size)
403		(void) memset(data + used_bytes, 0, size - used_bytes);
404
405	/*
406	 * The checksum includes trailing data after any DHCP_END character,
407	 * which we've just modified (by truncation or appending DHCP_END).
408	 */
409	data[0] -= efx_bootcfg_csum(enp, data, size);
410
411	return (0);
412
413fail8:
414	EFSYS_PROBE(fail8);
415fail7:
416	EFSYS_PROBE(fail7);
417fail6:
418	EFSYS_PROBE(fail6);
419fail5:
420	EFSYS_PROBE(fail5);
421	if (sector_length > size)
422		EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
423fail4:
424	EFSYS_PROBE(fail4);
425fail3:
426	EFSYS_PROBE(fail3);
427fail2:
428	EFSYS_PROBE(fail2);
429fail1:
430	EFSYS_PROBE1(fail1, efx_rc_t, rc);
431
432	return (rc);
433}
434
435				efx_rc_t
436efx_bootcfg_write(
437	__in			efx_nic_t *enp,
438	__in_bcount(size)	uint8_t *data,
439	__in			size_t size)
440{
441	uint8_t *partn_data;
442	uint8_t checksum;
443	size_t partn_length;
444	size_t sector_length;
445	size_t sector_offset;
446	size_t used_bytes;
447	efx_rc_t rc;
448	uint32_t sector_number;
449
450#if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD
451	sector_number = enp->en_nic_cfg.enc_pf;
452#else
453	sector_number = 0;
454#endif
455
456	rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length);
457	if (rc != 0)
458		goto fail1;
459
460	/* The bootcfg sector may be stored in a (larger) shared partition */
461	rc = efx_bootcfg_sector_info(enp, sector_number,
462	    NULL, &sector_offset, &sector_length);
463	if (rc != 0)
464		goto fail2;
465
466	if (sector_length > BOOTCFG_MAX_SIZE)
467		sector_length = BOOTCFG_MAX_SIZE;
468
469	if (sector_offset + sector_length > partn_length) {
470		/* Partition is too small */
471		rc = EFBIG;
472		goto fail3;
473	}
474
475	if ((rc = efx_bootcfg_verify(enp, data, size, &used_bytes)) != 0)
476		goto fail4;
477
478	/* The caller *must* terminate their block with a DHCP_END character */
479	if ((used_bytes < 2) || ((uint8_t)data[used_bytes - 1] != DHCP_END)) {
480		/* Block too short or DHCP_END missing */
481		rc = ENOENT;
482		goto fail5;
483	}
484
485	/* Check that the hardware has support for this much data */
486	if (used_bytes > MIN(sector_length, BOOTCFG_MAX_SIZE)) {
487		rc = ENOSPC;
488		goto fail6;
489	}
490
491	/*
492	 * If the BOOTCFG sector is stored in a shared partition, then we must
493	 * read the whole partition and insert the updated bootcfg sector at the
494	 * correct offset.
495	 */
496	EFSYS_KMEM_ALLOC(enp->en_esip, partn_length, partn_data);
497	if (partn_data == NULL) {
498		rc = ENOMEM;
499		goto fail7;
500	}
501
502	rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
503	if (rc != 0)
504		goto fail8;
505
506	/* Read the entire partition */
507	rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG, 0,
508				    (caddr_t)partn_data, partn_length);
509	if (rc != 0)
510		goto fail9;
511
512	/*
513	 * Insert the BOOTCFG sector into the partition, Zero out all data after
514	 * the DHCP_END tag, and adjust the checksum.
515	 */
516	(void) memset(partn_data + sector_offset, 0x0, sector_length);
517	(void) memcpy(partn_data + sector_offset, data, used_bytes);
518
519	checksum = efx_bootcfg_csum(enp, data, used_bytes);
520	partn_data[sector_offset] -= checksum;
521
522	if ((rc = efx_nvram_erase(enp, EFX_NVRAM_BOOTROM_CFG)) != 0)
523		goto fail10;
524
525	if ((rc = efx_nvram_write_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
526		    0, (caddr_t)partn_data, partn_length)) != 0)
527		goto fail11;
528
529	if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG)) != 0)
530		goto fail12;
531
532	EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data);
533
534	return (0);
535
536fail12:
537	EFSYS_PROBE(fail12);
538fail11:
539	EFSYS_PROBE(fail11);
540fail10:
541	EFSYS_PROBE(fail10);
542fail9:
543	EFSYS_PROBE(fail9);
544
545	(void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG);
546fail8:
547	EFSYS_PROBE(fail8);
548
549	EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data);
550fail7:
551	EFSYS_PROBE(fail7);
552fail6:
553	EFSYS_PROBE(fail6);
554fail5:
555	EFSYS_PROBE(fail5);
556fail4:
557	EFSYS_PROBE(fail4);
558fail3:
559	EFSYS_PROBE(fail3);
560fail2:
561	EFSYS_PROBE(fail2);
562fail1:
563	EFSYS_PROBE1(fail1, efx_rc_t, rc);
564
565	return (rc);
566}
567
568#endif	/* EFSYS_OPT_BOOTCFG */
569