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