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