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/malloc.h> 37 #include <sys/systm.h> 38 #else /* !_KERNEL */ 39 #include <ctype.h> 40 #include <errno.h> 41 #include <stdint.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #endif /* _KERNEL */ 46 47 #include "bhnd_nvram_private.h" 48 49 #include "bhnd_nvram_datavar.h" 50 51 #include "bhnd_nvram_data_tlvreg.h" 52 53 /* 54 * CFE TLV NVRAM data class. 55 * 56 * The CFE-defined TLV NVRAM format is used on the WGT634U. 57 */ 58 59 struct bhnd_nvram_tlv { 60 struct bhnd_nvram_data nv; /**< common instance state */ 61 struct bhnd_nvram_io *data; /**< backing buffer */ 62 size_t count; /**< variable count */ 63 }; 64 65 BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", BHND_NVRAM_DATA_CAP_DEVPATHS, 66 sizeof(struct bhnd_nvram_tlv)) 67 68 /** Minimal TLV_ENV record header */ 69 struct bhnd_nvram_tlv_env_hdr { 70 uint8_t tag; 71 uint8_t size; 72 } __packed; 73 74 /** Minimal TLV_ENV record */ 75 struct bhnd_nvram_tlv_env { 76 struct bhnd_nvram_tlv_env_hdr hdr; 77 uint8_t flags; 78 char envp[]; 79 } __packed; 80 81 /* Return the length in bytes of an TLV_ENV's envp data */ 82 #define NVRAM_TLV_ENVP_DATA_LEN(_env) \ 83 (((_env)->hdr.size < sizeof((_env)->flags)) ? 0 : \ 84 ((_env)->hdr.size - sizeof((_env)->flags))) 85 86 /* Maximum supported length of the envp data field, in bytes */ 87 #define NVRAM_TLV_ENVP_DATA_MAX_LEN \ 88 (UINT8_MAX - sizeof(uint8_t) /* flags */) 89 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 450 static bhnd_nvram_plist * 451 bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv) 452 { 453 return (NULL); 454 } 455 456 static uint32_t 457 bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv) 458 { 459 return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS); 460 } 461 462 static const char * 463 bhnd_nvram_tlv_next(struct bhnd_nvram_data *nv, void **cookiep) 464 { 465 struct bhnd_nvram_tlv *tlv; 466 struct bhnd_nvram_tlv_env *env; 467 size_t io_offset; 468 469 tlv = (struct bhnd_nvram_tlv *)nv; 470 471 /* Find next readable TLV record */ 472 if (*cookiep == NULL) { 473 /* Start search at offset 0x0 */ 474 io_offset = 0x0; 475 env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep); 476 } else { 477 /* Seek past the previous env record */ 478 io_offset = bhnd_nvram_tlv_to_offset(tlv, *cookiep); 479 env = bhnd_nvram_tlv_next_env(tlv, &io_offset, NULL); 480 if (env == NULL) 481 BHND_NV_PANIC("invalid cookiep; record missing"); 482 483 /* Advance to next env record, update the caller's cookiep */ 484 env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep); 485 } 486 487 /* Check for EOF */ 488 if (env == NULL) 489 return (NULL); 490 491 /* Return the NUL terminated name */ 492 return (env->envp); 493 } 494 495 static void * 496 bhnd_nvram_tlv_find(struct bhnd_nvram_data *nv, const char *name) 497 { 498 return (bhnd_nvram_data_generic_find(nv, name)); 499 } 500 501 static int 502 bhnd_nvram_tlv_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, 503 void *cookiep2) 504 { 505 if (cookiep1 < cookiep2) 506 return (-1); 507 508 if (cookiep1 > cookiep2) 509 return (1); 510 511 return (0); 512 } 513 514 static int 515 bhnd_nvram_tlv_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf, 516 size_t *len, bhnd_nvram_type type) 517 { 518 return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type)); 519 } 520 521 static int 522 bhnd_nvram_tlv_copy_val(struct bhnd_nvram_data *nv, void *cookiep, 523 bhnd_nvram_val **value) 524 { 525 return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value)); 526 } 527 528 static const void * 529 bhnd_nvram_tlv_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, 530 size_t *len, bhnd_nvram_type *type) 531 { 532 struct bhnd_nvram_tlv *tlv; 533 struct bhnd_nvram_tlv_env *env; 534 const char *val; 535 int error; 536 537 tlv = (struct bhnd_nvram_tlv *)nv; 538 539 /* Fetch pointer to the TLV_ENV record */ 540 if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL) 541 BHND_NV_PANIC("invalid cookiep: %p", cookiep); 542 543 /* Parse value pointer and length from key\0value data */ 544 error = bhnd_nvram_parse_env(env->envp, NVRAM_TLV_ENVP_DATA_LEN(env), 545 '\0', NULL, NULL, &val, len); 546 if (error) 547 BHND_NV_PANIC("unexpected error parsing '%s'", env->envp); 548 549 /* Type is always CSTR */ 550 *type = BHND_NVRAM_TYPE_STRING; 551 552 return (val); 553 } 554 555 static const char * 556 bhnd_nvram_tlv_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) 557 { 558 struct bhnd_nvram_tlv *tlv; 559 const struct bhnd_nvram_tlv_env *env; 560 561 tlv = (struct bhnd_nvram_tlv *)nv; 562 563 /* Fetch pointer to the TLV_ENV record */ 564 if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL) 565 BHND_NV_PANIC("invalid cookiep: %p", cookiep); 566 567 /* Return name pointer */ 568 return (&env->envp[0]); 569 } 570 571 static int 572 bhnd_nvram_tlv_filter_setvar(struct bhnd_nvram_data *nv, const char *name, 573 bhnd_nvram_val *value, bhnd_nvram_val **result) 574 { 575 bhnd_nvram_val *str; 576 const char *inp; 577 bhnd_nvram_type itype; 578 size_t ilen; 579 size_t name_len, tlv_nremain; 580 int error; 581 582 tlv_nremain = NVRAM_TLV_ENVP_DATA_MAX_LEN; 583 584 /* Name (trimmed of any path prefix) must be valid */ 585 if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name))) 586 return (EINVAL); 587 588 /* 'name=' must fit within the maximum TLV_ENV record length */ 589 name_len = strlen(name) + 1; /* '=' */ 590 if (tlv_nremain < name_len) { 591 BHND_NV_LOG("'%s=' exceeds maximum TLV_ENV record length\n", 592 name); 593 return (EINVAL); 594 } 595 tlv_nremain -= name_len; 596 597 /* Convert value to a (bcm-formatted) string */ 598 error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt, 599 value, BHND_NVRAM_VAL_DYNAMIC); 600 if (error) 601 return (error); 602 603 /* The string value must fit within remaining TLV_ENV record length */ 604 inp = bhnd_nvram_val_bytes(str, &ilen, &itype); 605 if (tlv_nremain < ilen) { 606 BHND_NV_LOG("'%.*s\\0' exceeds maximum TLV_ENV record length\n", 607 BHND_NV_PRINT_WIDTH(ilen), inp); 608 609 bhnd_nvram_val_release(str); 610 return (EINVAL); 611 } 612 tlv_nremain -= name_len; 613 614 /* Success. Transfer result ownership to the caller. */ 615 *result = str; 616 return (0); 617 } 618 619 static int 620 bhnd_nvram_tlv_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) 621 { 622 /* We permit deletion of any variable */ 623 return (0); 624 } 625 626 /** 627 * Iterate over the records starting at @p next, returning the parsed 628 * record's @p tag, @p size, and @p offset. 629 * 630 * @param io The I/O context to parse. 631 * @param[in,out] next The next offset to be parsed, or 0x0 632 * to begin parsing. Upon successful 633 * return, will be set to the offset of the 634 * next record (or EOF, if 635 * NVRAM_TLV_TYPE_END was parsed). 636 * @param[out] offset The record's value offset. 637 * @param[out] tag The record's tag. 638 * 639 * @retval 0 success 640 * @retval EINVAL if parsing @p io as TLV fails. 641 * @retval non-zero if reading @p io otherwise fails, a regular unix error 642 * code will be returned. 643 */ 644 static int 645 bhnd_nvram_tlv_next_record(struct bhnd_nvram_io *io, size_t *next, size_t 646 *offset, uint8_t *tag) 647 { 648 size_t io_offset, io_size; 649 uint16_t parsed_len; 650 uint8_t len_hdr[2]; 651 int error; 652 653 io_offset = *next; 654 io_size = bhnd_nvram_io_getsize(io); 655 656 /* Save the record offset */ 657 if (offset != NULL) 658 *offset = io_offset; 659 660 /* Fetch initial tag */ 661 error = bhnd_nvram_io_read(io, io_offset, tag, sizeof(*tag)); 662 if (error) 663 return (error); 664 io_offset++; 665 666 /* EOF */ 667 if (*tag == NVRAM_TLV_TYPE_END) { 668 *next = io_offset; 669 return (0); 670 } 671 672 /* Read length field */ 673 if (*tag & NVRAM_TLV_TF_U8_LEN) { 674 error = bhnd_nvram_io_read(io, io_offset, &len_hdr, 675 sizeof(len_hdr[0])); 676 if (error) { 677 BHND_NV_LOG("error reading TLV record size: %d\n", 678 error); 679 return (error); 680 } 681 682 parsed_len = len_hdr[0]; 683 io_offset++; 684 } else { 685 error = bhnd_nvram_io_read(io, io_offset, &len_hdr, 686 sizeof(len_hdr)); 687 if (error) { 688 BHND_NV_LOG("error reading 16-bit TLV record " 689 "size: %d\n", error); 690 return (error); 691 } 692 693 parsed_len = (len_hdr[0] << 8) | len_hdr[1]; 694 io_offset += 2; 695 } 696 697 /* Advance to next record */ 698 if (parsed_len > io_size || io_size - parsed_len < io_offset) { 699 /* Hit early EOF */ 700 BHND_NV_LOG("TLV record length %hu truncated by input " 701 "size of %zu\n", parsed_len, io_size); 702 return (EINVAL); 703 } 704 705 *next = io_offset + parsed_len; 706 707 /* Valid record found */ 708 return (0); 709 } 710 711 /** 712 * Parse the TLV data in @p io to determine the total size of the TLV 713 * data mapped by @p io (which may be less than the size of @p io). 714 */ 715 static int 716 bhnd_nvram_tlv_parse_size(struct bhnd_nvram_io *io, size_t *size) 717 { 718 size_t next; 719 uint8_t tag; 720 int error; 721 722 /* We have to perform a minimal parse to determine the actual length */ 723 next = 0x0; 724 *size = 0x0; 725 726 /* Iterate over the input until we hit END tag or the read fails */ 727 do { 728 error = bhnd_nvram_tlv_next_record(io, &next, NULL, &tag); 729 if (error) 730 return (error); 731 } while (tag != NVRAM_TLV_TYPE_END); 732 733 /* Offset should now point to EOF */ 734 BHND_NV_ASSERT(next <= bhnd_nvram_io_getsize(io), 735 ("parse returned invalid EOF offset")); 736 737 *size = next; 738 return (0); 739 } 740 741 /** 742 * Iterate over the records in @p tlv, returning a pointer to the next 743 * NVRAM_TLV_TYPE_ENV record, or NULL if EOF is reached. 744 * 745 * @param tlv The TLV instance. 746 * @param[in,out] next The next offset to be parsed, or 0x0 747 * to begin parsing. Upon successful 748 * return, will be set to the offset of the 749 * next record. 750 */ 751 static struct bhnd_nvram_tlv_env * 752 bhnd_nvram_tlv_next_env(struct bhnd_nvram_tlv *tlv, size_t *next, 753 void **cookiep) 754 { 755 uint8_t tag; 756 int error; 757 758 /* Find the next TLV_ENV record, starting at @p next */ 759 do { 760 void *c; 761 size_t offset; 762 763 /* Fetch the next TLV record */ 764 error = bhnd_nvram_tlv_next_record(tlv->data, next, &offset, 765 &tag); 766 if (error) { 767 BHND_NV_LOG("unexpected error in next_record(): %d\n", 768 error); 769 return (NULL); 770 } 771 772 /* Only interested in ENV records */ 773 if (tag != NVRAM_TLV_TYPE_ENV) 774 continue; 775 776 /* Map and return TLV_ENV record pointer */ 777 c = bhnd_nvram_tlv_to_cookie(tlv, offset); 778 779 /* Provide the cookiep value for the returned record */ 780 if (cookiep != NULL) 781 *cookiep = c; 782 783 return (bhnd_nvram_tlv_get_env(tlv, c)); 784 } while (tag != NVRAM_TLV_TYPE_END); 785 786 /* No remaining ENV records */ 787 return (NULL); 788 } 789 790 /** 791 * Return a pointer to the TLV_ENV record for @p cookiep, or NULL 792 * if none vailable. 793 */ 794 static struct bhnd_nvram_tlv_env * 795 bhnd_nvram_tlv_get_env(struct bhnd_nvram_tlv *tlv, void *cookiep) 796 { 797 struct bhnd_nvram_tlv_env *env; 798 void *ptr; 799 size_t navail; 800 size_t io_offset, io_size; 801 int error; 802 803 io_size = bhnd_nvram_io_getsize(tlv->data); 804 io_offset = bhnd_nvram_tlv_to_offset(tlv, cookiep); 805 806 /* At EOF? */ 807 if (io_offset == io_size) 808 return (NULL); 809 810 /* Fetch non-const pointer to the record entry */ 811 error = bhnd_nvram_io_write_ptr(tlv->data, io_offset, &ptr, 812 sizeof(env->hdr), &navail); 813 if (error) { 814 /* Should never occur with a valid cookiep */ 815 BHND_NV_LOG("error mapping record for cookiep: %d\n", error); 816 return (NULL); 817 } 818 819 /* Validate the record pointer */ 820 env = ptr; 821 if (env->hdr.tag != NVRAM_TLV_TYPE_ENV) { 822 /* Should never occur with a valid cookiep */ 823 BHND_NV_LOG("non-ENV record mapped for %p\n", cookiep); 824 return (NULL); 825 } 826 827 /* Is the required variable name data is mapped? */ 828 if (navail < sizeof(struct bhnd_nvram_tlv_env_hdr) + env->hdr.size || 829 env->hdr.size == sizeof(env->flags)) 830 { 831 /* Should never occur with a valid cookiep */ 832 BHND_NV_LOG("TLV_ENV variable data not mapped for %p\n", 833 cookiep); 834 return (NULL); 835 } 836 837 return (env); 838 } 839 840 /** 841 * Return a cookiep for the given I/O offset. 842 */ 843 static void * 844 bhnd_nvram_tlv_to_cookie(struct bhnd_nvram_tlv *tlv, size_t io_offset) 845 { 846 const void *ptr; 847 int error; 848 849 BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(tlv->data), 850 ("io_offset %zu out-of-range", io_offset)); 851 BHND_NV_ASSERT(io_offset < UINTPTR_MAX, 852 ("io_offset %#zx exceeds UINTPTR_MAX", io_offset)); 853 854 error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_offset, NULL); 855 if (error) 856 BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error); 857 858 ptr = (const uint8_t *)ptr + io_offset; 859 return (__DECONST(void *, ptr)); 860 } 861 862 /* Convert a cookiep back to an I/O offset */ 863 static size_t 864 bhnd_nvram_tlv_to_offset(struct bhnd_nvram_tlv *tlv, void *cookiep) 865 { 866 const void *ptr; 867 intptr_t offset; 868 size_t io_size; 869 int error; 870 871 BHND_NV_ASSERT(cookiep != NULL, ("null cookiep")); 872 873 io_size = bhnd_nvram_io_getsize(tlv->data); 874 875 error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_size, NULL); 876 if (error) 877 BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error); 878 879 offset = (const uint8_t *)cookiep - (const uint8_t *)ptr; 880 BHND_NV_ASSERT(offset >= 0, ("invalid cookiep")); 881 BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)")); 882 BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)")); 883 884 return ((size_t)offset); 885 } 886