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