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