/*- * Copyright (c) 2016 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. */ #include __FBSDID("$FreeBSD$"); #include #include #ifdef _KERNEL #include #include #include #include #else /* !_KERNEL */ #include #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_data_bcmreg.h" #include "bhnd_nvram_data_bcmvar.h" /* * Broadcom NVRAM data class. * * The Broadcom NVRAM NUL-delimited ASCII format is used by most * Broadcom SoCs. * * The NVRAM data is encoded as a standard header, followed by series of * NUL-terminated 'key=value' strings; the end of the stream is denoted * by a single extra NUL character. */ struct bhnd_nvram_bcm; static struct bhnd_nvram_bcm_hvar *bhnd_nvram_bcm_gethdrvar( struct bhnd_nvram_bcm *bcm, const char *name); static struct bhnd_nvram_bcm_hvar *bhnd_nvram_bcm_to_hdrvar( struct bhnd_nvram_bcm *bcm, void *cookiep); static size_t bhnd_nvram_bcm_hdrvar_index( struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_bcm_hvar *hvar); /* * Set of BCM NVRAM header values that are required to be mirrored in the * NVRAM data itself. * * If they're not included in the parsed NVRAM data, we need to vend the * header-parsed values with their appropriate keys, and add them in any * updates to the NVRAM data. * * If they're modified in NVRAM, we need to sync the changes with the * the NVRAM header values. */ static const struct bhnd_nvram_bcm_hvar bhnd_nvram_bcm_hvars[] = { { .name = BCM_NVRAM_CFG0_SDRAM_INIT_VAR, .type = BHND_NVRAM_TYPE_UINT16, .len = sizeof(uint16_t), .nelem = 1, }, { .name = BCM_NVRAM_CFG1_SDRAM_CFG_VAR, .type = BHND_NVRAM_TYPE_UINT16, .len = sizeof(uint16_t), .nelem = 1, }, { .name = BCM_NVRAM_CFG1_SDRAM_REFRESH_VAR, .type = BHND_NVRAM_TYPE_UINT16, .len = sizeof(uint16_t), .nelem = 1, }, { .name = BCM_NVRAM_SDRAM_NCDL_VAR, .type = BHND_NVRAM_TYPE_UINT32, .len = sizeof(uint32_t), .nelem = 1, }, }; /** BCM NVRAM data class instance */ struct bhnd_nvram_bcm { struct bhnd_nvram_data nv; /**< common instance state */ struct bhnd_nvram_io *data; /**< backing buffer */ bhnd_nvram_plist *opts; /**< serialization options */ /** BCM header values */ struct bhnd_nvram_bcm_hvar hvars[nitems(bhnd_nvram_bcm_hvars)]; size_t count; /**< total variable count */ }; BHND_NVRAM_DATA_CLASS_DEFN(bcm, "Broadcom", BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_bcm)) static int bhnd_nvram_bcm_probe(struct bhnd_nvram_io *io) { struct bhnd_nvram_bcmhdr hdr; int error; if ((error = bhnd_nvram_io_read(io, 0x0, &hdr, sizeof(hdr)))) return (error); if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC) return (ENXIO); if (le32toh(hdr.size) > bhnd_nvram_io_getsize(io)) return (ENXIO); return (BHND_NVRAM_DATA_PROBE_DEFAULT); } /** * Parser states for bhnd_nvram_bcm_getvar_direct_common(). */ typedef enum { BCM_PARSE_KEY_START, BCM_PARSE_KEY_CONT, BCM_PARSE_KEY, BCM_PARSE_NEXT_KEY, BCM_PARSE_VALUE_START, BCM_PARSE_VALUE } bcm_parse_state; static int bhnd_nvram_bcm_getvar_direct(struct bhnd_nvram_io *io, const char *name, void *outp, size_t *olen, bhnd_nvram_type otype) { return (bhnd_nvram_bcm_getvar_direct_common(io, name, outp, olen, otype, true)); } /** * Common BCM/BCMRAW implementation of bhnd_nvram_getvar_direct(). */ int bhnd_nvram_bcm_getvar_direct_common(struct bhnd_nvram_io *io, const char *name, void *outp, size_t *olen, bhnd_nvram_type otype, bool have_header) { struct bhnd_nvram_bcmhdr hdr; char buf[512]; bcm_parse_state pstate; size_t limit, offset; size_t buflen, bufpos; size_t namelen, namepos; size_t vlen; int error; limit = bhnd_nvram_io_getsize(io); offset = 0; /* Fetch and validate the header */ if (have_header) { if ((error = bhnd_nvram_io_read(io, offset, &hdr, sizeof(hdr)))) return (error); if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC) return (ENXIO); offset += sizeof(hdr); limit = bhnd_nv_ummin(le32toh(hdr.size), limit); } /* Loop our parser until we find the requested variable, or hit EOF */ pstate = BCM_PARSE_KEY_START; buflen = 0; bufpos = 0; namelen = strlen(name); namepos = 0; vlen = 0; while ((offset - bufpos) < limit) { BHND_NV_ASSERT(bufpos <= buflen, ("buf position invalid (%zu > %zu)", bufpos, buflen)); BHND_NV_ASSERT(buflen <= sizeof(buf), ("buf length invalid (%zu > %zu", buflen, sizeof(buf))); /* Repopulate our parse buffer? */ if (buflen - bufpos == 0) { BHND_NV_ASSERT(offset < limit, ("offset overrun")); buflen = bhnd_nv_ummin(sizeof(buf), limit - offset); bufpos = 0; error = bhnd_nvram_io_read(io, offset, buf, buflen); if (error) return (error); offset += buflen; } switch (pstate) { case BCM_PARSE_KEY_START: BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!")); /* An extra '\0' denotes NVRAM EOF */ if (buf[bufpos] == '\0') return (ENOENT); /* Reset name matching position */ namepos = 0; /* Start name matching */ pstate = BCM_PARSE_KEY_CONT; break; case BCM_PARSE_KEY_CONT: { size_t navail, nleft; nleft = namelen - namepos; navail = bhnd_nv_ummin(buflen - bufpos, nleft); if (strncmp(name+namepos, buf+bufpos, navail) == 0) { /* Matched */ namepos += navail; bufpos += navail; /* If we've matched the full variable name, * look for its trailing delimiter */ if (namepos == namelen) pstate = BCM_PARSE_KEY; } else { /* No match; advance to next entry and restart * name matching */ pstate = BCM_PARSE_NEXT_KEY; } break; } case BCM_PARSE_KEY: BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!")); if (buf[bufpos] == '=') { /* Key fully matched; advance past '=' and * parse the value */ bufpos++; pstate = BCM_PARSE_VALUE_START; } else { /* No match; advance to next entry and restart * name matching */ pstate = BCM_PARSE_NEXT_KEY; } break; case BCM_PARSE_NEXT_KEY: { const char *p; /* Scan for a '\0' terminator */ p = memchr(buf+bufpos, '\0', buflen - bufpos); if (p != NULL) { /* Found entry terminator; restart name * matching at next entry */ pstate = BCM_PARSE_KEY_START; bufpos = (p - buf) + 1 /* skip '\0' */; } else { /* Consumed full buffer looking for '\0'; * force repopulation of the buffer and * retry */ bufpos = buflen; } break; } case BCM_PARSE_VALUE_START: { const char *p; /* Scan for a '\0' terminator */ p = memchr(buf+bufpos, '\0', buflen - bufpos); if (p != NULL) { /* Found entry terminator; parse the value */ vlen = p - &buf[bufpos]; pstate = BCM_PARSE_VALUE; } else if (p == NULL && offset == limit) { /* Hit EOF without a terminating '\0'; * treat the entry as implicitly terminated */ vlen = buflen - bufpos; pstate = BCM_PARSE_VALUE; } else if (p == NULL && bufpos > 0) { size_t nread; /* Move existing value data to start of * buffer */ memmove(buf, buf+bufpos, buflen - bufpos); buflen = bufpos; bufpos = 0; /* Populate full buffer to allow retry of * value parsing */ nread = bhnd_nv_ummin(sizeof(buf) - buflen, limit - offset); error = bhnd_nvram_io_read(io, offset, buf+buflen, nread); if (error) return (error); offset += nread; buflen += nread; } else { /* Value exceeds our buffer capacity */ BHND_NV_LOG("cannot parse value for '%s' " "(exceeds %zu byte limit)\n", name, sizeof(buf)); return (ENXIO); } break; } case BCM_PARSE_VALUE: BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun")); return (bhnd_nvram_value_coerce(buf+bufpos, vlen, BHND_NVRAM_TYPE_STRING, outp, olen, otype)); } } /* Variable not found */ return (ENOENT); } static int bhnd_nvram_bcm_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, size_t *olen) { struct bhnd_nvram_bcmhdr hdr; bhnd_nvram_prop *prop; size_t limit, nbytes; uint32_t sdram_ncdl; uint16_t sdram_init, sdram_cfg, sdram_refresh; uint8_t bcm_ver, crc8; int error; /* Determine output byte limit */ if (outp != NULL) limit = *olen; else limit = 0; /* Fetch required header variables */ #define PROPS_GET_HDRVAR(_name, _dest, _type) do { \ const char *name = BCM_NVRAM_ ## _name ## _VAR; \ if (!bhnd_nvram_plist_contains(props, name)) { \ BHND_NV_LOG("missing required property: %s\n", \ name); \ return (EFTYPE); \ } \ \ error = bhnd_nvram_plist_get_encoded(props, name, \ (_dest), sizeof(*(_dest)), \ BHND_NVRAM_TYPE_ ##_type); \ if (error) { \ BHND_NV_LOG("error reading required header " \ "%s property: %d\n", name, error); \ return (EFTYPE); \ } \ } while (0) PROPS_GET_HDRVAR(SDRAM_NCDL, &sdram_ncdl, UINT32); PROPS_GET_HDRVAR(CFG0_SDRAM_INIT, &sdram_init, UINT16); PROPS_GET_HDRVAR(CFG1_SDRAM_CFG, &sdram_cfg, UINT16); PROPS_GET_HDRVAR(CFG1_SDRAM_REFRESH, &sdram_refresh, UINT16); #undef PROPS_GET_HDRVAR /* Fetch BCM nvram version from options */ if (options != NULL && bhnd_nvram_plist_contains(options, BCM_NVRAM_ENCODE_OPT_VERSION)) { error = bhnd_nvram_plist_get_uint8(options, BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver); if (error) { BHND_NV_LOG("error reading %s uint8 option value: %d\n", BCM_NVRAM_ENCODE_OPT_VERSION, error); return (EINVAL); } } else { bcm_ver = BCM_NVRAM_CFG0_VER_DEFAULT; } /* Construct our header */ hdr = (struct bhnd_nvram_bcmhdr) { .magic = htole32(BCM_NVRAM_MAGIC), .size = 0, .cfg0 = 0, .cfg1 = 0, .sdram_ncdl = htole32(sdram_ncdl) }; hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, 0x0); hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER, bcm_ver); hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_SDRAM_INIT, htole16(sdram_init)); hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_CFG, htole16(sdram_cfg)); hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_REFRESH, htole16(sdram_refresh)); /* Write the header */ nbytes = sizeof(hdr); if (limit >= nbytes) memcpy(outp, &hdr, sizeof(hdr)); /* Write all properties */ prop = NULL; while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { const char *name; char *p; size_t prop_limit; size_t name_len, value_len; if (outp == NULL || limit < nbytes) { p = NULL; prop_limit = 0; } else { p = ((char *)outp) + nbytes; prop_limit = limit - nbytes; } /* Fetch and write name + '=' to output */ name = bhnd_nvram_prop_name(prop); name_len = strlen(name) + 1; if (prop_limit > name_len) { memcpy(p, name, name_len - 1); p[name_len - 1] = '='; prop_limit -= name_len; p += name_len; } else { prop_limit = 0; p = NULL; } /* Advance byte count */ if (SIZE_MAX - nbytes < name_len) return (EFTYPE); /* would overflow size_t */ nbytes += name_len; /* Attempt to write NUL-terminated value to output */ value_len = prop_limit; error = bhnd_nvram_prop_encode(prop, p, &value_len, BHND_NVRAM_TYPE_STRING); /* If encoding failed for any reason other than ENOMEM (which * we'll detect and report after encoding all properties), * return immediately */ if (error && error != ENOMEM) { BHND_NV_LOG("error serializing %s to required type " "%s: %d\n", name, bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), error); return (error); } /* Advance byte count */ if (SIZE_MAX - nbytes < value_len) return (EFTYPE); /* would overflow size_t */ nbytes += value_len; } /* Write terminating '\0' */ if (limit > nbytes) *((char *)outp + nbytes) = '\0'; if (nbytes == SIZE_MAX) return (EFTYPE); /* would overflow size_t */ else nbytes++; /* Update header length; this must fit within the header's 32-bit size * field */ if (nbytes <= UINT32_MAX) { hdr.size = (uint32_t)nbytes; } else { BHND_NV_LOG("size %zu exceeds maximum supported size of %u " "bytes\n", nbytes, UINT32_MAX); return (EFTYPE); } /* Provide required length */ *olen = nbytes; if (limit < *olen) { if (outp == NULL) return (0); return (ENOMEM); } /* Calculate the CRC value */ BHND_NV_ASSERT(nbytes >= BCM_NVRAM_CRC_SKIP, ("invalid output size")); crc8 = bhnd_nvram_crc8((uint8_t *)outp + BCM_NVRAM_CRC_SKIP, nbytes - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL); /* Update CRC and write the finalized header */ BHND_NV_ASSERT(nbytes >= sizeof(hdr), ("invalid output size")); hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, crc8); memcpy(outp, &hdr, sizeof(hdr)); return (0); } /** * Initialize @p bcm with the provided NVRAM data mapped by @p src. * * @param bcm A newly allocated data instance. */ static int bhnd_nvram_bcm_init(struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_io *src) { struct bhnd_nvram_bcmhdr hdr; uint8_t *p; void *ptr; size_t io_offset, io_size; uint8_t crc, valid, bcm_ver; int error; if ((error = bhnd_nvram_io_read(src, 0x0, &hdr, sizeof(hdr)))) return (error); if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC) return (ENXIO); /* Fetch the actual NVRAM image size */ io_size = le32toh(hdr.size); if (io_size < sizeof(hdr)) { /* The header size must include the header itself */ BHND_NV_LOG("corrupt header size: %zu\n", io_size); return (EINVAL); } if (io_size > bhnd_nvram_io_getsize(src)) { BHND_NV_LOG("header size %zu exceeds input size %zu\n", io_size, bhnd_nvram_io_getsize(src)); return (EINVAL); } /* Allocate a buffer large enough to hold the NVRAM image, and * an extra EOF-signaling NUL (on the chance it's missing from the * source data) */ if (io_size == SIZE_MAX) return (ENOMEM); bcm->data = bhnd_nvram_iobuf_empty(io_size, io_size + 1); if (bcm->data == NULL) return (ENOMEM); /* Fetch a pointer into our backing buffer and copy in the * NVRAM image. */ error = bhnd_nvram_io_write_ptr(bcm->data, 0x0, &ptr, io_size, NULL); if (error) return (error); p = ptr; if ((error = bhnd_nvram_io_read(src, 0x0, p, io_size))) return (error); /* Verify the CRC */ valid = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC); crc = bhnd_nvram_crc8(p + BCM_NVRAM_CRC_SKIP, io_size - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL); if (crc != valid) { BHND_NV_LOG("warning: NVRAM CRC error (crc=%#hhx, " "expected=%hhx)\n", crc, valid); } /* Populate header variable definitions */ #define BCM_READ_HDR_VAR(_name, _dest, _swap) do { \ struct bhnd_nvram_bcm_hvar *data; \ data = bhnd_nvram_bcm_gethdrvar(bcm, _name ##_VAR); \ BHND_NV_ASSERT(data != NULL, \ ("no such header variable: " __STRING(_name))); \ \ \ data->value. _dest = _swap(BCM_NVRAM_GET_BITS( \ hdr. _name ## _FIELD, _name)); \ } while(0) BCM_READ_HDR_VAR(BCM_NVRAM_CFG0_SDRAM_INIT, u16, le16toh); BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_CFG, u16, le16toh); BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_REFRESH, u16, le16toh); BCM_READ_HDR_VAR(BCM_NVRAM_SDRAM_NCDL, u32, le32toh); _Static_assert(nitems(bcm->hvars) == 4, "missing initialization for" "NVRAM header variable(s)"); #undef BCM_READ_HDR_VAR /* Process the buffer */ bcm->count = 0; io_offset = sizeof(hdr); while (io_offset < io_size) { char *envp; const char *name, *value; size_t envp_len; size_t name_len, value_len; /* Parse the key=value string */ envp = (char *) (p + io_offset); envp_len = strnlen(envp, io_size - io_offset); error = bhnd_nvram_parse_env(envp, envp_len, '=', &name, &name_len, &value, &value_len); if (error) { BHND_NV_LOG("error parsing envp at offset %#zx: %d\n", io_offset, error); return (error); } /* Insert a '\0' character, replacing the '=' delimiter and * allowing us to vend references directly to the variable * name */ *(envp + name_len) = '\0'; /* Record any NVRAM variables that mirror our header variables. * This is a brute-force search -- for the amount of data we're * operating on, it shouldn't be an issue. */ for (size_t i = 0; i < nitems(bcm->hvars); i++) { struct bhnd_nvram_bcm_hvar *hvar; union bhnd_nvram_bcm_hvar_value hval; size_t hval_len; hvar = &bcm->hvars[i]; /* Already matched? */ if (hvar->envp != NULL) continue; /* Name matches? */ if ((strcmp(name, hvar->name)) != 0) continue; /* Save pointer to mirrored envp */ hvar->envp = envp; /* Check for stale value */ hval_len = sizeof(hval); error = bhnd_nvram_value_coerce(value, value_len, BHND_NVRAM_TYPE_STRING, &hval, &hval_len, hvar->type); if (error) { /* If parsing fails, we can likely only make * things worse by trying to synchronize the * variables */ BHND_NV_LOG("error parsing header variable " "'%s=%s': %d\n", name, value, error); } else if (hval_len != hvar->len) { hvar->stale = true; } else if (memcmp(&hval, &hvar->value, hval_len) != 0) { hvar->stale = true; } } /* Seek past the value's terminating '\0' */ io_offset += envp_len; if (io_offset == io_size) { BHND_NV_LOG("missing terminating NUL at offset %#zx\n", io_offset); return (EINVAL); } if (*(p + io_offset) != '\0') { BHND_NV_LOG("invalid terminator '%#hhx' at offset " "%#zx\n", *(p + io_offset), io_offset); return (EINVAL); } /* Update variable count */ bcm->count++; /* Seek to the next record */ if (++io_offset == io_size) { char ch; /* Hit EOF without finding a terminating NUL * byte; we need to grow our buffer and append * it */ io_size++; if ((error = bhnd_nvram_io_setsize(bcm->data, io_size))) return (error); /* Write NUL byte */ ch = '\0'; error = bhnd_nvram_io_write(bcm->data, io_size-1, &ch, sizeof(ch)); if (error) return (error); } /* Check for explicit EOF (encoded as a single empty NUL * terminated string) */ if (*(p + io_offset) == '\0') break; } /* Add non-mirrored header variables to total count variable */ for (size_t i = 0; i < nitems(bcm->hvars); i++) { if (bcm->hvars[i].envp == NULL) bcm->count++; } /* Populate serialization options from our header */ bcm_ver = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER); error = bhnd_nvram_plist_append_bytes(bcm->opts, BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver, sizeof(bcm_ver), BHND_NVRAM_TYPE_UINT8); if (error) return (error); return (0); } static int bhnd_nvram_bcm_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) { struct bhnd_nvram_bcm *bcm; int error; bcm = (struct bhnd_nvram_bcm *)nv; /* Populate default BCM mirrored header variable set */ _Static_assert(sizeof(bcm->hvars) == sizeof(bhnd_nvram_bcm_hvars), "hvar declarations must match bhnd_nvram_bcm_hvars template"); memcpy(bcm->hvars, bhnd_nvram_bcm_hvars, sizeof(bcm->hvars)); /* Allocate (empty) option list, to be populated by * bhnd_nvram_bcm_init() */ bcm->opts = bhnd_nvram_plist_new(); if (bcm->opts == NULL) return (ENOMEM); /* Parse the BCM input data and initialize our backing * data representation */ if ((error = bhnd_nvram_bcm_init(bcm, io))) { bhnd_nvram_bcm_free(nv); return (error); } return (0); } static void bhnd_nvram_bcm_free(struct bhnd_nvram_data *nv) { struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv; if (bcm->data != NULL) bhnd_nvram_io_free(bcm->data); if (bcm->opts != NULL) bhnd_nvram_plist_release(bcm->opts); } size_t bhnd_nvram_bcm_count(struct bhnd_nvram_data *nv) { struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv; return (bcm->count); } static bhnd_nvram_plist * bhnd_nvram_bcm_options(struct bhnd_nvram_data *nv) { struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv; return (bcm->opts); } static uint32_t bhnd_nvram_bcm_caps(struct bhnd_nvram_data *nv) { return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS); } static const char * bhnd_nvram_bcm_next(struct bhnd_nvram_data *nv, void **cookiep) { struct bhnd_nvram_bcm *bcm; struct bhnd_nvram_bcm_hvar *hvar, *hvar_next; const void *ptr; const char *envp, *basep; size_t io_size, io_offset; int error; bcm = (struct bhnd_nvram_bcm *)nv; io_offset = sizeof(struct bhnd_nvram_bcmhdr); io_size = bhnd_nvram_io_getsize(bcm->data) - io_offset; /* Map backing buffer */ error = bhnd_nvram_io_read_ptr(bcm->data, io_offset, &ptr, io_size, NULL); if (error) { BHND_NV_LOG("error mapping backing buffer: %d\n", error); return (NULL); } basep = ptr; /* If cookiep pointers into our header variable array, handle as header * variable iteration. */ hvar = bhnd_nvram_bcm_to_hdrvar(bcm, *cookiep); if (hvar != NULL) { size_t idx; /* Advance to next entry, if any */ idx = bhnd_nvram_bcm_hdrvar_index(bcm, hvar) + 1; /* Find the next header-defined variable that isn't defined in * the NVRAM data, start iteration there */ for (size_t i = idx; i < nitems(bcm->hvars); i++) { hvar_next = &bcm->hvars[i]; if (hvar_next->envp != NULL && !hvar_next->stale) continue; *cookiep = hvar_next; return (hvar_next->name); } /* No further header-defined variables; iteration * complete */ return (NULL); } /* Handle standard NVRAM data iteration */ if (*cookiep == NULL) { /* Start at the first NVRAM data record */ envp = basep; } else { /* Seek to next record */ envp = *cookiep; envp += strlen(envp) + 1; /* key + '\0' */ envp += strlen(envp) + 1; /* value + '\0' */ } /* * Skip entries that have an existing header variable entry that takes * precedence over the NVRAM data value. * * The header's value will be provided when performing header variable * iteration */ while ((size_t)(envp - basep) < io_size && *envp != '\0') { /* Locate corresponding header variable */ hvar = NULL; for (size_t i = 0; i < nitems(bcm->hvars); i++) { if (bcm->hvars[i].envp != envp) continue; hvar = &bcm->hvars[i]; break; } /* If no corresponding hvar entry, or the entry does not take * precedence over this NVRAM value, we can safely return this * value as-is. */ if (hvar == NULL || !hvar->stale) break; /* Seek to next record */ envp += strlen(envp) + 1; /* key + '\0' */ envp += strlen(envp) + 1; /* value + '\0' */ } /* On NVRAM data EOF, try switching to header variables */ if ((size_t)(envp - basep) == io_size || *envp == '\0') { /* Find first valid header variable */ for (size_t i = 0; i < nitems(bcm->hvars); i++) { if (bcm->hvars[i].envp != NULL) continue; *cookiep = &bcm->hvars[i]; return (bcm->hvars[i].name); } /* No header variables */ return (NULL); } *cookiep = __DECONST(void *, envp); return (envp); } static void * bhnd_nvram_bcm_find(struct bhnd_nvram_data *nv, const char *name) { return (bhnd_nvram_data_generic_find(nv, name)); } static int bhnd_nvram_bcm_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2) { struct bhnd_nvram_bcm *bcm; struct bhnd_nvram_bcm_hvar *hvar1, *hvar2; bcm = (struct bhnd_nvram_bcm *)nv; hvar1 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep1); hvar2 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep2); /* Header variables are always ordered below any variables defined * in the BCM data */ if (hvar1 != NULL && hvar2 == NULL) { return (1); /* hvar follows non-hvar */ } else if (hvar1 == NULL && hvar2 != NULL) { return (-1); /* non-hvar precedes hvar */ } /* Otherwise, both cookies are either hvars or non-hvars. We can * safely fall back on pointer order, which will provide a correct * ordering matching the behavior of bhnd_nvram_data_next() for * both cases */ if (cookiep1 < cookiep2) return (-1); if (cookiep1 > cookiep2) return (1); return (0); } static int bhnd_nvram_bcm_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf, size_t *len, bhnd_nvram_type type) { return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type)); } static int bhnd_nvram_bcm_copy_val(struct bhnd_nvram_data *nv, void *cookiep, bhnd_nvram_val **value) { return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value)); } static const void * bhnd_nvram_bcm_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type) { struct bhnd_nvram_bcm *bcm; struct bhnd_nvram_bcm_hvar *hvar; const char *envp; bcm = (struct bhnd_nvram_bcm *)nv; /* Handle header variables */ if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) { BHND_NV_ASSERT(bhnd_nvram_value_check_aligned(&hvar->value, hvar->len, hvar->type) == 0, ("value misaligned")); *type = hvar->type; *len = hvar->len; return (&hvar->value); } /* Cookie points to key\0value\0 -- get the value address */ BHND_NV_ASSERT(cookiep != NULL, ("NULL cookiep")); envp = cookiep; envp += strlen(envp) + 1; /* key + '\0' */ *len = strlen(envp) + 1; /* value + '\0' */ *type = BHND_NVRAM_TYPE_STRING; return (envp); } static const char * bhnd_nvram_bcm_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) { struct bhnd_nvram_bcm *bcm; struct bhnd_nvram_bcm_hvar *hvar; bcm = (struct bhnd_nvram_bcm *)nv; /* Handle header variables */ if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) { return (hvar->name); } /* Cookie points to key\0value\0 */ return (cookiep); } static int bhnd_nvram_bcm_filter_setvar(struct bhnd_nvram_data *nv, const char *name, bhnd_nvram_val *value, bhnd_nvram_val **result) { bhnd_nvram_val *str; int error; /* Name (trimmed of any path prefix) must be valid */ if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name))) return (EINVAL); /* Value must be bcm-formatted string */ error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt, value, BHND_NVRAM_VAL_DYNAMIC); if (error) return (error); /* Success. Transfer result ownership to the caller. */ *result = str; return (0); } static int bhnd_nvram_bcm_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { /* We permit deletion of any variable */ return (0); } /** * Return the internal BCM data reference for a header-defined variable * with @p name, or NULL if none exists. */ static struct bhnd_nvram_bcm_hvar * bhnd_nvram_bcm_gethdrvar(struct bhnd_nvram_bcm *bcm, const char *name) { for (size_t i = 0; i < nitems(bcm->hvars); i++) { if (strcmp(bcm->hvars[i].name, name) == 0) return (&bcm->hvars[i]); } /* Not found */ return (NULL); } /** * If @p cookiep references a header-defined variable, return the * internal BCM data reference. Otherwise, returns NULL. */ static struct bhnd_nvram_bcm_hvar * bhnd_nvram_bcm_to_hdrvar(struct bhnd_nvram_bcm *bcm, void *cookiep) { #ifdef BHND_NVRAM_INVARIANTS uintptr_t base, ptr; #endif /* If the cookie falls within the hvar array, it's a * header variable cookie */ if (nitems(bcm->hvars) == 0) return (NULL); if (cookiep < (void *)&bcm->hvars[0]) return (NULL); if (cookiep > (void *)&bcm->hvars[nitems(bcm->hvars)-1]) return (NULL); #ifdef BHND_NVRAM_INVARIANTS base = (uintptr_t)bcm->hvars; ptr = (uintptr_t)cookiep; BHND_NV_ASSERT((ptr - base) % sizeof(bcm->hvars[0]) == 0, ("misaligned hvar pointer %p/%p", cookiep, bcm->hvars)); #endif /* INVARIANTS */ return ((struct bhnd_nvram_bcm_hvar *)cookiep); } /** * Return the index of @p hdrvar within @p bcm's backing hvars array. */ static size_t bhnd_nvram_bcm_hdrvar_index(struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_bcm_hvar *hdrvar) { BHND_NV_ASSERT(bhnd_nvram_bcm_to_hdrvar(bcm, (void *)hdrvar) != NULL, ("%p is not a valid hdrvar reference", hdrvar)); return (hdrvar - &bcm->hvars[0]); }