]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | /* SPDX-License-Identifier: BSD-3-Clause |
2 | * | |
3 | * Copyright (c) 2009-2018 Solarflare Communications Inc. | |
4 | * All rights reserved. | |
5 | */ | |
6 | ||
7 | #include "efx.h" | |
8 | #include "efx_impl.h" | |
9 | ||
10 | #if EFSYS_OPT_BOOTCFG | |
11 | ||
12 | /* | |
13 | * Maximum size of BOOTCFG block across all nics as understood by SFCgPXE. | |
14 | * NOTE: This is larger than the Medford per-PF bootcfg sector. | |
15 | */ | |
16 | #define BOOTCFG_MAX_SIZE 0x1000 | |
17 | ||
18 | /* Medford per-PF bootcfg sector */ | |
19 | #define BOOTCFG_PER_PF 0x800 | |
20 | #define BOOTCFG_PF_COUNT 16 | |
21 | ||
9f95a23c TL |
22 | #define DHCP_OPT_HAS_VALUE(opt) \ |
23 | (((opt) > EFX_DHCP_PAD) && ((opt) < EFX_DHCP_END)) | |
24 | ||
25 | #define DHCP_MAX_VALUE 255 | |
26 | ||
27 | #define DHCP_ENCAPSULATOR(encap_opt) ((encap_opt) >> 8) | |
28 | #define DHCP_ENCAPSULATED(encap_opt) ((encap_opt) & 0xff) | |
29 | #define DHCP_IS_ENCAP_OPT(opt) DHCP_OPT_HAS_VALUE(DHCP_ENCAPSULATOR(opt)) | |
30 | ||
31 | typedef struct efx_dhcp_tag_hdr_s { | |
32 | uint8_t tag; | |
33 | uint8_t length; | |
34 | } efx_dhcp_tag_hdr_t; | |
35 | ||
36 | /* | |
37 | * Length calculations for tags with value field. PAD and END | |
38 | * have a fixed length of 1, with no length or value field. | |
39 | */ | |
40 | #define DHCP_FULL_TAG_LENGTH(hdr) \ | |
41 | (sizeof (efx_dhcp_tag_hdr_t) + (hdr)->length) | |
42 | ||
43 | #define DHCP_NEXT_TAG(hdr) \ | |
44 | ((efx_dhcp_tag_hdr_t *)(((uint8_t *)(hdr)) + \ | |
45 | DHCP_FULL_TAG_LENGTH((hdr)))) | |
46 | ||
47 | #define DHCP_CALC_TAG_LENGTH(payload_len) \ | |
48 | ((payload_len) + sizeof (efx_dhcp_tag_hdr_t)) | |
11fdf7f2 TL |
49 | |
50 | ||
51 | /* Report the layout of bootcfg sectors in NVRAM partition. */ | |
52 | __checkReturn efx_rc_t | |
53 | efx_bootcfg_sector_info( | |
54 | __in efx_nic_t *enp, | |
55 | __in uint32_t pf, | |
56 | __out_opt uint32_t *sector_countp, | |
57 | __out size_t *offsetp, | |
58 | __out size_t *max_sizep) | |
59 | { | |
60 | uint32_t count; | |
61 | size_t max_size; | |
62 | size_t offset; | |
63 | int rc; | |
64 | ||
65 | switch (enp->en_family) { | |
66 | #if EFSYS_OPT_SIENA | |
67 | case EFX_FAMILY_SIENA: | |
68 | max_size = BOOTCFG_MAX_SIZE; | |
69 | offset = 0; | |
70 | count = 1; | |
71 | break; | |
72 | #endif /* EFSYS_OPT_SIENA */ | |
73 | ||
74 | #if EFSYS_OPT_HUNTINGTON | |
75 | case EFX_FAMILY_HUNTINGTON: | |
76 | max_size = BOOTCFG_MAX_SIZE; | |
77 | offset = 0; | |
78 | count = 1; | |
79 | break; | |
80 | #endif /* EFSYS_OPT_HUNTINGTON */ | |
81 | ||
82 | #if EFSYS_OPT_MEDFORD | |
83 | case EFX_FAMILY_MEDFORD: { | |
84 | /* Shared partition (array indexed by PF) */ | |
85 | max_size = BOOTCFG_PER_PF; | |
86 | count = BOOTCFG_PF_COUNT; | |
87 | if (pf >= count) { | |
88 | rc = EINVAL; | |
89 | goto fail2; | |
90 | } | |
91 | offset = max_size * pf; | |
92 | break; | |
93 | } | |
94 | #endif /* EFSYS_OPT_MEDFORD */ | |
95 | ||
96 | #if EFSYS_OPT_MEDFORD2 | |
97 | case EFX_FAMILY_MEDFORD2: { | |
98 | /* Shared partition (array indexed by PF) */ | |
99 | max_size = BOOTCFG_PER_PF; | |
100 | count = BOOTCFG_PF_COUNT; | |
101 | if (pf >= count) { | |
102 | rc = EINVAL; | |
103 | goto fail3; | |
104 | } | |
105 | offset = max_size * pf; | |
106 | break; | |
107 | } | |
108 | #endif /* EFSYS_OPT_MEDFORD2 */ | |
109 | ||
110 | default: | |
111 | EFSYS_ASSERT(0); | |
112 | rc = ENOTSUP; | |
113 | goto fail1; | |
114 | } | |
115 | EFSYS_ASSERT3U(max_size, <=, BOOTCFG_MAX_SIZE); | |
116 | ||
117 | if (sector_countp != NULL) | |
118 | *sector_countp = count; | |
119 | *offsetp = offset; | |
120 | *max_sizep = max_size; | |
121 | ||
122 | return (0); | |
123 | ||
124 | #if EFSYS_OPT_MEDFORD2 | |
125 | fail3: | |
126 | EFSYS_PROBE(fail3); | |
127 | #endif | |
128 | #if EFSYS_OPT_MEDFORD | |
129 | fail2: | |
130 | EFSYS_PROBE(fail2); | |
131 | #endif | |
132 | fail1: | |
133 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
134 | return (rc); | |
135 | } | |
136 | ||
137 | ||
9f95a23c TL |
138 | __checkReturn uint8_t |
139 | efx_dhcp_csum( | |
11fdf7f2 TL |
140 | __in_bcount(size) uint8_t const *data, |
141 | __in size_t size) | |
142 | { | |
11fdf7f2 TL |
143 | unsigned int pos; |
144 | uint8_t checksum = 0; | |
145 | ||
146 | for (pos = 0; pos < size; pos++) | |
147 | checksum += data[pos]; | |
148 | return (checksum); | |
149 | } | |
150 | ||
9f95a23c TL |
151 | __checkReturn efx_rc_t |
152 | efx_dhcp_verify( | |
11fdf7f2 TL |
153 | __in_bcount(size) uint8_t const *data, |
154 | __in size_t size, | |
155 | __out_opt size_t *usedp) | |
156 | { | |
157 | size_t offset = 0; | |
158 | size_t used = 0; | |
159 | efx_rc_t rc; | |
160 | ||
161 | /* Start parsing tags immediately after the checksum */ | |
162 | for (offset = 1; offset < size; ) { | |
163 | uint8_t tag; | |
164 | uint8_t length; | |
165 | ||
166 | /* Consume tag */ | |
167 | tag = data[offset]; | |
9f95a23c | 168 | if (tag == EFX_DHCP_END) { |
11fdf7f2 TL |
169 | offset++; |
170 | used = offset; | |
171 | break; | |
172 | } | |
9f95a23c | 173 | if (tag == EFX_DHCP_PAD) { |
11fdf7f2 TL |
174 | offset++; |
175 | continue; | |
176 | } | |
177 | ||
178 | /* Consume length */ | |
179 | if (offset + 1 >= size) { | |
180 | rc = ENOSPC; | |
181 | goto fail1; | |
182 | } | |
183 | length = data[offset + 1]; | |
184 | ||
185 | /* Consume *length */ | |
186 | if (offset + 1 + length >= size) { | |
187 | rc = ENOSPC; | |
188 | goto fail2; | |
189 | } | |
190 | ||
191 | offset += 2 + length; | |
192 | used = offset; | |
193 | } | |
194 | ||
9f95a23c TL |
195 | /* Checksum the entire sector, including bytes after any EFX_DHCP_END */ |
196 | if (efx_dhcp_csum(data, size) != 0) { | |
11fdf7f2 TL |
197 | rc = EINVAL; |
198 | goto fail3; | |
199 | } | |
200 | ||
201 | if (usedp != NULL) | |
202 | *usedp = used; | |
203 | ||
204 | return (0); | |
205 | ||
206 | fail3: | |
207 | EFSYS_PROBE(fail3); | |
208 | fail2: | |
209 | EFSYS_PROBE(fail2); | |
210 | fail1: | |
211 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
212 | ||
213 | return (rc); | |
214 | } | |
215 | ||
9f95a23c TL |
216 | /* |
217 | * Walk the entire tag set looking for option. The sought option may be | |
218 | * encapsulated. ENOENT indicates the walk completed without finding the | |
219 | * option. If we run out of buffer during the walk the function will return | |
220 | * ENOSPC. | |
221 | */ | |
222 | static efx_rc_t | |
223 | efx_dhcp_walk_tags( | |
224 | __deref_inout uint8_t **tagpp, | |
225 | __inout size_t *buffer_sizep, | |
226 | __in uint16_t opt) | |
227 | { | |
228 | efx_rc_t rc = 0; | |
229 | boolean_t is_encap = B_FALSE; | |
230 | ||
231 | if (DHCP_IS_ENCAP_OPT(opt)) { | |
232 | /* | |
233 | * Look for the encapsulator and, if found, limit ourselves | |
234 | * to its payload. If it's not found then the entire tag | |
235 | * cannot be found, so the encapsulated opt search is | |
236 | * skipped. | |
237 | */ | |
238 | rc = efx_dhcp_walk_tags(tagpp, buffer_sizep, | |
239 | DHCP_ENCAPSULATOR(opt)); | |
240 | if (rc == 0) { | |
241 | *buffer_sizep = ((efx_dhcp_tag_hdr_t *)*tagpp)->length; | |
242 | (*tagpp) += sizeof (efx_dhcp_tag_hdr_t); | |
243 | } | |
244 | opt = DHCP_ENCAPSULATED(opt); | |
245 | is_encap = B_TRUE; | |
246 | } | |
247 | ||
248 | EFSYS_ASSERT(!DHCP_IS_ENCAP_OPT(opt)); | |
249 | ||
250 | while (rc == 0) { | |
251 | size_t size; | |
252 | ||
253 | if (*buffer_sizep == 0) { | |
254 | rc = ENOSPC; | |
255 | goto fail1; | |
256 | } | |
257 | ||
258 | if (DHCP_ENCAPSULATED(**tagpp) == opt) | |
259 | break; | |
260 | ||
261 | if ((**tagpp) == EFX_DHCP_END) { | |
262 | rc = ENOENT; | |
263 | break; | |
264 | } else if ((**tagpp) == EFX_DHCP_PAD) { | |
265 | size = 1; | |
266 | } else { | |
267 | if (*buffer_sizep < sizeof (efx_dhcp_tag_hdr_t)) { | |
268 | rc = ENOSPC; | |
269 | goto fail2; | |
270 | } | |
271 | ||
272 | size = | |
273 | DHCP_FULL_TAG_LENGTH((efx_dhcp_tag_hdr_t *)*tagpp); | |
274 | } | |
275 | ||
276 | if (size > *buffer_sizep) { | |
277 | rc = ENOSPC; | |
278 | goto fail3; | |
279 | } | |
280 | ||
281 | (*tagpp) += size; | |
282 | (*buffer_sizep) -= size; | |
283 | ||
284 | if ((*buffer_sizep == 0) && is_encap) { | |
285 | /* Search within encapulator tag finished */ | |
286 | rc = ENOENT; | |
287 | break; | |
288 | } | |
289 | } | |
290 | ||
291 | /* | |
292 | * Returns 0 if found otherwise ENOENT indicating search finished | |
293 | * correctly | |
294 | */ | |
295 | return (rc); | |
296 | ||
297 | fail3: | |
298 | EFSYS_PROBE(fail3); | |
299 | fail2: | |
300 | EFSYS_PROBE(fail2); | |
301 | fail1: | |
302 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
303 | ||
304 | return (rc); | |
305 | } | |
306 | ||
307 | /* | |
308 | * Locate value buffer for option in the given buffer. | |
309 | * Returns 0 if found, ENOENT indicating search finished | |
310 | * correctly, otherwise search failed before completion. | |
311 | */ | |
312 | __checkReturn efx_rc_t | |
313 | efx_dhcp_find_tag( | |
314 | __in_bcount(buffer_length) uint8_t *bufferp, | |
315 | __in size_t buffer_length, | |
316 | __in uint16_t opt, | |
317 | __deref_out uint8_t **valuepp, | |
318 | __out size_t *value_lengthp) | |
319 | { | |
320 | efx_rc_t rc; | |
321 | uint8_t *tagp = bufferp; | |
322 | size_t len = buffer_length; | |
323 | ||
324 | rc = efx_dhcp_walk_tags(&tagp, &len, opt); | |
325 | if (rc == 0) { | |
326 | efx_dhcp_tag_hdr_t *hdrp; | |
327 | ||
328 | hdrp = (efx_dhcp_tag_hdr_t *)tagp; | |
329 | *valuepp = (uint8_t *)(&hdrp[1]); | |
330 | *value_lengthp = hdrp->length; | |
331 | } else if (rc != ENOENT) { | |
332 | goto fail1; | |
333 | } | |
334 | ||
335 | return (rc); | |
336 | ||
337 | fail1: | |
338 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
339 | ||
340 | return (rc); | |
341 | } | |
342 | ||
343 | /* | |
344 | * Locate the end tag in the given buffer. | |
345 | * Returns 0 if found, ENOENT indicating search finished | |
346 | * correctly but end tag was not found; otherwise search | |
347 | * failed before completion. | |
348 | */ | |
349 | __checkReturn efx_rc_t | |
350 | efx_dhcp_find_end( | |
351 | __in_bcount(buffer_length) uint8_t *bufferp, | |
352 | __in size_t buffer_length, | |
353 | __deref_out uint8_t **endpp) | |
354 | { | |
355 | efx_rc_t rc; | |
356 | uint8_t *endp = bufferp; | |
357 | size_t len = buffer_length; | |
358 | ||
359 | rc = efx_dhcp_walk_tags(&endp, &len, EFX_DHCP_END); | |
360 | if (rc == 0) | |
361 | *endpp = endp; | |
362 | else if (rc != ENOENT) | |
363 | goto fail1; | |
364 | ||
365 | return (rc); | |
366 | ||
367 | fail1: | |
368 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
369 | ||
370 | return (rc); | |
371 | } | |
372 | ||
373 | ||
374 | /* | |
375 | * Delete the given tag from anywhere in the buffer. Copes with | |
376 | * encapsulated tags, and updates or deletes the encapsulating opt as | |
377 | * necessary. | |
378 | */ | |
379 | __checkReturn efx_rc_t | |
380 | efx_dhcp_delete_tag( | |
381 | __inout_bcount(buffer_length) uint8_t *bufferp, | |
382 | __in size_t buffer_length, | |
383 | __in uint16_t opt) | |
384 | { | |
385 | efx_rc_t rc; | |
386 | efx_dhcp_tag_hdr_t *hdrp; | |
387 | size_t len; | |
388 | uint8_t *startp; | |
389 | uint8_t *endp; | |
390 | ||
391 | len = buffer_length; | |
392 | startp = bufferp; | |
393 | ||
394 | if (!DHCP_OPT_HAS_VALUE(DHCP_ENCAPSULATED(opt))) { | |
395 | rc = EINVAL; | |
396 | goto fail1; | |
397 | } | |
398 | ||
399 | rc = efx_dhcp_walk_tags(&startp, &len, opt); | |
400 | if (rc != 0) | |
401 | goto fail1; | |
402 | ||
403 | hdrp = (efx_dhcp_tag_hdr_t *)startp; | |
404 | ||
405 | if (DHCP_IS_ENCAP_OPT(opt)) { | |
406 | uint8_t tag_length = DHCP_FULL_TAG_LENGTH(hdrp); | |
407 | uint8_t *encapp = bufferp; | |
408 | efx_dhcp_tag_hdr_t *encap_hdrp; | |
409 | ||
410 | len = buffer_length; | |
411 | rc = efx_dhcp_walk_tags(&encapp, &len, | |
412 | DHCP_ENCAPSULATOR(opt)); | |
413 | if (rc != 0) | |
414 | goto fail2; | |
415 | ||
416 | encap_hdrp = (efx_dhcp_tag_hdr_t *)encapp; | |
417 | if (encap_hdrp->length > tag_length) { | |
418 | encap_hdrp->length = (uint8_t)( | |
419 | (size_t)encap_hdrp->length - tag_length); | |
420 | } else { | |
421 | /* delete the encapsulating tag */ | |
422 | hdrp = encap_hdrp; | |
423 | } | |
424 | } | |
425 | ||
426 | startp = (uint8_t *)hdrp; | |
427 | endp = (uint8_t *)DHCP_NEXT_TAG(hdrp); | |
428 | ||
429 | if (startp < bufferp) { | |
430 | rc = EINVAL; | |
431 | goto fail3; | |
432 | } | |
433 | ||
434 | if (endp > &bufferp[buffer_length]) { | |
435 | rc = EINVAL; | |
436 | goto fail4; | |
437 | } | |
438 | ||
439 | memmove(startp, endp, | |
440 | buffer_length - (endp - bufferp)); | |
441 | ||
442 | return (0); | |
443 | ||
444 | fail4: | |
445 | EFSYS_PROBE(fail4); | |
446 | fail3: | |
447 | EFSYS_PROBE(fail3); | |
448 | fail2: | |
449 | EFSYS_PROBE(fail2); | |
450 | fail1: | |
451 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
452 | ||
453 | return (rc); | |
454 | } | |
455 | ||
456 | /* | |
457 | * Write the tag header into write_pointp and optionally copies the payload | |
458 | * into the space following. | |
459 | */ | |
460 | static void | |
461 | efx_dhcp_write_tag( | |
462 | __in uint8_t *write_pointp, | |
463 | __in uint16_t opt, | |
464 | __in_bcount_opt(value_length) | |
465 | uint8_t *valuep, | |
466 | __in size_t value_length) | |
467 | { | |
468 | efx_dhcp_tag_hdr_t *hdrp = (efx_dhcp_tag_hdr_t *)write_pointp; | |
469 | hdrp->tag = DHCP_ENCAPSULATED(opt); | |
470 | hdrp->length = (uint8_t)value_length; | |
471 | if ((value_length > 0) && (valuep != NULL)) | |
472 | memcpy(&hdrp[1], valuep, value_length); | |
473 | } | |
474 | ||
475 | /* | |
476 | * Add the given tag to the end of the buffer. Copes with creating an | |
477 | * encapsulated tag, and updates or creates the encapsulating opt as | |
478 | * necessary. | |
479 | */ | |
480 | __checkReturn efx_rc_t | |
481 | efx_dhcp_add_tag( | |
482 | __inout_bcount(buffer_length) uint8_t *bufferp, | |
483 | __in size_t buffer_length, | |
484 | __in uint16_t opt, | |
485 | __in_bcount_opt(value_length) uint8_t *valuep, | |
486 | __in size_t value_length) | |
487 | { | |
488 | efx_rc_t rc; | |
489 | efx_dhcp_tag_hdr_t *encap_hdrp = NULL; | |
490 | uint8_t *insert_pointp = NULL; | |
491 | uint8_t *endp; | |
492 | size_t available_space; | |
493 | size_t added_length; | |
494 | size_t search_size; | |
495 | uint8_t *searchp; | |
496 | ||
497 | if (!DHCP_OPT_HAS_VALUE(DHCP_ENCAPSULATED(opt))) { | |
498 | rc = EINVAL; | |
499 | goto fail1; | |
500 | } | |
501 | ||
502 | if (value_length > DHCP_MAX_VALUE) { | |
503 | rc = EINVAL; | |
504 | goto fail2; | |
505 | } | |
506 | ||
507 | if ((value_length > 0) && (valuep == NULL)) { | |
508 | rc = EINVAL; | |
509 | goto fail3; | |
510 | } | |
511 | ||
512 | endp = bufferp; | |
513 | available_space = buffer_length; | |
514 | rc = efx_dhcp_walk_tags(&endp, &available_space, EFX_DHCP_END); | |
515 | if (rc != 0) | |
516 | goto fail4; | |
517 | ||
518 | searchp = bufferp; | |
519 | search_size = buffer_length; | |
520 | if (DHCP_IS_ENCAP_OPT(opt)) { | |
521 | rc = efx_dhcp_walk_tags(&searchp, &search_size, | |
522 | DHCP_ENCAPSULATOR(opt)); | |
523 | if (rc == 0) { | |
524 | encap_hdrp = (efx_dhcp_tag_hdr_t *)searchp; | |
525 | ||
526 | /* Check encapsulated tag is not present */ | |
527 | search_size = encap_hdrp->length; | |
528 | rc = efx_dhcp_walk_tags(&searchp, &search_size, | |
529 | opt); | |
530 | if (rc != ENOENT) { | |
531 | rc = EINVAL; | |
532 | goto fail5; | |
533 | } | |
534 | ||
535 | /* Check encapsulator will not overflow */ | |
536 | if (((size_t)encap_hdrp->length + | |
537 | DHCP_CALC_TAG_LENGTH(value_length)) > | |
538 | DHCP_MAX_VALUE) { | |
539 | rc = E2BIG; | |
540 | goto fail6; | |
541 | } | |
542 | ||
543 | /* Insert at start of existing encapsulator */ | |
544 | insert_pointp = (uint8_t *)&encap_hdrp[1]; | |
545 | opt = DHCP_ENCAPSULATED(opt); | |
546 | } else if (rc == ENOENT) { | |
547 | encap_hdrp = NULL; | |
548 | } else { | |
549 | goto fail7; | |
550 | } | |
551 | } else { | |
552 | /* Check unencapsulated tag is not present */ | |
553 | rc = efx_dhcp_walk_tags(&searchp, &search_size, | |
554 | opt); | |
555 | if (rc != ENOENT) { | |
556 | rc = EINVAL; | |
557 | goto fail8; | |
558 | } | |
559 | } | |
560 | ||
561 | if (insert_pointp == NULL) { | |
562 | /* Insert at end of existing tags */ | |
563 | insert_pointp = endp; | |
564 | } | |
565 | ||
566 | /* Includes the new encapsulator tag hdr if required */ | |
567 | added_length = DHCP_CALC_TAG_LENGTH(value_length) + | |
568 | (DHCP_IS_ENCAP_OPT(opt) ? sizeof (efx_dhcp_tag_hdr_t) : 0); | |
569 | ||
570 | if (available_space <= added_length) { | |
571 | rc = ENOMEM; | |
572 | goto fail9; | |
573 | } | |
574 | ||
575 | memmove(insert_pointp + added_length, insert_pointp, | |
576 | available_space - added_length); | |
577 | ||
578 | if (DHCP_IS_ENCAP_OPT(opt)) { | |
579 | /* Create new encapsulator header */ | |
580 | added_length -= sizeof (efx_dhcp_tag_hdr_t); | |
581 | efx_dhcp_write_tag(insert_pointp, | |
582 | DHCP_ENCAPSULATOR(opt), NULL, added_length); | |
583 | insert_pointp += sizeof (efx_dhcp_tag_hdr_t); | |
584 | } else if (encap_hdrp) | |
585 | /* Modify existing encapsulator header */ | |
586 | encap_hdrp->length += | |
587 | ((uint8_t)DHCP_CALC_TAG_LENGTH(value_length)); | |
588 | ||
589 | efx_dhcp_write_tag(insert_pointp, opt, valuep, value_length); | |
590 | ||
591 | return (0); | |
592 | ||
593 | fail9: | |
594 | EFSYS_PROBE(fail9); | |
595 | fail8: | |
596 | EFSYS_PROBE(fail8); | |
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 | /* | |
616 | * Update an existing tag to the new value. Copes with encapsulated | |
617 | * tags, and updates the encapsulating opt as necessary. | |
618 | */ | |
619 | __checkReturn efx_rc_t | |
620 | efx_dhcp_update_tag( | |
621 | __inout_bcount(buffer_length) uint8_t *bufferp, | |
622 | __in size_t buffer_length, | |
623 | __in uint16_t opt, | |
624 | __in uint8_t *value_locationp, | |
625 | __in_bcount_opt(value_length) uint8_t *valuep, | |
626 | __in size_t value_length) | |
627 | { | |
628 | efx_rc_t rc; | |
629 | uint8_t *write_pointp = value_locationp - sizeof (efx_dhcp_tag_hdr_t); | |
630 | efx_dhcp_tag_hdr_t *hdrp = (efx_dhcp_tag_hdr_t *)write_pointp; | |
631 | efx_dhcp_tag_hdr_t *encap_hdrp = NULL; | |
632 | size_t old_length; | |
633 | ||
634 | if (!DHCP_OPT_HAS_VALUE(DHCP_ENCAPSULATED(opt))) { | |
635 | rc = EINVAL; | |
636 | goto fail1; | |
637 | } | |
638 | ||
639 | if (value_length > DHCP_MAX_VALUE) { | |
640 | rc = EINVAL; | |
641 | goto fail2; | |
642 | } | |
643 | ||
644 | if ((value_length > 0) && (valuep == NULL)) { | |
645 | rc = EINVAL; | |
646 | goto fail3; | |
647 | } | |
648 | ||
649 | old_length = hdrp->length; | |
650 | ||
651 | if (old_length < value_length) { | |
652 | uint8_t *endp = bufferp; | |
653 | size_t available_space = buffer_length; | |
654 | ||
655 | rc = efx_dhcp_walk_tags(&endp, &available_space, | |
656 | EFX_DHCP_END); | |
657 | if (rc != 0) | |
658 | goto fail4; | |
659 | ||
660 | if (available_space < (value_length - old_length)) { | |
661 | rc = EINVAL; | |
662 | goto fail5; | |
663 | } | |
664 | } | |
665 | ||
666 | if (DHCP_IS_ENCAP_OPT(opt)) { | |
667 | uint8_t *encapp = bufferp; | |
668 | size_t following_encap = buffer_length; | |
669 | size_t new_length; | |
670 | ||
671 | rc = efx_dhcp_walk_tags(&encapp, &following_encap, | |
672 | DHCP_ENCAPSULATOR(opt)); | |
673 | if (rc != 0) | |
674 | goto fail6; | |
675 | ||
676 | encap_hdrp = (efx_dhcp_tag_hdr_t *)encapp; | |
677 | ||
678 | new_length = ((size_t)encap_hdrp->length + | |
679 | value_length - old_length); | |
680 | /* Check encapsulator will not overflow */ | |
681 | if (new_length > DHCP_MAX_VALUE) { | |
682 | rc = E2BIG; | |
683 | goto fail7; | |
684 | } | |
685 | ||
686 | encap_hdrp->length = (uint8_t)new_length; | |
687 | } | |
688 | ||
689 | /* | |
690 | * Move the following data up/down to accomodate the new payload | |
691 | * length. | |
692 | */ | |
693 | if (old_length != value_length) { | |
694 | uint8_t *destp = (uint8_t *)DHCP_NEXT_TAG(hdrp) + | |
695 | value_length - old_length; | |
696 | size_t count = &bufferp[buffer_length] - | |
697 | (uint8_t *)DHCP_NEXT_TAG(hdrp); | |
698 | ||
699 | memmove(destp, DHCP_NEXT_TAG(hdrp), count); | |
700 | } | |
701 | ||
702 | EFSYS_ASSERT(hdrp->tag == DHCP_ENCAPSULATED(opt)); | |
703 | efx_dhcp_write_tag(write_pointp, opt, valuep, value_length); | |
704 | ||
705 | return (0); | |
706 | ||
707 | fail7: | |
708 | EFSYS_PROBE(fail7); | |
709 | fail6: | |
710 | EFSYS_PROBE(fail6); | |
711 | fail5: | |
712 | EFSYS_PROBE(fail5); | |
713 | fail4: | |
714 | EFSYS_PROBE(fail4); | |
715 | fail3: | |
716 | EFSYS_PROBE(fail3); | |
717 | fail2: | |
718 | EFSYS_PROBE(fail2); | |
719 | fail1: | |
720 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
721 | ||
722 | return (rc); | |
723 | } | |
724 | ||
725 | ||
11fdf7f2 TL |
726 | /* |
727 | * Copy bootcfg sector data to a target buffer which may differ in size. | |
728 | * Optionally corrects format errors in source buffer. | |
729 | */ | |
730 | efx_rc_t | |
731 | efx_bootcfg_copy_sector( | |
732 | __in efx_nic_t *enp, | |
733 | __inout_bcount(sector_length) | |
734 | uint8_t *sector, | |
735 | __in size_t sector_length, | |
736 | __out_bcount(data_size) uint8_t *data, | |
737 | __in size_t data_size, | |
738 | __in boolean_t handle_format_errors) | |
739 | { | |
9f95a23c TL |
740 | _NOTE(ARGUNUSED(enp)) |
741 | ||
11fdf7f2 TL |
742 | size_t used_bytes; |
743 | efx_rc_t rc; | |
744 | ||
9f95a23c | 745 | /* Minimum buffer is checksum byte and EFX_DHCP_END terminator */ |
11fdf7f2 TL |
746 | if (data_size < 2) { |
747 | rc = ENOSPC; | |
748 | goto fail1; | |
749 | } | |
750 | ||
751 | /* Verify that the area is correctly formatted and checksummed */ | |
9f95a23c | 752 | rc = efx_dhcp_verify(sector, sector_length, |
11fdf7f2 TL |
753 | &used_bytes); |
754 | ||
755 | if (!handle_format_errors) { | |
756 | if (rc != 0) | |
757 | goto fail2; | |
758 | ||
759 | if ((used_bytes < 2) || | |
9f95a23c TL |
760 | (sector[used_bytes - 1] != EFX_DHCP_END)) { |
761 | /* Block too short, or EFX_DHCP_END missing */ | |
11fdf7f2 TL |
762 | rc = ENOENT; |
763 | goto fail3; | |
764 | } | |
765 | } | |
766 | ||
767 | /* Synthesize empty format on verification failure */ | |
768 | if (rc != 0 || used_bytes == 0) { | |
769 | sector[0] = 0; | |
9f95a23c | 770 | sector[1] = EFX_DHCP_END; |
11fdf7f2 TL |
771 | used_bytes = 2; |
772 | } | |
9f95a23c | 773 | EFSYS_ASSERT(used_bytes >= 2); /* checksum and EFX_DHCP_END */ |
11fdf7f2 TL |
774 | EFSYS_ASSERT(used_bytes <= sector_length); |
775 | EFSYS_ASSERT(sector_length >= 2); | |
776 | ||
777 | /* | |
9f95a23c TL |
778 | * Legacy bootcfg sectors don't terminate with an EFX_DHCP_END |
779 | * character. Modify the returned payload so it does. | |
11fdf7f2 TL |
780 | * Reinitialise the sector if there isn't room for the character. |
781 | */ | |
9f95a23c | 782 | if (sector[used_bytes - 1] != EFX_DHCP_END) { |
11fdf7f2 TL |
783 | if (used_bytes >= sector_length) { |
784 | sector[0] = 0; | |
785 | used_bytes = 1; | |
786 | } | |
9f95a23c | 787 | sector[used_bytes] = EFX_DHCP_END; |
11fdf7f2 TL |
788 | ++used_bytes; |
789 | } | |
790 | ||
791 | /* | |
792 | * Verify that the target buffer is large enough for the | |
793 | * entire used bootcfg area, then copy into the target buffer. | |
794 | */ | |
795 | if (used_bytes > data_size) { | |
796 | rc = ENOSPC; | |
797 | goto fail4; | |
798 | } | |
799 | ||
800 | data[0] = 0; /* checksum, updated below */ | |
801 | ||
802 | /* Copy all after the checksum to the target buffer */ | |
803 | memcpy(data + 1, sector + 1, used_bytes - 1); | |
804 | ||
805 | /* Zero out the unused portion of the target buffer */ | |
806 | if (used_bytes < data_size) | |
807 | (void) memset(data + used_bytes, 0, data_size - used_bytes); | |
808 | ||
809 | /* | |
9f95a23c TL |
810 | * The checksum includes trailing data after any EFX_DHCP_END |
811 | * character, which we've just modified (by truncation or appending | |
812 | * EFX_DHCP_END). | |
11fdf7f2 | 813 | */ |
9f95a23c | 814 | data[0] -= efx_dhcp_csum(data, data_size); |
11fdf7f2 TL |
815 | |
816 | return (0); | |
817 | ||
818 | fail4: | |
819 | EFSYS_PROBE(fail4); | |
820 | fail3: | |
821 | EFSYS_PROBE(fail3); | |
822 | fail2: | |
823 | EFSYS_PROBE(fail2); | |
824 | fail1: | |
825 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
826 | ||
827 | return (rc); | |
828 | } | |
829 | ||
830 | efx_rc_t | |
831 | efx_bootcfg_read( | |
832 | __in efx_nic_t *enp, | |
833 | __out_bcount(size) uint8_t *data, | |
834 | __in size_t size) | |
835 | { | |
836 | uint8_t *payload = NULL; | |
837 | size_t used_bytes; | |
838 | size_t partn_length; | |
839 | size_t sector_length; | |
840 | size_t sector_offset; | |
841 | efx_rc_t rc; | |
842 | uint32_t sector_number; | |
843 | ||
9f95a23c | 844 | /* Minimum buffer is checksum byte and EFX_DHCP_END terminator */ |
11fdf7f2 TL |
845 | if (size < 2) { |
846 | rc = ENOSPC; | |
847 | goto fail1; | |
848 | } | |
849 | ||
9f95a23c | 850 | #if EFX_OPTS_EF10() |
11fdf7f2 TL |
851 | sector_number = enp->en_nic_cfg.enc_pf; |
852 | #else | |
853 | sector_number = 0; | |
854 | #endif | |
855 | rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length); | |
856 | if (rc != 0) | |
857 | goto fail2; | |
858 | ||
859 | /* The bootcfg sector may be stored in a (larger) shared partition */ | |
860 | rc = efx_bootcfg_sector_info(enp, sector_number, | |
861 | NULL, §or_offset, §or_length); | |
862 | if (rc != 0) | |
863 | goto fail3; | |
864 | ||
865 | if (sector_length < 2) { | |
866 | rc = EINVAL; | |
867 | goto fail4; | |
868 | } | |
869 | ||
870 | if (sector_length > BOOTCFG_MAX_SIZE) | |
871 | sector_length = BOOTCFG_MAX_SIZE; | |
872 | ||
873 | if (sector_offset + sector_length > partn_length) { | |
874 | /* Partition is too small */ | |
875 | rc = EFBIG; | |
876 | goto fail5; | |
877 | } | |
878 | ||
879 | /* | |
9f95a23c TL |
880 | * We need to read the entire BOOTCFG sector to ensure we read all |
881 | * tags, because legacy bootcfg sectors are not guaranteed to end | |
882 | * with an EFX_DHCP_END character. If the user hasn't supplied a | |
883 | * sufficiently large buffer then use our own buffer. | |
11fdf7f2 TL |
884 | */ |
885 | if (sector_length > size) { | |
886 | EFSYS_KMEM_ALLOC(enp->en_esip, sector_length, payload); | |
887 | if (payload == NULL) { | |
888 | rc = ENOMEM; | |
889 | goto fail6; | |
890 | } | |
891 | } else | |
892 | payload = (uint8_t *)data; | |
893 | ||
894 | if ((rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0) | |
895 | goto fail7; | |
896 | ||
897 | if ((rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG, | |
898 | sector_offset, (caddr_t)payload, sector_length)) != 0) { | |
899 | (void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL); | |
900 | goto fail8; | |
901 | } | |
902 | ||
903 | if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0) | |
904 | goto fail9; | |
905 | ||
906 | /* Verify that the area is correctly formatted and checksummed */ | |
9f95a23c | 907 | rc = efx_dhcp_verify(payload, sector_length, |
11fdf7f2 TL |
908 | &used_bytes); |
909 | if (rc != 0 || used_bytes == 0) { | |
910 | payload[0] = 0; | |
9f95a23c | 911 | payload[1] = EFX_DHCP_END; |
11fdf7f2 TL |
912 | used_bytes = 2; |
913 | } | |
914 | ||
9f95a23c | 915 | EFSYS_ASSERT(used_bytes >= 2); /* checksum and EFX_DHCP_END */ |
11fdf7f2 TL |
916 | EFSYS_ASSERT(used_bytes <= sector_length); |
917 | ||
918 | /* | |
9f95a23c TL |
919 | * Legacy bootcfg sectors don't terminate with an EFX_DHCP_END |
920 | * character. Modify the returned payload so it does. | |
921 | * BOOTCFG_MAX_SIZE is by definition large enough for any valid | |
922 | * (per-port) bootcfg sector, so reinitialise the sector if there | |
923 | * isn't room for the character. | |
11fdf7f2 | 924 | */ |
9f95a23c | 925 | if (payload[used_bytes - 1] != EFX_DHCP_END) { |
11fdf7f2 TL |
926 | if (used_bytes >= sector_length) |
927 | used_bytes = 1; | |
928 | ||
9f95a23c | 929 | payload[used_bytes] = EFX_DHCP_END; |
11fdf7f2 TL |
930 | ++used_bytes; |
931 | } | |
932 | ||
933 | /* | |
934 | * Verify that the user supplied buffer is large enough for the | |
935 | * entire used bootcfg area, then copy into the user supplied buffer. | |
936 | */ | |
937 | if (used_bytes > size) { | |
938 | rc = ENOSPC; | |
939 | goto fail10; | |
940 | } | |
941 | ||
942 | data[0] = 0; /* checksum, updated below */ | |
943 | ||
944 | if (sector_length > size) { | |
945 | /* Copy all after the checksum to the target buffer */ | |
946 | memcpy(data + 1, payload + 1, used_bytes - 1); | |
947 | EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload); | |
948 | } | |
949 | ||
950 | /* Zero out the unused portion of the user buffer */ | |
951 | if (used_bytes < size) | |
952 | (void) memset(data + used_bytes, 0, size - used_bytes); | |
953 | ||
954 | /* | |
9f95a23c TL |
955 | * The checksum includes trailing data after any EFX_DHCP_END character, |
956 | * which we've just modified (by truncation or appending EFX_DHCP_END). | |
11fdf7f2 | 957 | */ |
9f95a23c | 958 | data[0] -= efx_dhcp_csum(data, size); |
11fdf7f2 TL |
959 | |
960 | return (0); | |
961 | ||
962 | fail10: | |
963 | EFSYS_PROBE(fail10); | |
964 | fail9: | |
965 | EFSYS_PROBE(fail9); | |
966 | fail8: | |
967 | EFSYS_PROBE(fail8); | |
968 | fail7: | |
969 | EFSYS_PROBE(fail7); | |
970 | if (sector_length > size) | |
971 | EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload); | |
972 | fail6: | |
973 | EFSYS_PROBE(fail6); | |
974 | fail5: | |
975 | EFSYS_PROBE(fail5); | |
976 | fail4: | |
977 | EFSYS_PROBE(fail4); | |
978 | fail3: | |
979 | EFSYS_PROBE(fail3); | |
980 | fail2: | |
981 | EFSYS_PROBE(fail2); | |
982 | fail1: | |
983 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
984 | ||
985 | return (rc); | |
986 | } | |
987 | ||
988 | efx_rc_t | |
989 | efx_bootcfg_write( | |
990 | __in efx_nic_t *enp, | |
991 | __in_bcount(size) uint8_t *data, | |
992 | __in size_t size) | |
993 | { | |
994 | uint8_t *partn_data; | |
995 | uint8_t checksum; | |
996 | size_t partn_length; | |
997 | size_t sector_length; | |
998 | size_t sector_offset; | |
999 | size_t used_bytes; | |
1000 | efx_rc_t rc; | |
1001 | uint32_t sector_number; | |
1002 | ||
9f95a23c | 1003 | #if EFX_OPTS_EF10() |
11fdf7f2 TL |
1004 | sector_number = enp->en_nic_cfg.enc_pf; |
1005 | #else | |
1006 | sector_number = 0; | |
1007 | #endif | |
1008 | ||
1009 | rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &partn_length); | |
1010 | if (rc != 0) | |
1011 | goto fail1; | |
1012 | ||
1013 | /* The bootcfg sector may be stored in a (larger) shared partition */ | |
1014 | rc = efx_bootcfg_sector_info(enp, sector_number, | |
1015 | NULL, §or_offset, §or_length); | |
1016 | if (rc != 0) | |
1017 | goto fail2; | |
1018 | ||
1019 | if (sector_length > BOOTCFG_MAX_SIZE) | |
1020 | sector_length = BOOTCFG_MAX_SIZE; | |
1021 | ||
1022 | if (sector_offset + sector_length > partn_length) { | |
1023 | /* Partition is too small */ | |
1024 | rc = EFBIG; | |
1025 | goto fail3; | |
1026 | } | |
1027 | ||
9f95a23c | 1028 | if ((rc = efx_dhcp_verify(data, size, &used_bytes)) != 0) |
11fdf7f2 TL |
1029 | goto fail4; |
1030 | ||
9f95a23c TL |
1031 | /* |
1032 | * The caller *must* terminate their block with a EFX_DHCP_END | |
1033 | * character | |
1034 | */ | |
1035 | if ((used_bytes < 2) || ((uint8_t)data[used_bytes - 1] != | |
1036 | EFX_DHCP_END)) { | |
1037 | /* Block too short or EFX_DHCP_END missing */ | |
11fdf7f2 TL |
1038 | rc = ENOENT; |
1039 | goto fail5; | |
1040 | } | |
1041 | ||
1042 | /* Check that the hardware has support for this much data */ | |
1043 | if (used_bytes > MIN(sector_length, BOOTCFG_MAX_SIZE)) { | |
1044 | rc = ENOSPC; | |
1045 | goto fail6; | |
1046 | } | |
1047 | ||
1048 | /* | |
1049 | * If the BOOTCFG sector is stored in a shared partition, then we must | |
1050 | * read the whole partition and insert the updated bootcfg sector at the | |
1051 | * correct offset. | |
1052 | */ | |
1053 | EFSYS_KMEM_ALLOC(enp->en_esip, partn_length, partn_data); | |
1054 | if (partn_data == NULL) { | |
1055 | rc = ENOMEM; | |
1056 | goto fail7; | |
1057 | } | |
1058 | ||
1059 | rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL); | |
1060 | if (rc != 0) | |
1061 | goto fail8; | |
1062 | ||
1063 | /* Read the entire partition */ | |
1064 | rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG, 0, | |
1065 | (caddr_t)partn_data, partn_length); | |
1066 | if (rc != 0) | |
1067 | goto fail9; | |
1068 | ||
1069 | /* | |
9f95a23c TL |
1070 | * Insert the BOOTCFG sector into the partition, Zero out all data |
1071 | * after the EFX_DHCP_END tag, and adjust the checksum. | |
11fdf7f2 TL |
1072 | */ |
1073 | (void) memset(partn_data + sector_offset, 0x0, sector_length); | |
1074 | (void) memcpy(partn_data + sector_offset, data, used_bytes); | |
1075 | ||
9f95a23c | 1076 | checksum = efx_dhcp_csum(data, used_bytes); |
11fdf7f2 TL |
1077 | partn_data[sector_offset] -= checksum; |
1078 | ||
1079 | if ((rc = efx_nvram_erase(enp, EFX_NVRAM_BOOTROM_CFG)) != 0) | |
1080 | goto fail10; | |
1081 | ||
1082 | if ((rc = efx_nvram_write_chunk(enp, EFX_NVRAM_BOOTROM_CFG, | |
1083 | 0, (caddr_t)partn_data, partn_length)) != 0) | |
1084 | goto fail11; | |
1085 | ||
1086 | if ((rc = efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0) | |
1087 | goto fail12; | |
1088 | ||
1089 | EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data); | |
1090 | ||
1091 | return (0); | |
1092 | ||
1093 | fail12: | |
1094 | EFSYS_PROBE(fail12); | |
1095 | fail11: | |
1096 | EFSYS_PROBE(fail11); | |
1097 | fail10: | |
1098 | EFSYS_PROBE(fail10); | |
1099 | fail9: | |
1100 | EFSYS_PROBE(fail9); | |
1101 | ||
1102 | (void) efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG, NULL); | |
1103 | fail8: | |
1104 | EFSYS_PROBE(fail8); | |
1105 | ||
1106 | EFSYS_KMEM_FREE(enp->en_esip, partn_length, partn_data); | |
1107 | fail7: | |
1108 | EFSYS_PROBE(fail7); | |
1109 | fail6: | |
1110 | EFSYS_PROBE(fail6); | |
1111 | fail5: | |
1112 | EFSYS_PROBE(fail5); | |
1113 | fail4: | |
1114 | EFSYS_PROBE(fail4); | |
1115 | fail3: | |
1116 | EFSYS_PROBE(fail3); | |
1117 | fail2: | |
1118 | EFSYS_PROBE(fail2); | |
1119 | fail1: | |
1120 | EFSYS_PROBE1(fail1, efx_rc_t, rc); | |
1121 | ||
1122 | return (rc); | |
1123 | } | |
1124 | ||
1125 | #endif /* EFSYS_OPT_BOOTCFG */ |