xref: /freebsd/sys/dev/sfxge/common/efx_bootcfg.c (revision 792bbaba989533a1fc93823df1720c8c4aaf0442)
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$");
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
55 efx_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
113 fail2:
114 	EFSYS_PROBE(fail2);
115 #endif
116 fail1:
117 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
118 	return (rc);
119 }
120 
121 
122 static	__checkReturn		uint8_t
123 efx_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 
138 static	__checkReturn		efx_rc_t
139 efx_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 
194 fail3:
195 	EFSYS_PROBE(fail3);
196 fail2:
197 	EFSYS_PROBE(fail2);
198 fail1:
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
209 efx_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 
283 fail3:
284 	EFSYS_PROBE(fail3);
285 fail2:
286 	EFSYS_PROBE(fail2);
287 fail1:
288 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
289 
290 	return (rc);
291 }
292 
293 				efx_rc_t
294 efx_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 
411 fail8:
412 	EFSYS_PROBE(fail8);
413 fail7:
414 	EFSYS_PROBE(fail7);
415 fail6:
416 	EFSYS_PROBE(fail6);
417 fail5:
418 	EFSYS_PROBE(fail5);
419 	if (sector_length > size)
420 		EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
421 fail4:
422 	EFSYS_PROBE(fail4);
423 fail3:
424 	EFSYS_PROBE(fail3);
425 fail2:
426 	EFSYS_PROBE(fail2);
427 fail1:
428 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
429 
430 	return (rc);
431 }
432 
433 				efx_rc_t
434 efx_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 
534 fail12:
535 	EFSYS_PROBE(fail12);
536 fail11:
537 	EFSYS_PROBE(fail11);
538 fail10:
539 	EFSYS_PROBE(fail10);
540 fail9:
541 	EFSYS_PROBE(fail9);
542 
543 	(void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG);
544 fail8:
545 	EFSYS_PROBE(fail8);
546 
547 	EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data);
548 fail7:
549 	EFSYS_PROBE(fail7);
550 fail6:
551 	EFSYS_PROBE(fail6);
552 fail5:
553 	EFSYS_PROBE(fail5);
554 fail4:
555 	EFSYS_PROBE(fail4);
556 fail3:
557 	EFSYS_PROBE(fail3);
558 fail2:
559 	EFSYS_PROBE(fail2);
560 fail1:
561 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
562 
563 	return (rc);
564 }
565 
566 #endif	/* EFSYS_OPT_BOOTCFG */
567