1 /*- 2 * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org> 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 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer, 10 * without modification. 11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer 12 * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any 13 * redistribution must be conditioned upon including a substantially 14 * similar Disclaimer requirement for further binary redistribution. 15 * 16 * NO WARRANTY 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY 20 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, 22 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 * THE POSSIBILITY OF SUCH DAMAGES. 28 */ 29 30 #include <sys/cdefs.h> 31 __FBSDID("$FreeBSD$"); 32 33 #ifdef _KERNEL 34 #include <sys/param.h> 35 #include <sys/ctype.h> 36 #include <sys/limits.h> 37 #include <sys/malloc.h> 38 #include <sys/systm.h> 39 #else /* !_KERNEL */ 40 #include <ctype.h> 41 #include <errno.h> 42 #include <stdint.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #endif /* _KERNEL */ 47 48 #include "bhnd_nvram_private.h" 49 50 #include "bhnd_nvram_datavar.h" 51 52 #include "bhnd_nvram_data_tlvreg.h" 53 54 /* 55 * CFE TLV NVRAM data class. 56 * 57 * The CFE-defined TLV NVRAM format is used on the WGT634U. 58 */ 59 60 struct bhnd_nvram_tlv { 61 struct bhnd_nvram_data nv; /**< common instance state */ 62 struct bhnd_nvram_io *data; /**< backing buffer */ 63 size_t count; /**< variable count */ 64 }; 65 66 BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", BHND_NVRAM_DATA_CAP_DEVPATHS, 67 sizeof(struct bhnd_nvram_tlv)) 68 69 /** Minimal TLV_ENV record header */ 70 struct bhnd_nvram_tlv_env_hdr { 71 uint8_t tag; 72 uint8_t size; 73 } __packed; 74 75 /** Minimal TLV_ENV record */ 76 struct bhnd_nvram_tlv_env { 77 struct bhnd_nvram_tlv_env_hdr hdr; 78 uint8_t flags; 79 char envp[]; 80 } __packed; 81 82 /* Return the length in bytes of an TLV_ENV's envp data */ 83 #define NVRAM_TLV_ENVP_DATA_LEN(_env) \ 84 (((_env)->hdr.size < sizeof((_env)->flags)) ? 0 : \ 85 ((_env)->hdr.size - sizeof((_env)->flags))) 86 87 /* Maximum supported length of the envp data field, in bytes */ 88 #define NVRAM_TLV_ENVP_DATA_MAX_LEN \ 89 (UINT8_MAX - sizeof(uint8_t) /* flags */) 90 91 static int bhnd_nvram_tlv_parse_size( 92 struct bhnd_nvram_io *io, 93 size_t *size); 94 95 static int bhnd_nvram_tlv_next_record( 96 struct bhnd_nvram_io *io, 97 size_t *next, size_t *offset, 98 uint8_t *tag); 99 100 static struct bhnd_nvram_tlv_env *bhnd_nvram_tlv_next_env( 101 struct bhnd_nvram_tlv *tlv, 102 size_t *next, void **cookiep); 103 104 static struct bhnd_nvram_tlv_env *bhnd_nvram_tlv_get_env( 105 struct bhnd_nvram_tlv *tlv, 106 void *cookiep); 107 108 static void *bhnd_nvram_tlv_to_cookie( 109 struct bhnd_nvram_tlv *tlv, 110 size_t io_offset); 111 static size_t bhnd_nvram_tlv_to_offset( 112 struct bhnd_nvram_tlv *tlv, 113 void *cookiep); 114 115 static int 116 bhnd_nvram_tlv_probe(struct bhnd_nvram_io *io) 117 { 118 struct bhnd_nvram_tlv_env ident; 119 size_t nbytes; 120 int error; 121 122 nbytes = bhnd_nvram_io_getsize(io); 123 124 /* Handle what might be an empty TLV image */ 125 if (nbytes < sizeof(ident)) { 126 uint8_t tag; 127 128 /* Fetch just the first tag */ 129 error = bhnd_nvram_io_read(io, 0x0, &tag, sizeof(tag)); 130 if (error) 131 return (error); 132 133 /* This *could* be an empty TLV image, but all we're 134 * testing for here is a single 0x0 byte followed by EOF */ 135 if (tag == NVRAM_TLV_TYPE_END) 136 return (BHND_NVRAM_DATA_PROBE_MAYBE); 137 138 return (ENXIO); 139 } 140 141 /* Otherwise, look at the initial header for a valid TLV ENV tag, 142 * plus one byte of the entry data */ 143 error = bhnd_nvram_io_read(io, 0x0, &ident, 144 sizeof(ident) + sizeof(ident.envp[0])); 145 if (error) 146 return (error); 147 148 /* First entry should be a variable record (which we statically 149 * assert as being defined to use a single byte size field) */ 150 if (ident.hdr.tag != NVRAM_TLV_TYPE_ENV) 151 return (ENXIO); 152 153 _Static_assert(NVRAM_TLV_TYPE_ENV & NVRAM_TLV_TF_U8_LEN, 154 "TYPE_ENV is not a U8-sized field"); 155 156 /* The entry must be at least 3 characters ('x=\0') in length */ 157 if (ident.hdr.size < 3) 158 return (ENXIO); 159 160 /* The first character should be a valid key char (alpha) */ 161 if (!bhnd_nv_isalpha(ident.envp[0])) 162 return (ENXIO); 163 164 return (BHND_NVRAM_DATA_PROBE_DEFAULT); 165 } 166 167 static int 168 bhnd_nvram_tlv_getvar_direct(struct bhnd_nvram_io *io, const char *name, 169 void *buf, size_t *len, bhnd_nvram_type type) 170 { 171 struct bhnd_nvram_tlv_env env; 172 char data[NVRAM_TLV_ENVP_DATA_MAX_LEN]; 173 size_t data_len; 174 const char *key, *value; 175 size_t keylen, vlen; 176 size_t namelen; 177 size_t next, off; 178 uint8_t tag; 179 int error; 180 181 namelen = strlen(name); 182 183 /* Iterate over the input looking for the requested variable */ 184 next = 0; 185 while (!(error = bhnd_nvram_tlv_next_record(io, &next, &off, &tag))) { 186 switch (tag) { 187 case NVRAM_TLV_TYPE_END: 188 /* Not found */ 189 return (ENOENT); 190 191 case NVRAM_TLV_TYPE_ENV: 192 /* Read the record header */ 193 error = bhnd_nvram_io_read(io, off, &env, sizeof(env)); 194 if (error) { 195 BHND_NV_LOG("error reading TLV_ENV record " 196 "header: %d\n", error); 197 return (error); 198 } 199 200 /* Read the record data */ 201 data_len = NVRAM_TLV_ENVP_DATA_LEN(&env); 202 error = bhnd_nvram_io_read(io, off + sizeof(env), data, 203 data_len); 204 if (error) { 205 BHND_NV_LOG("error reading TLV_ENV record " 206 "data: %d\n", error); 207 return (error); 208 } 209 210 /* Parse the key=value string */ 211 error = bhnd_nvram_parse_env(data, data_len, '=', &key, 212 &keylen, &value, &vlen); 213 if (error) { 214 BHND_NV_LOG("error parsing TLV_ENV data: %d\n", 215 error); 216 return (error); 217 } 218 219 /* Match against requested variable name */ 220 if (keylen == namelen && 221 strncmp(key, name, namelen) == 0) 222 { 223 return (bhnd_nvram_value_coerce(value, vlen, 224 BHND_NVRAM_TYPE_STRING, buf, len, type)); 225 } 226 227 break; 228 229 default: 230 /* Skip unknown tags */ 231 break; 232 } 233 } 234 235 /* Hit I/O error */ 236 return (error); 237 } 238 239 static int 240 bhnd_nvram_tlv_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, 241 bhnd_nvram_plist *options, void *outp, size_t *olen) 242 { 243 bhnd_nvram_prop *prop; 244 size_t limit, nbytes; 245 int error; 246 247 /* Determine output byte limit */ 248 if (outp != NULL) 249 limit = *olen; 250 else 251 limit = 0; 252 253 nbytes = 0; 254 255 /* Write all properties */ 256 prop = NULL; 257 while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { 258 struct bhnd_nvram_tlv_env env; 259 const char *name; 260 uint8_t *p; 261 size_t name_len, value_len; 262 size_t rec_size; 263 264 env.hdr.tag = NVRAM_TLV_TYPE_ENV; 265 env.hdr.size = sizeof(env.flags); 266 env.flags = 0x0; 267 268 /* Fetch name value and add to record length */ 269 name = bhnd_nvram_prop_name(prop); 270 name_len = strlen(name) + 1 /* '=' */; 271 272 if (UINT8_MAX - env.hdr.size < name_len) { 273 BHND_NV_LOG("%s name exceeds maximum TLV record " 274 "length\n", name); 275 return (EFTYPE); /* would overflow TLV size */ 276 } 277 278 env.hdr.size += name_len; 279 280 /* Add string value to record length */ 281 error = bhnd_nvram_prop_encode(prop, NULL, &value_len, 282 BHND_NVRAM_TYPE_STRING); 283 if (error) { 284 BHND_NV_LOG("error serializing %s to required type " 285 "%s: %d\n", name, 286 bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), 287 error); 288 return (error); 289 } 290 291 if (UINT8_MAX - env.hdr.size < value_len) { 292 BHND_NV_LOG("%s value exceeds maximum TLV record " 293 "length\n", name); 294 return (EFTYPE); /* would overflow TLV size */ 295 } 296 297 env.hdr.size += value_len; 298 299 /* Calculate total record size */ 300 rec_size = sizeof(env.hdr) + env.hdr.size; 301 if (SIZE_MAX - nbytes < rec_size) 302 return (EFTYPE); /* would overflow size_t */ 303 304 /* Calculate our output pointer */ 305 if (nbytes > limit || limit - nbytes < rec_size) { 306 /* buffer is full; cannot write */ 307 p = NULL; 308 } else { 309 p = (uint8_t *)outp + nbytes; 310 } 311 312 /* Write to output */ 313 if (p != NULL) { 314 memcpy(p, &env, sizeof(env)); 315 p += sizeof(env); 316 317 memcpy(p, name, name_len - 1); 318 p[name_len - 1] = '='; 319 p += name_len; 320 321 error = bhnd_nvram_prop_encode(prop, p, &value_len, 322 BHND_NVRAM_TYPE_STRING); 323 if (error) { 324 BHND_NV_LOG("error serializing %s to required " 325 "type %s: %d\n", name, 326 bhnd_nvram_type_name( 327 BHND_NVRAM_TYPE_STRING), 328 error); 329 return (error); 330 } 331 } 332 333 nbytes += rec_size; 334 } 335 336 /* Write terminating END record */ 337 if (limit > nbytes) 338 *((uint8_t *)outp + nbytes) = NVRAM_TLV_TYPE_END; 339 340 if (nbytes == SIZE_MAX) 341 return (EFTYPE); /* would overflow size_t */ 342 nbytes++; 343 344 /* Provide required length */ 345 *olen = nbytes; 346 if (limit < *olen) { 347 if (outp == NULL) 348 return (0); 349 350 return (ENOMEM); 351 } 352 353 return (0); 354 } 355 356 /** 357 * Initialize @p tlv with the provided NVRAM TLV data mapped by @p src. 358 * 359 * @param tlv A newly allocated data instance. 360 */ 361 static int 362 bhnd_nvram_tlv_init(struct bhnd_nvram_tlv *tlv, struct bhnd_nvram_io *src) 363 { 364 struct bhnd_nvram_tlv_env *env; 365 size_t size; 366 size_t next; 367 int error; 368 369 BHND_NV_ASSERT(tlv->data == NULL, ("tlv data already initialized")); 370 371 /* Determine the actual size of the TLV source data */ 372 if ((error = bhnd_nvram_tlv_parse_size(src, &size))) 373 return (error); 374 375 /* Copy to our own internal buffer */ 376 if ((tlv->data = bhnd_nvram_iobuf_copy_range(src, 0x0, size)) == NULL) 377 return (ENOMEM); 378 379 /* Initialize our backing buffer */ 380 tlv->count = 0; 381 next = 0; 382 while ((env = bhnd_nvram_tlv_next_env(tlv, &next, NULL)) != NULL) { 383 size_t env_len; 384 size_t name_len; 385 386 /* TLV_ENV data must not be empty */ 387 env_len = NVRAM_TLV_ENVP_DATA_LEN(env); 388 if (env_len == 0) { 389 BHND_NV_LOG("cannot parse zero-length TLV_ENV record " 390 "data\n"); 391 return (EINVAL); 392 } 393 394 /* Parse the key=value string, and then replace the '=' 395 * delimiter with '\0' to allow us to provide direct 396 * name pointers from our backing buffer */ 397 error = bhnd_nvram_parse_env(env->envp, env_len, '=', NULL, 398 &name_len, NULL, NULL); 399 if (error) { 400 BHND_NV_LOG("error parsing TLV_ENV data: %d\n", error); 401 return (error); 402 } 403 404 /* Replace '=' with '\0' */ 405 *(env->envp + name_len) = '\0'; 406 407 /* Add to variable count */ 408 tlv->count++; 409 }; 410 411 return (0); 412 } 413 414 static int 415 bhnd_nvram_tlv_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) 416 { 417 418 struct bhnd_nvram_tlv *tlv; 419 int error; 420 421 /* Allocate and initialize the TLV data instance */ 422 tlv = (struct bhnd_nvram_tlv *)nv; 423 424 /* Parse the TLV input data and initialize our backing 425 * data representation */ 426 if ((error = bhnd_nvram_tlv_init(tlv, io))) { 427 bhnd_nvram_tlv_free(nv); 428 return (error); 429 } 430 431 return (0); 432 } 433 434 static void 435 bhnd_nvram_tlv_free(struct bhnd_nvram_data *nv) 436 { 437 struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv; 438 if (tlv->data != NULL) 439 bhnd_nvram_io_free(tlv->data); 440 } 441 442 size_t 443 bhnd_nvram_tlv_count(struct bhnd_nvram_data *nv) 444 { 445 struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv; 446 return (tlv->count); 447 } 448 449 static bhnd_nvram_plist * 450 bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv) 451 { 452 return (NULL); 453 } 454 455 static uint32_t 456 bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv) 457 { 458 return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS); 459 } 460 461 static const char * 462 bhnd_nvram_tlv_next(struct bhnd_nvram_data *nv, void **cookiep) 463 { 464 struct bhnd_nvram_tlv *tlv; 465 struct bhnd_nvram_tlv_env *env; 466 size_t io_offset; 467 468 tlv = (struct bhnd_nvram_tlv *)nv; 469 470 /* Find next readable TLV record */ 471 if (*cookiep == NULL) { 472 /* Start search at offset 0x0 */ 473 io_offset = 0x0; 474 env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep); 475 } else { 476 /* Seek past the previous env record */ 477 io_offset = bhnd_nvram_tlv_to_offset(tlv, *cookiep); 478 env = bhnd_nvram_tlv_next_env(tlv, &io_offset, NULL); 479 if (env == NULL) 480 BHND_NV_PANIC("invalid cookiep; record missing"); 481 482 /* Advance to next env record, update the caller's cookiep */ 483 env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep); 484 } 485 486 /* Check for EOF */ 487 if (env == NULL) 488 return (NULL); 489 490 /* Return the NUL terminated name */ 491 return (env->envp); 492 } 493 494 static void * 495 bhnd_nvram_tlv_find(struct bhnd_nvram_data *nv, const char *name) 496 { 497 return (bhnd_nvram_data_generic_find(nv, name)); 498 } 499 500 static int 501 bhnd_nvram_tlv_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, 502 void *cookiep2) 503 { 504 if (cookiep1 < cookiep2) 505 return (-1); 506 507 if (cookiep1 > cookiep2) 508 return (1); 509 510 return (0); 511 } 512 513 static int 514 bhnd_nvram_tlv_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf, 515 size_t *len, bhnd_nvram_type type) 516 { 517 return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type)); 518 } 519 520 static int 521 bhnd_nvram_tlv_copy_val(struct bhnd_nvram_data *nv, void *cookiep, 522 bhnd_nvram_val **value) 523 { 524 return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value)); 525 } 526 527 static const void * 528 bhnd_nvram_tlv_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, 529 size_t *len, bhnd_nvram_type *type) 530 { 531 struct bhnd_nvram_tlv *tlv; 532 struct bhnd_nvram_tlv_env *env; 533 const char *val; 534 int error; 535 536 tlv = (struct bhnd_nvram_tlv *)nv; 537 538 /* Fetch pointer to the TLV_ENV record */ 539 if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL) 540 BHND_NV_PANIC("invalid cookiep: %p", cookiep); 541 542 /* Parse value pointer and length from key\0value data */ 543 error = bhnd_nvram_parse_env(env->envp, NVRAM_TLV_ENVP_DATA_LEN(env), 544 '\0', NULL, NULL, &val, len); 545 if (error) 546 BHND_NV_PANIC("unexpected error parsing '%s'", env->envp); 547 548 /* Type is always CSTR */ 549 *type = BHND_NVRAM_TYPE_STRING; 550 551 return (val); 552 } 553 554 static const char * 555 bhnd_nvram_tlv_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) 556 { 557 struct bhnd_nvram_tlv *tlv; 558 const struct bhnd_nvram_tlv_env *env; 559 560 tlv = (struct bhnd_nvram_tlv *)nv; 561 562 /* Fetch pointer to the TLV_ENV record */ 563 if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL) 564 BHND_NV_PANIC("invalid cookiep: %p", cookiep); 565 566 /* Return name pointer */ 567 return (&env->envp[0]); 568 } 569 570 static int 571 bhnd_nvram_tlv_filter_setvar(struct bhnd_nvram_data *nv, const char *name, 572 bhnd_nvram_val *value, bhnd_nvram_val **result) 573 { 574 bhnd_nvram_val *str; 575 const char *inp; 576 bhnd_nvram_type itype; 577 size_t ilen; 578 size_t name_len, tlv_nremain; 579 int error; 580 581 tlv_nremain = NVRAM_TLV_ENVP_DATA_MAX_LEN; 582 583 /* Name (trimmed of any path prefix) must be valid */ 584 if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name))) 585 return (EINVAL); 586 587 /* 'name=' must fit within the maximum TLV_ENV record length */ 588 name_len = strlen(name) + 1; /* '=' */ 589 if (tlv_nremain < name_len) { 590 BHND_NV_LOG("'%s=' exceeds maximum TLV_ENV record length\n", 591 name); 592 return (EINVAL); 593 } 594 tlv_nremain -= name_len; 595 596 /* Convert value to a (bcm-formatted) string */ 597 error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt, 598 value, BHND_NVRAM_VAL_DYNAMIC); 599 if (error) 600 return (error); 601 602 /* The string value must fit within remaining TLV_ENV record length */ 603 inp = bhnd_nvram_val_bytes(str, &ilen, &itype); 604 if (tlv_nremain < ilen) { 605 BHND_NV_LOG("'%.*s\\0' exceeds maximum TLV_ENV record length\n", 606 BHND_NV_PRINT_WIDTH(ilen), inp); 607 608 bhnd_nvram_val_release(str); 609 return (EINVAL); 610 } 611 tlv_nremain -= name_len; 612 613 /* Success. Transfer result ownership to the caller. */ 614 *result = str; 615 return (0); 616 } 617 618 static int 619 bhnd_nvram_tlv_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) 620 { 621 /* We permit deletion of any variable */ 622 return (0); 623 } 624 625 /** 626 * Iterate over the records starting at @p next, returning the parsed 627 * record's @p tag, @p size, and @p offset. 628 * 629 * @param io The I/O context to parse. 630 * @param[in,out] next The next offset to be parsed, or 0x0 631 * to begin parsing. Upon successful 632 * return, will be set to the offset of the 633 * next record (or EOF, if 634 * NVRAM_TLV_TYPE_END was parsed). 635 * @param[out] offset The record's value offset. 636 * @param[out] tag The record's tag. 637 * 638 * @retval 0 success 639 * @retval EINVAL if parsing @p io as TLV fails. 640 * @retval non-zero if reading @p io otherwise fails, a regular unix error 641 * code will be returned. 642 */ 643 static int 644 bhnd_nvram_tlv_next_record(struct bhnd_nvram_io *io, size_t *next, size_t 645 *offset, uint8_t *tag) 646 { 647 size_t io_offset, io_size; 648 uint16_t parsed_len; 649 uint8_t len_hdr[2]; 650 int error; 651 652 io_offset = *next; 653 io_size = bhnd_nvram_io_getsize(io); 654 655 /* Save the record offset */ 656 if (offset != NULL) 657 *offset = io_offset; 658 659 /* Fetch initial tag */ 660 error = bhnd_nvram_io_read(io, io_offset, tag, sizeof(*tag)); 661 if (error) 662 return (error); 663 io_offset++; 664 665 /* EOF */ 666 if (*tag == NVRAM_TLV_TYPE_END) { 667 *next = io_offset; 668 return (0); 669 } 670 671 /* Read length field */ 672 if (*tag & NVRAM_TLV_TF_U8_LEN) { 673 error = bhnd_nvram_io_read(io, io_offset, &len_hdr, 674 sizeof(len_hdr[0])); 675 if (error) { 676 BHND_NV_LOG("error reading TLV record size: %d\n", 677 error); 678 return (error); 679 } 680 681 parsed_len = len_hdr[0]; 682 io_offset++; 683 } else { 684 error = bhnd_nvram_io_read(io, io_offset, &len_hdr, 685 sizeof(len_hdr)); 686 if (error) { 687 BHND_NV_LOG("error reading 16-bit TLV record " 688 "size: %d\n", error); 689 return (error); 690 } 691 692 parsed_len = (len_hdr[0] << 8) | len_hdr[1]; 693 io_offset += 2; 694 } 695 696 /* Advance to next record */ 697 if (parsed_len > io_size || io_size - parsed_len < io_offset) { 698 /* Hit early EOF */ 699 BHND_NV_LOG("TLV record length %hu truncated by input " 700 "size of %zu\n", parsed_len, io_size); 701 return (EINVAL); 702 } 703 704 *next = io_offset + parsed_len; 705 706 /* Valid record found */ 707 return (0); 708 } 709 710 /** 711 * Parse the TLV data in @p io to determine the total size of the TLV 712 * data mapped by @p io (which may be less than the size of @p io). 713 */ 714 static int 715 bhnd_nvram_tlv_parse_size(struct bhnd_nvram_io *io, size_t *size) 716 { 717 size_t next; 718 uint8_t tag; 719 int error; 720 721 /* We have to perform a minimal parse to determine the actual length */ 722 next = 0x0; 723 *size = 0x0; 724 725 /* Iterate over the input until we hit END tag or the read fails */ 726 do { 727 error = bhnd_nvram_tlv_next_record(io, &next, NULL, &tag); 728 if (error) 729 return (error); 730 } while (tag != NVRAM_TLV_TYPE_END); 731 732 /* Offset should now point to EOF */ 733 BHND_NV_ASSERT(next <= bhnd_nvram_io_getsize(io), 734 ("parse returned invalid EOF offset")); 735 736 *size = next; 737 return (0); 738 } 739 740 /** 741 * Iterate over the records in @p tlv, returning a pointer to the next 742 * NVRAM_TLV_TYPE_ENV record, or NULL if EOF is reached. 743 * 744 * @param tlv The TLV instance. 745 * @param[in,out] next The next offset to be parsed, or 0x0 746 * to begin parsing. Upon successful 747 * return, will be set to the offset of the 748 * next record. 749 */ 750 static struct bhnd_nvram_tlv_env * 751 bhnd_nvram_tlv_next_env(struct bhnd_nvram_tlv *tlv, size_t *next, 752 void **cookiep) 753 { 754 uint8_t tag; 755 int error; 756 757 /* Find the next TLV_ENV record, starting at @p next */ 758 do { 759 void *c; 760 size_t offset; 761 762 /* Fetch the next TLV record */ 763 error = bhnd_nvram_tlv_next_record(tlv->data, next, &offset, 764 &tag); 765 if (error) { 766 BHND_NV_LOG("unexpected error in next_record(): %d\n", 767 error); 768 return (NULL); 769 } 770 771 /* Only interested in ENV records */ 772 if (tag != NVRAM_TLV_TYPE_ENV) 773 continue; 774 775 /* Map and return TLV_ENV record pointer */ 776 c = bhnd_nvram_tlv_to_cookie(tlv, offset); 777 778 /* Provide the cookiep value for the returned record */ 779 if (cookiep != NULL) 780 *cookiep = c; 781 782 return (bhnd_nvram_tlv_get_env(tlv, c)); 783 } while (tag != NVRAM_TLV_TYPE_END); 784 785 /* No remaining ENV records */ 786 return (NULL); 787 } 788 789 /** 790 * Return a pointer to the TLV_ENV record for @p cookiep, or NULL 791 * if none vailable. 792 */ 793 static struct bhnd_nvram_tlv_env * 794 bhnd_nvram_tlv_get_env(struct bhnd_nvram_tlv *tlv, void *cookiep) 795 { 796 struct bhnd_nvram_tlv_env *env; 797 void *ptr; 798 size_t navail; 799 size_t io_offset, io_size; 800 int error; 801 802 io_size = bhnd_nvram_io_getsize(tlv->data); 803 io_offset = bhnd_nvram_tlv_to_offset(tlv, cookiep); 804 805 /* At EOF? */ 806 if (io_offset == io_size) 807 return (NULL); 808 809 /* Fetch non-const pointer to the record entry */ 810 error = bhnd_nvram_io_write_ptr(tlv->data, io_offset, &ptr, 811 sizeof(env->hdr), &navail); 812 if (error) { 813 /* Should never occur with a valid cookiep */ 814 BHND_NV_LOG("error mapping record for cookiep: %d\n", error); 815 return (NULL); 816 } 817 818 /* Validate the record pointer */ 819 env = ptr; 820 if (env->hdr.tag != NVRAM_TLV_TYPE_ENV) { 821 /* Should never occur with a valid cookiep */ 822 BHND_NV_LOG("non-ENV record mapped for %p\n", cookiep); 823 return (NULL); 824 } 825 826 /* Is the required variable name data is mapped? */ 827 if (navail < sizeof(struct bhnd_nvram_tlv_env_hdr) + env->hdr.size || 828 env->hdr.size == sizeof(env->flags)) 829 { 830 /* Should never occur with a valid cookiep */ 831 BHND_NV_LOG("TLV_ENV variable data not mapped for %p\n", 832 cookiep); 833 return (NULL); 834 } 835 836 return (env); 837 } 838 839 /** 840 * Return a cookiep for the given I/O offset. 841 */ 842 static void * 843 bhnd_nvram_tlv_to_cookie(struct bhnd_nvram_tlv *tlv, size_t io_offset) 844 { 845 const void *ptr; 846 int error; 847 848 BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(tlv->data), 849 ("io_offset %zu out-of-range", io_offset)); 850 BHND_NV_ASSERT(io_offset < UINTPTR_MAX, 851 ("io_offset %#zx exceeds UINTPTR_MAX", io_offset)); 852 853 error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_offset, NULL); 854 if (error) 855 BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error); 856 857 ptr = (const uint8_t *)ptr + io_offset; 858 return (__DECONST(void *, ptr)); 859 } 860 861 /* Convert a cookiep back to an I/O offset */ 862 static size_t 863 bhnd_nvram_tlv_to_offset(struct bhnd_nvram_tlv *tlv, void *cookiep) 864 { 865 const void *ptr; 866 intptr_t offset; 867 size_t io_size; 868 int error; 869 870 BHND_NV_ASSERT(cookiep != NULL, ("null cookiep")); 871 872 io_size = bhnd_nvram_io_getsize(tlv->data); 873 874 error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_size, NULL); 875 if (error) 876 BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error); 877 878 offset = (const uint8_t *)cookiep - (const uint8_t *)ptr; 879 BHND_NV_ASSERT(offset >= 0, ("invalid cookiep")); 880 BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)")); 881 BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)")); 882 883 return ((size_t)offset); 884 } 885