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 #include <sys/param.h> 32 #include <sys/endian.h> 33 34 #ifdef _KERNEL 35 36 #include <sys/bus.h> 37 #include <sys/ctype.h> 38 #include <sys/malloc.h> 39 #include <sys/systm.h> 40 41 #else /* !_KERNEL */ 42 43 #include <ctype.h> 44 #include <stdint.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 49 #endif /* _KERNEL */ 50 51 #include "bhnd_nvram_private.h" 52 53 #include "bhnd_nvram_datavar.h" 54 55 #include "bhnd_nvram_data_bcmreg.h" 56 #include "bhnd_nvram_data_bcmvar.h" 57 58 /* 59 * Broadcom NVRAM data class. 60 * 61 * The Broadcom NVRAM NUL-delimited ASCII format is used by most 62 * Broadcom SoCs. 63 * 64 * The NVRAM data is encoded as a standard header, followed by series of 65 * NUL-terminated 'key=value' strings; the end of the stream is denoted 66 * by a single extra NUL character. 67 */ 68 69 struct bhnd_nvram_bcm; 70 71 static struct bhnd_nvram_bcm_hvar *bhnd_nvram_bcm_gethdrvar( 72 struct bhnd_nvram_bcm *bcm, 73 const char *name); 74 static struct bhnd_nvram_bcm_hvar *bhnd_nvram_bcm_to_hdrvar( 75 struct bhnd_nvram_bcm *bcm, 76 void *cookiep); 77 static size_t bhnd_nvram_bcm_hdrvar_index( 78 struct bhnd_nvram_bcm *bcm, 79 struct bhnd_nvram_bcm_hvar *hvar); 80 /* 81 * Set of BCM NVRAM header values that are required to be mirrored in the 82 * NVRAM data itself. 83 * 84 * If they're not included in the parsed NVRAM data, we need to vend the 85 * header-parsed values with their appropriate keys, and add them in any 86 * updates to the NVRAM data. 87 * 88 * If they're modified in NVRAM, we need to sync the changes with the 89 * the NVRAM header values. 90 */ 91 static const struct bhnd_nvram_bcm_hvar bhnd_nvram_bcm_hvars[] = { 92 { 93 .name = BCM_NVRAM_CFG0_SDRAM_INIT_VAR, 94 .type = BHND_NVRAM_TYPE_UINT16, 95 .len = sizeof(uint16_t), 96 .nelem = 1, 97 }, 98 { 99 .name = BCM_NVRAM_CFG1_SDRAM_CFG_VAR, 100 .type = BHND_NVRAM_TYPE_UINT16, 101 .len = sizeof(uint16_t), 102 .nelem = 1, 103 }, 104 { 105 .name = BCM_NVRAM_CFG1_SDRAM_REFRESH_VAR, 106 .type = BHND_NVRAM_TYPE_UINT16, 107 .len = sizeof(uint16_t), 108 .nelem = 1, 109 }, 110 { 111 .name = BCM_NVRAM_SDRAM_NCDL_VAR, 112 .type = BHND_NVRAM_TYPE_UINT32, 113 .len = sizeof(uint32_t), 114 .nelem = 1, 115 }, 116 }; 117 118 /** BCM NVRAM data class instance */ 119 struct bhnd_nvram_bcm { 120 struct bhnd_nvram_data nv; /**< common instance state */ 121 struct bhnd_nvram_io *data; /**< backing buffer */ 122 bhnd_nvram_plist *opts; /**< serialization options */ 123 124 /** BCM header values */ 125 struct bhnd_nvram_bcm_hvar hvars[nitems(bhnd_nvram_bcm_hvars)]; 126 127 size_t count; /**< total variable count */ 128 }; 129 130 BHND_NVRAM_DATA_CLASS_DEFN(bcm, "Broadcom", BHND_NVRAM_DATA_CAP_DEVPATHS, 131 sizeof(struct bhnd_nvram_bcm)) 132 133 static int 134 bhnd_nvram_bcm_probe(struct bhnd_nvram_io *io) 135 { 136 struct bhnd_nvram_bcmhdr hdr; 137 int error; 138 139 if ((error = bhnd_nvram_io_read(io, 0x0, &hdr, sizeof(hdr)))) 140 return (error); 141 142 if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC) 143 return (ENXIO); 144 145 if (le32toh(hdr.size) > bhnd_nvram_io_getsize(io)) 146 return (ENXIO); 147 148 return (BHND_NVRAM_DATA_PROBE_DEFAULT); 149 } 150 151 /** 152 * Parser states for bhnd_nvram_bcm_getvar_direct_common(). 153 */ 154 typedef enum { 155 BCM_PARSE_KEY_START, 156 BCM_PARSE_KEY_CONT, 157 BCM_PARSE_KEY, 158 BCM_PARSE_NEXT_KEY, 159 BCM_PARSE_VALUE_START, 160 BCM_PARSE_VALUE 161 } bcm_parse_state; 162 163 static int 164 bhnd_nvram_bcm_getvar_direct(struct bhnd_nvram_io *io, const char *name, 165 void *outp, size_t *olen, bhnd_nvram_type otype) 166 { 167 return (bhnd_nvram_bcm_getvar_direct_common(io, name, outp, olen, otype, 168 true)); 169 } 170 171 /** 172 * Common BCM/BCMRAW implementation of bhnd_nvram_getvar_direct(). 173 */ 174 int 175 bhnd_nvram_bcm_getvar_direct_common(struct bhnd_nvram_io *io, const char *name, 176 void *outp, size_t *olen, bhnd_nvram_type otype, bool have_header) 177 { 178 struct bhnd_nvram_bcmhdr hdr; 179 char buf[512]; 180 bcm_parse_state pstate; 181 size_t limit, offset; 182 size_t buflen, bufpos; 183 size_t namelen, namepos; 184 size_t vlen; 185 int error; 186 187 limit = bhnd_nvram_io_getsize(io); 188 offset = 0; 189 190 /* Fetch and validate the header */ 191 if (have_header) { 192 if ((error = bhnd_nvram_io_read(io, offset, &hdr, sizeof(hdr)))) 193 return (error); 194 195 if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC) 196 return (ENXIO); 197 198 offset += sizeof(hdr); 199 limit = bhnd_nv_ummin(le32toh(hdr.size), limit); 200 } 201 202 /* Loop our parser until we find the requested variable, or hit EOF */ 203 pstate = BCM_PARSE_KEY_START; 204 buflen = 0; 205 bufpos = 0; 206 namelen = strlen(name); 207 namepos = 0; 208 vlen = 0; 209 210 while ((offset - bufpos) < limit) { 211 BHND_NV_ASSERT(bufpos <= buflen, 212 ("buf position invalid (%zu > %zu)", bufpos, buflen)); 213 BHND_NV_ASSERT(buflen <= sizeof(buf), 214 ("buf length invalid (%zu > %zu", buflen, sizeof(buf))); 215 216 /* Repopulate our parse buffer? */ 217 if (buflen - bufpos == 0) { 218 BHND_NV_ASSERT(offset < limit, ("offset overrun")); 219 220 buflen = bhnd_nv_ummin(sizeof(buf), limit - offset); 221 bufpos = 0; 222 223 error = bhnd_nvram_io_read(io, offset, buf, buflen); 224 if (error) 225 return (error); 226 227 offset += buflen; 228 } 229 230 switch (pstate) { 231 case BCM_PARSE_KEY_START: 232 BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!")); 233 234 /* An extra '\0' denotes NVRAM EOF */ 235 if (buf[bufpos] == '\0') 236 return (ENOENT); 237 238 /* Reset name matching position */ 239 namepos = 0; 240 241 /* Start name matching */ 242 pstate = BCM_PARSE_KEY_CONT; 243 break; 244 245 case BCM_PARSE_KEY_CONT: { 246 size_t navail, nleft; 247 248 nleft = namelen - namepos; 249 navail = bhnd_nv_ummin(buflen - bufpos, nleft); 250 251 if (strncmp(name+namepos, buf+bufpos, navail) == 0) { 252 /* Matched */ 253 namepos += navail; 254 bufpos += navail; 255 256 /* If we've matched the full variable name, 257 * look for its trailing delimiter */ 258 if (namepos == namelen) 259 pstate = BCM_PARSE_KEY; 260 } else { 261 /* No match; advance to next entry and restart 262 * name matching */ 263 pstate = BCM_PARSE_NEXT_KEY; 264 } 265 266 break; 267 } 268 269 case BCM_PARSE_KEY: 270 BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!")); 271 272 if (buf[bufpos] == '=') { 273 /* Key fully matched; advance past '=' and 274 * parse the value */ 275 bufpos++; 276 pstate = BCM_PARSE_VALUE_START; 277 } else { 278 /* No match; advance to next entry and restart 279 * name matching */ 280 pstate = BCM_PARSE_NEXT_KEY; 281 } 282 283 break; 284 285 case BCM_PARSE_NEXT_KEY: { 286 const char *p; 287 288 /* Scan for a '\0' terminator */ 289 p = memchr(buf+bufpos, '\0', buflen - bufpos); 290 291 if (p != NULL) { 292 /* Found entry terminator; restart name 293 * matching at next entry */ 294 pstate = BCM_PARSE_KEY_START; 295 bufpos = (p - buf) + 1 /* skip '\0' */; 296 } else { 297 /* Consumed full buffer looking for '\0'; 298 * force repopulation of the buffer and 299 * retry */ 300 bufpos = buflen; 301 } 302 303 break; 304 } 305 306 case BCM_PARSE_VALUE_START: { 307 const char *p; 308 309 /* Scan for a '\0' terminator */ 310 p = memchr(buf+bufpos, '\0', buflen - bufpos); 311 312 if (p != NULL) { 313 /* Found entry terminator; parse the value */ 314 vlen = p - &buf[bufpos]; 315 pstate = BCM_PARSE_VALUE; 316 317 } else if (p == NULL && offset == limit) { 318 /* Hit EOF without a terminating '\0'; 319 * treat the entry as implicitly terminated */ 320 vlen = buflen - bufpos; 321 pstate = BCM_PARSE_VALUE; 322 323 } else if (p == NULL && bufpos > 0) { 324 size_t nread; 325 326 /* Move existing value data to start of 327 * buffer */ 328 memmove(buf, buf+bufpos, buflen - bufpos); 329 buflen = bufpos; 330 bufpos = 0; 331 332 /* Populate full buffer to allow retry of 333 * value parsing */ 334 nread = bhnd_nv_ummin(sizeof(buf) - buflen, 335 limit - offset); 336 337 error = bhnd_nvram_io_read(io, offset, 338 buf+buflen, nread); 339 if (error) 340 return (error); 341 342 offset += nread; 343 buflen += nread; 344 } else { 345 /* Value exceeds our buffer capacity */ 346 BHND_NV_LOG("cannot parse value for '%s' " 347 "(exceeds %zu byte limit)\n", name, 348 sizeof(buf)); 349 350 return (ENXIO); 351 } 352 353 break; 354 } 355 356 case BCM_PARSE_VALUE: 357 BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun")); 358 359 return (bhnd_nvram_value_coerce(buf+bufpos, vlen, 360 BHND_NVRAM_TYPE_STRING, outp, olen, otype)); 361 } 362 } 363 364 /* Variable not found */ 365 return (ENOENT); 366 } 367 368 static int 369 bhnd_nvram_bcm_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, 370 bhnd_nvram_plist *options, void *outp, size_t *olen) 371 { 372 struct bhnd_nvram_bcmhdr hdr; 373 bhnd_nvram_prop *prop; 374 size_t limit, nbytes; 375 uint32_t sdram_ncdl; 376 uint16_t sdram_init, sdram_cfg, sdram_refresh; 377 uint8_t bcm_ver, crc8; 378 int error; 379 380 /* Determine output byte limit */ 381 if (outp != NULL) 382 limit = *olen; 383 else 384 limit = 0; 385 386 /* Fetch required header variables */ 387 #define PROPS_GET_HDRVAR(_name, _dest, _type) do { \ 388 const char *name = BCM_NVRAM_ ## _name ## _VAR; \ 389 if (!bhnd_nvram_plist_contains(props, name)) { \ 390 BHND_NV_LOG("missing required property: %s\n", \ 391 name); \ 392 return (EFTYPE); \ 393 } \ 394 \ 395 error = bhnd_nvram_plist_get_encoded(props, name, \ 396 (_dest), sizeof(*(_dest)), \ 397 BHND_NVRAM_TYPE_ ##_type); \ 398 if (error) { \ 399 BHND_NV_LOG("error reading required header " \ 400 "%s property: %d\n", name, error); \ 401 return (EFTYPE); \ 402 } \ 403 } while (0) 404 405 PROPS_GET_HDRVAR(SDRAM_NCDL, &sdram_ncdl, UINT32); 406 PROPS_GET_HDRVAR(CFG0_SDRAM_INIT, &sdram_init, UINT16); 407 PROPS_GET_HDRVAR(CFG1_SDRAM_CFG, &sdram_cfg, UINT16); 408 PROPS_GET_HDRVAR(CFG1_SDRAM_REFRESH, &sdram_refresh, UINT16); 409 410 #undef PROPS_GET_HDRVAR 411 412 /* Fetch BCM nvram version from options */ 413 if (options != NULL && 414 bhnd_nvram_plist_contains(options, BCM_NVRAM_ENCODE_OPT_VERSION)) 415 { 416 error = bhnd_nvram_plist_get_uint8(options, 417 BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver); 418 if (error) { 419 BHND_NV_LOG("error reading %s uint8 option value: %d\n", 420 BCM_NVRAM_ENCODE_OPT_VERSION, error); 421 return (EINVAL); 422 } 423 } else { 424 bcm_ver = BCM_NVRAM_CFG0_VER_DEFAULT; 425 } 426 427 /* Construct our header */ 428 hdr = (struct bhnd_nvram_bcmhdr) { 429 .magic = htole32(BCM_NVRAM_MAGIC), 430 .size = 0, 431 .cfg0 = 0, 432 .cfg1 = 0, 433 .sdram_ncdl = htole32(sdram_ncdl) 434 }; 435 436 hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, 0x0); 437 hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER, bcm_ver); 438 hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_SDRAM_INIT, 439 htole16(sdram_init)); 440 441 hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_CFG, 442 htole16(sdram_cfg)); 443 hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_REFRESH, 444 htole16(sdram_refresh)); 445 446 /* Write the header */ 447 nbytes = sizeof(hdr); 448 if (limit >= nbytes) 449 memcpy(outp, &hdr, sizeof(hdr)); 450 451 /* Write all properties */ 452 prop = NULL; 453 while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { 454 const char *name; 455 char *p; 456 size_t prop_limit; 457 size_t name_len, value_len; 458 459 if (outp == NULL || limit < nbytes) { 460 p = NULL; 461 prop_limit = 0; 462 } else { 463 p = ((char *)outp) + nbytes; 464 prop_limit = limit - nbytes; 465 } 466 467 /* Fetch and write name + '=' to output */ 468 name = bhnd_nvram_prop_name(prop); 469 name_len = strlen(name) + 1; 470 471 if (prop_limit > name_len) { 472 memcpy(p, name, name_len - 1); 473 p[name_len - 1] = '='; 474 475 prop_limit -= name_len; 476 p += name_len; 477 } else { 478 prop_limit = 0; 479 p = NULL; 480 } 481 482 /* Advance byte count */ 483 if (SIZE_MAX - nbytes < name_len) 484 return (EFTYPE); /* would overflow size_t */ 485 486 nbytes += name_len; 487 488 /* Attempt to write NUL-terminated value to output */ 489 value_len = prop_limit; 490 error = bhnd_nvram_prop_encode(prop, p, &value_len, 491 BHND_NVRAM_TYPE_STRING); 492 493 /* If encoding failed for any reason other than ENOMEM (which 494 * we'll detect and report after encoding all properties), 495 * return immediately */ 496 if (error && error != ENOMEM) { 497 BHND_NV_LOG("error serializing %s to required type " 498 "%s: %d\n", name, 499 bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), 500 error); 501 return (error); 502 } 503 504 /* Advance byte count */ 505 if (SIZE_MAX - nbytes < value_len) 506 return (EFTYPE); /* would overflow size_t */ 507 508 nbytes += value_len; 509 } 510 511 /* Write terminating '\0' */ 512 if (limit > nbytes) 513 *((char *)outp + nbytes) = '\0'; 514 515 if (nbytes == SIZE_MAX) 516 return (EFTYPE); /* would overflow size_t */ 517 else 518 nbytes++; 519 520 /* Update header length; this must fit within the header's 32-bit size 521 * field */ 522 if (nbytes <= UINT32_MAX) { 523 hdr.size = (uint32_t)nbytes; 524 } else { 525 BHND_NV_LOG("size %zu exceeds maximum supported size of %u " 526 "bytes\n", nbytes, UINT32_MAX); 527 return (EFTYPE); 528 } 529 530 /* Provide required length */ 531 *olen = nbytes; 532 if (limit < *olen) { 533 if (outp == NULL) 534 return (0); 535 536 return (ENOMEM); 537 } 538 539 /* Calculate the CRC value */ 540 BHND_NV_ASSERT(nbytes >= BCM_NVRAM_CRC_SKIP, ("invalid output size")); 541 crc8 = bhnd_nvram_crc8((uint8_t *)outp + BCM_NVRAM_CRC_SKIP, 542 nbytes - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL); 543 544 /* Update CRC and write the finalized header */ 545 BHND_NV_ASSERT(nbytes >= sizeof(hdr), ("invalid output size")); 546 hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, crc8); 547 memcpy(outp, &hdr, sizeof(hdr)); 548 549 return (0); 550 } 551 552 /** 553 * Initialize @p bcm with the provided NVRAM data mapped by @p src. 554 * 555 * @param bcm A newly allocated data instance. 556 */ 557 static int 558 bhnd_nvram_bcm_init(struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_io *src) 559 { 560 struct bhnd_nvram_bcmhdr hdr; 561 uint8_t *p; 562 void *ptr; 563 size_t io_offset, io_size; 564 uint8_t crc, valid, bcm_ver; 565 int error; 566 567 if ((error = bhnd_nvram_io_read(src, 0x0, &hdr, sizeof(hdr)))) 568 return (error); 569 570 if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC) 571 return (ENXIO); 572 573 /* Fetch the actual NVRAM image size */ 574 io_size = le32toh(hdr.size); 575 if (io_size < sizeof(hdr)) { 576 /* The header size must include the header itself */ 577 BHND_NV_LOG("corrupt header size: %zu\n", io_size); 578 return (EINVAL); 579 } 580 581 if (io_size > bhnd_nvram_io_getsize(src)) { 582 BHND_NV_LOG("header size %zu exceeds input size %zu\n", 583 io_size, bhnd_nvram_io_getsize(src)); 584 return (EINVAL); 585 } 586 587 /* Allocate a buffer large enough to hold the NVRAM image, and 588 * an extra EOF-signaling NUL (on the chance it's missing from the 589 * source data) */ 590 if (io_size == SIZE_MAX) 591 return (ENOMEM); 592 593 bcm->data = bhnd_nvram_iobuf_empty(io_size, io_size + 1); 594 if (bcm->data == NULL) 595 return (ENOMEM); 596 597 /* Fetch a pointer into our backing buffer and copy in the 598 * NVRAM image. */ 599 error = bhnd_nvram_io_write_ptr(bcm->data, 0x0, &ptr, io_size, NULL); 600 if (error) 601 return (error); 602 603 p = ptr; 604 if ((error = bhnd_nvram_io_read(src, 0x0, p, io_size))) 605 return (error); 606 607 /* Verify the CRC */ 608 valid = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC); 609 crc = bhnd_nvram_crc8(p + BCM_NVRAM_CRC_SKIP, 610 io_size - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL); 611 612 if (crc != valid) { 613 BHND_NV_LOG("warning: NVRAM CRC error (crc=%#hhx, " 614 "expected=%hhx)\n", crc, valid); 615 } 616 617 /* Populate header variable definitions */ 618 #define BCM_READ_HDR_VAR(_name, _dest, _swap) do { \ 619 struct bhnd_nvram_bcm_hvar *data; \ 620 data = bhnd_nvram_bcm_gethdrvar(bcm, _name ##_VAR); \ 621 BHND_NV_ASSERT(data != NULL, \ 622 ("no such header variable: " __STRING(_name))); \ 623 \ 624 \ 625 data->value. _dest = _swap(BCM_NVRAM_GET_BITS( \ 626 hdr. _name ## _FIELD, _name)); \ 627 } while(0) 628 629 BCM_READ_HDR_VAR(BCM_NVRAM_CFG0_SDRAM_INIT, u16, le16toh); 630 BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_CFG, u16, le16toh); 631 BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_REFRESH, u16, le16toh); 632 BCM_READ_HDR_VAR(BCM_NVRAM_SDRAM_NCDL, u32, le32toh); 633 634 _Static_assert(nitems(bcm->hvars) == 4, "missing initialization for" 635 "NVRAM header variable(s)"); 636 637 #undef BCM_READ_HDR_VAR 638 639 /* Process the buffer */ 640 bcm->count = 0; 641 io_offset = sizeof(hdr); 642 while (io_offset < io_size) { 643 char *envp; 644 const char *name, *value; 645 size_t envp_len; 646 size_t name_len, value_len; 647 648 /* Parse the key=value string */ 649 envp = (char *) (p + io_offset); 650 envp_len = strnlen(envp, io_size - io_offset); 651 error = bhnd_nvram_parse_env(envp, envp_len, '=', &name, 652 &name_len, &value, &value_len); 653 if (error) { 654 BHND_NV_LOG("error parsing envp at offset %#zx: %d\n", 655 io_offset, error); 656 return (error); 657 } 658 659 /* Insert a '\0' character, replacing the '=' delimiter and 660 * allowing us to vend references directly to the variable 661 * name */ 662 *(envp + name_len) = '\0'; 663 664 /* Record any NVRAM variables that mirror our header variables. 665 * This is a brute-force search -- for the amount of data we're 666 * operating on, it shouldn't be an issue. */ 667 for (size_t i = 0; i < nitems(bcm->hvars); i++) { 668 struct bhnd_nvram_bcm_hvar *hvar; 669 union bhnd_nvram_bcm_hvar_value hval; 670 size_t hval_len; 671 672 hvar = &bcm->hvars[i]; 673 674 /* Already matched? */ 675 if (hvar->envp != NULL) 676 continue; 677 678 /* Name matches? */ 679 if ((strcmp(name, hvar->name)) != 0) 680 continue; 681 682 /* Save pointer to mirrored envp */ 683 hvar->envp = envp; 684 685 /* Check for stale value */ 686 hval_len = sizeof(hval); 687 error = bhnd_nvram_value_coerce(value, value_len, 688 BHND_NVRAM_TYPE_STRING, &hval, &hval_len, 689 hvar->type); 690 if (error) { 691 /* If parsing fails, we can likely only make 692 * things worse by trying to synchronize the 693 * variables */ 694 BHND_NV_LOG("error parsing header variable " 695 "'%s=%s': %d\n", name, value, error); 696 } else if (hval_len != hvar->len) { 697 hvar->stale = true; 698 } else if (memcmp(&hval, &hvar->value, hval_len) != 0) { 699 hvar->stale = true; 700 } 701 } 702 703 /* Seek past the value's terminating '\0' */ 704 io_offset += envp_len; 705 if (io_offset == io_size) { 706 BHND_NV_LOG("missing terminating NUL at offset %#zx\n", 707 io_offset); 708 return (EINVAL); 709 } 710 711 if (*(p + io_offset) != '\0') { 712 BHND_NV_LOG("invalid terminator '%#hhx' at offset " 713 "%#zx\n", *(p + io_offset), io_offset); 714 return (EINVAL); 715 } 716 717 /* Update variable count */ 718 bcm->count++; 719 720 /* Seek to the next record */ 721 if (++io_offset == io_size) { 722 char ch; 723 724 /* Hit EOF without finding a terminating NUL 725 * byte; we need to grow our buffer and append 726 * it */ 727 io_size++; 728 if ((error = bhnd_nvram_io_setsize(bcm->data, io_size))) 729 return (error); 730 731 /* Write NUL byte */ 732 ch = '\0'; 733 error = bhnd_nvram_io_write(bcm->data, io_size-1, &ch, 734 sizeof(ch)); 735 if (error) 736 return (error); 737 } 738 739 /* Check for explicit EOF (encoded as a single empty NUL 740 * terminated string) */ 741 if (*(p + io_offset) == '\0') 742 break; 743 } 744 745 /* Add non-mirrored header variables to total count variable */ 746 for (size_t i = 0; i < nitems(bcm->hvars); i++) { 747 if (bcm->hvars[i].envp == NULL) 748 bcm->count++; 749 } 750 751 /* Populate serialization options from our header */ 752 bcm_ver = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER); 753 error = bhnd_nvram_plist_append_bytes(bcm->opts, 754 BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver, sizeof(bcm_ver), 755 BHND_NVRAM_TYPE_UINT8); 756 if (error) 757 return (error); 758 759 return (0); 760 } 761 762 static int 763 bhnd_nvram_bcm_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) 764 { 765 struct bhnd_nvram_bcm *bcm; 766 int error; 767 768 bcm = (struct bhnd_nvram_bcm *)nv; 769 770 /* Populate default BCM mirrored header variable set */ 771 _Static_assert(sizeof(bcm->hvars) == sizeof(bhnd_nvram_bcm_hvars), 772 "hvar declarations must match bhnd_nvram_bcm_hvars template"); 773 memcpy(bcm->hvars, bhnd_nvram_bcm_hvars, sizeof(bcm->hvars)); 774 775 /* Allocate (empty) option list, to be populated by 776 * bhnd_nvram_bcm_init() */ 777 bcm->opts = bhnd_nvram_plist_new(); 778 if (bcm->opts == NULL) 779 return (ENOMEM); 780 781 /* Parse the BCM input data and initialize our backing 782 * data representation */ 783 if ((error = bhnd_nvram_bcm_init(bcm, io))) { 784 bhnd_nvram_bcm_free(nv); 785 return (error); 786 } 787 788 return (0); 789 } 790 791 static void 792 bhnd_nvram_bcm_free(struct bhnd_nvram_data *nv) 793 { 794 struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv; 795 796 if (bcm->data != NULL) 797 bhnd_nvram_io_free(bcm->data); 798 799 if (bcm->opts != NULL) 800 bhnd_nvram_plist_release(bcm->opts); 801 } 802 803 size_t 804 bhnd_nvram_bcm_count(struct bhnd_nvram_data *nv) 805 { 806 struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv; 807 return (bcm->count); 808 } 809 810 static bhnd_nvram_plist * 811 bhnd_nvram_bcm_options(struct bhnd_nvram_data *nv) 812 { 813 struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv; 814 return (bcm->opts); 815 } 816 817 static uint32_t 818 bhnd_nvram_bcm_caps(struct bhnd_nvram_data *nv) 819 { 820 return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS); 821 } 822 823 static const char * 824 bhnd_nvram_bcm_next(struct bhnd_nvram_data *nv, void **cookiep) 825 { 826 struct bhnd_nvram_bcm *bcm; 827 struct bhnd_nvram_bcm_hvar *hvar, *hvar_next; 828 const void *ptr; 829 const char *envp, *basep; 830 size_t io_size, io_offset; 831 int error; 832 833 bcm = (struct bhnd_nvram_bcm *)nv; 834 835 io_offset = sizeof(struct bhnd_nvram_bcmhdr); 836 io_size = bhnd_nvram_io_getsize(bcm->data) - io_offset; 837 838 /* Map backing buffer */ 839 error = bhnd_nvram_io_read_ptr(bcm->data, io_offset, &ptr, io_size, 840 NULL); 841 if (error) { 842 BHND_NV_LOG("error mapping backing buffer: %d\n", error); 843 return (NULL); 844 } 845 846 basep = ptr; 847 848 /* If cookiep pointers into our header variable array, handle as header 849 * variable iteration. */ 850 hvar = bhnd_nvram_bcm_to_hdrvar(bcm, *cookiep); 851 if (hvar != NULL) { 852 size_t idx; 853 854 /* Advance to next entry, if any */ 855 idx = bhnd_nvram_bcm_hdrvar_index(bcm, hvar) + 1; 856 857 /* Find the next header-defined variable that isn't defined in 858 * the NVRAM data, start iteration there */ 859 for (size_t i = idx; i < nitems(bcm->hvars); i++) { 860 hvar_next = &bcm->hvars[i]; 861 if (hvar_next->envp != NULL && !hvar_next->stale) 862 continue; 863 864 *cookiep = hvar_next; 865 return (hvar_next->name); 866 } 867 868 /* No further header-defined variables; iteration 869 * complete */ 870 return (NULL); 871 } 872 873 /* Handle standard NVRAM data iteration */ 874 if (*cookiep == NULL) { 875 /* Start at the first NVRAM data record */ 876 envp = basep; 877 } else { 878 /* Seek to next record */ 879 envp = *cookiep; 880 envp += strlen(envp) + 1; /* key + '\0' */ 881 envp += strlen(envp) + 1; /* value + '\0' */ 882 } 883 884 /* 885 * Skip entries that have an existing header variable entry that takes 886 * precedence over the NVRAM data value. 887 * 888 * The header's value will be provided when performing header variable 889 * iteration 890 */ 891 while ((size_t)(envp - basep) < io_size && *envp != '\0') { 892 /* Locate corresponding header variable */ 893 hvar = NULL; 894 for (size_t i = 0; i < nitems(bcm->hvars); i++) { 895 if (bcm->hvars[i].envp != envp) 896 continue; 897 898 hvar = &bcm->hvars[i]; 899 break; 900 } 901 902 /* If no corresponding hvar entry, or the entry does not take 903 * precedence over this NVRAM value, we can safely return this 904 * value as-is. */ 905 if (hvar == NULL || !hvar->stale) 906 break; 907 908 /* Seek to next record */ 909 envp += strlen(envp) + 1; /* key + '\0' */ 910 envp += strlen(envp) + 1; /* value + '\0' */ 911 } 912 913 /* On NVRAM data EOF, try switching to header variables */ 914 if ((size_t)(envp - basep) == io_size || *envp == '\0') { 915 /* Find first valid header variable */ 916 for (size_t i = 0; i < nitems(bcm->hvars); i++) { 917 if (bcm->hvars[i].envp != NULL) 918 continue; 919 920 *cookiep = &bcm->hvars[i]; 921 return (bcm->hvars[i].name); 922 } 923 924 /* No header variables */ 925 return (NULL); 926 } 927 928 *cookiep = __DECONST(void *, envp); 929 return (envp); 930 } 931 932 static void * 933 bhnd_nvram_bcm_find(struct bhnd_nvram_data *nv, const char *name) 934 { 935 return (bhnd_nvram_data_generic_find(nv, name)); 936 } 937 938 static int 939 bhnd_nvram_bcm_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, 940 void *cookiep2) 941 { 942 struct bhnd_nvram_bcm *bcm; 943 struct bhnd_nvram_bcm_hvar *hvar1, *hvar2; 944 945 bcm = (struct bhnd_nvram_bcm *)nv; 946 947 hvar1 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep1); 948 hvar2 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep2); 949 950 /* Header variables are always ordered below any variables defined 951 * in the BCM data */ 952 if (hvar1 != NULL && hvar2 == NULL) { 953 return (1); /* hvar follows non-hvar */ 954 } else if (hvar1 == NULL && hvar2 != NULL) { 955 return (-1); /* non-hvar precedes hvar */ 956 } 957 958 /* Otherwise, both cookies are either hvars or non-hvars. We can 959 * safely fall back on pointer order, which will provide a correct 960 * ordering matching the behavior of bhnd_nvram_data_next() for 961 * both cases */ 962 if (cookiep1 < cookiep2) 963 return (-1); 964 965 if (cookiep1 > cookiep2) 966 return (1); 967 968 return (0); 969 } 970 971 static int 972 bhnd_nvram_bcm_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf, 973 size_t *len, bhnd_nvram_type type) 974 { 975 return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type)); 976 } 977 978 static int 979 bhnd_nvram_bcm_copy_val(struct bhnd_nvram_data *nv, void *cookiep, 980 bhnd_nvram_val **value) 981 { 982 return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value)); 983 } 984 985 static const void * 986 bhnd_nvram_bcm_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, 987 size_t *len, bhnd_nvram_type *type) 988 { 989 struct bhnd_nvram_bcm *bcm; 990 struct bhnd_nvram_bcm_hvar *hvar; 991 const char *envp; 992 993 bcm = (struct bhnd_nvram_bcm *)nv; 994 995 /* Handle header variables */ 996 if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) { 997 BHND_NV_ASSERT(bhnd_nvram_value_check_aligned(&hvar->value, 998 hvar->len, hvar->type) == 0, ("value misaligned")); 999 1000 *type = hvar->type; 1001 *len = hvar->len; 1002 return (&hvar->value); 1003 } 1004 1005 /* Cookie points to key\0value\0 -- get the value address */ 1006 BHND_NV_ASSERT(cookiep != NULL, ("NULL cookiep")); 1007 1008 envp = cookiep; 1009 envp += strlen(envp) + 1; /* key + '\0' */ 1010 *len = strlen(envp) + 1; /* value + '\0' */ 1011 *type = BHND_NVRAM_TYPE_STRING; 1012 1013 return (envp); 1014 } 1015 1016 static const char * 1017 bhnd_nvram_bcm_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) 1018 { 1019 struct bhnd_nvram_bcm *bcm; 1020 struct bhnd_nvram_bcm_hvar *hvar; 1021 1022 bcm = (struct bhnd_nvram_bcm *)nv; 1023 1024 /* Handle header variables */ 1025 if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) { 1026 return (hvar->name); 1027 } 1028 1029 /* Cookie points to key\0value\0 */ 1030 return (cookiep); 1031 } 1032 1033 static int 1034 bhnd_nvram_bcm_filter_setvar(struct bhnd_nvram_data *nv, const char *name, 1035 bhnd_nvram_val *value, bhnd_nvram_val **result) 1036 { 1037 bhnd_nvram_val *str; 1038 int error; 1039 1040 /* Name (trimmed of any path prefix) must be valid */ 1041 if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name))) 1042 return (EINVAL); 1043 1044 /* Value must be bcm-formatted string */ 1045 error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt, 1046 value, BHND_NVRAM_VAL_DYNAMIC); 1047 if (error) 1048 return (error); 1049 1050 /* Success. Transfer result ownership to the caller. */ 1051 *result = str; 1052 return (0); 1053 } 1054 1055 static int 1056 bhnd_nvram_bcm_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) 1057 { 1058 /* We permit deletion of any variable */ 1059 return (0); 1060 } 1061 1062 /** 1063 * Return the internal BCM data reference for a header-defined variable 1064 * with @p name, or NULL if none exists. 1065 */ 1066 static struct bhnd_nvram_bcm_hvar * 1067 bhnd_nvram_bcm_gethdrvar(struct bhnd_nvram_bcm *bcm, const char *name) 1068 { 1069 for (size_t i = 0; i < nitems(bcm->hvars); i++) { 1070 if (strcmp(bcm->hvars[i].name, name) == 0) 1071 return (&bcm->hvars[i]); 1072 } 1073 1074 /* Not found */ 1075 return (NULL); 1076 } 1077 1078 /** 1079 * If @p cookiep references a header-defined variable, return the 1080 * internal BCM data reference. Otherwise, returns NULL. 1081 */ 1082 static struct bhnd_nvram_bcm_hvar * 1083 bhnd_nvram_bcm_to_hdrvar(struct bhnd_nvram_bcm *bcm, void *cookiep) 1084 { 1085 #ifdef BHND_NVRAM_INVARIANTS 1086 uintptr_t base, ptr; 1087 #endif 1088 1089 /* If the cookie falls within the hvar array, it's a 1090 * header variable cookie */ 1091 if (nitems(bcm->hvars) == 0) 1092 return (NULL); 1093 1094 if (cookiep < (void *)&bcm->hvars[0]) 1095 return (NULL); 1096 1097 if (cookiep > (void *)&bcm->hvars[nitems(bcm->hvars)-1]) 1098 return (NULL); 1099 1100 #ifdef BHND_NVRAM_INVARIANTS 1101 base = (uintptr_t)bcm->hvars; 1102 ptr = (uintptr_t)cookiep; 1103 1104 BHND_NV_ASSERT((ptr - base) % sizeof(bcm->hvars[0]) == 0, 1105 ("misaligned hvar pointer %p/%p", cookiep, bcm->hvars)); 1106 #endif /* INVARIANTS */ 1107 1108 return ((struct bhnd_nvram_bcm_hvar *)cookiep); 1109 } 1110 1111 /** 1112 * Return the index of @p hdrvar within @p bcm's backing hvars array. 1113 */ 1114 static size_t 1115 bhnd_nvram_bcm_hdrvar_index(struct bhnd_nvram_bcm *bcm, 1116 struct bhnd_nvram_bcm_hvar *hdrvar) 1117 { 1118 BHND_NV_ASSERT(bhnd_nvram_bcm_to_hdrvar(bcm, (void *)hdrvar) != NULL, 1119 ("%p is not a valid hdrvar reference", hdrvar)); 1120 1121 return (hdrvar - &bcm->hvars[0]); 1122 } 1123