Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data.c (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data.c (revision 310296) @@ -1,710 +1,722 @@ /*- * Copyright (c) 2015-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$"); #ifdef _KERNEL #include #include #include #else /* !_KERNEL */ #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_io.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_data.h" /** * Return a human-readable description for the given NVRAM data class. * * @param cls The NVRAM class. */ const char * bhnd_nvram_data_class_desc(bhnd_nvram_data_class *cls) { return (cls->desc); } /** * Probe to see if this NVRAM data class class supports the data mapped by the * given I/O context, returning a BHND_NVRAM_DATA_PROBE probe result. * * @param cls The NVRAM class. * @param io An I/O context mapping the NVRAM data. * * @retval 0 if this is the only possible NVRAM data class for @p io. * @retval negative if the probe succeeds, a negative value should be returned; * the class returning the highest negative value should be selected to handle * NVRAM parsing. * @retval ENXIO If the NVRAM format is not handled by @p cls. * @retval positive if an error occurs during probing, a regular unix error * code should be returned. */ int bhnd_nvram_data_probe(bhnd_nvram_data_class *cls, struct bhnd_nvram_io *io) { return (cls->op_probe(io)); } /** * Probe to see if an NVRAM data class in @p classes supports parsing * of the data mapped by @p io, returning the parsed data in @p data. * * The caller is responsible for deallocating the returned instance via * bhnd_nvram_data_release(). * * @param[out] data On success, the parsed NVRAM data instance. * @param io An I/O context mapping the NVRAM data to be copied and parsed. * @param classes An array of NVRAM data classes to be probed, or NULL to * probe the default supported set. * @param num_classes The number of NVRAM data classes in @p classes. * * @retval 0 success * @retval ENXIO if no class is found capable of parsing @p io. * @retval non-zero if an error otherwise occurs during allocation, * initialization, or parsing of the NVRAM data, a regular unix error code * will be returned. */ int bhnd_nvram_data_probe_classes(struct bhnd_nvram_data **data, struct bhnd_nvram_io *io, bhnd_nvram_data_class *classes[], size_t num_classes) { bhnd_nvram_data_class *cls; int error, prio, result; cls = NULL; prio = 0; *data = NULL; /* If class array is NULL, default to our linker set */ if (classes == NULL) { classes = SET_BEGIN(bhnd_nvram_data_class_set); num_classes = SET_COUNT(bhnd_nvram_data_class_set); } /* Try to find the best data class capable of parsing io */ for (size_t i = 0; i < num_classes; i++) { bhnd_nvram_data_class *next_cls; next_cls = classes[i]; /* Try to probe */ result = bhnd_nvram_data_probe(next_cls, io); /* The parser did not match if an error was returned */ if (result > 0) continue; /* Lower priority than previous match; keep * searching */ if (cls != NULL && result <= prio) continue; /* Drop any previously parsed data */ if (*data != NULL) { bhnd_nvram_data_release(*data); *data = NULL; } /* If this is a 'maybe' match, attempt actual parsing to * verify that this does in fact match */ if (result <= BHND_NVRAM_DATA_PROBE_MAYBE) { /* If parsing fails, keep searching */ error = bhnd_nvram_data_new(next_cls, data, io); if (error) continue; } /* Record best new match */ prio = result; cls = next_cls; /* Terminate search immediately on * BHND_NVRAM_DATA_PROBE_SPECIFIC */ if (result == BHND_NVRAM_DATA_PROBE_SPECIFIC) break; } /* If no match, return error */ if (cls == NULL) return (ENXIO); /* If the NVRAM data was not parsed above, do so now */ if (*data == NULL) { if ((error = bhnd_nvram_data_new(cls, data, io))) return (error); } return (0); } /** * Allocate and initialize a new instance of data class @p cls, copying and * parsing NVRAM data from @p io. * * The caller is responsible for releasing the returned parser instance * reference via bhnd_nvram_data_release(). * * @param cls If non-NULL, the data class to be allocated. If NULL, * bhnd_nvram_data_probe_classes() will be used to determine the data format. * @param[out] nv On success, a pointer to the newly allocated NVRAM data instance. * @param io An I/O context mapping the NVRAM data to be copied and parsed. * * @retval 0 success * @retval non-zero if an error occurs during allocation or initialization, a * regular unix error code will be returned. */ int bhnd_nvram_data_new(bhnd_nvram_data_class *cls, struct bhnd_nvram_data **nv, struct bhnd_nvram_io *io) { struct bhnd_nvram_data *data; int error; /* If NULL, try to identify the appropriate class */ if (cls == NULL) return (bhnd_nvram_data_probe_classes(nv, io, NULL, 0)); /* Allocate new instance */ BHND_NV_ASSERT(sizeof(struct bhnd_nvram_data) <= cls->size, ("instance size %zu less than minimum %zu", cls->size, sizeof(struct bhnd_nvram_data))); data = bhnd_nv_calloc(1, cls->size); data->cls = cls; refcount_init(&data->refs, 1); /* Let the class handle initialization */ if ((error = cls->op_new(data, io))) { bhnd_nv_free(data); return (error); } *nv = data; return (0); } /** * Retain and return a reference to the given data instance. * * @param nv The reference to be retained. */ struct bhnd_nvram_data * bhnd_nvram_data_retain(struct bhnd_nvram_data *nv) { refcount_acquire(&nv->refs); return (nv); } /** * Release a reference to the given data instance. * * If this is the last reference, the data instance and its associated * resources will be freed. * * @param nv The reference to be released. */ void bhnd_nvram_data_release(struct bhnd_nvram_data *nv) { if (!refcount_release(&nv->refs)) return; /* Free any internal resources */ nv->cls->op_free(nv); /* Free the instance allocation */ bhnd_nv_free(nv); } /** * Return a pointer to @p nv's data class. * * @param nv The NVRAM data instance to be queried. */ bhnd_nvram_data_class * bhnd_nvram_data_get_class(struct bhnd_nvram_data *nv) { return (nv->cls); } /** * Return the number of variables in @p nv. * * @param nv The NVRAM data to be queried. */ size_t bhnd_nvram_data_count(struct bhnd_nvram_data *nv) { return (nv->cls->op_count(nv)); } /** + * Return a borrowed reference to the serialization options for @p nv, + * suitable for use with bhnd_nvram_data_serialize(), or NULL if none. + * + * @param nv The NVRAM data to be queried. + */ +bhnd_nvram_plist * +bhnd_nvram_data_options(struct bhnd_nvram_data *nv) +{ + return (nv->cls->op_options(nv)); +} + +/** * Compute the size of the serialized form of @p nv. * * Serialization may be performed via bhnd_nvram_data_serialize(). * * @param nv The NVRAM data to be queried. * @param[out] len On success, will be set to the computed size. * * @retval 0 success * @retval non-zero if computing the serialized size otherwise fails, a * regular unix error code will be returned. */ int bhnd_nvram_data_size(struct bhnd_nvram_data *nv, size_t *len) { return (nv->cls->op_size(nv, len)); } /** * Serialize the NVRAM data to @p buf, using the NVRAM data class' native * format. * * The resulting serialization may be reparsed with @p nv's BHND NVRAM data * class. * * @param nv The NVRAM data to be serialized. * @param[out] buf On success, the serialed NVRAM data will be * written to this buffer. This argment may be * NULL if the value is not desired. * @param[in,out] len The capacity of @p buf. On success, will be set * to the actual length of the serialized data. * * @retval 0 success * @retval ENOMEM If @p buf is non-NULL and a buffer of @p len is too * small to hold the serialized data. * @retval non-zero If serialization otherwise fails, a regular unix error * code will be returned. */ int bhnd_nvram_data_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) { return (nv->cls->op_serialize(nv, buf, len)); } /** * Return the capability flags (@see BHND_NVRAM_DATA_CAP_*) for @p nv. * * @param nv The NVRAM data to be queried. */ uint32_t bhnd_nvram_data_caps(struct bhnd_nvram_data *nv) { return (nv->cls->op_caps(nv)); } /** * Iterate over @p nv, returning the names of subsequent variables. * * @param nv The NVRAM data to be iterated. * @param[in,out] cookiep A pointer to a cookiep value previously returned * by bhnd_nvram_data_next(), or a NULL value to * begin iteration. * * @return Returns the next variable name, or NULL if there are no more * variables defined in @p nv. */ const char * bhnd_nvram_data_next(struct bhnd_nvram_data *nv, void **cookiep) { const char *name; #ifdef BHND_NV_INVARIANTS void *prev = *cookiep; #endif /* Fetch next */ if ((name = nv->cls->op_next(nv, cookiep)) == NULL) return (NULL); /* Enforce precedence ordering invariant between bhnd_nvram_data_next() * and bhnd_nvram_data_getvar_order() */ #ifdef BHND_NV_INVARIANTS if (prev != NULL && bhnd_nvram_data_getvar_order(nv, prev, *cookiep) > 0) { BHND_NV_PANIC("%s: returned out-of-order entry", __FUNCTION__); } #endif return (name); } /** * Search @p nv for a named variable, returning the variable's opaque reference * if found, or NULL if unavailable. * * The BHND_NVRAM_DATA_CAP_INDEXED capability flag will be returned by * bhnd_nvram_data_caps() if @p nv supports effecient name-based * lookups. * * @param nv The NVRAM data to search. * @param name The name to search for. * * @retval non-NULL If @p name is found, the opaque cookie value will be * returned. * @retval NULL If @p name is not found. */ void * bhnd_nvram_data_find(struct bhnd_nvram_data *nv, const char *name) { return (nv->cls->op_find(nv, name)); } /** * A generic implementation of bhnd_nvram_data_find(). * * This implementation will use bhnd_nvram_data_next() to perform a * simple O(n) case-insensitve search for @p name. */ void * bhnd_nvram_data_generic_find(struct bhnd_nvram_data *nv, const char *name) { const char *next; void *cookiep; cookiep = NULL; while ((next = bhnd_nvram_data_next(nv, &cookiep))) { if (strcmp(name, next) == 0) return (cookiep); } /* Not found */ return (NULL); } /** * Compare the declaration order of two NVRAM variables. * * Variable declaration order is used to determine the current order of * the variables in the source data, as well as to determine the precedence * of variable declarations in data sources that define duplicate names. * * The comparison order will match the order of variables returned via * bhnd_nvstore_path_data_next(). * * @param nv The NVRAM data. * @param cookiep1 An NVRAM variable cookie previously * returned via bhnd_nvram_data_next() or * bhnd_nvram_data_find(). * @param cookiep2 An NVRAM variable cookie previously * returned via bhnd_nvram_data_next() or * bhnd_nvram_data_find(). * * @retval <= -1 If @p cookiep1 has an earlier declaration order than * @p cookiep2. * @retval 0 If @p cookiep1 and @p cookiep2 are identical. * @retval >= 1 If @p cookiep has a later declaration order than * @p cookiep2. */ int bhnd_nvram_data_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2) { return (nv->cls->op_getvar_order(nv, cookiep1, cookiep2)); } /** * Read a variable and decode as @p type. * * @param nv The NVRAM data. * @param cookiep An NVRAM variable cookie previously returned * via bhnd_nvram_data_next() or * bhnd_nvram_data_find(). * @param[out] buf On success, the requested value will be written * to this buffer. This argment may be NULL if * the value is not desired. * @param[in,out] len The capacity of @p buf. On success, will be set * to the actual size of the requested value. * @param type The data type to be written to @p buf. * * @retval 0 success * @retval ENOMEM If @p buf is non-NULL and a buffer of @p len is too * small to hold the requested value. * @retval EFTYPE If the variable data cannot be coerced to @p type. * @retval ERANGE If value coercion would overflow @p type. */ int bhnd_nvram_data_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf, size_t *len, bhnd_nvram_type type) { return (nv->cls->op_getvar(nv, cookiep, buf, len, type)); } /* * Common bhnd_nvram_data_getvar_ptr() wrapper used by * bhnd_nvram_data_generic_rp_getvar() and * bhnd_nvram_data_generic_rp_copy_val(). * * If a variable definition for the requested variable is found via * bhnd_nvram_find_vardefn(), the definition will be used to populate fmt. */ static const void * bhnd_nvram_data_getvar_ptr_info(struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type, const bhnd_nvram_val_fmt **fmt) { const struct bhnd_nvram_vardefn *vdefn; const char *name; const void *vptr; BHND_NV_ASSERT(bhnd_nvram_data_caps(nv) & BHND_NVRAM_DATA_CAP_READ_PTR, ("instance does not advertise READ_PTR support")); /* Fetch pointer to variable data */ vptr = bhnd_nvram_data_getvar_ptr(nv, cookiep, len, type); if (vptr == NULL) return (NULL); /* Select a default value format implementation */ /* Fetch the reference variable name */ name = bhnd_nvram_data_getvar_name(nv, cookiep); /* Trim path prefix, if any; the Broadcom NVRAM format assumes a global * namespace for all variable definitions */ if (bhnd_nvram_data_caps(nv) & BHND_NVRAM_DATA_CAP_DEVPATHS) name = bhnd_nvram_trim_path_name(name); /* Check the variable definition table for a matching entry; if * it exists, use it to populate the value format. */ vdefn = bhnd_nvram_find_vardefn(name); if (vdefn != NULL) { BHND_NV_ASSERT(vdefn->fmt != NULL, ("NULL format for %s", name)); *fmt = vdefn->fmt; } else if (*type == BHND_NVRAM_TYPE_STRING) { /* Default to Broadcom-specific string interpretation */ *fmt = &bhnd_nvram_val_bcm_string_fmt; } else { /* Fall back on native formatting */ *fmt = bhnd_nvram_val_default_fmt(*type); } return (vptr); } /** * A generic implementation of bhnd_nvram_data_getvar(). * * This implementation will call bhnd_nvram_data_getvar_ptr() to fetch * a pointer to the variable data and perform data coercion on behalf * of the caller. * * If a variable definition for the requested variable is available via * bhnd_nvram_find_vardefn(), the definition will be used to provide a * formatting instance to bhnd_nvram_val_init(). */ int bhnd_nvram_data_generic_rp_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *outp, size_t *olen, bhnd_nvram_type otype) { bhnd_nvram_val val; const bhnd_nvram_val_fmt *fmt; const void *vptr; bhnd_nvram_type vtype; size_t vlen; int error; BHND_NV_ASSERT(bhnd_nvram_data_caps(nv) & BHND_NVRAM_DATA_CAP_READ_PTR, ("instance does not advertise READ_PTR support")); /* Fetch variable data and value format*/ vptr = bhnd_nvram_data_getvar_ptr_info(nv, cookiep, &vlen, &vtype, &fmt); if (vptr == NULL) return (EINVAL); /* Attempt value coercion */ error = bhnd_nvram_val_init(&val, fmt, vptr, vlen, vtype, BHND_NVRAM_VAL_BORROW_DATA); if (error) return (error); error = bhnd_nvram_val_encode(&val, outp, olen, otype); /* Clean up */ bhnd_nvram_val_release(&val); return (error); } /** * Return a caller-owned copy of an NVRAM entry's variable data. * * The caller is responsible for deallocating the returned value via * bhnd_nvram_val_release(). * * @param nv The NVRAM data. * @param cookiep An NVRAM variable cookie previously returned * via bhnd_nvram_data_next() or bhnd_nvram_data_find(). * @param[out] value On success, the caller-owned value instance. * * @retval 0 success * @retval ENOMEM If allocation fails. * @retval non-zero If initialization of the value otherwise fails, a * regular unix error code will be returned. */ int bhnd_nvram_data_copy_val(struct bhnd_nvram_data *nv, void *cookiep, bhnd_nvram_val **value) { return (nv->cls->op_copy_val(nv, cookiep, value)); } /** * A generic implementation of bhnd_nvram_data_copy_val(). * * This implementation will call bhnd_nvram_data_getvar_ptr() to fetch * a pointer to the variable data and perform data coercion on behalf * of the caller. * * If a variable definition for the requested variable is available via * bhnd_nvram_find_vardefn(), the definition will be used to provide a * formatting instance to bhnd_nvram_val_init(). */ int bhnd_nvram_data_generic_rp_copy_val(struct bhnd_nvram_data *nv, void *cookiep, bhnd_nvram_val **value) { const bhnd_nvram_val_fmt *fmt; const void *vptr; bhnd_nvram_type vtype; size_t vlen; BHND_NV_ASSERT(bhnd_nvram_data_caps(nv) & BHND_NVRAM_DATA_CAP_READ_PTR, ("instance does not advertise READ_PTR support")); /* Fetch variable data and value format*/ vptr = bhnd_nvram_data_getvar_ptr_info(nv, cookiep, &vlen, &vtype, &fmt); if (vptr == NULL) return (EINVAL); /* Allocate and return the new value instance */ return (bhnd_nvram_val_new(value, fmt, vptr, vlen, vtype, BHND_NVRAM_VAL_DYNAMIC)); } /** * If available and supported by the NVRAM data instance, return a reference * to the internal buffer containing an entry's variable data, * * Note that string values may not be NUL terminated. * * @param nv The NVRAM data. * @param cookiep An NVRAM variable cookie previously returned * via bhnd_nvram_data_next() or * bhnd_nvram_data_find(). * @param[out] len On success, will be set to the actual size of * the requested value. * @param[out] type The data type of the entry data. * * @retval non-NULL success * @retval NULL if direct data access is unsupported by @p nv, or * unavailable for @p cookiep. */ const void * bhnd_nvram_data_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type) { return (nv->cls->op_getvar_ptr(nv, cookiep, len, type)); } /** * Return the variable name associated with a given @p cookiep. * @param nv The NVRAM data to be iterated. * @param[in,out] cookiep A pointer to a cookiep value previously returned * via bhnd_nvram_data_next() or * bhnd_nvram_data_find(). * * @return Returns the variable's name. */ const char * bhnd_nvram_data_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) { return (nv->cls->op_getvar_name(nv, cookiep)); } /** * Filter a request to set variable @p name with @p value. * * On success, the caller owns a reference to @p result, and must release * any held resources via bhnd_nvram_val_release(). * * @param nv The NVRAM data instance. * @param name The name of the variable to be set. * @param value The proposed value to be set. * @param[out] result On success, a caller-owned reference to the filtered * value to be set. * * @retval 0 success * @retval ENOENT if @p name is unrecognized by @p nv. * @retval EINVAL if @p name is read-only. * @retval EINVAL if @p value cannot be converted to the required value * type. */ int bhnd_nvram_data_filter_setvar(struct bhnd_nvram_data *nv, const char *name, bhnd_nvram_val *value, bhnd_nvram_val **result) { return (nv->cls->op_filter_setvar(nv, name, value, result)); } /** * Filter a request to delete variable @p name. * * @param nv The NVRAM data instance. * @param name The name of the variable to be deleted. * * @retval 0 success * @retval ENOENT if @p name is unrecognized by @p nv. * @retval EINVAL if @p name is read-only. */ int bhnd_nvram_data_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { return (nv->cls->op_filter_unsetvar(nv, name)); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data.h (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data.h (revision 310296) @@ -1,148 +1,150 @@ /*- * Copyright (c) 2015-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. * * $FreeBSD$ */ #ifndef _BHND_NVRAM_BHND_NVRAM_DATA_H_ #define _BHND_NVRAM_BHND_NVRAM_DATA_H_ #ifdef _KERNEL #include #include #else /* !_KERNEL */ #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram.h" #include "bhnd_nvram_io.h" +#include "bhnd_nvram_plist.h" #include "bhnd_nvram_value.h" /* NVRAM data class */ typedef struct bhnd_nvram_data_class bhnd_nvram_data_class; /* NVRAM data instance */ struct bhnd_nvram_data; /** Declare a bhnd_nvram_data_class with name @p _n */ #define BHND_NVRAM_DATA_CLASS_DECL(_n) \ extern struct bhnd_nvram_data_class bhnd_nvram_ ## _n ## _class BHND_NVRAM_DATA_CLASS_DECL(bcm); BHND_NVRAM_DATA_CLASS_DECL(bcmraw); BHND_NVRAM_DATA_CLASS_DECL(tlv); BHND_NVRAM_DATA_CLASS_DECL(btxt); BHND_NVRAM_DATA_CLASS_DECL(sprom); /** bhnd_nvram_data capabilities */ enum { /** Supports efficient lookup of variables by name */ BHND_NVRAM_DATA_CAP_INDEXED = (1<<0), /** Supports direct access to backing buffer */ BHND_NVRAM_DATA_CAP_READ_PTR = (1<<1), /** Supports device path prefixed variables */ BHND_NVRAM_DATA_CAP_DEVPATHS = (1<<2), }; /** * A standard set of probe priorities returned by bhnd_nvram_data_probe(). * * Priority is defined in ascending order, with 0 being the highest priority. * Return values greater than zero are interpreted as regular unix error codes. */ enum { BHND_NVRAM_DATA_PROBE_MAYBE = -40, /**< Possible match */ BHND_NVRAM_DATA_PROBE_DEFAULT = -20, /**< Definite match of a base OS-supplied data class */ BHND_NVRAM_DATA_PROBE_SPECIFIC = 0, /**< Terminate search and use this data class for parsing */ }; const char *bhnd_nvram_data_class_desc(bhnd_nvram_data_class *cls); int bhnd_nvram_data_probe(bhnd_nvram_data_class *cls, struct bhnd_nvram_io *io); int bhnd_nvram_data_probe_classes( struct bhnd_nvram_data **data, struct bhnd_nvram_io *io, bhnd_nvram_data_class *classes[], size_t num_classes); int bhnd_nvram_data_new(bhnd_nvram_data_class *cls, struct bhnd_nvram_data **nv, struct bhnd_nvram_io *io); struct bhnd_nvram_data *bhnd_nvram_data_retain(struct bhnd_nvram_data *nv); void bhnd_nvram_data_release(struct bhnd_nvram_data *nv); bhnd_nvram_data_class *bhnd_nvram_data_get_class(struct bhnd_nvram_data *nv); size_t bhnd_nvram_data_count(struct bhnd_nvram_data *nv); int bhnd_nvram_data_size(struct bhnd_nvram_data *nv, size_t *size); int bhnd_nvram_data_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len); +bhnd_nvram_plist *bhnd_nvram_data_options(struct bhnd_nvram_data *nv); uint32_t bhnd_nvram_data_caps(struct bhnd_nvram_data *nv); const char *bhnd_nvram_data_next(struct bhnd_nvram_data *nv, void **cookiep); void *bhnd_nvram_data_find(struct bhnd_nvram_data *nv, const char *name); int bhnd_nvram_data_getvar_order( struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2); int bhnd_nvram_data_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf, size_t *len, bhnd_nvram_type type); const void *bhnd_nvram_data_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type); const char *bhnd_nvram_data_getvar_name(struct bhnd_nvram_data *nv, void *cookiep); int bhnd_nvram_data_copy_val(struct bhnd_nvram_data *nv, void *cookiep, bhnd_nvram_val **val); int bhnd_nvram_data_filter_setvar( struct bhnd_nvram_data *nv, const char *name, bhnd_nvram_val *value, bhnd_nvram_val **result); int bhnd_nvram_data_filter_unsetvar( struct bhnd_nvram_data *nv, const char *name); #endif /* _BHND_NVRAM_BHND_NVRAM_DATA_H_ */ Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c (revision 310296) @@ -1,815 +1,840 @@ /*- * 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", 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); return (BHND_NVRAM_DATA_PROBE_DEFAULT); } /** * 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; + 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 int bhnd_nvram_bcm_size(struct bhnd_nvram_data *nv, size_t *size) { return (bhnd_nvram_bcm_serialize(nv, NULL, size)); } static int bhnd_nvram_bcm_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) { struct bhnd_nvram_bcm *bcm; struct bhnd_nvram_bcmhdr hdr; void *cookiep; const char *name; size_t nbytes, limit; uint8_t crc; int error; bcm = (struct bhnd_nvram_bcm *)nv; nbytes = 0; /* Save the output buffer limit */ if (buf == NULL) limit = 0; else limit = *len; /* Reserve space for the NVRAM header */ nbytes += sizeof(struct bhnd_nvram_bcmhdr); /* Write all variables to the output buffer */ cookiep = NULL; while ((name = bhnd_nvram_data_next(nv, &cookiep))) { uint8_t *outp; size_t olen; size_t name_len, val_len; if (limit > nbytes) { outp = (uint8_t *)buf + nbytes; olen = limit - nbytes; } else { outp = NULL; olen = 0; } /* Determine length of variable name */ name_len = strlen(name) + 1; /* Write the variable name and '=' delimiter */ if (olen >= name_len) { /* Copy name */ memcpy(outp, name, name_len - 1); /* Append '=' */ *(outp + name_len - 1) = '='; } /* Adjust byte counts */ if (SIZE_MAX - name_len < nbytes) return (ERANGE); nbytes += name_len; /* Reposition output */ if (limit > nbytes) { outp = (uint8_t *)buf + nbytes; olen = limit - nbytes; } else { outp = NULL; olen = 0; } /* Coerce to NUL-terminated C string, writing to the output * buffer (or just calculating the length if outp is NULL) */ val_len = olen; error = bhnd_nvram_data_getvar(nv, cookiep, outp, &val_len, BHND_NVRAM_TYPE_STRING); if (error && error != ENOMEM) return (error); /* Adjust byte counts */ if (SIZE_MAX - val_len < nbytes) return (ERANGE); nbytes += val_len; } /* Write terminating NUL */ if (nbytes < limit) *((uint8_t *)buf + nbytes) = '\0'; nbytes++; /* Provide actual size */ *len = nbytes; if (buf == NULL || nbytes > limit) { if (buf != NULL) return (ENOMEM); return (0); } /* Fetch current NVRAM header */ if ((error = bhnd_nvram_io_read(bcm->data, 0x0, &hdr, sizeof(hdr)))) return (error); /* Update values covered by CRC and write to output buffer */ hdr.size = htole32(*len); memcpy(buf, &hdr, sizeof(hdr)); /* Calculate new CRC */ crc = bhnd_nvram_crc8((uint8_t *)buf + BCM_NVRAM_CRC_SKIP, *len - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL); /* Update header with valid CRC */ hdr.cfg0 &= ~BCM_NVRAM_CFG0_CRC_MASK; hdr.cfg0 |= (crc << BCM_NVRAM_CFG0_CRC_SHIFT); memcpy(buf, &hdr, sizeof(hdr)); return (0); } 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]); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c (revision 310296) @@ -1,429 +1,447 @@ /*- * 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$"); #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" /* * Broadcom-RAW NVRAM data class. * * The Broadcom NVRAM NUL-delimited ASCII format is used by most * Broadcom SoCs. * * The NVRAM data is encoded as a stream of of NUL-terminated 'key=value' * strings; the end of the stream is denoted by a single extra NUL character. */ struct bhnd_nvram_bcmraw; /** BCM-RAW NVRAM data class instance */ struct bhnd_nvram_bcmraw { struct bhnd_nvram_data nv; /**< common instance state */ char *data; /**< backing buffer */ size_t size; /**< buffer size */ size_t count; /**< variable count */ }; BHND_NVRAM_DATA_CLASS_DEFN(bcmraw, "Broadcom (RAW)", sizeof(struct bhnd_nvram_bcmraw)) static int bhnd_nvram_bcmraw_probe(struct bhnd_nvram_io *io) { char envp[16]; size_t envp_len; + size_t io_size; int error; + io_size = bhnd_nvram_io_getsize(io); + /* - * Fetch the initial bytes to try to identify BCM data. - * - * We always assert a low probe priority, as we only scan the initial - * bytes of the file. + * Fetch initial bytes */ - envp_len = bhnd_nv_ummin(sizeof(envp), bhnd_nvram_io_getsize(io)); + envp_len = bhnd_nv_ummin(sizeof(envp), io_size); if ((error = bhnd_nvram_io_read(io, 0x0, envp, envp_len))) return (error); - /* A zero-length BCM-RAW buffer should contain a single terminating + /* An empty BCM-RAW buffer should still contain a single terminating * NUL */ if (envp_len == 0) return (ENXIO); if (envp_len == 1) { if (envp[0] != '\0') return (ENXIO); return (BHND_NVRAM_DATA_PROBE_MAYBE); } - /* Don't match on non-ASCII, non-printable data */ + /* Must contain only printable ASCII characters delimited + * by NUL record delimiters */ for (size_t i = 0; i < envp_len; i++) { char c = envp[i]; - if (envp[i] == '\0') - break; - if (!bhnd_nv_isprint(c)) + /* If we hit a newline, this is probably BCM-TXT */ + if (c == '\n') return (ENXIO); + + if (c == '\0' && !bhnd_nv_isprint(c)) + continue; } - /* The first character should be a valid key char */ - if (!bhnd_nv_isalpha(envp[0])) + /* A valid BCM-RAW buffer should contain a terminating NUL for + * the last record, followed by a final empty record terminated by + * NUL */ + envp_len = 2; + if (io_size < envp_len) return (ENXIO); - return (BHND_NVRAM_DATA_PROBE_MAYBE); + if ((error = bhnd_nvram_io_read(io, io_size-envp_len, envp, envp_len))) + return (error); + + if (envp[0] != '\0' || envp[1] != '\0') + return (ENXIO); + + return (BHND_NVRAM_DATA_PROBE_MAYBE + 1); } /** * Initialize @p bcm with the provided NVRAM data mapped by @p src. * * @param bcm A newly allocated data instance. */ static int bhnd_nvram_bcmraw_init(struct bhnd_nvram_bcmraw *bcm, struct bhnd_nvram_io *src) { size_t io_size; size_t capacity, offset; int error; /* Fetch the input image size */ io_size = bhnd_nvram_io_getsize(src); /* 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); capacity = io_size + 1 /* room for extra NUL */; bcm->size = io_size; if ((bcm->data = bhnd_nv_malloc(capacity)) == NULL) return (ENOMEM); /* Copy in the NVRAM image */ if ((error = bhnd_nvram_io_read(src, 0x0, bcm->data, io_size))) return (error); /* Process the buffer */ bcm->count = 0; for (offset = 0; offset < bcm->size; offset++) { char *envp; const char *name, *value; size_t envp_len; size_t name_len, value_len; /* Parse the key=value string */ envp = (char *) (bcm->data + offset); envp_len = strnlen(envp, bcm->size - 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", 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'; /* Add to variable count */ bcm->count++; /* Seek past the value's terminating '\0' */ offset += envp_len; if (offset == io_size) { BHND_NV_LOG("missing terminating NUL at offset %#zx\n", offset); return (EINVAL); } /* If we hit EOF without finding a terminating NUL * byte, we need to append it */ if (++offset == bcm->size) { BHND_NV_ASSERT(offset < capacity, ("appending past end of buffer")); bcm->size++; *(bcm->data + offset) = '\0'; } /* Check for explicit EOF (encoded as a single empty NUL * terminated string) */ if (*(bcm->data + offset) == '\0') break; } /* Reclaim any unused space in he backing buffer */ if (offset < bcm->size) { bcm->data = bhnd_nv_reallocf(bcm->data, bcm->size); if (bcm->data == NULL) return (ENOMEM); } return (0); } static int bhnd_nvram_bcmraw_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) { struct bhnd_nvram_bcmraw *bcm; int error; bcm = (struct bhnd_nvram_bcmraw *)nv; /* Parse the BCM input data and initialize our backing * data representation */ if ((error = bhnd_nvram_bcmraw_init(bcm, io))) { bhnd_nvram_bcmraw_free(nv); return (error); } return (0); } static void bhnd_nvram_bcmraw_free(struct bhnd_nvram_data *nv) { struct bhnd_nvram_bcmraw *bcm = (struct bhnd_nvram_bcmraw *)nv; if (bcm->data != NULL) bhnd_nv_free(bcm->data); } static size_t bhnd_nvram_bcmraw_count(struct bhnd_nvram_data *nv) { struct bhnd_nvram_bcmraw *bcm = (struct bhnd_nvram_bcmraw *)nv; return (bcm->count); +} + +static bhnd_nvram_plist * +bhnd_nvram_bcmraw_options(struct bhnd_nvram_data *nv) +{ + return (NULL); } static int bhnd_nvram_bcmraw_size(struct bhnd_nvram_data *nv, size_t *size) { return (bhnd_nvram_bcmraw_serialize(nv, NULL, size)); } static int bhnd_nvram_bcmraw_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) { struct bhnd_nvram_bcmraw *bcm; char * const p = (char *)buf; size_t limit; size_t offset; bcm = (struct bhnd_nvram_bcmraw *)nv; /* Save the output buffer limit */ if (buf == NULL) limit = 0; else limit = *len; /* The serialized form will be exactly the length * of our backing buffer representation */ *len = bcm->size; /* Skip serialization if not requested, or report ENOMEM if * buffer is too small */ if (buf == NULL) { return (0); } else if (*len > limit) { return (ENOMEM); } /* Write all variables to the output buffer */ memcpy(buf, bcm->data, *len); /* Rewrite all '\0' delimiters back to '=' */ offset = 0; while (offset < bcm->size) { size_t name_len, value_len; name_len = strlen(p + offset); /* EOF? */ if (name_len == 0) { BHND_NV_ASSERT(*(p + offset) == '\0', ("no NUL terminator")); offset++; break; } /* Rewrite 'name\0' to 'name=' */ offset += name_len; BHND_NV_ASSERT(*(p + offset) == '\0', ("incorrect offset")); *(p + offset) = '='; offset++; value_len = strlen(p + offset); offset += value_len + 1; } return (0); } static uint32_t bhnd_nvram_bcmraw_caps(struct bhnd_nvram_data *nv) { return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS); } static const char * bhnd_nvram_bcmraw_next(struct bhnd_nvram_data *nv, void **cookiep) { struct bhnd_nvram_bcmraw *bcm; const char *envp; bcm = (struct bhnd_nvram_bcmraw *)nv; if (*cookiep == NULL) { /* Start at the first NVRAM data record */ envp = bcm->data; } else { /* Seek to next record */ envp = *cookiep; envp += strlen(envp) + 1; /* key + '\0' */ envp += strlen(envp) + 1; /* value + '\0' */ } /* EOF? */ if (*envp == '\0') return (NULL); *cookiep = (void *)(uintptr_t)envp; return (envp); } static void * bhnd_nvram_bcmraw_find(struct bhnd_nvram_data *nv, const char *name) { return (bhnd_nvram_data_generic_find(nv, name)); } static int bhnd_nvram_bcmraw_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2) { if (cookiep1 < cookiep2) return (-1); if (cookiep1 > cookiep2) return (1); return (0); } static int bhnd_nvram_bcmraw_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_bcmraw_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_bcmraw_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type) { const char *envp; /* Cookie points to key\0value\0 -- get the value address */ 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_bcmraw_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) { /* Cookie points to key\0value\0 */ return (cookiep); } static int bhnd_nvram_bcmraw_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_bcmraw_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { /* We permit deletion of any variable */ return (0); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmvar.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmvar.h (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmvar.h (revision 310296) @@ -1,70 +1,72 @@ /*- * Copyright (c) 2015-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. * * $FreeBSD$ */ #ifndef _BHND_NVRAM_BHND_NVRAM_BCMVAR_H_ #define _BHND_NVRAM_BHND_NVRAM_BCMVAR_H_ +#define BCM_NVRAM_ENCODE_OPT_VERSION "bcm_version" + /** * BCM NVRAM header value data. */ union bhnd_nvram_bcm_hvar_value { uint16_t u16; uint32_t u32; }; /** * Internal representation of BCM NVRAM values that mirror (and must be * vended as) NVRAM variables. */ struct bhnd_nvram_bcm_hvar { const char *name; /**< variable name */ bhnd_nvram_type type; /**< value type */ size_t nelem; /**< value element count */ size_t len; /**< value length */ const char *envp; /**< Pointer to the NVRAM variable mirroring this header value, or NULL. */ bool stale; /**< header value does not match mirrored NVRAM value */ /** variable data */ union bhnd_nvram_bcm_hvar_value value; }; /** BCM NVRAM header */ struct bhnd_nvram_bcmhdr { uint32_t magic; uint32_t size; uint32_t cfg0; /**< crc:8, version:8, sdram_init:16 */ uint32_t cfg1; /**< sdram_config:16, sdram_refresh:16 */ uint32_t sdram_ncdl; /**< sdram_ncdl */ } __packed; #endif /* _BHND_NVRAM_BHND_NVRAM_BCMVAR_H_ */ Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c (revision 310296) @@ -1,691 +1,697 @@ /*- * Copyright (c) 2015-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 #ifdef _KERNEL #include #include #include #include #else /* !_KERNEL */ #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_data_bcmreg.h" /* for BCM_NVRAM_MAGIC */ /** * Broadcom "Board Text" data class. * * This format is used to provide external NVRAM data for some * fullmac WiFi devices, and as an input format when programming * NVRAM/SPROM/OTP. */ struct bhnd_nvram_btxt { struct bhnd_nvram_data nv; /**< common instance state */ struct bhnd_nvram_io *data; /**< memory-backed board text data */ size_t count; /**< variable count */ }; BHND_NVRAM_DATA_CLASS_DEFN(btxt, "Broadcom Board Text", sizeof(struct bhnd_nvram_btxt)) /** Minimal identification header */ union bhnd_nvram_btxt_ident { uint32_t bcm_magic; char btxt[8]; }; static void *bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt, size_t io_offset); static size_t bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt, void *cookiep); static int bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io, size_t offset, size_t *line_len, size_t *env_len); static int bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io, size_t *offset); static int bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io, size_t *offset); static int bhnd_nvram_btxt_probe(struct bhnd_nvram_io *io) { union bhnd_nvram_btxt_ident ident; char c; int error; /* Look at the initial header for something that looks like * an ASCII board text file */ if ((error = bhnd_nvram_io_read(io, 0x0, &ident, sizeof(ident)))) return (error); /* The BCM NVRAM format uses a 'FLSH' little endian magic value, which * shouldn't be interpreted as BTXT */ if (le32toh(ident.bcm_magic) == BCM_NVRAM_MAGIC) return (ENXIO); /* Don't match on non-ASCII/non-printable data */ for (size_t i = 0; i < nitems(ident.btxt); i++) { c = ident.btxt[i]; if (!bhnd_nv_isprint(c)) return (ENXIO); } /* The first character should either be a valid key char (alpha), * whitespace, or the start of a comment ('#') */ c = ident.btxt[0]; if (!bhnd_nv_isspace(c) && !bhnd_nv_isalpha(c) && c != '#') return (ENXIO); /* We assert a low priority, given that we've only scanned an * initial few bytes of the file. */ return (BHND_NVRAM_DATA_PROBE_MAYBE); } /** * Initialize @p btxt with the provided board text data mapped by @p src. * * @param btxt A newly allocated data instance. */ static int bhnd_nvram_btxt_init(struct bhnd_nvram_btxt *btxt, struct bhnd_nvram_io *src) { const void *ptr; const char *name, *value; size_t name_len, value_len; size_t line_len, env_len; size_t io_offset, io_size, str_size; int error; BHND_NV_ASSERT(btxt->data == NULL, ("btxt data already allocated")); if ((btxt->data = bhnd_nvram_iobuf_copy(src)) == NULL) return (ENOMEM); io_size = bhnd_nvram_io_getsize(btxt->data); io_offset = 0; /* Fetch a pointer mapping the entirity of the board text data */ error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL); if (error) return (error); /* Determine the actual size, minus any terminating NUL. We * parse NUL-terminated C strings, but do not include NUL termination * in our internal or serialized representations */ str_size = strnlen(ptr, io_size); /* If the terminating NUL is not found at the end of the buffer, * this is BCM-RAW or other NUL-delimited NVRAM format. */ if (str_size < io_size && str_size + 1 < io_size) return (EINVAL); /* Adjust buffer size to account for NUL termination (if any) */ io_size = str_size; if ((error = bhnd_nvram_io_setsize(btxt->data, io_size))) return (error); /* Process the buffer */ btxt->count = 0; while (io_offset < io_size) { const void *envp; /* Seek to the next key=value entry */ if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset))) return (error); /* Determine the entry and line length */ error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset, &line_len, &env_len); if (error) return (error); /* EOF? */ if (env_len == 0) { BHND_NV_ASSERT(io_offset == io_size, ("zero-length record returned from " "bhnd_nvram_btxt_seek_next()")); break; } /* Fetch a pointer to the line start */ error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &envp, env_len, NULL); if (error) return (error); /* Parse the key=value string */ error = bhnd_nvram_parse_env(envp, env_len, '=', &name, &name_len, &value, &value_len); if (error) { return (error); } /* Insert a '\0' character, replacing the '=' delimiter and * allowing us to vend references directly to the variable * name */ error = bhnd_nvram_io_write(btxt->data, io_offset+name_len, &(char){'\0'}, 1); if (error) return (error); /* Add to variable count */ btxt->count++; /* Advance past EOL */ io_offset += line_len; } return (0); } static int bhnd_nvram_btxt_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) { struct bhnd_nvram_btxt *btxt; int error; /* Allocate and initialize the BTXT data instance */ btxt = (struct bhnd_nvram_btxt *)nv; /* Parse the BTXT input data and initialize our backing * data representation */ if ((error = bhnd_nvram_btxt_init(btxt, io))) { bhnd_nvram_btxt_free(nv); return (error); } return (0); } static void bhnd_nvram_btxt_free(struct bhnd_nvram_data *nv) { struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv; if (btxt->data != NULL) bhnd_nvram_io_free(btxt->data); } size_t bhnd_nvram_btxt_count(struct bhnd_nvram_data *nv) { struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv; return (btxt->count); } +static bhnd_nvram_plist * +bhnd_nvram_btxt_options(struct bhnd_nvram_data *nv) +{ + return (NULL); +} + static int bhnd_nvram_btxt_size(struct bhnd_nvram_data *nv, size_t *size) { struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv; /* The serialized form will be identical in length * to our backing buffer representation */ *size = bhnd_nvram_io_getsize(btxt->data); return (0); } static int bhnd_nvram_btxt_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) { struct bhnd_nvram_btxt *btxt; size_t limit; int error; btxt = (struct bhnd_nvram_btxt *)nv; limit = *len; /* Provide actual output size */ if ((error = bhnd_nvram_data_size(nv, len))) return (error); if (buf == NULL) { return (0); } else if (limit < *len) { return (ENOMEM); } /* Copy our internal representation to the output buffer */ if ((error = bhnd_nvram_io_read(btxt->data, 0x0, buf, *len))) return (error); /* Restore the original key=value format, rewriting all '\0' * key\0value delimiters back to '=' */ for (char *p = buf; (size_t)(p - (char *)buf) < *len; p++) { if (*p == '\0') *p = '='; } return (0); } static uint32_t bhnd_nvram_btxt_caps(struct bhnd_nvram_data *nv) { return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS); } static void * bhnd_nvram_btxt_find(struct bhnd_nvram_data *nv, const char *name) { return (bhnd_nvram_data_generic_find(nv, name)); } static const char * bhnd_nvram_btxt_next(struct bhnd_nvram_data *nv, void **cookiep) { struct bhnd_nvram_btxt *btxt; const void *nptr; size_t io_offset, io_size; int error; btxt = (struct bhnd_nvram_btxt *)nv; io_size = bhnd_nvram_io_getsize(btxt->data); if (*cookiep == NULL) { /* Start search at initial file offset */ io_offset = 0x0; } else { /* Start search after the current entry */ io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, *cookiep); /* Scan past the current entry by finding the next newline */ error = bhnd_nvram_btxt_seek_eol(btxt->data, &io_offset); if (error) { BHND_NV_LOG("unexpected error in seek_eol(): %d\n", error); return (NULL); } } /* Already at EOF? */ if (io_offset == io_size) return (NULL); /* Seek to the first valid entry, or EOF */ if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset))) { BHND_NV_LOG("unexpected error in seek_next(): %d\n", error); return (NULL); } /* Hit EOF? */ if (io_offset == io_size) return (NULL); /* Provide the new cookie for this offset */ *cookiep = bhnd_nvram_btxt_offset_to_cookiep(btxt, io_offset); /* Fetch the name pointer; it must be at least 1 byte long */ error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &nptr, 1, NULL); if (error) { BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error); return (NULL); } /* Return the name pointer */ return (nptr); } static int bhnd_nvram_btxt_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2) { if (cookiep1 < cookiep2) return (-1); if (cookiep1 > cookiep2) return (1); return (0); } static int bhnd_nvram_btxt_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_btxt_copy_val(struct bhnd_nvram_data *nv, void *cookiep, bhnd_nvram_val **value) { return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value)); } const void * bhnd_nvram_btxt_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type) { struct bhnd_nvram_btxt *btxt; const void *eptr; const char *vptr; size_t io_offset, io_size; size_t line_len, env_len; int error; btxt = (struct bhnd_nvram_btxt *)nv; io_size = bhnd_nvram_io_getsize(btxt->data); io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep); /* At EOF? */ if (io_offset == io_size) return (NULL); /* Determine the entry length */ error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset, &line_len, &env_len); if (error) { BHND_NV_LOG("unexpected error in entry_len(): %d\n", error); return (NULL); } /* Fetch the entry's value pointer and length */ error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &eptr, env_len, NULL); if (error) { BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error); return (NULL); } error = bhnd_nvram_parse_env(eptr, env_len, '\0', NULL, NULL, &vptr, len); if (error) { BHND_NV_LOG("unexpected error in parse_env(): %d\n", error); return (NULL); } /* Type is always CSTR */ *type = BHND_NVRAM_TYPE_STRING; return (vptr); } static const char * bhnd_nvram_btxt_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) { struct bhnd_nvram_btxt *btxt; const void *ptr; size_t io_offset, io_size; int error; btxt = (struct bhnd_nvram_btxt *)nv; io_size = bhnd_nvram_io_getsize(btxt->data); io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep); /* At EOF? */ if (io_offset == io_size) BHND_NV_PANIC("invalid cookiep: %p", cookiep); /* Variable name is found directly at the given offset; trailing * NUL means we can assume that it's at least 1 byte long */ error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &ptr, 1, NULL); if (error) BHND_NV_PANIC("unexpected error in read_ptr(): %d\n", error); return (ptr); } /** * Return a cookiep for the given I/O offset. */ static void * bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt, size_t io_offset) { const void *ptr; int error; BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(btxt->data), ("io_offset %zu out-of-range", io_offset)); BHND_NV_ASSERT(io_offset < UINTPTR_MAX, ("io_offset %#zx exceeds UINTPTR_MAX", io_offset)); error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_offset, NULL); if (error) BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error); ptr = (const uint8_t *)ptr + io_offset; return (__DECONST(void *, ptr)); } /* Convert a cookiep back to an I/O offset */ static size_t bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt, void *cookiep) { const void *ptr; intptr_t offset; size_t io_size; int error; BHND_NV_ASSERT(cookiep != NULL, ("null cookiep")); io_size = bhnd_nvram_io_getsize(btxt->data); error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL); if (error) BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error); offset = (const uint8_t *)cookiep - (const uint8_t *)ptr; BHND_NV_ASSERT(offset >= 0, ("invalid cookiep")); BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)")); BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)")); return ((size_t)offset); } /* Determine the entry length and env 'key=value' string length of the entry * at @p offset */ static int bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io, size_t offset, size_t *line_len, size_t *env_len) { const uint8_t *baseptr, *p; const void *rbuf; size_t nbytes; int error; /* Fetch read buffer */ if ((error = bhnd_nvram_io_read_ptr(io, offset, &rbuf, 0, &nbytes))) return (error); /* Find record termination (EOL, or '#') */ p = rbuf; baseptr = rbuf; while ((size_t)(p - baseptr) < nbytes) { if (*p == '#' || *p == '\n' || *p == '\r') break; p++; } /* Got line length, now trim any trailing whitespace to determine * actual env length */ *line_len = p - baseptr; *env_len = *line_len; for (size_t i = 0; i < *line_len; i++) { char c = baseptr[*line_len - i - 1]; if (!bhnd_nv_isspace(c)) break; *env_len -= 1; } return (0); } /* Seek past the next line ending (\r, \r\n, or \n) */ static int bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io, size_t *offset) { const uint8_t *baseptr, *p; const void *rbuf; size_t nbytes; int error; /* Fetch read buffer */ if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes))) return (error); baseptr = rbuf; p = rbuf; while ((size_t)(p - baseptr) < nbytes) { char c = *p; /* Advance to next char. The next position may be EOF, in which * case a read will be invalid */ p++; if (c == '\r') { /* CR, check for optional LF */ if ((size_t)(p - baseptr) < nbytes) { if (*p == '\n') p++; } break; } else if (c == '\n') { break; } } /* Hit newline or EOF */ *offset += (p - baseptr); return (0); } /* Seek to the next valid non-comment line (or EOF) */ static int bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io, size_t *offset) { const uint8_t *baseptr, *p; const void *rbuf; size_t nbytes; int error; /* Fetch read buffer */ if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes))) return (error); /* Skip leading whitespace and comments */ baseptr = rbuf; p = rbuf; while ((size_t)(p - baseptr) < nbytes) { char c = *p; /* Skip whitespace */ if (bhnd_nv_isspace(c)) { p++; continue; } /* Skip entire comment line */ if (c == '#') { size_t line_off = *offset + (p - baseptr); if ((error = bhnd_nvram_btxt_seek_eol(io, &line_off))) return (error); p = baseptr + (line_off - *offset); continue; } /* Non-whitespace, non-comment */ break; } *offset += (p - baseptr); return (0); } static int bhnd_nvram_btxt_filter_setvar(struct bhnd_nvram_data *nv, const char *name, bhnd_nvram_val *value, bhnd_nvram_val **result) { bhnd_nvram_val *str; const char *inp; bhnd_nvram_type itype; size_t ilen; 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); /* Value string must not contain our record delimiter character ('\n'), * or our comment character ('#') */ inp = bhnd_nvram_val_bytes(str, &ilen, &itype); BHND_NV_ASSERT(itype == BHND_NVRAM_TYPE_STRING, ("non-string value")); for (size_t i = 0; i < ilen; i++) { switch (inp[i]) { case '\n': case '#': BHND_NV_LOG("invalid character (%#hhx) in value\n", inp[i]); bhnd_nvram_val_release(str); return (EINVAL); } } /* Success. Transfer result ownership to the caller. */ *result = str; return (0); } static int bhnd_nvram_btxt_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { /* We permit deletion of any variable */ return (0); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom.c (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom.c (revision 310296) @@ -1,2068 +1,2074 @@ /*- * Copyright (c) 2015-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 #ifdef _KERNEL #include #include #include #include #include #else /* !_KERNEL */ #include #include #include #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_data_spromvar.h" /* * BHND SPROM NVRAM data class * * The SPROM data format is a fixed-layout, non-self-descriptive binary format, * used on Broadcom wireless and wired adapters, that provides a subset of the * variables defined by Broadcom SoC NVRAM formats. */ BHND_NVRAM_DATA_CLASS_DEFN(sprom, "Broadcom SPROM", sizeof(struct bhnd_nvram_sprom)) static int sprom_sort_idx(const void *lhs, const void *rhs); static int sprom_opcode_state_init(struct sprom_opcode_state *state, const struct bhnd_sprom_layout *layout); static int sprom_opcode_state_reset(struct sprom_opcode_state *state); static int sprom_opcode_state_seek(struct sprom_opcode_state *state, struct sprom_opcode_idx *indexed); static int sprom_opcode_next_var(struct sprom_opcode_state *state); static int sprom_opcode_parse_var(struct sprom_opcode_state *state, struct sprom_opcode_idx *indexed); static int sprom_opcode_next_binding(struct sprom_opcode_state *state); static int sprom_opcode_set_type(struct sprom_opcode_state *state, bhnd_nvram_type type); static int sprom_opcode_set_var(struct sprom_opcode_state *state, size_t vid); static int sprom_opcode_clear_var(struct sprom_opcode_state *state); static int sprom_opcode_flush_bind(struct sprom_opcode_state *state); static int sprom_opcode_read_opval32(struct sprom_opcode_state *state, uint8_t type, uint32_t *opval); static int sprom_opcode_apply_scale(struct sprom_opcode_state *state, uint32_t *value); static int sprom_opcode_step(struct sprom_opcode_state *state, uint8_t *opcode); #define SPROM_OP_BAD(_state, _fmt, ...) \ BHND_NV_LOG("bad encoding at %td: " _fmt, \ (_state)->input - (_state)->layout->bindings, ##__VA_ARGS__) #define SPROM_COOKIE_TO_NVRAM(_cookie) \ bhnd_nvram_get_vardefn(((struct sprom_opcode_idx *)_cookie)->vid) /** * Read the magic value from @p io, and verify that it matches * the @p layout's expected magic value. * * If @p layout does not defined a magic value, @p magic is set to 0x0 * and success is returned. * * @param io An I/O context mapping the SPROM data to be identified. * @param layout The SPROM layout against which @p io should be verified. * @param[out] magic On success, the SPROM magic value. * * @retval 0 success * @retval non-zero If checking @p io otherwise fails, a regular unix * error code will be returned. */ static int bhnd_nvram_sprom_check_magic(struct bhnd_nvram_io *io, const struct bhnd_sprom_layout *layout, uint16_t *magic) { int error; /* Skip if layout does not define a magic value */ if (layout->flags & SPROM_LAYOUT_MAGIC_NONE) return (0); /* Read the magic value */ error = bhnd_nvram_io_read(io, layout->magic_offset, magic, sizeof(*magic)); if (error) return (error); *magic = le16toh(*magic); /* If the signature does not match, skip to next layout */ if (*magic != layout->magic_value) return (ENXIO); return (0); } /** * Attempt to identify the format of the SPROM data mapped by @p io. * * The SPROM data format does not provide any identifying information at a * known offset, instead requiring that we iterate over the known SPROM image * sizes until we are able to compute a valid checksum (and, for later * revisions, validate a signature at a revision-specific offset). * * @param io An I/O context mapping the SPROM data to be identified. * @param[out] ident On success, the identified SPROM layout. * @param[out] shadow On success, a correctly sized iobuf instance mapping * a copy of the identified SPROM image. The caller is * responsible for deallocating this instance via * bhnd_nvram_io_free() * * @retval 0 success * @retval non-zero If identifying @p io otherwise fails, a regular unix * error code will be returned. */ static int bhnd_nvram_sprom_ident(struct bhnd_nvram_io *io, const struct bhnd_sprom_layout **ident, struct bhnd_nvram_io **shadow) { struct bhnd_nvram_io *buf; uint8_t crc; size_t crc_errors; size_t sprom_sz_max; int error; /* Find the largest SPROM layout size */ sprom_sz_max = 0; for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) { sprom_sz_max = bhnd_nv_ummax(sprom_sz_max, bhnd_sprom_layouts[i].size); } /* Allocate backing buffer and initialize CRC state */ buf = bhnd_nvram_iobuf_empty(0, sprom_sz_max); crc = BHND_NVRAM_CRC8_INITIAL; crc_errors = 0; /* We iterate the SPROM layouts smallest to largest, allowing us to * perform incremental checksum calculation */ for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) { const struct bhnd_sprom_layout *layout; void *ptr; size_t nbytes, nr; uint16_t magic; uint8_t srev; bool crc_valid; bool have_magic; layout = &bhnd_sprom_layouts[i]; nbytes = bhnd_nvram_io_getsize(buf); if ((layout->flags & SPROM_LAYOUT_MAGIC_NONE)) { have_magic = false; } else { have_magic = true; } /* Layout instances must be ordered from smallest to largest by * the nvram_map compiler */ if (nbytes > layout->size) BHND_NV_PANIC("SPROM layout is defined out-of-order"); /* Calculate number of additional bytes to be read */ nr = layout->size - nbytes; /* Adjust the buffer size and fetch a write pointer */ if ((error = bhnd_nvram_io_setsize(buf, layout->size))) goto failed; error = bhnd_nvram_io_write_ptr(buf, nbytes, &ptr, nr, NULL); if (error) goto failed; /* Read image data and update CRC (errors are reported * after the signature check) */ if ((error = bhnd_nvram_io_read(io, nbytes, ptr, nr))) goto failed; crc = bhnd_nvram_crc8(ptr, nr, crc); crc_valid = (crc == BHND_NVRAM_CRC8_VALID); if (!crc_valid) crc_errors++; /* Fetch SPROM revision */ error = bhnd_nvram_io_read(buf, layout->srev_offset, &srev, sizeof(srev)); if (error) goto failed; /* Early sromrev 1 devices (specifically some BCM440x enet * cards) are reported to have been incorrectly programmed * with a revision of 0x10. */ if (layout->rev == 1 && srev == 0x10) srev = 0x1; /* Check revision against the layout definition */ if (srev != layout->rev) continue; /* Check the magic value, skipping to the next layout on * failure. */ error = bhnd_nvram_sprom_check_magic(buf, layout, &magic); if (error) { /* If the CRC is was valid, log the mismatch */ if (crc_valid || BHND_NV_VERBOSE) { BHND_NV_LOG("invalid sprom %hhu signature: " "0x%hx (expected 0x%hx)\n", srev, magic, layout->magic_value); error = ENXIO; goto failed; } continue; } /* Check for an earlier CRC error */ if (!crc_valid) { /* If the magic check succeeded, then we may just have * data corruption -- log the CRC error */ if (have_magic || BHND_NV_VERBOSE) { BHND_NV_LOG("sprom %hhu CRC error (crc=%#hhx, " "expected=%#x)\n", srev, crc, BHND_NVRAM_CRC8_VALID); } continue; } /* Identified */ *shadow = buf; *ident = layout; return (0); } /* No match -- set error and fallthrough */ error = ENXIO; if (crc_errors > 0 && BHND_NV_VERBOSE) { BHND_NV_LOG("sprom parsing failed with %zu CRC errors\n", crc_errors); } failed: bhnd_nvram_io_free(buf); return (error); } static int bhnd_nvram_sprom_probe(struct bhnd_nvram_io *io) { const struct bhnd_sprom_layout *layout; struct bhnd_nvram_io *shadow; int error; /* Try to parse the input */ if ((error = bhnd_nvram_sprom_ident(io, &layout, &shadow))) return (error); /* Clean up the shadow iobuf */ bhnd_nvram_io_free(shadow); return (BHND_NVRAM_DATA_PROBE_DEFAULT); } static int bhnd_nvram_sprom_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) { struct bhnd_nvram_sprom *sp; size_t num_vars; int error; sp = (struct bhnd_nvram_sprom *)nv; /* Identify the SPROM input data */ if ((error = bhnd_nvram_sprom_ident(io, &sp->layout, &sp->data))) goto failed; /* Initialize SPROM binding eval state */ if ((error = sprom_opcode_state_init(&sp->state, sp->layout))) goto failed; /* Allocate our opcode index */ sp->num_idx = sp->layout->num_vars; if ((sp->idx = bhnd_nv_calloc(sp->num_idx, sizeof(*sp->idx))) == NULL) goto failed; /* Parse out index entries from our stateful opcode stream */ for (num_vars = 0; num_vars < sp->num_idx; num_vars++) { size_t opcodes; /* Seek to next entry */ if ((error = sprom_opcode_next_var(&sp->state))) { SPROM_OP_BAD(&sp->state, "error reading expected variable entry: %d\n", error); goto failed; } /* We limit the SPROM index representations to the minimal * type widths capable of covering all known layouts */ /* Save SPROM image offset */ if (sp->state.offset > UINT16_MAX) { SPROM_OP_BAD(&sp->state, "cannot index large offset %u\n", sp->state.offset); } sp->idx[num_vars].offset = sp->state.offset; /* Save current variable ID */ if (sp->state.vid > UINT16_MAX) { SPROM_OP_BAD(&sp->state, "cannot index large vid %zu\n", sp->state.vid); } sp->idx[num_vars].vid = sp->state.vid; /* Save opcode position */ opcodes = (sp->state.input - sp->layout->bindings); if (opcodes > UINT16_MAX) { SPROM_OP_BAD(&sp->state, "cannot index large opcode offset %zu\n", opcodes); } sp->idx[num_vars].opcodes = opcodes; } /* Should have reached end of binding table; next read must return * ENOENT */ if ((error = sprom_opcode_next_var(&sp->state)) != ENOENT) { BHND_NV_LOG("expected EOF parsing binding table: %d\n", error); goto failed; } /* Sort index by variable ID, ascending */ qsort(sp->idx, sp->num_idx, sizeof(sp->idx[0]), sprom_sort_idx); return (0); failed: if (sp->data != NULL) bhnd_nvram_io_free(sp->data); if (sp->idx != NULL) bhnd_nv_free(sp->idx); return (error); } /* sort function for sprom_opcode_idx values */ static int sprom_sort_idx(const void *lhs, const void *rhs) { const struct sprom_opcode_idx *l, *r; l = lhs; r = rhs; if (l->vid < r->vid) return (-1); if (l->vid > r->vid) return (1); return (0); } static void bhnd_nvram_sprom_free(struct bhnd_nvram_data *nv) { struct bhnd_nvram_sprom *sp = (struct bhnd_nvram_sprom *)nv; bhnd_nvram_io_free(sp->data); bhnd_nv_free(sp->idx); } +static bhnd_nvram_plist * +bhnd_nvram_sprom_options(struct bhnd_nvram_data *nv) +{ + return (NULL); +} + size_t bhnd_nvram_sprom_count(struct bhnd_nvram_data *nv) { struct bhnd_nvram_sprom *sprom = (struct bhnd_nvram_sprom *)nv; return (sprom->layout->num_vars); } static int bhnd_nvram_sprom_size(struct bhnd_nvram_data *nv, size_t *size) { struct bhnd_nvram_sprom *sprom = (struct bhnd_nvram_sprom *)nv; /* The serialized form will be identical in length * to our backing buffer representation */ *size = bhnd_nvram_io_getsize(sprom->data); return (0); } static int bhnd_nvram_sprom_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) { struct bhnd_nvram_sprom *sprom; size_t limit, req_len; int error; sprom = (struct bhnd_nvram_sprom *)nv; limit = *len; /* Provide the required size */ if ((error = bhnd_nvram_sprom_size(nv, &req_len))) return (error); *len = req_len; if (buf == NULL) { return (0); } else if (*len > limit) { return (ENOMEM); } /* Write to the output buffer */ return (bhnd_nvram_io_read(sprom->data, 0x0, buf, *len)); } static uint32_t bhnd_nvram_sprom_caps(struct bhnd_nvram_data *nv) { return (BHND_NVRAM_DATA_CAP_INDEXED); } static const char * bhnd_nvram_sprom_next(struct bhnd_nvram_data *nv, void **cookiep) { struct bhnd_nvram_sprom *sp; struct sprom_opcode_idx *idx_entry; size_t idx_next; const struct bhnd_nvram_vardefn *var; sp = (struct bhnd_nvram_sprom *)nv; /* Seek to appropriate starting point */ if (*cookiep == NULL) { /* Start search at first index entry */ idx_next = 0; } else { /* Determine current index position */ idx_entry = *cookiep; idx_next = (size_t)(idx_entry - sp->idx); BHND_NV_ASSERT(idx_next < sp->num_idx, ("invalid index %zu; corrupt cookie?", idx_next)); /* Advance to next entry */ idx_next++; /* Check for EOF */ if (idx_next == sp->num_idx) return (NULL); } /* Skip entries that are disabled by virtue of IGNALL1 */ for (; idx_next < sp->num_idx; idx_next++) { /* Fetch index entry and update cookiep */ idx_entry = &sp->idx[idx_next]; *cookiep = idx_entry; /* Fetch variable definition */ var = bhnd_nvram_get_vardefn(idx_entry->vid); /* We might need to parse the variable's value to determine * whether it should be treated as unset */ if (var->flags & BHND_NVRAM_VF_IGNALL1) { int error; size_t len; error = bhnd_nvram_sprom_getvar(nv, *cookiep, NULL, &len, var->type); if (error) { BHND_NV_ASSERT(error == ENOENT, ("unexpected " "error parsing variable: %d", error)); continue; } } /* Found! */ return (var->name); } /* Reached end of index entries */ return (NULL); } /* bsearch function used by bhnd_nvram_sprom_find() */ static int bhnd_nvram_sprom_find_vid_compare(const void *key, const void *rhs) { const struct sprom_opcode_idx *r; size_t l; l = *(const size_t *)key; r = rhs; if (l < r->vid) return (-1); if (l > r->vid) return (1); return (0); } static void * bhnd_nvram_sprom_find(struct bhnd_nvram_data *nv, const char *name) { struct bhnd_nvram_sprom *sp; const struct bhnd_nvram_vardefn *var; size_t vid; sp = (struct bhnd_nvram_sprom *)nv; /* Determine the variable ID for the given name */ if ((var = bhnd_nvram_find_vardefn(name)) == NULL) return (NULL); vid = bhnd_nvram_get_vardefn_id(var); /* Search our index for the variable ID */ return (bsearch(&vid, sp->idx, sp->num_idx, sizeof(sp->idx[0]), bhnd_nvram_sprom_find_vid_compare)); } /** * Read the value of @p type from the SPROM data at @p offset, apply @p mask * and @p shift, and OR with the existing @p value. * * @param sp The SPROM data instance. * @param var The NVRAM variable definition * @param type The type to read at @p offset * @param offset The data offset to be read. * @param mask The mask to be applied to the value read at @p offset. * @param shift The shift to be applied after masking; if positive, a right * shift will be applied, if negative, a left shift. * @param value The read destination; the parsed value will be OR'd with the * current contents of @p value. */ static int bhnd_nvram_sprom_read_offset(struct bhnd_nvram_sprom *sp, const struct bhnd_nvram_vardefn *var, bhnd_nvram_type type, size_t offset, uint32_t mask, int8_t shift, union bhnd_nvram_sprom_intv *value) { size_t sp_width; int error; union { uint8_t u8; uint16_t u16; uint32_t u32; int8_t s8; int16_t s16; int32_t s32; } sp_value; /* Determine type width */ sp_width = bhnd_nvram_type_width(type); if (sp_width == 0) { /* Variable-width types are unsupported */ BHND_NV_LOG("invalid %s SPROM offset type %d\n", var->name, type); return (EFTYPE); } /* Perform read */ error = bhnd_nvram_io_read(sp->data, offset, &sp_value, sp_width); if (error) { BHND_NV_LOG("error reading %s SPROM offset %#zx: %d\n", var->name, offset, error); return (EFTYPE); } #define NV_PARSE_INT(_type, _src, _dest, _swap) do { \ /* Swap to host byte order */ \ sp_value. _src = (_type) _swap(sp_value. _src); \ \ /* Mask and shift the value */ \ sp_value. _src &= mask; \ if (shift > 0) { \ sp_value. _src >>= shift; \ } else if (shift < 0) { \ sp_value. _src <<= -shift; \ } \ \ /* Emit output, widening to 32-bit representation */ \ value-> _dest |= sp_value. _src; \ } while(0) /* Apply mask/shift and widen to a common 32bit representation */ switch (type) { case BHND_NVRAM_TYPE_UINT8: NV_PARSE_INT(uint8_t, u8, u32, ); break; case BHND_NVRAM_TYPE_UINT16: NV_PARSE_INT(uint16_t, u16, u32, le16toh); break; case BHND_NVRAM_TYPE_UINT32: NV_PARSE_INT(uint32_t, u32, u32, le32toh); break; case BHND_NVRAM_TYPE_INT8: NV_PARSE_INT(int8_t, s8, s32, ); break; case BHND_NVRAM_TYPE_INT16: NV_PARSE_INT(int16_t, s16, s32, le16toh); break; case BHND_NVRAM_TYPE_INT32: NV_PARSE_INT(int32_t, s32, s32, le32toh); break; case BHND_NVRAM_TYPE_CHAR: NV_PARSE_INT(uint8_t, u8, u32, ); break; case BHND_NVRAM_TYPE_UINT64: case BHND_NVRAM_TYPE_INT64: case BHND_NVRAM_TYPE_STRING: /* fallthrough (unused by SPROM) */ default: BHND_NV_LOG("unhandled %s offset type: %d\n", var->name, type); return (EFTYPE); } return (0); } /** * Common variable decoding; fetches and decodes variable to @p val, * using @p storage for actual data storage. * * The returned @p val instance will hold a borrowed reference to @p storage, * and must be copied via bhnd_nvram_val_copy() if it will be referenced beyond * the lifetime of @p storage. * * The caller is responsible for releasing any allocated value state * via bhnd_nvram_val_release(). */ static int bhnd_nvram_sprom_getvar_common(struct bhnd_nvram_data *nv, void *cookiep, union bhnd_nvram_sprom_storage *storage, bhnd_nvram_val *val) { struct bhnd_nvram_sprom *sp; struct sprom_opcode_idx *entry; const struct bhnd_nvram_vardefn *var; union bhnd_nvram_sprom_storage *inp; bhnd_nvram_type var_btype; union bhnd_nvram_sprom_intv intv; size_t ilen, ipos, iwidth; size_t nelem; bool all_bits_set; int error; sp = (struct bhnd_nvram_sprom *)nv; entry = cookiep; BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep")); /* Fetch canonical variable definition */ var = SPROM_COOKIE_TO_NVRAM(cookiep); BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep)); /* * Fetch the array length from the SPROM variable definition. * * This generally be identical to the array length provided by the * canonical NVRAM variable definition, but some SPROM layouts may * define a smaller element count. */ if ((error = sprom_opcode_parse_var(&sp->state, entry))) { BHND_NV_LOG("variable evaluation failed: %d\n", error); return (error); } nelem = sp->state.var.nelem; if (nelem > var->nelem) { BHND_NV_LOG("SPROM array element count %zu cannot be " "represented by '%s' element count of %hhu\n", nelem, var->name, var->nelem); return (EFTYPE); } /* Fetch the var's base element type */ var_btype = bhnd_nvram_base_type(var->type); /* Calculate total byte length of the native encoding */ if ((iwidth = bhnd_nvram_value_size(NULL, 0, var_btype, 1)) == 0) { /* SPROM does not use (and we do not support) decoding of * variable-width data types */ BHND_NV_LOG("invalid SPROM data type: %d", var->type); return (EFTYPE); } ilen = nelem * iwidth; /* Decode into our caller's local storage */ inp = storage; if (ilen > sizeof(*storage)) { BHND_NV_LOG("error decoding '%s', SPROM_ARRAY_MAXLEN " "incorrect\n", var->name); return (EFTYPE); } /* Zero-initialize our decode buffer; any output elements skipped * during decode should default to zero. */ memset(inp, 0, ilen); /* * Decode the SPROM data, iteratively decoding up to nelem values. */ if ((error = sprom_opcode_state_seek(&sp->state, entry))) { BHND_NV_LOG("variable seek failed: %d\n", error); return (error); } ipos = 0; intv.u32 = 0x0; if (var->flags & BHND_NVRAM_VF_IGNALL1) all_bits_set = true; else all_bits_set = false; while ((error = sprom_opcode_next_binding(&sp->state)) == 0) { struct sprom_opcode_bind *binding; struct sprom_opcode_var *binding_var; bhnd_nvram_type intv_type; size_t offset; size_t nbyte; uint32_t skip_in_bytes; void *ptr; BHND_NV_ASSERT( sp->state.var_state >= SPROM_OPCODE_VAR_STATE_OPEN, ("invalid var state")); BHND_NV_ASSERT(sp->state.var.have_bind, ("invalid bind state")); binding_var = &sp->state.var; binding = &sp->state.var.bind; if (ipos >= nelem) { BHND_NV_LOG("output skip %u positioned " "%zu beyond nelem %zu\n", binding->skip_out, ipos, nelem); return (EINVAL); } /* Calculate input skip bytes for this binding */ skip_in_bytes = binding->skip_in; error = sprom_opcode_apply_scale(&sp->state, &skip_in_bytes); if (error) return (error); /* Bind */ offset = sp->state.offset; for (size_t i = 0; i < binding->count; i++) { /* Read the offset value, OR'ing with the current * value of intv */ error = bhnd_nvram_sprom_read_offset(sp, var, binding_var->base_type, offset, binding_var->mask, binding_var->shift, &intv); if (error) return (error); /* If IGNALL1, record whether value does not have * all bits set. */ if (var->flags & BHND_NVRAM_VF_IGNALL1 && all_bits_set) { uint32_t all1; all1 = binding_var->mask; if (binding_var->shift > 0) all1 >>= binding_var->shift; else if (binding_var->shift < 0) all1 <<= -binding_var->shift; if ((intv.u32 & all1) != all1) all_bits_set = false; } /* Adjust input position; this was already verified to * not overflow/underflow during SPROM opcode * evaluation */ if (binding->skip_in_negative) { offset -= skip_in_bytes; } else { offset += skip_in_bytes; } /* Skip writing to inp if additional bindings are * required to fully populate intv */ if (binding->skip_out == 0) continue; /* We use bhnd_nvram_value_coerce() to perform * overflow-checked coercion from the widened * uint32/int32 intv value to the requested output * type */ if (bhnd_nvram_is_signed_type(var_btype)) intv_type = BHND_NVRAM_TYPE_INT32; else intv_type = BHND_NVRAM_TYPE_UINT32; /* Calculate address of the current element output * position */ ptr = (uint8_t *)inp + (iwidth * ipos); /* Perform coercion of the array element */ nbyte = iwidth; error = bhnd_nvram_value_coerce(&intv.u32, sizeof(intv.u32), intv_type, ptr, &nbyte, var_btype); if (error) return (error); /* Clear temporary state */ intv.u32 = 0x0; /* Advance output position */ if (SIZE_MAX - binding->skip_out < ipos) { BHND_NV_LOG("output skip %u would overflow " "%zu\n", binding->skip_out, ipos); return (EINVAL); } ipos += binding->skip_out; } } /* Did we iterate all bindings until hitting end of the variable * definition? */ BHND_NV_ASSERT(error != 0, ("loop terminated early")); if (error != ENOENT) { return (error); } /* If marked IGNALL1 and all bits are set, treat variable as * unavailable */ if ((var->flags & BHND_NVRAM_VF_IGNALL1) && all_bits_set) return (ENOENT); /* Provide value wrapper */ return (bhnd_nvram_val_init(val, var->fmt, inp, ilen, var->type, BHND_NVRAM_VAL_BORROW_DATA)); return (error); } static int bhnd_nvram_sprom_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2) { struct sprom_opcode_idx_entry *e1, *e2; e1 = cookiep1; e2 = cookiep2; /* Use the index entry order; this matches the order of variables * returned via bhnd_nvram_sprom_next() */ if (e1 < e2) return (-1); else if (e1 > e2) return (1); return (0); } static int bhnd_nvram_sprom_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf, size_t *len, bhnd_nvram_type otype) { bhnd_nvram_val val; union bhnd_nvram_sprom_storage storage; int error; /* Decode variable to a new value instance */ error = bhnd_nvram_sprom_getvar_common(nv, cookiep, &storage, &val); if (error) return (error); /* Perform value coercion */ error = bhnd_nvram_val_encode(&val, buf, len, otype); /* Clean up */ bhnd_nvram_val_release(&val); return (error); } static int bhnd_nvram_sprom_copy_val(struct bhnd_nvram_data *nv, void *cookiep, bhnd_nvram_val **value) { bhnd_nvram_val val; union bhnd_nvram_sprom_storage storage; int error; /* Decode variable to a new value instance */ error = bhnd_nvram_sprom_getvar_common(nv, cookiep, &storage, &val); if (error) return (error); /* Attempt to copy to heap */ *value = bhnd_nvram_val_copy(&val); bhnd_nvram_val_release(&val); if (*value == NULL) return (ENOMEM); return (0); } static const void * bhnd_nvram_sprom_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type) { /* Unsupported */ return (NULL); } static const char * bhnd_nvram_sprom_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) { const struct bhnd_nvram_vardefn *var; BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep")); var = SPROM_COOKIE_TO_NVRAM(cookiep); BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep)); return (var->name); } static int bhnd_nvram_sprom_filter_setvar(struct bhnd_nvram_data *nv, const char *name, bhnd_nvram_val *value, bhnd_nvram_val **result) { // XXX TODO return (ENXIO); } static int bhnd_nvram_sprom_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { // XXX TODO return (ENXIO); } /** * Initialize SPROM opcode evaluation state. * * @param state The opcode state to be initialized. * @param layout The SPROM layout to be parsed by this instance. * * * @retval 0 success * @retval non-zero If initialization fails, a regular unix error code will be * returned. */ static int sprom_opcode_state_init(struct sprom_opcode_state *state, const struct bhnd_sprom_layout *layout) { memset(state, 0, sizeof(*state)); state->layout = layout; state->input = layout->bindings; state->var_state = SPROM_OPCODE_VAR_STATE_NONE; bit_set(state->revs, layout->rev); return (0); } /** * Reset SPROM opcode evaluation state; future evaluation will be performed * starting at the first opcode. * * @param state The opcode state to be reset. * * @retval 0 success * @retval non-zero If reset fails, a regular unix error code will be returned. */ static int sprom_opcode_state_reset(struct sprom_opcode_state *state) { return (sprom_opcode_state_init(state, state->layout)); } /** * Reset SPROM opcode evaluation state and seek to the @p indexed position. * * @param state The opcode state to be reset. * @param indexed The indexed location to which we'll seek the opcode state. */ static int sprom_opcode_state_seek(struct sprom_opcode_state *state, struct sprom_opcode_idx *indexed) { int error; BHND_NV_ASSERT(indexed->opcodes < state->layout->bindings_size, ("index entry references invalid opcode position")); /* Reset state */ if ((error = sprom_opcode_state_reset(state))) return (error); /* Seek to the indexed sprom opcode offset */ state->input = state->layout->bindings + indexed->opcodes; /* Restore the indexed sprom data offset and VID */ state->offset = indexed->offset; /* Restore the indexed sprom variable ID */ if ((error = sprom_opcode_set_var(state, indexed->vid))) return (error); return (0); } /** * Set the current revision range for @p state. This also resets * variable state. * * @param state The opcode state to update * @param start The first revision in the range. * @param end The last revision in the range. * * @retval 0 success * @retval non-zero If updating @p state fails, a regular unix error code will * be returned. */ static inline int sprom_opcode_set_revs(struct sprom_opcode_state *state, uint8_t start, uint8_t end) { int error; /* Validate the revision range */ if (start > SPROM_OP_REV_MAX || end > SPROM_OP_REV_MAX || end < start) { SPROM_OP_BAD(state, "invalid revision range: %hhu-%hhu\n", start, end); return (EINVAL); } /* Clear variable state */ if ((error = sprom_opcode_clear_var(state))) return (error); /* Reset revision mask */ memset(state->revs, 0x0, sizeof(state->revs)); bit_nset(state->revs, start, end); return (0); } /** * Set the current variable's value mask for @p state. * * @param state The opcode state to update * @param mask The mask to be set * * @retval 0 success * @retval non-zero If updating @p state fails, a regular unix error code will * be returned. */ static inline int sprom_opcode_set_mask(struct sprom_opcode_state *state, uint32_t mask) { if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) { SPROM_OP_BAD(state, "no open variable definition\n"); return (EINVAL); } state->var.mask = mask; return (0); } /** * Set the current variable's value shift for @p state. * * @param state The opcode state to update * @param shift The shift to be set * * @retval 0 success * @retval non-zero If updating @p state fails, a regular unix error code will * be returned. */ static inline int sprom_opcode_set_shift(struct sprom_opcode_state *state, int8_t shift) { if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) { SPROM_OP_BAD(state, "no open variable definition\n"); return (EINVAL); } state->var.shift = shift; return (0); } /** * Register a new BIND/BINDN operation with @p state. * * @param state The opcode state to update. * @param count The number of elements to be bound. * @param skip_in The number of input elements to skip after each bind. * @param skip_in_negative If true, the input skip should be subtracted from * the current offset after each bind. If false, the input skip should be * added. * @param skip_out The number of output elements to skip after each bind. * * @retval 0 success * @retval EINVAL if a variable definition is not open. * @retval EINVAL if @p skip_in and @p count would trigger an overflow or * underflow when applied to the current input offset. * @retval ERANGE if @p skip_in would overflow uint32_t when multiplied by * @p count and the scale value. * @retval ERANGE if @p skip_out would overflow uint32_t when multiplied by * @p count and the scale value. * @retval non-zero If updating @p state otherwise fails, a regular unix error * code will be returned. */ static inline int sprom_opcode_set_bind(struct sprom_opcode_state *state, uint8_t count, uint8_t skip_in, bool skip_in_negative, uint8_t skip_out) { uint32_t iskip_total; uint32_t iskip_scaled; int error; /* Must have an open variable */ if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) { SPROM_OP_BAD(state, "no open variable definition\n"); SPROM_OP_BAD(state, "BIND outside of variable definition\n"); return (EINVAL); } /* Cannot overwite an existing bind definition */ if (state->var.have_bind) { SPROM_OP_BAD(state, "BIND overwrites existing definition\n"); return (EINVAL); } /* Must have a count of at least 1 */ if (count == 0) { SPROM_OP_BAD(state, "BIND with zero count\n"); return (EINVAL); } /* Scale skip_in by the current type width */ iskip_scaled = skip_in; if ((error = sprom_opcode_apply_scale(state, &iskip_scaled))) return (error); /* Calculate total input bytes skipped: iskip_scaled * count) */ if (iskip_scaled > 0 && UINT32_MAX / iskip_scaled < count) { SPROM_OP_BAD(state, "skip_in %hhu would overflow", skip_in); return (EINVAL); } iskip_total = iskip_scaled * count; /* Verify that the skip_in value won't under/overflow the current * input offset. */ if (skip_in_negative) { if (iskip_total > state->offset) { SPROM_OP_BAD(state, "skip_in %hhu would underflow " "offset %u\n", skip_in, state->offset); return (EINVAL); } } else { if (UINT32_MAX - iskip_total < state->offset) { SPROM_OP_BAD(state, "skip_in %hhu would overflow " "offset %u\n", skip_in, state->offset); return (EINVAL); } } /* Set the actual count and skip values */ state->var.have_bind = true; state->var.bind.count = count; state->var.bind.skip_in = skip_in; state->var.bind.skip_out = skip_out; state->var.bind.skip_in_negative = skip_in_negative; /* Update total bind count for the current variable */ state->var.bind_total++; return (0); } /** * Apply and clear the current opcode bind state, if any. * * @param state The opcode state to update. * * @retval 0 success * @retval non-zero If updating @p state otherwise fails, a regular unix error * code will be returned. */ static int sprom_opcode_flush_bind(struct sprom_opcode_state *state) { int error; uint32_t skip; /* Nothing to do? */ if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN || !state->var.have_bind) return (0); /* Apply SPROM offset adjustment */ if (state->var.bind.count > 0) { skip = state->var.bind.skip_in * state->var.bind.count; if ((error = sprom_opcode_apply_scale(state, &skip))) return (error); if (state->var.bind.skip_in_negative) { state->offset -= skip; } else { state->offset += skip; } } /* Clear bind state */ memset(&state->var.bind, 0, sizeof(state->var.bind)); state->var.have_bind = false; return (0); } /** * Set the current type to @p type, and reset type-specific * stream state. * * @param state The opcode state to update. * @param type The new type. * * @retval 0 success * @retval EINVAL if @p vid is not a valid variable ID. */ static int sprom_opcode_set_type(struct sprom_opcode_state *state, bhnd_nvram_type type) { bhnd_nvram_type base_type; size_t width; uint32_t mask; /* Must have an open variable definition */ if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) { SPROM_OP_BAD(state, "type set outside variable definition\n"); return (EINVAL); } /* Fetch type width for use as our scale value */ width = bhnd_nvram_type_width(type); if (width == 0) { SPROM_OP_BAD(state, "unsupported variable-width type: %d\n", type); return (EINVAL); } else if (width > UINT32_MAX) { SPROM_OP_BAD(state, "invalid type width %zu for type: %d\n", width, type); return (EINVAL); } /* Determine default mask value for the element type */ base_type = bhnd_nvram_base_type(type); switch (base_type) { case BHND_NVRAM_TYPE_UINT8: case BHND_NVRAM_TYPE_INT8: case BHND_NVRAM_TYPE_CHAR: mask = UINT8_MAX; break; case BHND_NVRAM_TYPE_UINT16: case BHND_NVRAM_TYPE_INT16: mask = UINT16_MAX; break; case BHND_NVRAM_TYPE_UINT32: case BHND_NVRAM_TYPE_INT32: mask = UINT32_MAX; break; case BHND_NVRAM_TYPE_STRING: /* fallthrough (unused by SPROM) */ default: SPROM_OP_BAD(state, "unsupported type: %d\n", type); return (EINVAL); } /* Update state */ state->var.base_type = base_type; state->var.mask = mask; state->var.scale = (uint32_t)width; return (0); } /** * Clear current variable state, if any. * * @param state The opcode state to update. */ static int sprom_opcode_clear_var(struct sprom_opcode_state *state) { if (state->var_state == SPROM_OPCODE_VAR_STATE_NONE) return (0); BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_DONE, ("incomplete variable definition")); BHND_NV_ASSERT(!state->var.have_bind, ("stale bind state")); memset(&state->var, 0, sizeof(state->var)); state->var_state = SPROM_OPCODE_VAR_STATE_NONE; return (0); } /** * Set the current variable's array element count to @p nelem. * * @param state The opcode state to update. * @param nelem The new array length. * * @retval 0 success * @retval EINVAL if no open variable definition exists. * @retval EINVAL if @p nelem is zero. * @retval ENXIO if @p nelem is greater than one, and the current variable does * not have an array type. * @retval ENXIO if @p nelem exceeds the array length of the NVRAM variable * definition. */ static int sprom_opcode_set_nelem(struct sprom_opcode_state *state, uint8_t nelem) { const struct bhnd_nvram_vardefn *var; /* Must have a defined variable */ if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) { SPROM_OP_BAD(state, "array length set without open variable " "state"); return (EINVAL); } /* Locate the actual variable definition */ if ((var = bhnd_nvram_get_vardefn(state->vid)) == NULL) { SPROM_OP_BAD(state, "unknown variable ID: %zu\n", state->vid); return (EINVAL); } /* Must be greater than zero */ if (nelem == 0) { SPROM_OP_BAD(state, "invalid nelem: %hhu\n", nelem); return (EINVAL); } /* If the variable is not an array-typed value, the array length * must be 1 */ if (!bhnd_nvram_is_array_type(var->type) && nelem != 1) { SPROM_OP_BAD(state, "nelem %hhu on non-array %zu\n", nelem, state->vid); return (ENXIO); } /* Cannot exceed the variable's defined array length */ if (nelem > var->nelem) { SPROM_OP_BAD(state, "nelem %hhu exceeds %zu length %hhu\n", nelem, state->vid, var->nelem); return (ENXIO); } /* Valid length; update state */ state->var.nelem = nelem; return (0); } /** * Set the current variable ID to @p vid, and reset variable-specific * stream state. * * @param state The opcode state to update. * @param vid The new variable ID. * * @retval 0 success * @retval EINVAL if @p vid is not a valid variable ID. */ static int sprom_opcode_set_var(struct sprom_opcode_state *state, size_t vid) { const struct bhnd_nvram_vardefn *var; int error; BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_NONE, ("overwrite of open variable definition")); /* Locate the variable definition */ if ((var = bhnd_nvram_get_vardefn(vid)) == NULL) { SPROM_OP_BAD(state, "unknown variable ID: %zu\n", vid); return (EINVAL); } /* Update vid and var state */ state->vid = vid; state->var_state = SPROM_OPCODE_VAR_STATE_OPEN; /* Initialize default variable record values */ memset(&state->var, 0x0, sizeof(state->var)); /* Set initial base type */ if ((error = sprom_opcode_set_type(state, var->type))) return (error); /* Set default array length */ if ((error = sprom_opcode_set_nelem(state, var->nelem))) return (error); return (0); } /** * Mark the currently open variable definition as complete. * * @param state The opcode state to update. * * @retval 0 success * @retval EINVAL if no incomplete open variable definition exists. */ static int sprom_opcode_end_var(struct sprom_opcode_state *state) { if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) { SPROM_OP_BAD(state, "no open variable definition\n"); return (EINVAL); } state->var_state = SPROM_OPCODE_VAR_STATE_DONE; return (0); } /** * Apply the current scale to @p value. * * @param state The SPROM opcode state. * @param[in,out] value The value to scale * * @retval 0 success * @retval EINVAL if no open variable definition exists. * @retval EINVAL if applying the current scale would overflow. */ static int sprom_opcode_apply_scale(struct sprom_opcode_state *state, uint32_t *value) { /* Must have a defined variable (and thus, scale) */ if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) { SPROM_OP_BAD(state, "scaled value encoded without open " "variable state"); return (EINVAL); } /* Applying the scale value must not overflow */ if (UINT32_MAX / state->var.scale < *value) { SPROM_OP_BAD(state, "cannot represent %" PRIu32 " * %" PRIu32 "\n", *value, state->var.scale); return (EINVAL); } *value = (*value) * state->var.scale; return (0); } /** * Read a SPROM_OP_DATA_* value from @p opcodes. * * @param state The SPROM opcode state. * @param type The SROM_OP_DATA_* type to be read. * @param opval On success, the 32bit data representation. If @p type is signed, * the value will be appropriately sign extended and may be directly cast to * int32_t. * * @retval 0 success * @retval non-zero If reading the value otherwise fails, a regular unix error * code will be returned. */ static int sprom_opcode_read_opval32(struct sprom_opcode_state *state, uint8_t type, uint32_t *opval) { const uint8_t *p; int error; p = state->input; switch (type) { case SPROM_OP_DATA_I8: /* Convert to signed value first, then sign extend */ *opval = (int32_t)(int8_t)(*p); p += 1; break; case SPROM_OP_DATA_U8: *opval = *p; p += 1; break; case SPROM_OP_DATA_U8_SCALED: *opval = *p; if ((error = sprom_opcode_apply_scale(state, opval))) return (error); p += 1; break; case SPROM_OP_DATA_U16: *opval = le16dec(p); p += 2; break; case SPROM_OP_DATA_U32: *opval = le32dec(p); p += 4; break; default: SPROM_OP_BAD(state, "unsupported data type: %hhu\n", type); return (EINVAL); } /* Update read address */ state->input = p; return (0); } /** * Return true if our layout revision is currently defined by the SPROM * opcode state. * * This may be used to test whether the current opcode stream state applies * to the layout that we are actually parsing. * * A given opcode stream may cover multiple layout revisions, switching * between them prior to defining a set of variables. */ static inline bool sprom_opcode_matches_layout_rev(struct sprom_opcode_state *state) { return (bit_test(state->revs, state->layout->rev)); } /** * When evaluating @p state and @p opcode, rewrite @p opcode and the current * evaluation state, as required. * * If @p opcode is rewritten, it should be returned from * sprom_opcode_step() instead of the opcode parsed from @p state's opcode * stream. * * If @p opcode remains unmodified, then sprom_opcode_step() should proceed * to standard evaluation. */ static int sprom_opcode_rewrite_opcode(struct sprom_opcode_state *state, uint8_t *opcode) { uint8_t op; int error; op = SPROM_OPCODE_OP(*opcode); switch (state->var_state) { case SPROM_OPCODE_VAR_STATE_NONE: /* No open variable definition */ return (0); case SPROM_OPCODE_VAR_STATE_OPEN: /* Open variable definition; check for implicit closure. */ /* * If a variable definition contains no explicit bind * instructions prior to closure, we must generate a DO_BIND * instruction with count and skip values of 1. */ if (SPROM_OP_IS_VAR_END(op) && state->var.bind_total == 0) { uint8_t count, skip_in, skip_out; bool skip_in_negative; /* Create bind with skip_in/skip_out of 1, count of 1 */ count = 1; skip_in = 1; skip_out = 1; skip_in_negative = false; error = sprom_opcode_set_bind(state, count, skip_in, skip_in_negative, skip_out); if (error) return (error); /* Return DO_BIND */ *opcode = SPROM_OPCODE_DO_BIND | (0 << SPROM_OP_BIND_SKIP_IN_SIGN) | (1 << SPROM_OP_BIND_SKIP_IN_SHIFT) | (1 << SPROM_OP_BIND_SKIP_OUT_SHIFT); return (0); } /* * If a variable is implicitly closed (e.g. by a new variable * definition), we must generate a VAR_END instruction. */ if (SPROM_OP_IS_IMPLICIT_VAR_END(op)) { /* Mark as complete */ if ((error = sprom_opcode_end_var(state))) return (error); /* Return VAR_END */ *opcode = SPROM_OPCODE_VAR_END; return (0); } break; case SPROM_OPCODE_VAR_STATE_DONE: /* Previously completed variable definition. Discard variable * state */ return (sprom_opcode_clear_var(state)); } /* Nothing to do */ return (0); } /** * Evaluate one opcode from @p state. * * @param state The opcode state to be evaluated. * @param[out] opcode On success, the evaluated opcode * * @retval 0 success * @retval ENOENT if EOF is reached * @retval non-zero if evaluation otherwise fails, a regular unix error * code will be returned. */ static int sprom_opcode_step(struct sprom_opcode_state *state, uint8_t *opcode) { int error; while (*state->input != SPROM_OPCODE_EOF) { uint32_t val; uint8_t op, rewrite, immd; /* Fetch opcode */ *opcode = *state->input; op = SPROM_OPCODE_OP(*opcode); immd = SPROM_OPCODE_IMM(*opcode); /* Clear any existing bind state */ if ((error = sprom_opcode_flush_bind(state))) return (error); /* Insert local opcode based on current state? */ rewrite = *opcode; if ((error = sprom_opcode_rewrite_opcode(state, &rewrite))) return (error); if (rewrite != *opcode) { /* Provide rewritten opcode */ *opcode = rewrite; /* We must keep evaluating until we hit a state * applicable to the SPROM revision we're parsing */ if (!sprom_opcode_matches_layout_rev(state)) continue; return (0); } /* Advance input */ state->input++; switch (op) { case SPROM_OPCODE_VAR_IMM: if ((error = sprom_opcode_set_var(state, immd))) return (error); break; case SPROM_OPCODE_VAR_REL_IMM: error = sprom_opcode_set_var(state, state->vid + immd); if (error) return (error); break; case SPROM_OPCODE_VAR: error = sprom_opcode_read_opval32(state, immd, &val); if (error) return (error); if ((error = sprom_opcode_set_var(state, val))) return (error); break; case SPROM_OPCODE_VAR_END: if ((error = sprom_opcode_end_var(state))) return (error); break; case SPROM_OPCODE_NELEM: immd = *state->input; if ((error = sprom_opcode_set_nelem(state, immd))) return (error); state->input++; break; case SPROM_OPCODE_DO_BIND: case SPROM_OPCODE_DO_BINDN: { uint8_t count, skip_in, skip_out; bool skip_in_negative; /* Fetch skip arguments */ skip_in = (immd & SPROM_OP_BIND_SKIP_IN_MASK) >> SPROM_OP_BIND_SKIP_IN_SHIFT; skip_in_negative = ((immd & SPROM_OP_BIND_SKIP_IN_SIGN) != 0); skip_out = (immd & SPROM_OP_BIND_SKIP_OUT_MASK) >> SPROM_OP_BIND_SKIP_OUT_SHIFT; /* Fetch count argument (if any) */ if (op == SPROM_OPCODE_DO_BINDN) { /* Count is provided as trailing U8 */ count = *state->input; state->input++; } else { count = 1; } /* Set BIND state */ error = sprom_opcode_set_bind(state, count, skip_in, skip_in_negative, skip_out); if (error) return (error); break; } case SPROM_OPCODE_DO_BINDN_IMM: { uint8_t count, skip_in, skip_out; bool skip_in_negative; /* Implicit skip_in/skip_out of 1, count encoded as immd * value */ count = immd; skip_in = 1; skip_out = 1; skip_in_negative = false; error = sprom_opcode_set_bind(state, count, skip_in, skip_in_negative, skip_out); if (error) return (error); break; } case SPROM_OPCODE_REV_IMM: if ((error = sprom_opcode_set_revs(state, immd, immd))) return (error); break; case SPROM_OPCODE_REV_RANGE: { uint8_t range; uint8_t rstart, rend; /* Revision range is encoded in next byte, as * { uint8_t start:4, uint8_t end:4 } */ range = *state->input; rstart = (range & SPROM_OP_REV_START_MASK) >> SPROM_OP_REV_START_SHIFT; rend = (range & SPROM_OP_REV_END_MASK) >> SPROM_OP_REV_END_SHIFT; /* Update revision bitmask */ error = sprom_opcode_set_revs(state, rstart, rend); if (error) return (error); /* Advance input */ state->input++; break; } case SPROM_OPCODE_MASK_IMM: if ((error = sprom_opcode_set_mask(state, immd))) return (error); break; case SPROM_OPCODE_MASK: error = sprom_opcode_read_opval32(state, immd, &val); if (error) return (error); if ((error = sprom_opcode_set_mask(state, val))) return (error); break; case SPROM_OPCODE_SHIFT_IMM: if ((error = sprom_opcode_set_shift(state, immd * 2))) return (error); break; case SPROM_OPCODE_SHIFT: { int8_t shift; if (immd == SPROM_OP_DATA_I8) { shift = (int8_t)(*state->input); } else if (immd == SPROM_OP_DATA_U8) { val = *state->input; if (val > INT8_MAX) { SPROM_OP_BAD(state, "invalid shift " "value: %#x\n", val); } shift = val; } else { SPROM_OP_BAD(state, "unsupported shift data " "type: %#hhx\n", immd); return (EINVAL); } if ((error = sprom_opcode_set_shift(state, shift))) return (error); state->input++; break; } case SPROM_OPCODE_OFFSET_REL_IMM: /* Fetch unscaled relative offset */ val = immd; /* Apply scale */ if ((error = sprom_opcode_apply_scale(state, &val))) return (error); /* Adding val must not overflow our offset */ if (UINT32_MAX - state->offset < val) { BHND_NV_LOG("offset out of range\n"); return (EINVAL); } /* Adjust offset */ state->offset += val; break; case SPROM_OPCODE_OFFSET: error = sprom_opcode_read_opval32(state, immd, &val); if (error) return (error); state->offset = val; break; case SPROM_OPCODE_TYPE: /* Type follows as U8 */ immd = *state->input; state->input++; /* fall through */ case SPROM_OPCODE_TYPE_IMM: switch (immd) { case BHND_NVRAM_TYPE_UINT8: case BHND_NVRAM_TYPE_UINT16: case BHND_NVRAM_TYPE_UINT32: case BHND_NVRAM_TYPE_UINT64: case BHND_NVRAM_TYPE_INT8: case BHND_NVRAM_TYPE_INT16: case BHND_NVRAM_TYPE_INT32: case BHND_NVRAM_TYPE_INT64: case BHND_NVRAM_TYPE_CHAR: case BHND_NVRAM_TYPE_STRING: error = sprom_opcode_set_type(state, (bhnd_nvram_type)immd); if (error) return (error); break; default: BHND_NV_LOG("unrecognized type %#hhx\n", immd); return (EINVAL); } break; default: BHND_NV_LOG("unrecognized opcode %#hhx\n", *opcode); return (EINVAL); } /* We must keep evaluating until we hit a state applicable to * the SPROM revision we're parsing */ if (sprom_opcode_matches_layout_rev(state)) return (0); } /* End of opcode stream */ return (ENOENT); } /** * Reset SPROM opcode evaluation state, seek to the @p indexed position, * and perform complete evaluation of the variable's opcodes. * * @param state The opcode state to be to be evaluated. * @param indexed The indexed variable location. * * @retval 0 success * @retval non-zero If evaluation fails, a regular unix error code will be * returned. */ static int sprom_opcode_parse_var(struct sprom_opcode_state *state, struct sprom_opcode_idx *indexed) { uint8_t opcode; int error; /* Seek to entry */ if ((error = sprom_opcode_state_seek(state, indexed))) return (error); /* Parse full variable definition */ while ((error = sprom_opcode_step(state, &opcode)) == 0) { /* Iterate until VAR_END */ if (SPROM_OPCODE_OP(opcode) != SPROM_OPCODE_VAR_END) continue; BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_DONE, ("incomplete variable definition")); return (0); } /* Error parsing definition */ return (error); } /** * Evaluate @p state until the next variable definition is found. * * @param state The opcode state to be evaluated. * * @retval 0 success * @retval ENOENT if no additional variable definitions are available. * @retval non-zero if evaluation otherwise fails, a regular unix error * code will be returned. */ static int sprom_opcode_next_var(struct sprom_opcode_state *state) { uint8_t opcode; int error; /* Step until we hit a variable opcode */ while ((error = sprom_opcode_step(state, &opcode)) == 0) { switch (SPROM_OPCODE_OP(opcode)) { case SPROM_OPCODE_VAR: case SPROM_OPCODE_VAR_IMM: case SPROM_OPCODE_VAR_REL_IMM: BHND_NV_ASSERT( state->var_state == SPROM_OPCODE_VAR_STATE_OPEN, ("missing variable definition")); return (0); default: continue; } } /* Reached EOF, or evaluation failed */ return (error); } /** * Evaluate @p state until the next binding for the current variable definition * is found. * * @param state The opcode state to be evaluated. * * @retval 0 success * @retval ENOENT if no additional binding opcodes are found prior to reaching * a new variable definition, or the end of @p state's binding opcodes. * @retval non-zero if evaluation otherwise fails, a regular unix error * code will be returned. */ static int sprom_opcode_next_binding(struct sprom_opcode_state *state) { uint8_t opcode; int error; if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) return (EINVAL); /* Step until we hit a bind opcode, or a new variable */ while ((error = sprom_opcode_step(state, &opcode)) == 0) { switch (SPROM_OPCODE_OP(opcode)) { case SPROM_OPCODE_DO_BIND: case SPROM_OPCODE_DO_BINDN: case SPROM_OPCODE_DO_BINDN_IMM: /* Found next bind */ BHND_NV_ASSERT( state->var_state == SPROM_OPCODE_VAR_STATE_OPEN, ("missing variable definition")); BHND_NV_ASSERT(state->var.have_bind, ("missing bind")); return (0); case SPROM_OPCODE_VAR_END: /* No further binding opcodes */ BHND_NV_ASSERT( state->var_state == SPROM_OPCODE_VAR_STATE_DONE, ("variable definition still available")); return (ENOENT); } } /* Not found, or evaluation failed */ return (error); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c (revision 310296) @@ -1,758 +1,773 @@ /*- * 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$"); #ifdef _KERNEL #include #include #include #include #else /* !_KERNEL */ #include #include #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_data_tlvreg.h" /* * CFE TLV NVRAM data class. * * The CFE-defined TLV NVRAM format is used on the WGT634U. */ struct bhnd_nvram_tlv { struct bhnd_nvram_data nv; /**< common instance state */ struct bhnd_nvram_io *data; /**< backing buffer */ size_t count; /**< variable count */ }; BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", sizeof(struct bhnd_nvram_tlv)) /** Minimal TLV_ENV record header */ struct bhnd_nvram_tlv_env_hdr { uint8_t tag; uint8_t size; } __packed; /** Minimal TLV_ENV record */ struct bhnd_nvram_tlv_env { struct bhnd_nvram_tlv_env_hdr hdr; uint8_t flags; char envp[]; } __packed; /* Return the length in bytes of an TLV_ENV's envp data */ #define NVRAM_TLV_ENVP_DATA_LEN(_env) \ (((_env)->hdr.size < sizeof((_env)->flags)) ? 0 : \ ((_env)->hdr.size - sizeof((_env)->flags))) /* Maximum supported length of the envp data field, in bytes */ #define NVRAM_TLV_ENVP_DATA_MAX_LEN \ (UINT8_MAX - sizeof(uint8_t) /* flags */) static int bhnd_nvram_tlv_parse_size( struct bhnd_nvram_io *io, size_t *size); static int bhnd_nvram_tlv_next_record( struct bhnd_nvram_io *io, size_t *next, size_t *offset, uint8_t *tag); static struct bhnd_nvram_tlv_env *bhnd_nvram_tlv_next_env( struct bhnd_nvram_tlv *tlv, size_t *next, void **cookiep); static struct bhnd_nvram_tlv_env *bhnd_nvram_tlv_get_env( struct bhnd_nvram_tlv *tlv, void *cookiep); static void *bhnd_nvram_tlv_to_cookie( struct bhnd_nvram_tlv *tlv, size_t io_offset); static size_t bhnd_nvram_tlv_to_offset( struct bhnd_nvram_tlv *tlv, void *cookiep); static int bhnd_nvram_tlv_probe(struct bhnd_nvram_io *io) { struct bhnd_nvram_tlv_env ident; size_t nbytes; int error; nbytes = bhnd_nvram_io_getsize(io); /* Handle what might be an empty TLV image */ if (nbytes < sizeof(ident)) { uint8_t tag; /* Fetch just the first tag */ error = bhnd_nvram_io_read(io, 0x0, &tag, sizeof(tag)); if (error) return (error); /* This *could* be an empty TLV image, but all we're * testing for here is a single 0x0 byte followed by EOF */ if (tag == NVRAM_TLV_TYPE_END) return (BHND_NVRAM_DATA_PROBE_MAYBE); return (ENXIO); } /* Otherwise, look at the initial header for a valid TLV ENV tag, * plus one byte of the entry data */ error = bhnd_nvram_io_read(io, 0x0, &ident, sizeof(ident) + sizeof(ident.envp[0])); if (error) return (error); /* First entry should be a variable record (which we statically * assert as being defined to use a single byte size field) */ if (ident.hdr.tag != NVRAM_TLV_TYPE_ENV) return (ENXIO); _Static_assert(NVRAM_TLV_TYPE_ENV & NVRAM_TLV_TF_U8_LEN, "TYPE_ENV is not a U8-sized field"); /* The entry must be at least 3 characters ('x=\0') in length */ if (ident.hdr.size < 3) return (ENXIO); /* The first character should be a valid key char (alpha) */ if (!bhnd_nv_isalpha(ident.envp[0])) return (ENXIO); return (BHND_NVRAM_DATA_PROBE_DEFAULT); } /** * Initialize @p tlv with the provided NVRAM TLV data mapped by @p src. * * @param tlv A newly allocated data instance. */ static int bhnd_nvram_tlv_init(struct bhnd_nvram_tlv *tlv, struct bhnd_nvram_io *src) { struct bhnd_nvram_tlv_env *env; size_t size; size_t next; int error; BHND_NV_ASSERT(tlv->data == NULL, ("tlv data already initialized")); /* Determine the actual size of the TLV source data */ if ((error = bhnd_nvram_tlv_parse_size(src, &size))) return (error); /* Copy to our own internal buffer */ if ((tlv->data = bhnd_nvram_iobuf_copy_range(src, 0x0, size)) == NULL) return (ENOMEM); /* Initialize our backing buffer */ tlv->count = 0; next = 0; while ((env = bhnd_nvram_tlv_next_env(tlv, &next, NULL)) != NULL) { size_t env_len; size_t name_len; /* TLV_ENV data must not be empty */ env_len = NVRAM_TLV_ENVP_DATA_LEN(env); if (env_len == 0) { BHND_NV_LOG("cannot parse zero-length TLV_ENV record " "data\n"); return (EINVAL); } /* Parse the key=value string, and then replace the '=' * delimiter with '\0' to allow us to provide direct * name pointers from our backing buffer */ error = bhnd_nvram_parse_env(env->envp, env_len, '=', NULL, &name_len, NULL, NULL); if (error) { BHND_NV_LOG("error parsing TLV_ENV data: %d\n", error); return (error); } /* Replace '=' with '\0' */ *(env->envp + name_len) = '\0'; /* Add to variable count */ tlv->count++; }; return (0); } static int bhnd_nvram_tlv_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) { struct bhnd_nvram_tlv *tlv; int error; /* Allocate and initialize the TLV data instance */ tlv = (struct bhnd_nvram_tlv *)nv; /* Parse the TLV input data and initialize our backing * data representation */ if ((error = bhnd_nvram_tlv_init(tlv, io))) { bhnd_nvram_tlv_free(nv); return (error); } return (0); } static void bhnd_nvram_tlv_free(struct bhnd_nvram_data *nv) { struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv; if (tlv->data != NULL) bhnd_nvram_io_free(tlv->data); } size_t bhnd_nvram_tlv_count(struct bhnd_nvram_data *nv) { struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv; return (tlv->count); } +static bhnd_nvram_plist * +bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv) +{ + return (NULL); +} + static int bhnd_nvram_tlv_size(struct bhnd_nvram_data *nv, size_t *size) { /* Let the serialization implementation calculate the length */ return (bhnd_nvram_data_serialize(nv, NULL, size)); } static int bhnd_nvram_tlv_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) { struct bhnd_nvram_tlv *tlv; size_t limit; size_t next; uint8_t tag; int error; tlv = (struct bhnd_nvram_tlv *)nv; /* Save the buffer capacity */ if (buf == NULL) limit = 0; else limit = *len; /* Write all of our TLV records to the output buffer (or just * calculate the buffer size that would be required) */ next = 0; do { struct bhnd_nvram_tlv_env *env; uint8_t *p; size_t name_len; size_t rec_offset, rec_size; /* Parse the TLV record */ error = bhnd_nvram_tlv_next_record(tlv->data, &next, &rec_offset, &tag); if (error) return (error); rec_size = next - rec_offset; /* Calculate our output pointer */ if (rec_offset > limit || limit - rec_offset < rec_size) { /* buffer is full; cannot write */ p = NULL; } else { p = (uint8_t *)buf + rec_offset; } /* If not writing, nothing further to do for this record */ if (p == NULL) continue; /* Copy to the output buffer */ error = bhnd_nvram_io_read(tlv->data, rec_offset, p, rec_size); if (error) return (error); /* All further processing is TLV_ENV-specific */ if (tag != NVRAM_TLV_TYPE_ENV) continue; /* Restore the original key=value format, rewriting '\0' * delimiter back to '=' */ env = (struct bhnd_nvram_tlv_env *)p; name_len = strlen(env->envp); /* skip variable name */ *(env->envp + name_len) = '='; /* set '=' */ } while (tag != NVRAM_TLV_TYPE_END); /* The 'next' offset should now point at EOF, and represents * the total length of the serialized output. */ *len = next; if (buf != NULL && limit < *len) return (ENOMEM); return (0); } static uint32_t bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv) { return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS); } static const char * bhnd_nvram_tlv_next(struct bhnd_nvram_data *nv, void **cookiep) { struct bhnd_nvram_tlv *tlv; struct bhnd_nvram_tlv_env *env; size_t io_offset; tlv = (struct bhnd_nvram_tlv *)nv; - /* Seek past the TLV_ENV record referenced by cookiep */ - io_offset = bhnd_nvram_tlv_to_offset(tlv, *cookiep); - if (bhnd_nvram_tlv_next_env(tlv, &io_offset, NULL) == NULL) - BHND_NV_PANIC("invalid cookiep: %p\n", cookiep); + /* Find next readable TLV record */ + if (*cookiep == NULL) { + /* Start search at offset 0x0 */ + io_offset = 0x0; + env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep); + } else { + /* Seek past the previous env record */ + io_offset = bhnd_nvram_tlv_to_offset(tlv, *cookiep); + env = bhnd_nvram_tlv_next_env(tlv, &io_offset, NULL); + if (env == NULL) + BHND_NV_PANIC("invalid cookiep; record missing"); - /* Fetch the next TLV_ENV record */ - if ((env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep)) == NULL) { - /* No remaining ENV records */ - return (NULL); + /* Advance to next env record, update the caller's cookiep */ + env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep); } + + /* Check for EOF */ + if (env == NULL) + return (NULL); /* Return the NUL terminated name */ return (env->envp); } static void * bhnd_nvram_tlv_find(struct bhnd_nvram_data *nv, const char *name) { return (bhnd_nvram_data_generic_find(nv, name)); } static int bhnd_nvram_tlv_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2) { if (cookiep1 < cookiep2) return (-1); if (cookiep1 > cookiep2) return (1); return (0); } static int bhnd_nvram_tlv_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_tlv_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_tlv_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type) { struct bhnd_nvram_tlv *tlv; struct bhnd_nvram_tlv_env *env; const char *val; int error; tlv = (struct bhnd_nvram_tlv *)nv; /* Fetch pointer to the TLV_ENV record */ if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL) BHND_NV_PANIC("invalid cookiep: %p", cookiep); /* Parse value pointer and length from key\0value data */ error = bhnd_nvram_parse_env(env->envp, NVRAM_TLV_ENVP_DATA_LEN(env), '\0', NULL, NULL, &val, len); if (error) BHND_NV_PANIC("unexpected error parsing '%s'", env->envp); /* Type is always CSTR */ *type = BHND_NVRAM_TYPE_STRING; return (val); } static const char * bhnd_nvram_tlv_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) { struct bhnd_nvram_tlv *tlv; const struct bhnd_nvram_tlv_env *env; tlv = (struct bhnd_nvram_tlv *)nv; /* Fetch pointer to the TLV_ENV record */ if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL) BHND_NV_PANIC("invalid cookiep: %p", cookiep); /* Return name pointer */ return (&env->envp[0]); } static int bhnd_nvram_tlv_filter_setvar(struct bhnd_nvram_data *nv, const char *name, bhnd_nvram_val *value, bhnd_nvram_val **result) { bhnd_nvram_val *str; const char *inp; bhnd_nvram_type itype; size_t ilen; size_t name_len, tlv_nremain; int error; tlv_nremain = NVRAM_TLV_ENVP_DATA_MAX_LEN; /* Name (trimmed of any path prefix) must be valid */ if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name))) return (EINVAL); /* 'name=' must fit within the maximum TLV_ENV record length */ name_len = strlen(name) + 1; /* '=' */ if (tlv_nremain < name_len) { BHND_NV_LOG("'%s=' exceeds maximum TLV_ENV record length\n", name); return (EINVAL); } tlv_nremain -= name_len; /* Convert value to a (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); /* The string value must fit within remaining TLV_ENV record length */ inp = bhnd_nvram_val_bytes(str, &ilen, &itype); if (tlv_nremain < ilen) { BHND_NV_LOG("'%.*s\\0' exceeds maximum TLV_ENV record length\n", BHND_NV_PRINT_WIDTH(ilen), inp); bhnd_nvram_val_release(str); return (EINVAL); } tlv_nremain -= name_len; /* Success. Transfer result ownership to the caller. */ *result = str; return (0); } static int bhnd_nvram_tlv_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { /* We permit deletion of any variable */ return (0); } /** * Iterate over the records starting at @p next, returning the parsed * record's @p tag, @p size, and @p offset. * * @param io The I/O context to parse. * @param[in,out] next The next offset to be parsed, or 0x0 * to begin parsing. Upon successful * return, will be set to the offset of the * next record (or EOF, if * NVRAM_TLV_TYPE_END was parsed). * @param[out] offset The record's value offset. * @param[out] tag The record's tag. * * @retval 0 success * @retval EINVAL if parsing @p io as TLV fails. * @retval non-zero if reading @p io otherwise fails, a regular unix error * code will be returned. */ static int bhnd_nvram_tlv_next_record(struct bhnd_nvram_io *io, size_t *next, size_t *offset, uint8_t *tag) { size_t io_offset, io_size; uint16_t parsed_len; uint8_t len_hdr[2]; int error; io_offset = *next; io_size = bhnd_nvram_io_getsize(io); /* Save the record offset */ if (offset != NULL) *offset = io_offset; /* Fetch initial tag */ error = bhnd_nvram_io_read(io, io_offset, tag, sizeof(*tag)); if (error) return (error); io_offset++; /* EOF */ if (*tag == NVRAM_TLV_TYPE_END) { *next = io_offset; return (0); } /* Read length field */ if (*tag & NVRAM_TLV_TF_U8_LEN) { error = bhnd_nvram_io_read(io, io_offset, &len_hdr, sizeof(len_hdr[0])); if (error) { BHND_NV_LOG("error reading TLV record size: %d\n", error); return (error); } parsed_len = len_hdr[0]; io_offset++; } else { error = bhnd_nvram_io_read(io, io_offset, &len_hdr, sizeof(len_hdr)); if (error) { BHND_NV_LOG("error reading 16-bit TLV record " "size: %d\n", error); return (error); } parsed_len = (len_hdr[0] << 8) | len_hdr[1]; io_offset += 2; } /* Advance to next record */ if (parsed_len > io_size || io_size - parsed_len < io_offset) { /* Hit early EOF */ BHND_NV_LOG("TLV record length %hu truncated by input " "size of %zu\n", parsed_len, io_size); return (EINVAL); } *next = io_offset + parsed_len; /* Valid record found */ return (0); } /** * Parse the TLV data in @p io to determine the total size of the TLV * data mapped by @p io (which may be less than the size of @p io). */ static int bhnd_nvram_tlv_parse_size(struct bhnd_nvram_io *io, size_t *size) { size_t next; uint8_t tag; int error; /* We have to perform a minimal parse to determine the actual length */ next = 0x0; *size = 0x0; /* Iterate over the input until we hit END tag or the read fails */ do { error = bhnd_nvram_tlv_next_record(io, &next, NULL, &tag); if (error) return (error); } while (tag != NVRAM_TLV_TYPE_END); /* Offset should now point to EOF */ BHND_NV_ASSERT(next <= bhnd_nvram_io_getsize(io), ("parse returned invalid EOF offset")); *size = next; return (0); } /** * Iterate over the records in @p tlv, returning a pointer to the next * NVRAM_TLV_TYPE_ENV record, or NULL if EOF is reached. * * @param tlv The TLV instance. * @param[in,out] next The next offset to be parsed, or 0x0 * to begin parsing. Upon successful * return, will be set to the offset of the * next record. */ static struct bhnd_nvram_tlv_env * bhnd_nvram_tlv_next_env(struct bhnd_nvram_tlv *tlv, size_t *next, void **cookiep) { uint8_t tag; int error; /* Find the next TLV_ENV record, starting at @p next */ do { void *c; size_t offset; /* Fetch the next TLV record */ error = bhnd_nvram_tlv_next_record(tlv->data, next, &offset, &tag); if (error) { BHND_NV_LOG("unexpected error in next_record(): %d\n", error); return (NULL); } /* Only interested in ENV records */ if (tag != NVRAM_TLV_TYPE_ENV) continue; /* Map and return TLV_ENV record pointer */ c = bhnd_nvram_tlv_to_cookie(tlv, offset); /* Provide the cookiep value for the returned record */ if (cookiep != NULL) *cookiep = c; return (bhnd_nvram_tlv_get_env(tlv, c)); } while (tag != NVRAM_TLV_TYPE_END); /* No remaining ENV records */ return (NULL); } /** * Return a pointer to the TLV_ENV record for @p cookiep, or NULL * if none vailable. */ static struct bhnd_nvram_tlv_env * bhnd_nvram_tlv_get_env(struct bhnd_nvram_tlv *tlv, void *cookiep) { struct bhnd_nvram_tlv_env *env; void *ptr; size_t navail; size_t io_offset, io_size; int error; io_size = bhnd_nvram_io_getsize(tlv->data); io_offset = bhnd_nvram_tlv_to_offset(tlv, cookiep); /* At EOF? */ if (io_offset == io_size) return (NULL); /* Fetch non-const pointer to the record entry */ error = bhnd_nvram_io_write_ptr(tlv->data, io_offset, &ptr, sizeof(env->hdr), &navail); if (error) { /* Should never occur with a valid cookiep */ BHND_NV_LOG("error mapping record for cookiep: %d\n", error); return (NULL); } /* Validate the record pointer */ env = ptr; if (env->hdr.tag != NVRAM_TLV_TYPE_ENV) { /* Should never occur with a valid cookiep */ BHND_NV_LOG("non-ENV record mapped for %p\n", cookiep); return (NULL); } /* Is the required variable name data is mapped? */ if (navail < sizeof(struct bhnd_nvram_tlv_env_hdr) + env->hdr.size || env->hdr.size == sizeof(env->flags)) { /* Should never occur with a valid cookiep */ BHND_NV_LOG("TLV_ENV variable data not mapped for %p\n", cookiep); return (NULL); } return (env); } /** * Return a cookiep for the given I/O offset. */ static void * bhnd_nvram_tlv_to_cookie(struct bhnd_nvram_tlv *tlv, size_t io_offset) { const void *ptr; int error; BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(tlv->data), ("io_offset %zu out-of-range", io_offset)); BHND_NV_ASSERT(io_offset < UINTPTR_MAX, ("io_offset %#zx exceeds UINTPTR_MAX", io_offset)); error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_offset, NULL); if (error) BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error); ptr = (const uint8_t *)ptr + io_offset; return (__DECONST(void *, ptr)); } /* Convert a cookiep back to an I/O offset */ static size_t bhnd_nvram_tlv_to_offset(struct bhnd_nvram_tlv *tlv, void *cookiep) { const void *ptr; intptr_t offset; size_t io_size; int error; BHND_NV_ASSERT(cookiep != NULL, ("null cookiep")); io_size = bhnd_nvram_io_getsize(tlv->data); error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_size, NULL); if (error) BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error); offset = (const uint8_t *)cookiep - (const uint8_t *)ptr; BHND_NV_ASSERT(offset >= 0, ("invalid cookiep")); BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)")); BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)")); return ((size_t)offset); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_datavar.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_datavar.h (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_datavar.h (revision 310296) @@ -1,218 +1,224 @@ /*- * 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. * * $FreeBSD$ */ #ifndef _BHND_NVRAM_BHND_NVRAM_DATAVAR_H_ #define _BHND_NVRAM_BHND_NVRAM_DATAVAR_H_ #include #include #include #include "bhnd_nvram_io.h" #include "bhnd_nvram_data.h" /** Registered NVRAM parser class instances. */ SET_DECLARE(bhnd_nvram_data_class_set, bhnd_nvram_data_class); void *bhnd_nvram_data_generic_find( struct bhnd_nvram_data *nv, const char *name); int bhnd_nvram_data_generic_rp_getvar( struct bhnd_nvram_data *nv, void *cookiep, void *outp, size_t *olen, bhnd_nvram_type otype); int bhnd_nvram_data_generic_rp_copy_val( struct bhnd_nvram_data *nv, void *cookiep, bhnd_nvram_val **val); /** @see bhnd_nvram_data_probe() */ typedef int (bhnd_nvram_data_op_probe)(struct bhnd_nvram_io *io); /** @see bhnd_nvram_data_new() */ typedef int (bhnd_nvram_data_op_new)(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io); /** Free all resources associated with @p nv. Called by * bhnd_nvram_data_release() when the reference count reaches zero. */ typedef void (bhnd_nvram_data_op_free)(struct bhnd_nvram_data *nv); /** @see bhnd_nvram_data_count() */ typedef size_t (bhnd_nvram_data_op_count)(struct bhnd_nvram_data *nv); /** @see bhnd_nvram_data_size() */ typedef int (bhnd_nvram_data_op_size)(struct bhnd_nvram_data *nv, size_t *len); /** @see bhnd_nvram_data_serialize() */ typedef int (bhnd_nvram_data_op_serialize)( struct bhnd_nvram_data *nv, void *buf, size_t *len); +/** @see bhnd_nvram_data_options() */ +typedef bhnd_nvram_plist*(bhnd_nvram_data_op_options)( + struct bhnd_nvram_data *nv); + /** @see bhnd_nvram_data_caps() */ typedef uint32_t (bhnd_nvram_data_op_caps)(struct bhnd_nvram_data *nv); /** @see bhnd_nvram_data_next() */ typedef const char *(bhnd_nvram_data_op_next)(struct bhnd_nvram_data *nv, void **cookiep); /** @see bhnd_nvram_data_find() */ typedef void *(bhnd_nvram_data_op_find)(struct bhnd_nvram_data *nv, const char *name); /** @see bhnd_nvram_data_copy_val() */ typedef int (bhnd_nvram_data_op_copy_val)( struct bhnd_nvram_data *nv, void *cookiep, bhnd_nvram_val **value); /** @see bhnd_nvram_data_getvar_order() */ typedef int (bhnd_nvram_data_op_getvar_order)( struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2); /** @see bhnd_nvram_data_getvar_name() */ typedef const char *(bhnd_nvram_data_op_getvar_name)( struct bhnd_nvram_data *nv, void *cookiep); /** @see bhnd_nvram_data_getvar() */ typedef int (bhnd_nvram_data_op_getvar)(struct bhnd_nvram_data *nv, void *cookiep, void *buf, size_t *len, bhnd_nvram_type type); /** @see bhnd_nvram_data_getvar_ptr() */ typedef const void *(bhnd_nvram_data_op_getvar_ptr)( struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type); /** @see bhnd_nvram_data_filter_setvar() */ typedef int (bhnd_nvram_data_op_filter_setvar)( struct bhnd_nvram_data *nv, const char *name, bhnd_nvram_val *value, bhnd_nvram_val **result); /** @see bhnd_nvram_data_filter_unsetvar() */ typedef int (bhnd_nvram_data_op_filter_unsetvar)( struct bhnd_nvram_data *nv, const char *name); /** * NVRAM data class. */ struct bhnd_nvram_data_class { const char *desc; /**< description */ size_t size; /**< instance size */ bhnd_nvram_data_op_probe *op_probe; bhnd_nvram_data_op_new *op_new; bhnd_nvram_data_op_free *op_free; bhnd_nvram_data_op_count *op_count; bhnd_nvram_data_op_size *op_size; bhnd_nvram_data_op_serialize *op_serialize; + bhnd_nvram_data_op_options *op_options; bhnd_nvram_data_op_caps *op_caps; bhnd_nvram_data_op_next *op_next; bhnd_nvram_data_op_find *op_find; bhnd_nvram_data_op_copy_val *op_copy_val; bhnd_nvram_data_op_getvar_order *op_getvar_order; bhnd_nvram_data_op_getvar *op_getvar; bhnd_nvram_data_op_getvar_ptr *op_getvar_ptr; bhnd_nvram_data_op_getvar_name *op_getvar_name; bhnd_nvram_data_op_filter_setvar *op_filter_setvar; bhnd_nvram_data_op_filter_unsetvar *op_filter_unsetvar; }; /** * NVRAM data instance. */ struct bhnd_nvram_data { struct bhnd_nvram_data_class *cls; volatile u_int refs; }; /* * Helper macro for BHND_NVRAM_DATA_CLASS_DEFN(). * * Declares a bhnd_nvram_data_class method implementation with class name * _cname and method name _mname */ #define BHND_NVRAM_DATA_CLASS_DECL_METHOD(_cname, _mname) \ static bhnd_nvram_data_op_ ## _mname \ bhnd_nvram_ ## _cname ## _ ## _mname; \ /* * Helper macro for BHND_NVRAM_DATA_CLASS_DEFN(). * * Assign a bhnd_nvram_data_class method implementation with class name * @p _cname and method name @p _mname */ #define BHND_NVRAM_DATA_CLASS_ASSIGN_METHOD(_cname, _mname) \ .op_ ## _mname = bhnd_nvram_ ## _cname ## _ ## _mname, /* * Helper macro for BHND_NVRAM_DATA_CLASS_DEFN(). * * Iterate over all bhnd_nvram_data_class method names, calling * _macro with the class name _cname as the first argument, and * a bhnd_nvram_data_class method name as the second. */ #define BHND_NVRAM_DATA_CLASS_ITER_METHODS(_cname, _macro) \ _macro(_cname, probe) \ _macro(_cname, new) \ _macro(_cname, free) \ _macro(_cname, count) \ _macro(_cname, size) \ _macro(_cname, serialize) \ + _macro(_cname, options) \ _macro(_cname, caps) \ _macro(_cname, next) \ _macro(_cname, find) \ _macro(_cname, copy_val) \ _macro(_cname, getvar_order) \ _macro(_cname, getvar) \ _macro(_cname, getvar_ptr) \ _macro(_cname, getvar_name) \ _macro(_cname, filter_setvar) \ _macro(_cname, filter_unsetvar) /** * Define a bhnd_nvram_data_class with class name @p _n and description * @p _desc, and register with bhnd_nvram_data_class_set. */ #define BHND_NVRAM_DATA_CLASS_DEFN(_cname, _desc, _size) \ BHND_NVRAM_DATA_CLASS_ITER_METHODS(_cname, \ BHND_NVRAM_DATA_CLASS_DECL_METHOD) \ \ struct bhnd_nvram_data_class bhnd_nvram_## _cname ## _class = { \ .desc = (_desc), \ .size = (_size), \ BHND_NVRAM_DATA_CLASS_ITER_METHODS(_cname, \ BHND_NVRAM_DATA_CLASS_ASSIGN_METHOD) \ }; \ \ DATA_SET(bhnd_nvram_data_class_set, \ bhnd_nvram_## _cname ## _class); #endif /* _BHND_NVRAM_BHND_NVRAM_DATAVAR_H_ */ Index: head/sys/dev/bhnd/nvram/bhnd_nvram_store.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_store.c (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_store.c (revision 310296) @@ -1,587 +1,1184 @@ /*- * Copyright (c) 2015-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 #include #ifdef _KERNEL #include #include #include #else /* !_KERNEL */ #include #include #include #include #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_storevar.h" /* * BHND NVRAM Store * * Manages in-memory and persistent representations of NVRAM data. */ static int bhnd_nvstore_parse_data( struct bhnd_nvram_store *sc); static int bhnd_nvstore_parse_path_entries( struct bhnd_nvram_store *sc); +static int bhnd_nvram_store_export_child( + struct bhnd_nvram_store *sc, + bhnd_nvstore_path *top, + bhnd_nvstore_path *child, + bhnd_nvram_plist *plist, + uint32_t flags); + +static int bhnd_nvstore_export_merge( + struct bhnd_nvram_store *sc, + bhnd_nvstore_path *path, + bhnd_nvram_plist *merged, + uint32_t flags); + +static int bhnd_nvstore_export_devpath_alias( + struct bhnd_nvram_store *sc, + bhnd_nvstore_path *path, + const char *devpath, + bhnd_nvram_plist *plist, + u_long *alias_val); + /** * Allocate and initialize a new NVRAM data store instance. * * The caller is responsible for deallocating the instance via * bhnd_nvram_store_free(). * * @param[out] store On success, a pointer to the newly allocated NVRAM data * instance. * @param data The NVRAM data to be managed by the returned NVRAM data store * instance. * * @retval 0 success * @retval non-zero if an error occurs during allocation or initialization, a * regular unix error code will be returned. */ int bhnd_nvram_store_new(struct bhnd_nvram_store **store, struct bhnd_nvram_data *data) { struct bhnd_nvram_store *sc; int error; /* Allocate new instance */ sc = bhnd_nv_calloc(1, sizeof(*sc)); if (sc == NULL) return (ENOMEM); BHND_NVSTORE_LOCK_INIT(sc); BHND_NVSTORE_LOCK(sc); /* Initialize path hash table */ sc->num_paths = 0; for (size_t i = 0; i < nitems(sc->paths); i++) LIST_INIT(&sc->paths[i]); /* Initialize alias hash table */ sc->num_aliases = 0; for (size_t i = 0; i < nitems(sc->aliases); i++) LIST_INIT(&sc->aliases[i]); /* Retain the NVRAM data */ sc->data = bhnd_nvram_data_retain(data); sc->data_caps = bhnd_nvram_data_caps(data); + sc->data_opts = bhnd_nvram_data_options(data); + if (sc->data_opts != NULL) { + bhnd_nvram_plist_retain(sc->data_opts); + } else { + sc->data_opts = bhnd_nvram_plist_new(); + if (sc->data_opts == NULL) { + error = ENOMEM; + goto cleanup; + } + } /* Register required root path */ error = bhnd_nvstore_register_path(sc, BHND_NVSTORE_ROOT_PATH, BHND_NVSTORE_ROOT_PATH_LEN); if (error) goto cleanup; sc->root_path = bhnd_nvstore_get_path(sc, BHND_NVSTORE_ROOT_PATH, BHND_NVSTORE_ROOT_PATH_LEN); BHND_NV_ASSERT(sc->root_path, ("missing root path")); /* Parse all variables vended by our backing NVRAM data instance, * generating all path entries, alias entries, and variable indexes */ if ((error = bhnd_nvstore_parse_data(sc))) goto cleanup; *store = sc; BHND_NVSTORE_UNLOCK(sc); return (0); cleanup: BHND_NVSTORE_UNLOCK(sc); bhnd_nvram_store_free(sc); return (error); } /** * Allocate and initialize a new NVRAM data store instance, parsing the * NVRAM data from @p io. * * The caller is responsible for deallocating the instance via * bhnd_nvram_store_free(). * * The NVRAM data mapped by @p io will be copied, and @p io may be safely * deallocated after bhnd_nvram_store_new() returns. * * @param[out] store On success, a pointer to the newly allocated NVRAM data * instance. * @param io An I/O context mapping the NVRAM data to be copied and parsed. * @param cls The NVRAM data class to be used when parsing @p io, or NULL * to perform runtime identification of the appropriate data class. * * @retval 0 success * @retval non-zero if an error occurs during allocation or initialization, a * regular unix error code will be returned. */ int bhnd_nvram_store_parse_new(struct bhnd_nvram_store **store, struct bhnd_nvram_io *io, bhnd_nvram_data_class *cls) { struct bhnd_nvram_data *data; int error; /* Try to parse the data */ if ((error = bhnd_nvram_data_new(cls, &data, io))) return (error); /* Try to create our new store instance */ error = bhnd_nvram_store_new(store, data); bhnd_nvram_data_release(data); return (error); } /** * Free an NVRAM store instance, releasing all associated resources. * * @param sc A store instance previously allocated via * bhnd_nvram_store_new(). */ void bhnd_nvram_store_free(struct bhnd_nvram_store *sc) { /* Clean up alias hash table */ for (size_t i = 0; i < nitems(sc->aliases); i++) { bhnd_nvstore_alias *alias, *anext; LIST_FOREACH_SAFE(alias, &sc->aliases[i], na_link, anext) bhnd_nv_free(alias); } /* Clean up path hash table */ for (size_t i = 0; i < nitems(sc->paths); i++) { bhnd_nvstore_path *path, *pnext; LIST_FOREACH_SAFE(path, &sc->paths[i], np_link, pnext) bhnd_nvstore_path_free(path); } if (sc->data != NULL) bhnd_nvram_data_release(sc->data); + if (sc->data_opts != NULL) + bhnd_nvram_plist_release(sc->data_opts); BHND_NVSTORE_LOCK_DESTROY(sc); bhnd_nv_free(sc); } /** * Parse all variables vended by our backing NVRAM data instance, * generating all path entries, alias entries, and variable indexes. * * @param sc The NVRAM store instance to be initialized with * paths, aliases, and data parsed from its backing * data. * * @retval 0 success * @retval non-zero if an error occurs during parsing, a regular unix error * code will be returned. */ static int bhnd_nvstore_parse_data(struct bhnd_nvram_store *sc) { const char *name; void *cookiep; int error; /* Parse and register all device paths and path aliases. This enables * resolution of _forward_ references to device paths aliases when * scanning variable entries below */ if ((error = bhnd_nvstore_parse_path_entries(sc))) return (error); /* Calculate the per-path variable counts, and report dangling alias * references as an error. */ cookiep = NULL; while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) { bhnd_nvstore_path *path; bhnd_nvstore_name_info info; /* Parse the name info */ error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info); if (error) return (error); switch (info.type) { case BHND_NVSTORE_VAR: /* Fetch referenced path */ path = bhnd_nvstore_var_get_path(sc, &info); if (path == NULL) { BHND_NV_LOG("variable '%s' has dangling " "path reference\n", name); return (EFTYPE); } /* Increment path variable count */ if (path->num_vars == SIZE_MAX) { BHND_NV_LOG("more than SIZE_MAX variables in " "path %s\n", path->path_str); return (EFTYPE); } path->num_vars++; break; case BHND_NVSTORE_ALIAS_DECL: /* Skip -- path alias already parsed and recorded */ break; } } /* If the backing NVRAM data instance vends only a single root ("/") * path, we may be able to skip generating an index for the root * path */ if (sc->num_paths == 1) { bhnd_nvstore_path *path; /* If the backing instance provides its own name-based lookup * indexing, we can skip generating a duplicate here */ if (sc->data_caps & BHND_NVRAM_DATA_CAP_INDEXED) return (0); /* If the sole root path contains fewer variables than the * minimum indexing threshhold, we do not need to generate an * index */ path = bhnd_nvstore_get_root_path(sc); if (path->num_vars < BHND_NV_IDX_VAR_THRESHOLD) return (0); } /* Allocate per-path index instances */ for (size_t i = 0; i < nitems(sc->paths); i++) { bhnd_nvstore_path *path; LIST_FOREACH(path, &sc->paths[i], np_link) { path->index = bhnd_nvstore_index_new(path->num_vars); if (path->index == NULL) return (ENOMEM); } } /* Populate per-path indexes */ cookiep = NULL; while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) { bhnd_nvstore_name_info info; bhnd_nvstore_path *path; /* Parse the name info */ error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info); if (error) return (error); switch (info.type) { case BHND_NVSTORE_VAR: /* Fetch referenced path */ path = bhnd_nvstore_var_get_path(sc, &info); BHND_NV_ASSERT(path != NULL, ("dangling path reference")); /* Append to index */ error = bhnd_nvstore_index_append(sc, path->index, cookiep); if (error) return (error); break; case BHND_NVSTORE_ALIAS_DECL: /* Skip */ break; } } /* Prepare indexes for querying */ for (size_t i = 0; i < nitems(sc->paths); i++) { bhnd_nvstore_path *path; LIST_FOREACH(path, &sc->paths[i], np_link) { error = bhnd_nvstore_index_prepare(sc, path->index); if (error) return (error); } } return (0); } /** * Parse and register path and path alias entries for all declarations found in * the NVRAM data backing @p nvram. * * @param sc The NVRAM store instance. * * @retval 0 success * @retval non-zero If parsing fails, a regular unix error code will be * returned. */ static int bhnd_nvstore_parse_path_entries(struct bhnd_nvram_store *sc) { const char *name; void *cookiep; int error; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* Skip path registration if the data source does not support device * paths. */ if (!(sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS)) { BHND_NV_ASSERT(sc->root_path != NULL, ("missing root path")); return (0); } /* Otherwise, parse and register all paths and path aliases */ cookiep = NULL; while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) { bhnd_nvstore_name_info info; /* Parse the name info */ error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info); if (error) return (error); /* Register the path */ error = bhnd_nvstore_var_register_path(sc, &info, cookiep); if (error) { BHND_NV_LOG("failed to register path for %s: %d\n", name, error); return (error); } } return (0); } + + +/** + * Merge exported per-path variables (uncommitted, committed, or both) into + * the empty @p merged property list. + * + * @param sc The NVRAM store instance. + * @param path The NVRAM path to be exported. + * @param merged The property list to populate with the merged results. + * @param flags Export flags. See BHND_NVSTORE_EXPORT_*. + * + * @retval 0 success + * @retval ENOMEM If allocation fails. + * @retval non-zero If merging the variables defined in @p path otherwise + * fails, a regular unix error code will be returned. + */ +static int +bhnd_nvstore_export_merge(struct bhnd_nvram_store *sc, + bhnd_nvstore_path *path, bhnd_nvram_plist *merged, uint32_t flags) +{ + void *cookiep, *idxp; + int error; + + /* Populate merged list with all pending variables */ + if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED)) { + bhnd_nvram_prop *prop; + + prop = NULL; + while ((prop = bhnd_nvram_plist_next(path->pending, prop))) { + /* Skip variables marked for deletion */ + if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED)) { + if (bhnd_nvram_prop_is_null(prop)) + continue; + } + + /* Append to merged list */ + error = bhnd_nvram_plist_append(merged, prop); + if (error) + return (error); + } + } + + /* Skip merging committed variables? */ + if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMMITTED)) + return (0); + + /* Merge in the committed NVRAM variables */ + idxp = NULL; + while ((cookiep = bhnd_nvstore_path_data_next(sc, path, &idxp))) { + const char *name; + bhnd_nvram_val *val; + + /* Fetch the variable name */ + name = bhnd_nvram_data_getvar_name(sc->data, cookiep); + + /* Trim device path prefix */ + if (sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) + name = bhnd_nvram_trim_path_name(name); + + /* Skip if already defined in pending updates */ + if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED)) { + if (bhnd_nvram_plist_contains(path->pending, name)) + continue; + } + + /* Skip if higher precedence value was already defined. This + * may occur if the underlying data store contains duplicate + * keys; iteration will always return the definition with + * the highest precedence first */ + if (bhnd_nvram_plist_contains(merged, name)) + continue; + + /* Fetch the variable's value representation */ + if ((error = bhnd_nvram_data_copy_val(sc->data, cookiep, &val))) + return (error); + + /* Add to path variable list */ + error = bhnd_nvram_plist_append_val(merged, name, val); + bhnd_nvram_val_release(val); + if (error) + return (error); + } + + return (0); +} + +/** + * Find a free alias value for @p path, and append the devpathXX alias + * declaration to @p plist. + * + * @param sc The NVRAM store instance. + * @param path The NVRAM path for which a devpath alias + * variable should be produced. + * @param devpath The devpathXX path value for @p path. + * @param plist The property list to which @p path's devpath + * variable will be appended. + * @param[out] alias_val On success, will be set to the alias value + * allocated for @p path. + * + * @retval 0 success + * @retval ENOMEM If allocation fails. + * @retval non-zero If merging the variables defined in @p path otherwise + * fails, a regular unix error code will be returned. + */ +static int +bhnd_nvstore_export_devpath_alias(struct bhnd_nvram_store *sc, + bhnd_nvstore_path *path, const char *devpath, bhnd_nvram_plist *plist, + u_long *alias_val) +{ + bhnd_nvstore_alias *alias; + char *pathvar; + int error; + + *alias_val = 0; + + /* Prefer alias value already reserved for this path. */ + alias = bhnd_nvstore_find_alias(sc, path->path_str); + if (alias != NULL) { + *alias_val = alias->alias; + + /* Allocate devpathXX variable name */ + bhnd_nv_asprintf(&pathvar, "devpath%lu", *alias_val); + if (pathvar == NULL) + return (ENOMEM); + + /* Append alias variable to property list */ + error = bhnd_nvram_plist_append_string(plist, pathvar, devpath); + + BHND_NV_ASSERT(error != EEXIST, ("reserved alias %lu:%s in use", + * alias_val, path->path_str)); + + bhnd_nv_free(pathvar); + return (error); + } + + /* Find the next free devpathXX alias entry */ + while (1) { + /* Skip existing reserved alias values */ + while (bhnd_nvstore_get_alias(sc, *alias_val) != NULL) { + if (*alias_val == ULONG_MAX) + return (ENOMEM); + + (*alias_val)++; + } + + /* Allocate devpathXX variable name */ + bhnd_nv_asprintf(&pathvar, "devpath%lu", *alias_val); + if (pathvar == NULL) + return (ENOMEM); + + /* If not in-use, we can terminate the search */ + if (!bhnd_nvram_plist_contains(plist, pathvar)) + break; + + /* Keep searching */ + bhnd_nv_free(pathvar); + + if (*alias_val == ULONG_MAX) + return (ENOMEM); + + (*alias_val)++; + } + + /* Append alias variable to property list */ + error = bhnd_nvram_plist_append_string(plist, pathvar, devpath); + + bhnd_nv_free(pathvar); + return (error); +} + +/** + * Export a single @p child path's properties, appending the result to @p plist. + * + * @param sc The NVRAM store instance. + * @param top The root NVRAM path being exported. + * @param child The NVRAM path to be exported. + * @param plist The property list to which @p child's exported + * properties should be appended. + * @param flags Export flags. See BHND_NVSTORE_EXPORT_*. + * + * @retval 0 success + * @retval ENOMEM If allocation fails. + * @retval non-zero If merging the variables defined in @p path otherwise + * fails, a regular unix error code will be returned. + */ +static int +bhnd_nvram_store_export_child(struct bhnd_nvram_store *sc, + bhnd_nvstore_path *top, bhnd_nvstore_path *child, bhnd_nvram_plist *plist, + uint32_t flags) +{ + bhnd_nvram_plist *path_vars; + bhnd_nvram_prop *prop; + const char *relpath; + char *prefix, *namebuf; + size_t prefix_len, relpath_len; + size_t namebuf_size, num_props; + bool emit_compact_devpath; + int error; + + BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); + + prefix = NULL; + num_props = 0; + path_vars = NULL; + namebuf = NULL; + + /* Determine the path relative to the top-level path */ + relpath = bhnd_nvstore_parse_relpath(top->path_str, child->path_str); + if (relpath == NULL) { + /* Skip -- not a child of the root path */ + return (0); + } + relpath_len = strlen(relpath); + + /* Skip sub-path if export of children was not requested, */ + if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_CHILDREN) && relpath_len > 0) + return (0); + + /* Collect all variables to be included in the export */ + if ((path_vars = bhnd_nvram_plist_new()) == NULL) + return (ENOMEM); + + if ((error = bhnd_nvstore_export_merge(sc, child, path_vars, flags))) { + bhnd_nvram_plist_release(path_vars); + return (error); + } + + /* Skip if no children are to be exported */ + if (bhnd_nvram_plist_count(path_vars) == 0) { + bhnd_nvram_plist_release(path_vars); + return (0); + } + + /* Determine appropriate device path encoding */ + emit_compact_devpath = false; + if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS)) { + /* Re-encode as compact (if non-empty path) */ + if (relpath_len > 0) + emit_compact_devpath = true; + } else if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS)) { + /* Re-encode with fully expanded device path */ + emit_compact_devpath = false; + } else if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS)) { + /* Preserve existing encoding of this path */ + if (bhnd_nvstore_find_alias(sc, child->path_str) != NULL) + emit_compact_devpath = true; + } else { + BHND_NV_LOG("invalid device path flag: %#" PRIx32, flags); + error = EINVAL; + goto finished; + } + + /* Allocate variable device path prefix to use for all property names, + * and if using compact encoding, emit the devpathXX= variable */ + prefix = NULL; + prefix_len = 0; + if (emit_compact_devpath) { + u_long alias_val; + int len; + + /* Reserve an alias value and append the devpathXX= variable to + * the property list */ + error = bhnd_nvstore_export_devpath_alias(sc, child, relpath, + plist, &alias_val); + if (error) + goto finished; + + /* Allocate variable name prefix */ + len = bhnd_nv_asprintf(&prefix, "%lu:", alias_val); + if (prefix == NULL) { + error = ENOMEM; + goto finished; + } + + prefix_len = len; + } else if (relpath_len > 0) { + int len; + + /* Allocate the variable name prefix, appending '/' to the + * relative path */ + len = bhnd_nv_asprintf(&prefix, "%s/", relpath); + if (prefix == NULL) { + error = ENOMEM; + goto finished; + } + + prefix_len = len; + } + + /* If prefixing of variable names is required, allocate a name + * formatting buffer */ + namebuf_size = 0; + if (prefix != NULL) { + size_t maxlen; + + /* Find the maximum name length */ + maxlen = 0; + prop = NULL; + while ((prop = bhnd_nvram_plist_next(path_vars, prop))) { + const char *name; + + name = bhnd_nvram_prop_name(prop); + maxlen = bhnd_nv_ummax(strlen(name), maxlen); + } + + /* Allocate name buffer (path-prefix + name + '\0') */ + namebuf_size = prefix_len + maxlen + 1; + namebuf = bhnd_nv_malloc(namebuf_size); + if (namebuf == NULL) { + error = ENOMEM; + goto finished; + } + } + + /* Append all path variables to the export plist, prepending the + * device-path prefix to the variable names, if required */ + prop = NULL; + while ((prop = bhnd_nvram_plist_next(path_vars, prop)) != NULL) { + const char *name; + + /* Prepend device prefix to the variable name */ + name = bhnd_nvram_prop_name(prop); + if (prefix != NULL) { + int len; + + /* + * Write prefixed variable name to our name buffer. + * + * We precalcuate the size when scanning all names + * above, so this should always succeed. + */ + len = snprintf(namebuf, namebuf_size, "%s%s", prefix, + name); + if (len < 0 || (size_t)len >= namebuf_size) + BHND_NV_PANIC("invalid max_name_len"); + + name = namebuf; + } + + /* Add property to export plist */ + error = bhnd_nvram_plist_append_val(plist, name, + bhnd_nvram_prop_val(prop)); + if (error) + goto finished; + } + + /* Success */ + error = 0; + +finished: + if (prefix != NULL) + bhnd_nv_free(prefix); + + if (namebuf != NULL) + bhnd_nv_free(namebuf); + + if (path_vars != NULL) + bhnd_nvram_plist_release(path_vars); + + return (error); +} + +/** + * Export a flat, ordered NVRAM property list representation of all NVRAM + * properties at @p path. + * + * @param sc The NVRAM store instance. + * @param path The NVRAM path to export, or NULL to select the root + * path. + * @param[out] cls On success, will be set to the backing data class + * of @p sc. If the data class is are not desired, + * a NULL pointer may be provided. + * @param[out] props On success, will be set to a caller-owned property + * list containing the exported properties. The caller is + * responsible for releasing this value via + * bhnd_nvram_plist_release(). + * @param[out] options On success, will be set to a caller-owned property + * list containing the current NVRAM serialization options + * for @p sc. The caller is responsible for releasing this + * value via bhnd_nvram_plist_release(). + * @param flags Export flags. See BHND_NVSTORE_EXPORT_*. + * + * @retval 0 success + * @retval EINVAL If @p flags is invalid. + * @retval ENOENT The requested path was not found. + * @retval ENOMEM If allocation fails. + * @retval non-zero If export of @p path otherwise fails, a regular unix + * error code will be returned. + */ +int +bhnd_nvram_store_export(struct bhnd_nvram_store *sc, const char *path, + bhnd_nvram_data_class **cls, bhnd_nvram_plist **props, + bhnd_nvram_plist **options, uint32_t flags) +{ + bhnd_nvram_plist *unordered; + bhnd_nvstore_path *top; + bhnd_nvram_prop *prop; + const char *name; + void *cookiep; + size_t num_dpath_flags; + int error; + + *props = NULL; + unordered = NULL; + num_dpath_flags = 0; + if (options != NULL) + *options = NULL; + + /* Default to exporting root path */ + if (path == NULL) + path = BHND_NVSTORE_ROOT_PATH; + + /* Default to exporting all properties */ + if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMMITTED) && + !BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED)) + { + flags |= BHND_NVSTORE_EXPORT_ALL_VARS; + } + + /* Default to preserving the current device path encoding */ + if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS) && + !BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS)) + { + flags |= BHND_NVSTORE_EXPORT_PRESERVE_DEVPATHS; + } + + /* Exactly one device path encoding flag must be set */ + if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS)) + num_dpath_flags++; + + if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS)) + num_dpath_flags++; + + if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS)) + num_dpath_flags++; + + if (num_dpath_flags != 1) + return (EINVAL); + + /* If EXPORT_DELETED is set, EXPORT_UNCOMMITTED must be set too */ + if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED) && + !BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED)) + { + return (EINVAL); + } + + /* Lock internal state before querying paths/properties */ + BHND_NVSTORE_LOCK(sc); + + /* Fetch referenced path */ + top = bhnd_nvstore_get_path(sc, path, strlen(path)); + if (top == NULL) { + error = ENOENT; + goto failed; + } + + /* Allocate new, empty property list */ + if ((unordered = bhnd_nvram_plist_new()) == NULL) { + error = ENOMEM; + goto failed; + } + + /* Export the top-level path first */ + error = bhnd_nvram_store_export_child(sc, top, top, unordered, flags); + if (error) + goto failed; + + /* Attempt to export any children of the root path */ + for (size_t i = 0; i < nitems(sc->paths); i++) { + bhnd_nvstore_path *child; + + LIST_FOREACH(child, &sc->paths[i], np_link) { + /* Top-level path was already exported */ + if (child == top) + continue; + + error = bhnd_nvram_store_export_child(sc, top, + child, unordered, flags); + if (error) + goto failed; + } + } + + /* If requested, provide the current class and serialization options */ + if (cls != NULL) + *cls = bhnd_nvram_data_get_class(sc->data); + + if (options != NULL) + *options = bhnd_nvram_plist_retain(sc->data_opts); + + /* + * If we're re-encoding device paths, don't bother preserving the + * existing NVRAM variable order; our variable names will not match + * the existing backing NVRAM data. + */ + if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS)) { + *props = unordered; + unordered = NULL; + + goto finished; + } + + /* + * Re-order the flattened output to match the existing NVRAM variable + * ordering. + * + * We append all new variables at the end of the input; this should + * reduce the delta that needs to be written (e.g. to flash) when + * committing NVRAM updates, and should result in a serialization + * identical to the input serialization if uncommitted updates are + * excluded from the export. + */ + if ((*props = bhnd_nvram_plist_new()) == NULL) { + error = ENOMEM; + goto failed; + } + + /* Using the backing NVRAM data ordering to order all variables + * currently defined in the backing store */ + cookiep = NULL; + while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) { + prop = bhnd_nvram_plist_get_prop(unordered, name); + if (prop == NULL) + continue; + + /* Append to ordered result */ + if ((error = bhnd_nvram_plist_append(*props, prop))) + goto failed; + + /* Remove from unordered list */ + bhnd_nvram_plist_remove(unordered, name); + } + + /* Any remaining variables are new, and should be appended to the + * end of the export list */ + prop = NULL; + while ((prop = bhnd_nvram_plist_next(unordered, prop)) != NULL) { + if ((error = bhnd_nvram_plist_append(*props, prop))) + goto failed; + } + + /* Export complete */ +finished: + BHND_NVSTORE_UNLOCK(sc); + + if (unordered != NULL) + bhnd_nvram_plist_release(unordered); + + return (0); + +failed: + BHND_NVSTORE_UNLOCK(sc); + + if (unordered != NULL) + bhnd_nvram_plist_release(unordered); + + if (options != NULL && *options != NULL) + bhnd_nvram_plist_release(*options); + + if (*props != NULL) + bhnd_nvram_plist_release(*props); + + return (error); +} + /** * Read an NVRAM variable. * * @param sc The NVRAM parser state. * @param name The NVRAM variable name. * @param[out] outp On success, the requested value will be written * to this buffer. This argment may be NULL if * the value is not desired. * @param[in,out] olen The capacity of @p outp. On success, will be set * to the actual size of the requested value. * @param otype The requested data type to be written to * @p outp. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENOMEM If @p outp is non-NULL and a buffer of @p olen is too * small to hold the requested value. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_store_getvar(struct bhnd_nvram_store *sc, const char *name, void *outp, size_t *olen, bhnd_nvram_type otype) { bhnd_nvstore_name_info info; bhnd_nvstore_path *path; bhnd_nvram_prop *prop; void *cookiep; int error; BHND_NVSTORE_LOCK(sc); /* Parse the variable name */ error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_EXTERNAL, sc->data_caps, &info); if (error) goto finished; /* Fetch the variable's enclosing path entry */ if ((path = bhnd_nvstore_var_get_path(sc, &info)) == NULL) { error = ENOENT; goto finished; } /* Search uncommitted updates first */ prop = bhnd_nvstore_path_get_update(sc, path, info.name); if (prop != NULL) { if (bhnd_nvram_prop_is_null(prop)) { /* NULL denotes a pending deletion */ error = ENOENT; } else { error = bhnd_nvram_prop_encode(prop, outp, olen, otype); } goto finished; } /* Search the backing NVRAM data */ cookiep = bhnd_nvstore_path_data_lookup(sc, path, info.name); if (cookiep != NULL) { /* Found in backing store */ error = bhnd_nvram_data_getvar(sc->data, cookiep, outp, olen, otype); goto finished; } /* Not found */ error = ENOENT; finished: BHND_NVSTORE_UNLOCK(sc); return (error); } /** * Common bhnd_nvram_store_set*() and bhnd_nvram_store_unsetvar() * implementation. * * If @p value is NULL, the variable will be marked for deletion. */ static int bhnd_nvram_store_setval_common(struct bhnd_nvram_store *sc, const char *name, bhnd_nvram_val *value) { bhnd_nvstore_path *path; bhnd_nvstore_name_info info; int error; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* Parse the variable name */ error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_EXTERNAL, sc->data_caps, &info); if (error) return (error); /* Fetch the variable's enclosing path entry */ if ((path = bhnd_nvstore_var_get_path(sc, &info)) == NULL) return (error); /* Register the update entry */ return (bhnd_nvstore_path_register_update(sc, path, info.name, value)); } /** * Set an NVRAM variable. * * @param sc The NVRAM parser state. * @param name The NVRAM variable name. * @param value The new value. * * @retval 0 success * @retval ENOENT The requested variable @p name was not found. * @retval EINVAL If @p value is invalid. */ int bhnd_nvram_store_setval(struct bhnd_nvram_store *sc, const char *name, bhnd_nvram_val *value) { int error; BHND_NVSTORE_LOCK(sc); error = bhnd_nvram_store_setval_common(sc, name, value); BHND_NVSTORE_UNLOCK(sc); return (error); } /** * Set an NVRAM variable. * * @param sc The NVRAM parser state. * @param name The NVRAM variable name. * @param[out] inp The new value. * @param[in,out] ilen The size of @p inp. * @param itype The data type of @p inp. * * @retval 0 success * @retval ENOENT The requested variable @p name was not found. * @retval EINVAL If the new value is invalid. * @retval EINVAL If @p name is read-only. */ int bhnd_nvram_store_setvar(struct bhnd_nvram_store *sc, const char *name, const void *inp, size_t ilen, bhnd_nvram_type itype) { bhnd_nvram_val val; int error; error = bhnd_nvram_val_init(&val, NULL, inp, ilen, itype, BHND_NVRAM_VAL_FIXED|BHND_NVRAM_VAL_BORROW_DATA); if (error) { BHND_NV_LOG("error initializing value: %d\n", error); return (EINVAL); } BHND_NVSTORE_LOCK(sc); error = bhnd_nvram_store_setval_common(sc, name, &val); BHND_NVSTORE_UNLOCK(sc); bhnd_nvram_val_release(&val); return (error); } /** * Unset an NVRAM variable. * * @param sc The NVRAM parser state. * @param name The NVRAM variable name. * * @retval 0 success * @retval ENOENT The requested variable @p name was not found. * @retval EINVAL If @p name is read-only. */ int bhnd_nvram_store_unsetvar(struct bhnd_nvram_store *sc, const char *name) { int error; BHND_NVSTORE_LOCK(sc); error = bhnd_nvram_store_setval_common(sc, name, BHND_NVRAM_VAL_NULL); BHND_NVSTORE_UNLOCK(sc); return (error); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_store.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_store.h (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_store.h (revision 310296) @@ -1,71 +1,89 @@ /*- * Copyright (c) 2015-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. * * $FreeBSD$ */ #ifndef _BHND_NVRAM_BHND_NVRAM_STORE_H_ #define _BHND_NVRAM_BHND_NVRAM_STORE_H_ #ifdef _KERNEL #include #include #else /* !_KERNEL */ #include #include #include #endif #include #include "bhnd_nvram_data.h" #include "bhnd_nvram_io.h" -#include "bhnd_nvram_value.h" struct bhnd_nvram_store; +/** + * NVRAM export flags. + */ +enum { + BHND_NVSTORE_EXPORT_CHILDREN = (1<<0), /**< Include all subpaths */ + BHND_NVSTORE_EXPORT_PRESERVE_DEVPATHS = (1<<1), /**< Preserve existing device path definitions (default) */ + BHND_NVSTORE_EXPORT_COMPACT_DEVPATHS = (1<<2), /**< Re-encode all device paths using compact syntax */ + BHND_NVSTORE_EXPORT_EXPAND_DEVPATHS = (1<<3), /**< Re-encode all device paths using non-compact syntax */ + BHND_NVSTORE_EXPORT_ALL_VARS = (1<<6|1<<7), /**< Include all variables (default) */ + BHND_NVSTORE_EXPORT_COMMITTED = (1<<6), /**< Include all committed changes */ + BHND_NVSTORE_EXPORT_UNCOMMITTED = (1<<7), /**< Include all uncommitted changes (not including deletions) */ + BHND_NVSTORE_EXPORT_DELETED = (1<<8), /**< Include all uncommitted deltions (as + properties of type BHND_NVRAM_TYPE_NULL) */ +}; + int bhnd_nvram_store_new(struct bhnd_nvram_store **store, struct bhnd_nvram_data *data); int bhnd_nvram_store_parse_new(struct bhnd_nvram_store **store, struct bhnd_nvram_io *io, bhnd_nvram_data_class *cls); void bhnd_nvram_store_free(struct bhnd_nvram_store *store); +int bhnd_nvram_store_export(struct bhnd_nvram_store *store, + const char *path, bhnd_nvram_data_class **cls, + bhnd_nvram_plist **props, bhnd_nvram_plist **options, + uint32_t flags); int bhnd_nvram_store_getvar(struct bhnd_nvram_store *sc, const char *name, void *outp, size_t *olen, bhnd_nvram_type otype); int bhnd_nvram_store_setvar(struct bhnd_nvram_store *sc, const char *name, const void *inp, size_t ilen, bhnd_nvram_type itype); int bhnd_nvram_store_unsetvar(struct bhnd_nvram_store *sc, const char *name); int bhnd_nvram_store_setval(struct bhnd_nvram_store *sc, const char *name, bhnd_nvram_val *value); #endif /* _BHND_NVRAM_BHND_NVRAM_STORE_H_ */ Index: head/sys/dev/bhnd/nvram/bhnd_nvram_store_subr.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_store_subr.c (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_store_subr.c (revision 310296) @@ -1,1182 +1,1182 @@ /*- * Copyright (c) 2015-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 #include #ifdef _KERNEL #include #include #include #else /* !_KERNEL */ #include #include #include #include #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_storevar.h" static int bhnd_nvstore_idx_cmp(void *ctx, const void *lhs, const void *rhs); /** * Allocate and initialize a new path instance. * * The caller is responsible for deallocating the instance via * bhnd_nvstore_path_free(). * * @param path_str The path's canonical string representation. * @param path_len The length of @p path_str. * * @retval non-NULL success * @retval NULL if allocation fails. */ bhnd_nvstore_path * bhnd_nvstore_path_new(const char *path_str, size_t path_len) { bhnd_nvstore_path *path; /* Allocate new entry */ path = bhnd_nv_malloc(sizeof(*path)); if (path == NULL) return (NULL); path->index = NULL; path->num_vars = 0; path->pending = bhnd_nvram_plist_new(); if (path->pending == NULL) goto failed; path->path_str = bhnd_nv_strndup(path_str, path_len); if (path->path_str == NULL) goto failed; return (path); failed: if (path->pending != NULL) bhnd_nvram_plist_release(path->pending); if (path->path_str != NULL) bhnd_nv_free(path->path_str); bhnd_nv_free(path); return (NULL); } /** * Free an NVRAM path instance, releasing all associated resources. */ void bhnd_nvstore_path_free(struct bhnd_nvstore_path *path) { /* Free the per-path index */ if (path->index != NULL) bhnd_nvstore_index_free(path->index); bhnd_nvram_plist_release(path->pending); bhnd_nv_free(path->path_str); bhnd_nv_free(path); } /** * Allocate and initialize a new index instance with @p capacity. * * The caller is responsible for deallocating the instance via * bhnd_nvstore_index_free(). * * @param capacity The maximum number of variables to be indexed. * * @retval non-NULL success * @retval NULL if allocation fails. */ bhnd_nvstore_index * bhnd_nvstore_index_new(size_t capacity) { bhnd_nvstore_index *index; size_t bytes; /* Allocate and populate variable index */ bytes = sizeof(struct bhnd_nvstore_index) + (sizeof(void *) * capacity); index = bhnd_nv_malloc(bytes); if (index == NULL) { BHND_NV_LOG("error allocating %zu byte index\n", bytes); return (NULL); } index->count = 0; index->capacity = capacity; return (index); } /** * Free an index instance, releasing all associated resources. * * @param index An index instance previously allocated via * bhnd_nvstore_index_new(). */ void bhnd_nvstore_index_free(bhnd_nvstore_index *index) { bhnd_nv_free(index); } /** * Append a new NVRAM variable's @p cookiep value to @p index. * * After one or more append requests, the index must be prepared via * bhnd_nvstore_index_prepare() before any indexed lookups are performed. * * @param sc The NVRAM store from which NVRAM values will be queried. * @param index The index to be modified. * @param cookiep The cookiep value (as provided by the backing NVRAM * data instance of @p sc) to be included in @p index. * * @retval 0 success * @retval ENOMEM if appending an additional entry would exceed the * capacity of @p index. */ int bhnd_nvstore_index_append(struct bhnd_nvram_store *sc, bhnd_nvstore_index *index, void *cookiep) { BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); if (index->count >= index->capacity) return (ENOMEM); index->cookiep[index->count] = cookiep; index->count++; return (0); } /* sort function for bhnd_nvstore_index_prepare() */ static int bhnd_nvstore_idx_cmp(void *ctx, const void *lhs, const void *rhs) { struct bhnd_nvram_store *sc; void *l_cookiep, *r_cookiep; const char *l_str, *r_str; const char *l_name, *r_name; int order; sc = ctx; l_cookiep = *(void * const *)lhs; r_cookiep = *(void * const *)rhs; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* Fetch string pointers from the cookiep values */ l_str = bhnd_nvram_data_getvar_name(sc->data, l_cookiep); r_str = bhnd_nvram_data_getvar_name(sc->data, r_cookiep); /* Trim device path prefixes */ if (sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) { l_name = bhnd_nvram_trim_path_name(l_str); r_name = bhnd_nvram_trim_path_name(r_str); } else { l_name = l_str; r_name = r_str; } /* Perform comparison */ order = strcmp(l_name, r_name); if (order != 0 || lhs == rhs) return (order); /* If the backing data incorrectly contains variables with duplicate * names, we need a sort order that provides stable behavior. * * Since Broadcom's own code varies wildly on this question, we just * use a simple precedence rule: The first declaration of a variable * takes precedence. */ return (bhnd_nvram_data_getvar_order(sc->data, l_cookiep, r_cookiep)); } /** * Prepare @p index for querying via bhnd_nvstore_index_lookup(). * * After one or more append requests, the index must be prepared via * bhnd_nvstore_index_prepare() before any indexed lookups are performed. * * @param sc The NVRAM store from which NVRAM values will be queried. * @param index The index to be prepared. * * @retval 0 success * @retval non-zero if preparing @p index otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvstore_index_prepare(struct bhnd_nvram_store *sc, bhnd_nvstore_index *index) { BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* Sort the index table */ qsort_r(index->cookiep, index->count, sizeof(index->cookiep[0]), sc, bhnd_nvstore_idx_cmp); return (0); } /** * Return a borrowed reference to the root path node. * * @param sc The NVRAM store. */ bhnd_nvstore_path * bhnd_nvstore_get_root_path(struct bhnd_nvram_store *sc) { BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); return (sc->root_path); } /** * Return true if @p path is the root path node. * * @param sc The NVRAM store. * @param path The path to query. */ bool bhnd_nvstore_is_root_path(struct bhnd_nvram_store *sc, bhnd_nvstore_path *path) { BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); return (sc->root_path == path); } /** * Return the update entry matching @p name in @p path, or NULL if no entry * found. * * @param sc The NVRAM store. * @param path The path to query. * @param name The NVRAM variable name to search for in @p path's update list. * * @retval non-NULL success * @retval NULL if @p name is not found in @p path. */ bhnd_nvram_prop * bhnd_nvstore_path_get_update(struct bhnd_nvram_store *sc, bhnd_nvstore_path *path, const char *name) { BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); return (bhnd_nvram_plist_get_prop(path->pending, name)); } /** * Register or remove an update record for @p name in @p path. * * @param sc The NVRAM store. * @param path The path to be modified. * @param name The path-relative variable name to be modified. * @param value The new value. A value of BHND_NVRAM_TYPE_NULL denotes deletion. * * @retval 0 success * @retval ENOMEM if allocation fails. * @retval ENOENT if @p name is unknown. * @retval EINVAL if @p value is NULL, and deletion of @p is not * supported. * @retval EINVAL if @p value cannot be converted to a supported value * type. */ int bhnd_nvstore_path_register_update(struct bhnd_nvram_store *sc, bhnd_nvstore_path *path, const char *name, bhnd_nvram_val *value) { bhnd_nvram_val *prop_val; const char *full_name; void *cookiep; char *namebuf; int error; bool nvram_committed; namebuf = NULL; prop_val = NULL; /* Determine whether the variable is currently defined in the * backing NVRAM data, and derive its full path-prefixed name */ nvram_committed = false; - cookiep = bhnd_nvstore_index_lookup(sc, path->index, name); + cookiep = bhnd_nvstore_path_data_lookup(sc, path, name); if (cookiep != NULL) { /* Variable is defined in the backing data */ nvram_committed = true; /* Use the existing variable name */ full_name = bhnd_nvram_data_getvar_name(sc->data, cookiep); } else if (path == sc->root_path) { /* No prefix required for root path */ full_name = name; } else { bhnd_nvstore_alias *alias; int len; /* New variable is being set; we need to determine the * appropriate path prefix */ alias = bhnd_nvstore_find_alias(sc, path->path_str); if (alias != NULL) { /* Use :name */ len = bhnd_nv_asprintf(&namebuf, "%lu:%s", alias->alias, name); } else { /* Use path/name */ len = bhnd_nv_asprintf(&namebuf, "%s/%s", path->path_str, name); } if (len < 0) return (ENOMEM); full_name = namebuf; } /* Allow the data store to filter the NVRAM operation */ if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL) { error = bhnd_nvram_data_filter_unsetvar(sc->data, full_name); if (error) { BHND_NV_LOG("cannot unset property %s: %d\n", full_name, error); goto cleanup; } if ((prop_val = bhnd_nvram_val_copy(value)) == NULL) { error = ENOMEM; goto cleanup; } } else { error = bhnd_nvram_data_filter_setvar(sc->data, full_name, value, &prop_val); if (error) { BHND_NV_LOG("cannot set property %s: %d\n", full_name, error); goto cleanup; } } /* Add relative variable name to the per-path update list */ if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL && !nvram_committed) { /* This is a deletion request for a variable not defined in * out backing store; we can simply remove the corresponding * update entry. */ bhnd_nvram_plist_remove(path->pending, name); } else { /* Update or append a pending update entry */ error = bhnd_nvram_plist_replace_val(path->pending, name, prop_val); if (error) goto cleanup; } /* Success */ error = 0; cleanup: if (namebuf != NULL) bhnd_nv_free(namebuf); if (prop_val != NULL) bhnd_nvram_val_release(prop_val); return (error); } /** * Iterate over all variable cookiep values retrievable from the backing * data store in @p path. * * @warning Pending updates in @p path are ignored by this function. * * @param sc The NVRAM store. * @param path The NVRAM path to be iterated. * @param[in,out] indexp A pointer to an opaque indexp value previously * returned by bhnd_nvstore_path_data_next(), or a * NULL value to begin iteration. * * @return Returns the next variable name, or NULL if there are no more * variables defined in @p path. */ void * bhnd_nvstore_path_data_next(struct bhnd_nvram_store *sc, bhnd_nvstore_path *path, void **indexp) { void **index_ref; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* No index */ if (path->index == NULL) { /* An index is required for all non-empty, non-root path * instances */ BHND_NV_ASSERT(bhnd_nvstore_is_root_path(sc, path), ("missing index for non-root path %s", path->path_str)); /* Iterate NVRAM data directly, using the NVRAM data's cookiep * value as our indexp context */ if ((bhnd_nvram_data_next(sc->data, indexp)) == NULL) return (NULL); return (*indexp); } /* Empty index */ if (path->index->count == 0) return (NULL); if (*indexp == NULL) { /* First index entry */ index_ref = &path->index->cookiep[0]; } else { size_t idxpos; /* Advance to next index entry */ index_ref = *indexp; index_ref++; /* Hit end of index? */ BHND_NV_ASSERT(index_ref > path->index->cookiep, ("invalid indexp")); idxpos = (index_ref - path->index->cookiep); if (idxpos >= path->index->count) return (NULL); } /* Provide new index position */ *indexp = index_ref; /* Return the data's cookiep value */ return (*index_ref); } /** * Perform an lookup of @p name in the backing NVRAM data for @p path, * returning the associated cookiep value, or NULL if the variable is not found * in the backing NVRAM data. * * @warning Pending updates in @p path are ignored by this function. * * @param sc The NVRAM store from which NVRAM values will be queried. * @param path The path to be queried. * @param name The variable name to be queried. * * @retval non-NULL success * @retval NULL if @p name is not found in @p index. */ void * bhnd_nvstore_path_data_lookup(struct bhnd_nvram_store *sc, bhnd_nvstore_path *path, const char *name) { BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* No index */ if (path->index == NULL) { /* An index is required for all non-empty, non-root path * instances */ BHND_NV_ASSERT(bhnd_nvstore_is_root_path(sc, path), ("missing index for non-root path %s", path->path_str)); /* Look up directly in NVRAM data */ return (bhnd_nvram_data_find(sc->data, name)); } /* Otherwise, delegate to an index-based lookup */ return (bhnd_nvstore_index_lookup(sc, path->index, name)); } /** * Perform an index lookup of @p name, returning the associated cookiep * value, or NULL if the variable does not exist. * * @param sc The NVRAM store from which NVRAM values will be queried. * @param index The index to be queried. * @param name The variable name to be queried. * * @retval non-NULL success * @retval NULL if @p name is not found in @p index. */ void * bhnd_nvstore_index_lookup(struct bhnd_nvram_store *sc, bhnd_nvstore_index *index, const char *name) { void *cookiep; const char *indexed_name; size_t min, mid, max; uint32_t data_caps; int order; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); BHND_NV_ASSERT(index != NULL, ("NULL index")); /* * Locate the requested variable using a binary search. */ if (index->count == 0) return (NULL); data_caps = sc->data_caps; min = 0; max = index->count - 1; while (max >= min) { /* Select midpoint */ mid = (min + max) / 2; cookiep = index->cookiep[mid]; /* Fetch variable name */ indexed_name = bhnd_nvram_data_getvar_name(sc->data, cookiep); /* Trim any path prefix */ if (data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) indexed_name = bhnd_nvram_trim_path_name(indexed_name); /* Determine which side of the partition to search */ order = strcmp(indexed_name, name); if (order < 0) { /* Search upper partition */ min = mid + 1; } else if (order > 0) { /* Search (non-empty) lower partition */ if (mid == 0) break; max = mid - 1; } else if (order == 0) { size_t idx; /* * Match found. * * If this happens to be a key with multiple definitions * in the backing store, we need to find the entry with * the highest declaration precedence. * * Duplicates are sorted in order of descending * precedence; to find the highest precedence entry, * we search backwards through the index. */ idx = mid; while (idx > 0) { void *dup_cookiep; const char *dup_name; /* Fetch preceding index entry */ idx--; dup_cookiep = index->cookiep[idx]; dup_name = bhnd_nvram_data_getvar_name(sc->data, dup_cookiep); /* Trim any path prefix */ if (data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) { dup_name = bhnd_nvram_trim_path_name( dup_name); } /* If no match, current cookiep is the variable * definition with the highest precedence */ if (strcmp(indexed_name, dup_name) != 0) return (cookiep); /* Otherwise, prefer this earlier definition, * and keep searching for a higher-precedence * definitions */ cookiep = dup_cookiep; } return (cookiep); } } /* Not found */ return (NULL); } /** * Return the device path entry registered for @p path, if any. * * @param sc The NVRAM store to be queried. * @param path The device path to search for. * @param path_len The length of @p path. * * @retval non-NULL if found. * @retval NULL if not found. */ bhnd_nvstore_path * bhnd_nvstore_get_path(struct bhnd_nvram_store *sc, const char *path, size_t path_len) { bhnd_nvstore_path_list *plist; bhnd_nvstore_path *p; uint32_t h; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* Use hash lookup */ h = hash32_strn(path, path_len, HASHINIT); plist = &sc->paths[h % nitems(sc->paths)]; LIST_FOREACH(p, plist, np_link) { /* Check for prefix match */ if (strncmp(p->path_str, path, path_len) != 0) continue; /* Check for complete match */ if (strnlen(path, path_len) != strlen(p->path_str)) continue; return (p); } /* Not found */ return (NULL); } /** * Resolve @p aval to its corresponding device path entry, if any. * * @param sc The NVRAM store to be queried. * @param aval The device path alias value to search for. * * @retval non-NULL if found. * @retval NULL if not found. */ bhnd_nvstore_path * bhnd_nvstore_resolve_path_alias(struct bhnd_nvram_store *sc, u_long aval) { bhnd_nvstore_alias *alias; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* Fetch alias entry */ if ((alias = bhnd_nvstore_get_alias(sc, aval)) == NULL) return (NULL); return (alias->path); } /** * Register a device path entry for the path referenced by variable name * @p info, if any. * * @param sc The NVRAM store to be updated. * @param info The NVRAM variable name info. * @param cookiep The NVRAM variable's cookiep value. * * @retval 0 if the path was successfully registered, or an identical * path or alias entry exists. * @retval EEXIST if a conflicting entry already exists for the path or * alias referenced by @p info. * @retval ENOENT if @p info contains a dangling alias reference. * @retval EINVAL if @p info contains an unsupported bhnd_nvstore_var_type * and bhnd_nvstore_path_type combination. * @retval ENOMEM if allocation fails. */ int bhnd_nvstore_var_register_path(struct bhnd_nvram_store *sc, bhnd_nvstore_name_info *info, void *cookiep) { switch (info->type) { case BHND_NVSTORE_VAR: /* Variable */ switch (info->path_type) { case BHND_NVSTORE_PATH_STRING: /* Variable contains a full path string * (pci/1/1/varname); register the path */ return (bhnd_nvstore_register_path(sc, info->path.str.value, info->path.str.value_len)); case BHND_NVSTORE_PATH_ALIAS: /* Variable contains an alias reference (0:varname). * There's no path to register */ return (0); } BHND_NV_PANIC("unsupported path type %d", info->path_type); break; case BHND_NVSTORE_ALIAS_DECL: /* Alias declaration */ return (bhnd_nvstore_register_alias(sc, info, cookiep)); } BHND_NV_PANIC("unsupported var type %d", info->type); } /** * Resolve the device path entry referenced referenced by @p info. * * @param sc The NVRAM store to be updated. * @param info Variable name information descriptor containing * the path or path alias to be resolved. * * @retval non-NULL if found. * @retval NULL if not found. */ bhnd_nvstore_path * bhnd_nvstore_var_get_path(struct bhnd_nvram_store *sc, bhnd_nvstore_name_info *info) { switch (info->path_type) { case BHND_NVSTORE_PATH_STRING: return (bhnd_nvstore_get_path(sc, info->path.str.value, info->path.str.value_len)); case BHND_NVSTORE_PATH_ALIAS: return (bhnd_nvstore_resolve_path_alias(sc, info->path.alias.value)); } BHND_NV_PANIC("unsupported path type %d", info->path_type); } /** * Return the device path alias entry registered for @p alias_val, if any. * * @param sc The NVRAM store to be queried. * @param alias_val The alias value to search for. * * @retval non-NULL if found. * @retval NULL if not found. */ bhnd_nvstore_alias * bhnd_nvstore_get_alias(struct bhnd_nvram_store *sc, u_long alias_val) { bhnd_nvstore_alias_list *alist; bhnd_nvstore_alias *alias; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* Can use hash lookup */ alist = &sc->aliases[alias_val % nitems(sc->aliases)]; LIST_FOREACH(alias, alist, na_link) { if (alias->alias == alias_val) return (alias); } /* Not found */ return (NULL); } /** * Return the device path alias entry registered for @p path, if any. * * @param sc The NVRAM store to be queried. * @param path The alias path to search for. * * @retval non-NULL if found. * @retval NULL if not found. */ bhnd_nvstore_alias * bhnd_nvstore_find_alias(struct bhnd_nvram_store *sc, const char *path) { bhnd_nvstore_alias *alias; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* Have to scan the full table */ for (size_t i = 0; i < nitems(sc->aliases); i++) { LIST_FOREACH(alias, &sc->aliases[i], na_link) { if (strcmp(alias->path->path_str, path) == 0) return (alias); } } /* Not found */ return (NULL); } /** * Register a device path entry for @p path. * * @param sc The NVRAM store to be updated. * @param path_str The absolute device path string. * @param path_len The length of @p path_str. * * @retval 0 if the path was successfully registered, or an identical * path/alias entry already exists. * @retval ENOMEM if allocation fails. */ int bhnd_nvstore_register_path(struct bhnd_nvram_store *sc, const char *path_str, size_t path_len) { bhnd_nvstore_path_list *plist; bhnd_nvstore_path *path; uint32_t h; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); /* Already exists? */ if (bhnd_nvstore_get_path(sc, path_str, path_len) != NULL) return (0); /* Can't represent more than SIZE_MAX paths */ if (sc->num_paths == SIZE_MAX) return (ENOMEM); /* Allocate new entry */ path = bhnd_nvstore_path_new(path_str, path_len); if (path == NULL) return (ENOMEM); /* Insert in path hash table */ h = hash32_str(path->path_str, HASHINIT); plist = &sc->paths[h % nitems(sc->paths)]; LIST_INSERT_HEAD(plist, path, np_link); /* Increment path count */ sc->num_paths++; return (0); } /** * Register a device path alias for an NVRAM 'devpathX' variable. * * The path value for the alias will be fetched from the backing NVRAM data. * * @param sc The NVRAM store to be updated. * @param info The NVRAM variable name info. * @param cookiep The NVRAM variable's cookiep value. * * @retval 0 if the alias was successfully registered, or an * identical alias entry exists. * @retval EEXIST if a conflicting alias or path entry already exists. * @retval EINVAL if @p info is not a BHND_NVSTORE_ALIAS_DECL or does * not contain a BHND_NVSTORE_PATH_ALIAS entry. * @retval ENOMEM if allocation fails. */ int bhnd_nvstore_register_alias(struct bhnd_nvram_store *sc, const bhnd_nvstore_name_info *info, void *cookiep) { bhnd_nvstore_alias_list *alist; bhnd_nvstore_alias *alias; bhnd_nvstore_path *path; char *path_str; size_t path_len; int error; BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED); path_str = NULL; alias = NULL; /* Can't represent more than SIZE_MAX aliases */ if (sc->num_aliases == SIZE_MAX) return (ENOMEM); /* Must be an alias declaration */ if (info->type != BHND_NVSTORE_ALIAS_DECL) return (EINVAL); if (info->path_type != BHND_NVSTORE_PATH_ALIAS) return (EINVAL); /* Fetch the devpath variable's value length */ error = bhnd_nvram_data_getvar(sc->data, cookiep, NULL, &path_len, BHND_NVRAM_TYPE_STRING); if (error) return (ENOMEM); /* Allocate path string buffer */ if ((path_str = bhnd_nv_malloc(path_len)) == NULL) return (ENOMEM); /* Decode to our new buffer */ error = bhnd_nvram_data_getvar(sc->data, cookiep, path_str, &path_len, BHND_NVRAM_TYPE_STRING); if (error) goto failed; /* Trim trailing '/' character(s) from the path length */ path_len = strnlen(path_str, path_len); while (path_len > 0 && path_str[path_len-1] == '/') { path_str[path_len-1] = '\0'; path_len--; } /* Is a conflicting alias entry already registered for this alias * value? */ alias = bhnd_nvstore_get_alias(sc, info->path.alias.value); if (alias != NULL) { if (alias->cookiep != cookiep || strcmp(alias->path->path_str, path_str) != 0) { error = EEXIST; goto failed; } } /* Is a conflicting entry already registered for the alias path? */ if ((alias = bhnd_nvstore_find_alias(sc, path_str)) != NULL) { if (alias->alias != info->path.alias.value || alias->cookiep != cookiep || strcmp(alias->path->path_str, path_str) != 0) { error = EEXIST; goto failed; } } /* Get (or register) the target path entry */ path = bhnd_nvstore_get_path(sc, path_str, path_len); if (path == NULL) { error = bhnd_nvstore_register_path(sc, path_str, path_len); if (error) goto failed; path = bhnd_nvstore_get_path(sc, path_str, path_len); BHND_NV_ASSERT(path != NULL, ("missing registered path")); } /* Allocate alias entry */ alias = bhnd_nv_calloc(1, sizeof(*alias)); if (alias == NULL) { error = ENOMEM; goto failed; } alias->path = path; alias->cookiep = cookiep; alias->alias = info->path.alias.value; /* Insert in alias hash table */ alist = &sc->aliases[alias->alias % nitems(sc->aliases)]; LIST_INSERT_HEAD(alist, alias, na_link); /* Increment alias count */ sc->num_aliases++; bhnd_nv_free(path_str); return (0); failed: if (path_str != NULL) bhnd_nv_free(path_str); if (alias != NULL) bhnd_nv_free(alias); return (error); } /** * If @p child is equal to or a child path of @p parent, return a pointer to * @p child's path component(s) relative to @p parent; otherwise, return NULL. */ const char * bhnd_nvstore_parse_relpath(const char *parent, const char *child) { size_t prefix_len; /* All paths have an implicit leading '/'; this allows us to treat * our manufactured root path of "/" as a prefix to all NVRAM-defined * paths (which do not necessarily include a leading '/' */ if (*parent == '/') parent++; if (*child == '/') child++; /* Is parent a prefix of child? */ prefix_len = strlen(parent); if (strncmp(parent, child, prefix_len) != 0) return (NULL); /* A zero-length prefix matches everything */ if (prefix_len == 0) return (child); /* Is child equal to parent? */ if (child[prefix_len] == '\0') return (child + prefix_len); /* Is child actually a child of parent? */ if (child[prefix_len] == '/') return (child + prefix_len + 1); /* No match (e.g. parent=/foo..., child=/fooo...) */ return (NULL); } /** * Parse a raw NVRAM variable name and return its @p entry_type, its * type-specific @p prefix (e.g. '0:', 'pci/1/1', 'devpath'), and its * type-specific @p suffix (e.g. 'varname', '0'). * * @param name The NVRAM variable name to be parsed. This * value must remain valid for the lifetime of * @p info. * @param type The NVRAM name type -- either INTERNAL for names * parsed from backing NVRAM data, or EXTERNAL for * names provided by external NVRAM store clients. * @param data_caps The backing NVRAM data capabilities * (see bhnd_nvram_data_caps()). * @param[out] info On success, the parsed variable name info. * * @retval 0 success * @retval non-zero if parsing @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvstore_parse_name_info(const char *name, bhnd_nvstore_name_type type, uint32_t data_caps, bhnd_nvstore_name_info *info) { const char *p; char *endp; /* Skip path parsing? */ if (data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) { /* devpath declaration? (devpath0=pci/1/1) */ if (strncmp(name, "devpath", strlen("devpath")) == 0) { u_long alias; /* Perform standard validation on the relative * variable name */ if (type != BHND_NVSTORE_NAME_INTERNAL && !bhnd_nvram_validate_name(name)) { return (ENOENT); } /* Parse alias value that should follow a 'devpath' * prefix */ p = name + strlen("devpath"); alias = strtoul(p, &endp, 10); if (endp != p && *endp == '\0') { info->type = BHND_NVSTORE_ALIAS_DECL; info->path_type = BHND_NVSTORE_PATH_ALIAS; info->name = name; info->path.alias.value = alias; return (0); } } /* device aliased variable? (0:varname) */ if (bhnd_nv_isdigit(*name)) { u_long alias; /* Parse '0:' alias prefix */ alias = strtoul(name, &endp, 10); if (endp != name && *endp == ':') { /* Perform standard validation on the relative * variable name */ if (type != BHND_NVSTORE_NAME_INTERNAL && !bhnd_nvram_validate_name(name)) { return (ENOENT); } info->type = BHND_NVSTORE_VAR; info->path_type = BHND_NVSTORE_PATH_ALIAS; /* name follows 0: prefix */ info->name = endp + 1; info->path.alias.value = alias; return (0); } } /* device variable? (pci/1/1/varname) */ if ((p = strrchr(name, '/')) != NULL) { const char *path, *relative_name; size_t path_len; /* Determine the path length; 'p' points at the last * path separator in 'name' */ path_len = p - name; path = name; /* The relative variable name directly follows the * final path separator '/' */ relative_name = path + path_len + 1; /* Now that we calculated the name offset, exclude all * trailing '/' characters from the path length */ while (path_len > 0 && path[path_len-1] == '/') path_len--; /* Perform standard validation on the relative * variable name */ if (type != BHND_NVSTORE_NAME_INTERNAL && !bhnd_nvram_validate_name(relative_name)) { return (ENOENT); } /* Initialize result with pointers into the name * buffer */ info->type = BHND_NVSTORE_VAR; info->path_type = BHND_NVSTORE_PATH_STRING; info->name = relative_name; info->path.str.value = path; info->path.str.value_len = path_len; return (0); } } /* If all other parsing fails, the result is a simple variable with * an implicit path of "/" */ if (type != BHND_NVSTORE_NAME_INTERNAL && !bhnd_nvram_validate_name(name)) { /* Invalid relative name */ return (ENOENT); } info->type = BHND_NVSTORE_VAR; info->path_type = BHND_NVSTORE_PATH_STRING; info->name = name; info->path.str.value = BHND_NVSTORE_ROOT_PATH; info->path.str.value_len = BHND_NVSTORE_ROOT_PATH_LEN; return (0); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_storevar.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_storevar.h (revision 310295) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_storevar.h (revision 310296) @@ -1,308 +1,309 @@ /*- * Copyright (c) 2015-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. * * $FreeBSD$ */ #ifndef _BHND_NVRAM_BHND_NVRAM_STOREVAR_H_ #define _BHND_NVRAM_BHND_NVRAM_STOREVAR_H_ #include #ifndef _KERNEL #include #endif #include "bhnd_nvram_plist.h" #include "bhnd_nvram_store.h" /** Index is only generated if minimum variable count is met */ #define BHND_NV_IDX_VAR_THRESHOLD 15 #define BHND_NVSTORE_ROOT_PATH "/" #define BHND_NVSTORE_ROOT_PATH_LEN sizeof(BHND_NVSTORE_ROOT_PATH) #define BHND_NVSTORE_GET_FLAG(_value, _flag) \ (((_value) & BHND_NVSTORE_ ## _flag) != 0) #define BHND_NVSTORE_GET_BITS(_value, _field) \ ((_value) & BHND_NVSTORE_ ## _field ## _MASK) /* Forward declarations */ typedef struct bhnd_nvstore_name_info bhnd_nvstore_name_info; typedef struct bhnd_nvstore_index bhnd_nvstore_index; typedef struct bhnd_nvstore_path bhnd_nvstore_path; typedef struct bhnd_nvstore_alias bhnd_nvstore_alias; typedef struct bhnd_nvstore_alias_list bhnd_nvstore_alias_list; typedef struct bhnd_nvstore_update_list bhnd_nvstore_update_list; typedef struct bhnd_nvstore_path_list bhnd_nvstore_path_list; LIST_HEAD(bhnd_nvstore_alias_list, bhnd_nvstore_alias); LIST_HEAD(bhnd_nvstore_update_list, bhnd_nvstore_update); LIST_HEAD(bhnd_nvstore_path_list, bhnd_nvstore_path); /** * NVRAM store variable entry types. */ typedef enum { BHND_NVSTORE_VAR = 0, /**< simple variable (var=...) */ BHND_NVSTORE_ALIAS_DECL = 1, /**< alias declaration ('devpath0=pci/1/1') */ } bhnd_nvstore_var_type; /** * NVRAM path descriptor types. */ typedef enum { BHND_NVSTORE_PATH_STRING = 0, /**< path is a string value */ BHND_NVSTORE_PATH_ALIAS = 1 /**< path is an alias reference */ } bhnd_nvstore_path_type; /** * NVRAM variable namespaces. */ typedef enum { BHND_NVSTORE_NAME_INTERNAL = 1, /**< internal namespace. permits use of reserved devpath and alias name prefixes. */ BHND_NVSTORE_NAME_EXTERNAL = 2, /**< external namespace. forbids use of name prefixes used for device path handling */ } bhnd_nvstore_name_type; bhnd_nvstore_path *bhnd_nvstore_path_new(const char *path_str, size_t path_len); void bhnd_nvstore_path_free(struct bhnd_nvstore_path *path); bhnd_nvstore_index *bhnd_nvstore_index_new(size_t capacity); void bhnd_nvstore_index_free(bhnd_nvstore_index *index); int bhnd_nvstore_index_append(struct bhnd_nvram_store *sc, bhnd_nvstore_index *index, void *cookiep); int bhnd_nvstore_index_prepare( struct bhnd_nvram_store *sc, bhnd_nvstore_index *index); void *bhnd_nvstore_index_lookup(struct bhnd_nvram_store *sc, bhnd_nvstore_index *index, const char *name); bhnd_nvstore_path *bhnd_nvstore_get_root_path( struct bhnd_nvram_store *sc); bool bhnd_nvstore_is_root_path(struct bhnd_nvram_store *sc, bhnd_nvstore_path *path); void *bhnd_nvstore_path_data_next( struct bhnd_nvram_store *sc, bhnd_nvstore_path *path, void **indexp); void *bhnd_nvstore_path_data_lookup( struct bhnd_nvram_store *sc, bhnd_nvstore_path *path, const char *name); bhnd_nvram_prop *bhnd_nvstore_path_get_update( struct bhnd_nvram_store *sc, bhnd_nvstore_path *path, const char *name); int bhnd_nvstore_path_register_update( struct bhnd_nvram_store *sc, bhnd_nvstore_path *path, const char *name, bhnd_nvram_val *value); bhnd_nvstore_alias *bhnd_nvstore_find_alias(struct bhnd_nvram_store *sc, const char *path); bhnd_nvstore_alias *bhnd_nvstore_get_alias(struct bhnd_nvram_store *sc, u_long alias_val); bhnd_nvstore_path *bhnd_nvstore_get_path(struct bhnd_nvram_store *sc, const char *path, size_t path_len); bhnd_nvstore_path *bhnd_nvstore_resolve_path_alias( struct bhnd_nvram_store *sc, u_long aval); bhnd_nvstore_path *bhnd_nvstore_var_get_path(struct bhnd_nvram_store *sc, bhnd_nvstore_name_info *info); int bhnd_nvstore_var_register_path( struct bhnd_nvram_store *sc, bhnd_nvstore_name_info *info, void *cookiep); int bhnd_nvstore_register_path(struct bhnd_nvram_store *sc, const char *path, size_t path_len); int bhnd_nvstore_register_alias( struct bhnd_nvram_store *sc, const bhnd_nvstore_name_info *info, void *cookiep); const char *bhnd_nvstore_parse_relpath(const char *parent, const char *child); int bhnd_nvstore_parse_name_info(const char *name, bhnd_nvstore_name_type name_type, uint32_t data_caps, bhnd_nvstore_name_info *info); /** * NVRAM variable name descriptor. * * For NVRAM data instances supporting BHND_NVRAM_DATA_CAP_DEVPATHS, the * NVRAM-vended variable name will be in one of four formats: * * - Simple Variable: * 'variable' * - Device Variable: * 'pci/1/1/variable' * - Device Alias Variable: * '0:variable' * - Device Path Alias Definition: * 'devpath0=pci/1/1/variable' * * Device Paths: * * The device path format is device class-specific; the known supported device * classes are: * - sb: BCMA/SIBA SoC core device path. * - pci: PCI device path (and PCIe on some earlier devices). * - pcie: PCIe device path. * - usb: USB device path. * * The device path format is loosely defined as '[class]/[domain]/[bus]/[slot]', * with missing values either assumed to be zero, a value specific to the * device class, or irrelevant to the device class in question. * * Examples: * sb/1 BCMA/SIBA backplane 0, core 1. * pc/1/1 PCMCIA bus 1, slot 1 * pci/1/1 PCI/PCIe domain 0, bus 1, device 1 * pcie/1/1 PCIe domain 0, bus 1, device 1 * usb/0xbd17 USB PID 0xbd17 (VID defaults to Broadcom 0x0a5c) * * Device Path Aliases: * * Device path aliases reduce duplication of device paths in the flash encoding * of NVRAM data; a single devpath[alias]=[devpath] variable entry is defined, * and then later variables may reference the device path via its alias: * devpath1=usb/0xbd17 * 1:mcs5gpo0=0x1100 * * Alias values are always positive, base 10 integers. */ struct bhnd_nvstore_name_info { const char *name; /**< variable name */ bhnd_nvstore_var_type type; /**< variable type */ bhnd_nvstore_path_type path_type; /**< path type */ /** Path information */ union { /* BHND_NVSTORE_PATH_STRING */ struct { const char *value; /**< device path */ size_t value_len; /**< device path length */ } str; /** BHND_NVSTORE_PATH_ALIAS */ struct { u_long value; /**< device alias */ } alias; } path; }; /** * NVRAM variable index. * * Provides effecient name-based lookup by maintaining an array of cached * cookiep values, sorted lexicographically by relative variable name. */ struct bhnd_nvstore_index { size_t count; /**< entry count */ size_t capacity; /**< entry capacity */ void *cookiep[]; /**< cookiep values */ }; /** * NVRAM device path. */ struct bhnd_nvstore_path { char *path_str; /**< canonical path string */ size_t num_vars; /**< per-path count of committed (non-pending) variables */ bhnd_nvstore_index *index; /**< per-path index, or NULL if this is a root path for which the data source may be queried directly. */ bhnd_nvram_plist *pending; /**< pending changes */ LIST_ENTRY(bhnd_nvstore_path) np_link; }; /** * NVRAM device path alias. */ struct bhnd_nvstore_alias { bhnd_nvstore_path *path; /**< borrowed path reference */ void *cookiep; /**< NVRAM variable's cookiep value */ u_long alias; /**< alias value */ LIST_ENTRY(bhnd_nvstore_alias) na_link; }; /** bhnd nvram store instance state */ struct bhnd_nvram_store { #ifdef _KERNEL struct mtx mtx; #else pthread_mutex_t mtx; #endif struct bhnd_nvram_data *data; /**< backing data */ uint32_t data_caps; /**< data capability flags */ + bhnd_nvram_plist *data_opts; /**< data serialization options */ bhnd_nvstore_alias_list aliases[4]; /**< path alias hash table */ size_t num_aliases; /**< alias count */ bhnd_nvstore_path *root_path; /**< root path instance */ bhnd_nvstore_path_list paths[4]; /**< path hash table */ size_t num_paths; /**< path count */ }; #ifdef _KERNEL #define BHND_NVSTORE_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, "BHND NVRAM store lock", NULL, MTX_DEF) #define BHND_NVSTORE_LOCK(sc) mtx_lock(&(sc)->mtx) #define BHND_NVSTORE_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define BHND_NVSTORE_LOCK_ASSERT(sc, what) mtx_assert(&(sc)->mtx, what) #define BHND_NVSTORE_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx) #else /* !_KERNEL */ #define BHND_NVSTORE_LOCK_INIT(sc) do { \ int error = pthread_mutex_init(&(sc)->mtx, NULL); \ if (error) \ BHND_NV_PANIC("pthread_mutex_init() failed: %d", \ error); \ } while(0) #define BHND_NVSTORE_LOCK(sc) pthread_mutex_lock(&(sc)->mtx) #define BHND_NVSTORE_UNLOCK(sc) pthread_mutex_unlock(&(sc)->mtx) #define BHND_NVSTORE_LOCK_DESTROY(sc) pthread_mutex_destroy(&(sc)->mtx) #define BHND_NVSTORE_LOCK_ASSERT(sc, what) #endif /* _KERNEL */ #endif /* _BHND_NVRAM_BHND_NVRAM_STOREVAR_H_ */