xref: /freebsd/sys/dev/sfxge/common/efx_bootcfg.c (revision d34048812292b714a0bf99967270d18fe3097c62)
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
57 efx_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 #if EFSYS_OPT_MEDFORD2
101 	case EFX_FAMILY_MEDFORD2: {
102 		/* Shared partition (array indexed by PF) */
103 		max_size = BOOTCFG_PER_PF;
104 		count = BOOTCFG_PF_COUNT;
105 		if (pf >= count) {
106 			rc = EINVAL;
107 			goto fail3;
108 		}
109 		offset = max_size * pf;
110 		break;
111 	}
112 #endif /* EFSYS_OPT_MEDFORD2 */
113 
114 	default:
115 		EFSYS_ASSERT(0);
116 		rc = ENOTSUP;
117 		goto fail1;
118 	}
119 	EFSYS_ASSERT3U(max_size, <=, BOOTCFG_MAX_SIZE);
120 
121 	if (sector_countp != NULL)
122 		*sector_countp = count;
123 	*offsetp = offset;
124 	*max_sizep = max_size;
125 
126 	return (0);
127 
128 #if EFSYS_OPT_MEDFORD2
129 fail3:
130 	EFSYS_PROBE(fail3);
131 #endif
132 #if EFSYS_OPT_MEDFORD
133 fail2:
134 	EFSYS_PROBE(fail2);
135 #endif
136 fail1:
137 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
138 	return (rc);
139 }
140 
141 
142 static	__checkReturn		uint8_t
143 efx_bootcfg_csum(
144 	__in			efx_nic_t *enp,
145 	__in_bcount(size)	uint8_t const *data,
146 	__in			size_t size)
147 {
148 	_NOTE(ARGUNUSED(enp))
149 
150 	unsigned int pos;
151 	uint8_t checksum = 0;
152 
153 	for (pos = 0; pos < size; pos++)
154 		checksum += data[pos];
155 	return (checksum);
156 }
157 
158 static	__checkReturn		efx_rc_t
159 efx_bootcfg_verify(
160 	__in			efx_nic_t *enp,
161 	__in_bcount(size)	uint8_t const *data,
162 	__in			size_t size,
163 	__out_opt		size_t *usedp)
164 {
165 	size_t offset = 0;
166 	size_t used = 0;
167 	efx_rc_t rc;
168 
169 	/* Start parsing tags immediately after the checksum */
170 	for (offset = 1; offset < size; ) {
171 		uint8_t tag;
172 		uint8_t length;
173 
174 		/* Consume tag */
175 		tag = data[offset];
176 		if (tag == DHCP_END) {
177 			offset++;
178 			used = offset;
179 			break;
180 		}
181 		if (tag == DHCP_PAD) {
182 			offset++;
183 			continue;
184 		}
185 
186 		/* Consume length */
187 		if (offset + 1 >= size) {
188 			rc = ENOSPC;
189 			goto fail1;
190 		}
191 		length = data[offset + 1];
192 
193 		/* Consume *length */
194 		if (offset + 1 + length >= size) {
195 			rc = ENOSPC;
196 			goto fail2;
197 		}
198 
199 		offset += 2 + length;
200 		used = offset;
201 	}
202 
203 	/* Checksum the entire sector, including bytes after any DHCP_END */
204 	if (efx_bootcfg_csum(enp, data, size) != 0) {
205 		rc = EINVAL;
206 		goto fail3;
207 	}
208 
209 	if (usedp != NULL)
210 		*usedp = used;
211 
212 	return (0);
213 
214 fail3:
215 	EFSYS_PROBE(fail3);
216 fail2:
217 	EFSYS_PROBE(fail2);
218 fail1:
219 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
220 
221 	return (rc);
222 }
223 
224 /*
225  * Copy bootcfg sector data to a target buffer which may differ in size.
226  * Optionally corrects format errors in source buffer.
227  */
228 				efx_rc_t
229 efx_bootcfg_copy_sector(
230 	__in			efx_nic_t *enp,
231 	__inout_bcount(sector_length)
232 				uint8_t *sector,
233 	__in			size_t sector_length,
234 	__out_bcount(data_size)	uint8_t *data,
235 	__in			size_t data_size,
236 	__in			boolean_t handle_format_errors)
237 {
238 	size_t used_bytes;
239 	efx_rc_t rc;
240 
241 	/* Minimum buffer is checksum byte and DHCP_END terminator */
242 	if (data_size < 2) {
243 		rc = ENOSPC;
244 		goto fail1;
245 	}
246 
247 	/* Verify that the area is correctly formatted and checksummed */
248 	rc = efx_bootcfg_verify(enp, sector, sector_length,
249 				    &used_bytes);
250 
251 	if (!handle_format_errors) {
252 		if (rc != 0)
253 			goto fail2;
254 
255 		if ((used_bytes < 2) ||
256 		    (sector[used_bytes - 1] != DHCP_END)) {
257 			/* Block too short, or DHCP_END missing */
258 			rc = ENOENT;
259 			goto fail3;
260 		}
261 	}
262 
263 	/* Synthesize empty format on verification failure */
264 	if (rc != 0 || used_bytes == 0) {
265 		sector[0] = 0;
266 		sector[1] = DHCP_END;
267 		used_bytes = 2;
268 	}
269 	EFSYS_ASSERT(used_bytes >= 2);	/* checksum and DHCP_END */
270 	EFSYS_ASSERT(used_bytes <= sector_length);
271 	EFSYS_ASSERT(sector_length >= 2);
272 
273 	/*
274 	 * Legacy bootcfg sectors don't terminate with a DHCP_END character.
275 	 * Modify the returned payload so it does.
276 	 * Reinitialise the sector if there isn't room for the character.
277 	 */
278 	if (sector[used_bytes - 1] != DHCP_END) {
279 		if (used_bytes >= sector_length) {
280 			sector[0] = 0;
281 			used_bytes = 1;
282 		}
283 		sector[used_bytes] = DHCP_END;
284 		++used_bytes;
285 	}
286 
287 	/*
288 	 * Verify that the target buffer is large enough for the
289 	 * entire used bootcfg area, then copy into the target buffer.
290 	 */
291 	if (used_bytes > data_size) {
292 		rc = ENOSPC;
293 		goto fail4;
294 	}
295 
296 	data[0] = 0; /* checksum, updated below */
297 
298 	/* Copy all after the checksum to the target buffer */
299 	memcpy(data + 1, sector + 1, used_bytes - 1);
300 
301 	/* Zero out the unused portion of the target buffer */
302 	if (used_bytes < data_size)
303 		(void) memset(data + used_bytes, 0, data_size - used_bytes);
304 
305 	/*
306 	 * The checksum includes trailing data after any DHCP_END character,
307 	 * which we've just modified (by truncation or appending DHCP_END).
308 	 */
309 	data[0] -= efx_bootcfg_csum(enp, data, data_size);
310 
311 	return (0);
312 
313 fail4:
314 	EFSYS_PROBE(fail4);
315 fail3:
316 	EFSYS_PROBE(fail3);
317 fail2:
318 	EFSYS_PROBE(fail2);
319 fail1:
320 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
321 
322 	return (rc);
323 }
324 
325 				efx_rc_t
326 efx_bootcfg_read(
327 	__in			efx_nic_t *enp,
328 	__out_bcount(size)	uint8_t *data,
329 	__in			size_t size)
330 {
331 	uint8_t *payload = NULL;
332 	size_t used_bytes;
333 	size_t partn_length;
334 	size_t sector_length;
335 	size_t sector_offset;
336 	efx_rc_t rc;
337 	uint32_t sector_number;
338 
339 	/* Minimum buffer is checksum byte and DHCP_END terminator */
340 	if (size < 2) {
341 		rc = ENOSPC;
342 		goto fail1;
343 	}
344 
345 #if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD || EFSYS_OPT_MEDFORD2
346 	sector_number = enp->en_nic_cfg.enc_pf;
347 #else
348 	sector_number = 0;
349 #endif
350 	rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length);
351 	if (rc != 0)
352 		goto fail2;
353 
354 	/* The bootcfg sector may be stored in a (larger) shared partition */
355 	rc = efx_bootcfg_sector_info(enp, sector_number,
356 	    NULL, &sector_offset, &sector_length);
357 	if (rc != 0)
358 		goto fail3;
359 
360 	if (sector_length < 2) {
361 		rc = EINVAL;
362 		goto fail4;
363 	}
364 
365 	if (sector_length > BOOTCFG_MAX_SIZE)
366 		sector_length = BOOTCFG_MAX_SIZE;
367 
368 	if (sector_offset + sector_length > partn_length) {
369 		/* Partition is too small */
370 		rc = EFBIG;
371 		goto fail5;
372 	}
373 
374 	/*
375 	 * We need to read the entire BOOTCFG sector to ensure we read all the
376 	 * tags, because legacy bootcfg sectors are not guaranteed to end with
377 	 * a DHCP_END character. If the user hasn't supplied a sufficiently
378 	 * large buffer then use our own buffer.
379 	 */
380 	if (sector_length > size) {
381 		EFSYS_KMEM_ALLOC(enp->en_esip, sector_length, payload);
382 		if (payload == NULL) {
383 			rc = ENOMEM;
384 			goto fail6;
385 		}
386 	} else
387 		payload = (uint8_t *)data;
388 
389 	if ((rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
390 		goto fail7;
391 
392 	if ((rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
393 	    sector_offset, (caddr_t)payload, sector_length)) != 0) {
394 		(void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
395 		goto fail8;
396 	}
397 
398 	if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
399 		goto fail9;
400 
401 	/* Verify that the area is correctly formatted and checksummed */
402 	rc = efx_bootcfg_verify(enp, payload, sector_length,
403 	    &used_bytes);
404 	if (rc != 0 || used_bytes == 0) {
405 		payload[0] = 0;
406 		payload[1] = DHCP_END;
407 		used_bytes = 2;
408 	}
409 
410 	EFSYS_ASSERT(used_bytes >= 2);	/* checksum and DHCP_END */
411 	EFSYS_ASSERT(used_bytes <= sector_length);
412 
413 	/*
414 	 * Legacy bootcfg sectors don't terminate with a DHCP_END character.
415 	 * Modify the returned payload so it does. BOOTCFG_MAX_SIZE is by
416 	 * definition large enough for any valid (per-port) bootcfg sector,
417 	 * so reinitialise the sector if there isn't room for the character.
418 	 */
419 	if (payload[used_bytes - 1] != DHCP_END) {
420 		if (used_bytes >= sector_length)
421 			used_bytes = 1;
422 
423 		payload[used_bytes] = DHCP_END;
424 		++used_bytes;
425 	}
426 
427 	/*
428 	 * Verify that the user supplied buffer is large enough for the
429 	 * entire used bootcfg area, then copy into the user supplied buffer.
430 	 */
431 	if (used_bytes > size) {
432 		rc = ENOSPC;
433 		goto fail10;
434 	}
435 
436 	data[0] = 0; /* checksum, updated below */
437 
438 	if (sector_length > size) {
439 		/* Copy all after the checksum to the target buffer */
440 		memcpy(data + 1, payload + 1, used_bytes - 1);
441 		EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
442 	}
443 
444 	/* Zero out the unused portion of the user buffer */
445 	if (used_bytes < size)
446 		(void) memset(data + used_bytes, 0, size - used_bytes);
447 
448 	/*
449 	 * The checksum includes trailing data after any DHCP_END character,
450 	 * which we've just modified (by truncation or appending DHCP_END).
451 	 */
452 	data[0] -= efx_bootcfg_csum(enp, data, size);
453 
454 	return (0);
455 
456 fail10:
457 	EFSYS_PROBE(fail10);
458 fail9:
459 	EFSYS_PROBE(fail9);
460 fail8:
461 	EFSYS_PROBE(fail8);
462 fail7:
463 	EFSYS_PROBE(fail7);
464 	if (sector_length > size)
465 		EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
466 fail6:
467 	EFSYS_PROBE(fail6);
468 fail5:
469 	EFSYS_PROBE(fail5);
470 fail4:
471 	EFSYS_PROBE(fail4);
472 fail3:
473 	EFSYS_PROBE(fail3);
474 fail2:
475 	EFSYS_PROBE(fail2);
476 fail1:
477 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
478 
479 	return (rc);
480 }
481 
482 				efx_rc_t
483 efx_bootcfg_write(
484 	__in			efx_nic_t *enp,
485 	__in_bcount(size)	uint8_t *data,
486 	__in			size_t size)
487 {
488 	uint8_t *partn_data;
489 	uint8_t checksum;
490 	size_t partn_length;
491 	size_t sector_length;
492 	size_t sector_offset;
493 	size_t used_bytes;
494 	efx_rc_t rc;
495 	uint32_t sector_number;
496 
497 #if EFSYS_OPT_HUNTINGTON || EFSYS_OPT_MEDFORD || EFSYS_OPT_MEDFORD2
498 	sector_number = enp->en_nic_cfg.enc_pf;
499 #else
500 	sector_number = 0;
501 #endif
502 
503 	rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length);
504 	if (rc != 0)
505 		goto fail1;
506 
507 	/* The bootcfg sector may be stored in a (larger) shared partition */
508 	rc = efx_bootcfg_sector_info(enp, sector_number,
509 	    NULL, &sector_offset, &sector_length);
510 	if (rc != 0)
511 		goto fail2;
512 
513 	if (sector_length > BOOTCFG_MAX_SIZE)
514 		sector_length = BOOTCFG_MAX_SIZE;
515 
516 	if (sector_offset + sector_length > partn_length) {
517 		/* Partition is too small */
518 		rc = EFBIG;
519 		goto fail3;
520 	}
521 
522 	if ((rc = efx_bootcfg_verify(enp, data, size, &used_bytes)) != 0)
523 		goto fail4;
524 
525 	/* The caller *must* terminate their block with a DHCP_END character */
526 	if ((used_bytes < 2) || ((uint8_t)data[used_bytes - 1] != DHCP_END)) {
527 		/* Block too short or DHCP_END missing */
528 		rc = ENOENT;
529 		goto fail5;
530 	}
531 
532 	/* Check that the hardware has support for this much data */
533 	if (used_bytes > MIN(sector_length, BOOTCFG_MAX_SIZE)) {
534 		rc = ENOSPC;
535 		goto fail6;
536 	}
537 
538 	/*
539 	 * If the BOOTCFG sector is stored in a shared partition, then we must
540 	 * read the whole partition and insert the updated bootcfg sector at the
541 	 * correct offset.
542 	 */
543 	EFSYS_KMEM_ALLOC(enp->en_esip, partn_length, partn_data);
544 	if (partn_data == NULL) {
545 		rc = ENOMEM;
546 		goto fail7;
547 	}
548 
549 	rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
550 	if (rc != 0)
551 		goto fail8;
552 
553 	/* Read the entire partition */
554 	rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG, 0,
555 				    (caddr_t)partn_data, partn_length);
556 	if (rc != 0)
557 		goto fail9;
558 
559 	/*
560 	 * Insert the BOOTCFG sector into the partition, Zero out all data after
561 	 * the DHCP_END tag, and adjust the checksum.
562 	 */
563 	(void) memset(partn_data + sector_offset, 0x0, sector_length);
564 	(void) memcpy(partn_data + sector_offset, data, used_bytes);
565 
566 	checksum = efx_bootcfg_csum(enp, data, used_bytes);
567 	partn_data[sector_offset] -= checksum;
568 
569 	if ((rc = efx_nvram_erase(enp, EFX_NVRAM_BOOTROM_CFG)) != 0)
570 		goto fail10;
571 
572 	if ((rc = efx_nvram_write_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
573 		    0, (caddr_t)partn_data, partn_length)) != 0)
574 		goto fail11;
575 
576 	if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
577 		goto fail12;
578 
579 	EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data);
580 
581 	return (0);
582 
583 fail12:
584 	EFSYS_PROBE(fail12);
585 fail11:
586 	EFSYS_PROBE(fail11);
587 fail10:
588 	EFSYS_PROBE(fail10);
589 fail9:
590 	EFSYS_PROBE(fail9);
591 
592 	(void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL);
593 fail8:
594 	EFSYS_PROBE(fail8);
595 
596 	EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data);
597 fail7:
598 	EFSYS_PROBE(fail7);
599 fail6:
600 	EFSYS_PROBE(fail6);
601 fail5:
602 	EFSYS_PROBE(fail5);
603 fail4:
604 	EFSYS_PROBE(fail4);
605 fail3:
606 	EFSYS_PROBE(fail3);
607 fail2:
608 	EFSYS_PROBE(fail2);
609 fail1:
610 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
611 
612 	return (rc);
613 }
614 
615 #endif	/* EFSYS_OPT_BOOTCFG */
616