Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data.c (revision 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data.c (revision 315866) @@ -1,725 +1,760 @@ /*- * 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); } /** * Return the class-level capability flags (@see BHND_NVRAM_DATA_CAP_*) for * of @p cls. * * @param cls The NVRAM class. */ uint32_t bhnd_nvram_data_class_caps(bhnd_nvram_data_class *cls) { return (cls->caps); } /** * Serialize all NVRAM properties in @p plist using @p cls's NVRAM data * format, writing the result to @p outp. * * @param cls The NVRAM data class to be used to perform * serialization. * @param props The raw property values to be serialized to * @p outp, in serialization order. * @param options Serialization options for @p cls, or NULL. * @param[out] outp 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] olen 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 outp is non-NULL and a buffer of @p olen is too * small to hold the serialized data. * @retval EINVAL If a property value required by @p cls is not found in * @p plist. * @retval EFTYPE If a property value in @p plist cannot be represented * as the data type required by @p cls. * @retval ERANGE If a property value in @p plist would would overflow * (or underflow) the data type required by @p cls. * @retval non-zero If serialization otherwise fails, a regular unix error * code will be returned. */ int bhnd_nvram_data_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, size_t *olen) { return (cls->op_serialize(cls, props, options, outp, olen)); } /** * 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); } /** + * Read a variable directly from @p io and decode as @p type. + * + * This may be used to perform reading of NVRAM variables during the very + * early boot process, prior to the availability of the kernel allocator. + * + * @param cls An NVRAM class capable of parsing @p io. + * @param io NVRAM data to be parsed. + * @param name The raw name of the variable to be fetched, + * including any device path (/pci/1/1/varname) or + * alias prefix (0:varname). + * @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 ENOENT If @p name is not found in @p io. + * @retval EFTYPE If the variable data cannot be coerced to @p type. + * @retval ERANGE If value coercion would overflow @p type. + * @retval non-zero If parsing @p io otherwise fails, a regular unix error + * code will be returned. + */ +int +bhnd_nvram_data_getvar_direct(bhnd_nvram_data_class *cls, + struct bhnd_nvram_io *io, const char *name, void *buf, size_t *len, + bhnd_nvram_type type) +{ + return (cls->op_getvar_direct(io, name, buf, len, type)); +} + +/** * 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)); } /** * 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 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data.h (revision 315866) @@ -1,149 +1,154 @@ /*- * 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); uint32_t bhnd_nvram_data_class_caps(bhnd_nvram_data_class *cls); int bhnd_nvram_data_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, size_t *olen); 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_getvar_direct( + bhnd_nvram_data_class *cls, + struct bhnd_nvram_io *io, const char *name, + void *buf, size_t *len, bhnd_nvram_type type); + 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); 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 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c (revision 315866) @@ -1,904 +1,1124 @@ /*- * Copyright (c) 2016 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. */ #include __FBSDID("$FreeBSD$"); #include #include #ifdef _KERNEL #include #include #include #include #else /* !_KERNEL */ #include #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_data_bcmreg.h" #include "bhnd_nvram_data_bcmvar.h" /* * Broadcom NVRAM data class. * * The Broadcom NVRAM NUL-delimited ASCII format is used by most * Broadcom SoCs. * * The NVRAM data is encoded as a standard header, followed by series of * NUL-terminated 'key=value' strings; the end of the stream is denoted * by a single extra NUL character. */ struct bhnd_nvram_bcm; static struct bhnd_nvram_bcm_hvar *bhnd_nvram_bcm_gethdrvar( struct bhnd_nvram_bcm *bcm, const char *name); static struct bhnd_nvram_bcm_hvar *bhnd_nvram_bcm_to_hdrvar( struct bhnd_nvram_bcm *bcm, void *cookiep); static size_t bhnd_nvram_bcm_hdrvar_index( struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_bcm_hvar *hvar); /* * Set of BCM NVRAM header values that are required to be mirrored in the * NVRAM data itself. * * If they're not included in the parsed NVRAM data, we need to vend the * header-parsed values with their appropriate keys, and add them in any * updates to the NVRAM data. * * If they're modified in NVRAM, we need to sync the changes with the * the NVRAM header values. */ static const struct bhnd_nvram_bcm_hvar bhnd_nvram_bcm_hvars[] = { { .name = BCM_NVRAM_CFG0_SDRAM_INIT_VAR, .type = BHND_NVRAM_TYPE_UINT16, .len = sizeof(uint16_t), .nelem = 1, }, { .name = BCM_NVRAM_CFG1_SDRAM_CFG_VAR, .type = BHND_NVRAM_TYPE_UINT16, .len = sizeof(uint16_t), .nelem = 1, }, { .name = BCM_NVRAM_CFG1_SDRAM_REFRESH_VAR, .type = BHND_NVRAM_TYPE_UINT16, .len = sizeof(uint16_t), .nelem = 1, }, { .name = BCM_NVRAM_SDRAM_NCDL_VAR, .type = BHND_NVRAM_TYPE_UINT32, .len = sizeof(uint32_t), .nelem = 1, }, }; /** BCM NVRAM data class instance */ struct bhnd_nvram_bcm { struct bhnd_nvram_data nv; /**< common instance state */ struct bhnd_nvram_io *data; /**< backing buffer */ bhnd_nvram_plist *opts; /**< serialization options */ /** BCM header values */ struct bhnd_nvram_bcm_hvar hvars[nitems(bhnd_nvram_bcm_hvars)]; size_t count; /**< total variable count */ }; BHND_NVRAM_DATA_CLASS_DEFN(bcm, "Broadcom", BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_bcm)) static int bhnd_nvram_bcm_probe(struct bhnd_nvram_io *io) { struct bhnd_nvram_bcmhdr hdr; int error; if ((error = bhnd_nvram_io_read(io, 0x0, &hdr, sizeof(hdr)))) return (error); if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC) return (ENXIO); + if (le32toh(hdr.size) > bhnd_nvram_io_getsize(io)) + return (ENXIO); + return (BHND_NVRAM_DATA_PROBE_DEFAULT); +} + +/** + * Parser states for bhnd_nvram_bcm_getvar_direct_common(). + */ +typedef enum { + BCM_PARSE_KEY_START, + BCM_PARSE_KEY_CONT, + BCM_PARSE_KEY, + BCM_PARSE_NEXT_KEY, + BCM_PARSE_VALUE_START, + BCM_PARSE_VALUE +} bcm_parse_state; + +static int +bhnd_nvram_bcm_getvar_direct(struct bhnd_nvram_io *io, const char *name, + void *outp, size_t *olen, bhnd_nvram_type otype) +{ + return (bhnd_nvram_bcm_getvar_direct_common(io, name, outp, olen, otype, + true)); +} + +/** + * Common BCM/BCMRAW implementation of bhnd_nvram_getvar_direct(). + */ +int +bhnd_nvram_bcm_getvar_direct_common(struct bhnd_nvram_io *io, const char *name, + void *outp, size_t *olen, bhnd_nvram_type otype, bool have_header) +{ + struct bhnd_nvram_bcmhdr hdr; + char buf[512]; + bcm_parse_state pstate; + size_t limit, offset; + size_t buflen, bufpos; + size_t namelen, namepos; + size_t vlen; + int error; + + limit = bhnd_nvram_io_getsize(io); + offset = 0; + + /* Fetch and validate the header */ + if (have_header) { + if ((error = bhnd_nvram_io_read(io, offset, &hdr, sizeof(hdr)))) + return (error); + + if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC) + return (ENXIO); + + offset += sizeof(hdr); + limit = bhnd_nv_ummin(le32toh(hdr.size), limit); + } + + /* Loop our parser until we find the requested variable, or hit EOF */ + pstate = BCM_PARSE_KEY_START; + buflen = 0; + bufpos = 0; + namelen = strlen(name); + namepos = 0; + vlen = 0; + + while ((offset - bufpos) < limit) { + BHND_NV_ASSERT(bufpos <= buflen, + ("buf position invalid (%zu > %zu)", bufpos, buflen)); + BHND_NV_ASSERT(buflen <= sizeof(buf), + ("buf length invalid (%zu > %zu", buflen, sizeof(buf))); + + /* Repopulate our parse buffer? */ + if (buflen - bufpos == 0) { + BHND_NV_ASSERT(offset < limit, ("offset overrun")); + + buflen = bhnd_nv_ummin(sizeof(buf), limit - offset); + bufpos = 0; + + error = bhnd_nvram_io_read(io, offset, buf, buflen); + if (error) + return (error); + + offset += buflen; + } + + switch (pstate) { + case BCM_PARSE_KEY_START: + BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!")); + + /* An extra '\0' denotes NVRAM EOF */ + if (buf[bufpos] == '\0') + return (ENOENT); + + /* Reset name matching position */ + namepos = 0; + + /* Start name matching */ + pstate = BCM_PARSE_KEY_CONT; + break; + + case BCM_PARSE_KEY_CONT: { + size_t navail, nleft; + + nleft = namelen - namepos; + navail = bhnd_nv_ummin(buflen - bufpos, nleft); + + if (strncmp(name+namepos, buf+bufpos, navail) == 0) { + /* Matched */ + namepos += navail; + bufpos += navail; + + /* If we've matched the full variable name, + * look for its trailing delimiter */ + if (namepos == namelen) + pstate = BCM_PARSE_KEY; + } else { + /* No match; advance to next entry and restart + * name matching */ + pstate = BCM_PARSE_NEXT_KEY; + } + + break; + } + + case BCM_PARSE_KEY: + BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!")); + + if (buf[bufpos] == '=') { + /* Key fully matched; advance past '=' and + * parse the value */ + bufpos++; + pstate = BCM_PARSE_VALUE_START; + } else { + /* No match; advance to next entry and restart + * name matching */ + pstate = BCM_PARSE_NEXT_KEY; + } + + break; + + case BCM_PARSE_NEXT_KEY: { + const char *p; + + /* Scan for a '\0' terminator */ + p = memchr(buf+bufpos, '\0', buflen - bufpos); + + if (p != NULL) { + /* Found entry terminator; restart name + * matching at next entry */ + pstate = BCM_PARSE_KEY_START; + bufpos = (p - buf) + 1 /* skip '\0' */; + } else { + /* Consumed full buffer looking for '\0'; + * force repopulation of the buffer and + * retry */ + bufpos = buflen; + } + + break; + } + + case BCM_PARSE_VALUE_START: { + const char *p; + + /* Scan for a '\0' terminator */ + p = memchr(buf+bufpos, '\0', buflen - bufpos); + + if (p != NULL) { + /* Found entry terminator; parse the value */ + vlen = p - &buf[bufpos]; + pstate = BCM_PARSE_VALUE; + + } else if (p == NULL && offset == limit) { + /* Hit EOF without a terminating '\0'; + * treat the entry as implicitly terminated */ + vlen = buflen - bufpos; + pstate = BCM_PARSE_VALUE; + + } else if (p == NULL && bufpos > 0) { + size_t nread; + + /* Move existing value data to start of + * buffer */ + memmove(buf, buf+bufpos, buflen - bufpos); + buflen = bufpos; + bufpos = 0; + + /* Populate full buffer to allow retry of + * value parsing */ + nread = bhnd_nv_ummin(sizeof(buf) - buflen, + limit - offset); + + error = bhnd_nvram_io_read(io, offset, + buf+buflen, nread); + if (error) + return (error); + + offset += nread; + buflen += nread; + } else { + /* Value exceeds our buffer capacity */ + BHND_NV_LOG("cannot parse value for '%s' " + "(exceeds %zu byte limit)\n", name, + sizeof(buf)); + + return (ENXIO); + } + + break; + } + + case BCM_PARSE_VALUE: + BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun")); + + return (bhnd_nvram_value_coerce(buf+bufpos, vlen, + BHND_NVRAM_TYPE_STRING, outp, olen, otype)); + } + } + + /* Variable not found */ + return (ENOENT); } static int bhnd_nvram_bcm_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, size_t *olen) { struct bhnd_nvram_bcmhdr hdr; bhnd_nvram_prop *prop; size_t limit, nbytes; uint32_t sdram_ncdl; uint16_t sdram_init, sdram_cfg, sdram_refresh; uint8_t bcm_ver, crc8; int error; /* Determine output byte limit */ if (outp != NULL) limit = *olen; else limit = 0; /* Fetch required header variables */ #define PROPS_GET_HDRVAR(_name, _dest, _type) do { \ const char *name = BCM_NVRAM_ ## _name ## _VAR; \ if (!bhnd_nvram_plist_contains(props, name)) { \ BHND_NV_LOG("missing required property: %s\n", \ name); \ return (EFTYPE); \ } \ \ error = bhnd_nvram_plist_get_encoded(props, name, \ (_dest), sizeof(*(_dest)), \ BHND_NVRAM_TYPE_ ##_type); \ if (error) { \ BHND_NV_LOG("error reading required header " \ "%s property: %d\n", name, error); \ return (EFTYPE); \ } \ } while (0) PROPS_GET_HDRVAR(SDRAM_NCDL, &sdram_ncdl, UINT32); PROPS_GET_HDRVAR(CFG0_SDRAM_INIT, &sdram_init, UINT16); PROPS_GET_HDRVAR(CFG1_SDRAM_CFG, &sdram_cfg, UINT16); PROPS_GET_HDRVAR(CFG1_SDRAM_REFRESH, &sdram_refresh, UINT16); #undef PROPS_GET_HDRVAR /* Fetch BCM nvram version from options */ if (options != NULL && bhnd_nvram_plist_contains(options, BCM_NVRAM_ENCODE_OPT_VERSION)) { error = bhnd_nvram_plist_get_uint8(options, BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver); if (error) { BHND_NV_LOG("error reading %s uint8 option value: %d\n", BCM_NVRAM_ENCODE_OPT_VERSION, error); return (EINVAL); } } else { bcm_ver = BCM_NVRAM_CFG0_VER_DEFAULT; } /* Construct our header */ hdr = (struct bhnd_nvram_bcmhdr) { .magic = htole32(BCM_NVRAM_MAGIC), .size = 0, .cfg0 = 0, .cfg1 = 0, .sdram_ncdl = htole32(sdram_ncdl) }; hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, 0x0); hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER, bcm_ver); hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_SDRAM_INIT, htole16(sdram_init)); hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_CFG, htole16(sdram_cfg)); hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_REFRESH, htole16(sdram_refresh)); /* Write the header */ nbytes = sizeof(hdr); if (limit >= nbytes) memcpy(outp, &hdr, sizeof(hdr)); /* Write all properties */ prop = NULL; while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { const char *name; char *p; size_t prop_limit; size_t name_len, value_len; if (outp == NULL || limit < nbytes) { p = NULL; prop_limit = 0; } else { p = ((char *)outp) + nbytes; prop_limit = limit - nbytes; } /* Fetch and write name + '=' to output */ name = bhnd_nvram_prop_name(prop); name_len = strlen(name) + 1; if (prop_limit > name_len) { memcpy(p, name, name_len - 1); p[name_len - 1] = '='; prop_limit -= name_len; p += name_len; } else { prop_limit = 0; p = NULL; } /* Advance byte count */ if (SIZE_MAX - nbytes < name_len) return (EFTYPE); /* would overflow size_t */ nbytes += name_len; /* Attempt to write NUL-terminated value to output */ value_len = prop_limit; error = bhnd_nvram_prop_encode(prop, p, &value_len, BHND_NVRAM_TYPE_STRING); /* If encoding failed for any reason other than ENOMEM (which * we'll detect and report after encoding all properties), * return immediately */ if (error && error != ENOMEM) { BHND_NV_LOG("error serializing %s to required type " "%s: %d\n", name, bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), error); return (error); } /* Advance byte count */ if (SIZE_MAX - nbytes < value_len) return (EFTYPE); /* would overflow size_t */ nbytes += value_len; } /* Write terminating '\0' */ if (limit > nbytes) *((char *)outp + nbytes) = '\0'; if (nbytes == SIZE_MAX) return (EFTYPE); /* would overflow size_t */ else nbytes++; /* Update header length; this must fit within the header's 32-bit size * field */ if (nbytes <= UINT32_MAX) { hdr.size = (uint32_t)nbytes; } else { BHND_NV_LOG("size %zu exceeds maximum supported size of %u " "bytes\n", nbytes, UINT32_MAX); return (EFTYPE); } /* Provide required length */ *olen = nbytes; if (limit < *olen) { if (outp == NULL) return (0); return (ENOMEM); } /* Calculate the CRC value */ BHND_NV_ASSERT(nbytes >= BCM_NVRAM_CRC_SKIP, ("invalid output size")); crc8 = bhnd_nvram_crc8((uint8_t *)outp + BCM_NVRAM_CRC_SKIP, nbytes - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL); /* Update CRC and write the finalized header */ BHND_NV_ASSERT(nbytes >= sizeof(hdr), ("invalid output size")); hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, crc8); memcpy(outp, &hdr, sizeof(hdr)); return (0); } /** * Initialize @p bcm with the provided NVRAM data mapped by @p src. * * @param bcm A newly allocated data instance. */ static int bhnd_nvram_bcm_init(struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_io *src) { struct bhnd_nvram_bcmhdr hdr; uint8_t *p; void *ptr; size_t io_offset, io_size; uint8_t crc, valid, bcm_ver; int error; if ((error = bhnd_nvram_io_read(src, 0x0, &hdr, sizeof(hdr)))) return (error); if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC) return (ENXIO); /* Fetch the actual NVRAM image size */ io_size = le32toh(hdr.size); if (io_size < sizeof(hdr)) { /* The header size must include the header itself */ BHND_NV_LOG("corrupt header size: %zu\n", io_size); return (EINVAL); } if (io_size > bhnd_nvram_io_getsize(src)) { BHND_NV_LOG("header size %zu exceeds input size %zu\n", io_size, bhnd_nvram_io_getsize(src)); return (EINVAL); } /* Allocate a buffer large enough to hold the NVRAM image, and * an extra EOF-signaling NUL (on the chance it's missing from the * source data) */ if (io_size == SIZE_MAX) return (ENOMEM); bcm->data = bhnd_nvram_iobuf_empty(io_size, io_size + 1); if (bcm->data == NULL) return (ENOMEM); /* Fetch a pointer into our backing buffer and copy in the * NVRAM image. */ error = bhnd_nvram_io_write_ptr(bcm->data, 0x0, &ptr, io_size, NULL); if (error) return (error); p = ptr; if ((error = bhnd_nvram_io_read(src, 0x0, p, io_size))) return (error); /* Verify the CRC */ valid = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC); crc = bhnd_nvram_crc8(p + BCM_NVRAM_CRC_SKIP, io_size - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL); if (crc != valid) { BHND_NV_LOG("warning: NVRAM CRC error (crc=%#hhx, " "expected=%hhx)\n", crc, valid); } /* Populate header variable definitions */ #define BCM_READ_HDR_VAR(_name, _dest, _swap) do { \ struct bhnd_nvram_bcm_hvar *data; \ data = bhnd_nvram_bcm_gethdrvar(bcm, _name ##_VAR); \ BHND_NV_ASSERT(data != NULL, \ ("no such header variable: " __STRING(_name))); \ \ \ data->value. _dest = _swap(BCM_NVRAM_GET_BITS( \ hdr. _name ## _FIELD, _name)); \ } while(0) BCM_READ_HDR_VAR(BCM_NVRAM_CFG0_SDRAM_INIT, u16, le16toh); BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_CFG, u16, le16toh); BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_REFRESH, u16, le16toh); BCM_READ_HDR_VAR(BCM_NVRAM_SDRAM_NCDL, u32, le32toh); _Static_assert(nitems(bcm->hvars) == 4, "missing initialization for" "NVRAM header variable(s)"); #undef BCM_READ_HDR_VAR /* Process the buffer */ bcm->count = 0; io_offset = sizeof(hdr); while (io_offset < io_size) { char *envp; const char *name, *value; size_t envp_len; size_t name_len, value_len; /* Parse the key=value string */ envp = (char *) (p + io_offset); envp_len = strnlen(envp, io_size - io_offset); error = bhnd_nvram_parse_env(envp, envp_len, '=', &name, &name_len, &value, &value_len); if (error) { BHND_NV_LOG("error parsing envp at offset %#zx: %d\n", io_offset, error); return (error); } /* Insert a '\0' character, replacing the '=' delimiter and * allowing us to vend references directly to the variable * name */ *(envp + name_len) = '\0'; /* Record any NVRAM variables that mirror our header variables. * This is a brute-force search -- for the amount of data we're * operating on, it shouldn't be an issue. */ for (size_t i = 0; i < nitems(bcm->hvars); i++) { struct bhnd_nvram_bcm_hvar *hvar; union bhnd_nvram_bcm_hvar_value hval; size_t hval_len; hvar = &bcm->hvars[i]; /* Already matched? */ if (hvar->envp != NULL) continue; /* Name matches? */ if ((strcmp(name, hvar->name)) != 0) continue; /* Save pointer to mirrored envp */ hvar->envp = envp; /* Check for stale value */ hval_len = sizeof(hval); error = bhnd_nvram_value_coerce(value, value_len, BHND_NVRAM_TYPE_STRING, &hval, &hval_len, hvar->type); if (error) { /* If parsing fails, we can likely only make * things worse by trying to synchronize the * variables */ BHND_NV_LOG("error parsing header variable " "'%s=%s': %d\n", name, value, error); } else if (hval_len != hvar->len) { hvar->stale = true; } else if (memcmp(&hval, &hvar->value, hval_len) != 0) { hvar->stale = true; } } /* Seek past the value's terminating '\0' */ io_offset += envp_len; if (io_offset == io_size) { BHND_NV_LOG("missing terminating NUL at offset %#zx\n", io_offset); return (EINVAL); } if (*(p + io_offset) != '\0') { BHND_NV_LOG("invalid terminator '%#hhx' at offset " "%#zx\n", *(p + io_offset), io_offset); return (EINVAL); } /* Update variable count */ bcm->count++; /* Seek to the next record */ if (++io_offset == io_size) { char ch; /* Hit EOF without finding a terminating NUL * byte; we need to grow our buffer and append * it */ io_size++; if ((error = bhnd_nvram_io_setsize(bcm->data, io_size))) return (error); /* Write NUL byte */ ch = '\0'; error = bhnd_nvram_io_write(bcm->data, io_size-1, &ch, sizeof(ch)); if (error) return (error); } /* Check for explicit EOF (encoded as a single empty NUL * terminated string) */ if (*(p + io_offset) == '\0') break; } /* Add non-mirrored header variables to total count variable */ for (size_t i = 0; i < nitems(bcm->hvars); i++) { if (bcm->hvars[i].envp == NULL) bcm->count++; } /* Populate serialization options from our header */ bcm_ver = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER); error = bhnd_nvram_plist_append_bytes(bcm->opts, BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver, sizeof(bcm_ver), BHND_NVRAM_TYPE_UINT8); if (error) return (error); return (0); } static int bhnd_nvram_bcm_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) { struct bhnd_nvram_bcm *bcm; int error; bcm = (struct bhnd_nvram_bcm *)nv; /* Populate default BCM mirrored header variable set */ _Static_assert(sizeof(bcm->hvars) == sizeof(bhnd_nvram_bcm_hvars), "hvar declarations must match bhnd_nvram_bcm_hvars template"); memcpy(bcm->hvars, bhnd_nvram_bcm_hvars, sizeof(bcm->hvars)); /* Allocate (empty) option list, to be populated by * bhnd_nvram_bcm_init() */ bcm->opts = bhnd_nvram_plist_new(); if (bcm->opts == NULL) return (ENOMEM); /* Parse the BCM input data and initialize our backing * data representation */ if ((error = bhnd_nvram_bcm_init(bcm, io))) { bhnd_nvram_bcm_free(nv); return (error); } return (0); } static void bhnd_nvram_bcm_free(struct bhnd_nvram_data *nv) { struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv; if (bcm->data != NULL) bhnd_nvram_io_free(bcm->data); if (bcm->opts != NULL) bhnd_nvram_plist_release(bcm->opts); } size_t bhnd_nvram_bcm_count(struct bhnd_nvram_data *nv) { struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv; return (bcm->count); } static bhnd_nvram_plist * bhnd_nvram_bcm_options(struct bhnd_nvram_data *nv) { struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv; return (bcm->opts); } static uint32_t bhnd_nvram_bcm_caps(struct bhnd_nvram_data *nv) { return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS); } static const char * bhnd_nvram_bcm_next(struct bhnd_nvram_data *nv, void **cookiep) { struct bhnd_nvram_bcm *bcm; struct bhnd_nvram_bcm_hvar *hvar, *hvar_next; const void *ptr; const char *envp, *basep; size_t io_size, io_offset; int error; bcm = (struct bhnd_nvram_bcm *)nv; io_offset = sizeof(struct bhnd_nvram_bcmhdr); io_size = bhnd_nvram_io_getsize(bcm->data) - io_offset; /* Map backing buffer */ error = bhnd_nvram_io_read_ptr(bcm->data, io_offset, &ptr, io_size, NULL); if (error) { BHND_NV_LOG("error mapping backing buffer: %d\n", error); return (NULL); } basep = ptr; /* If cookiep pointers into our header variable array, handle as header * variable iteration. */ hvar = bhnd_nvram_bcm_to_hdrvar(bcm, *cookiep); if (hvar != NULL) { size_t idx; /* Advance to next entry, if any */ idx = bhnd_nvram_bcm_hdrvar_index(bcm, hvar) + 1; /* Find the next header-defined variable that isn't defined in * the NVRAM data, start iteration there */ for (size_t i = idx; i < nitems(bcm->hvars); i++) { hvar_next = &bcm->hvars[i]; if (hvar_next->envp != NULL && !hvar_next->stale) continue; *cookiep = hvar_next; return (hvar_next->name); } /* No further header-defined variables; iteration * complete */ return (NULL); } /* Handle standard NVRAM data iteration */ if (*cookiep == NULL) { /* Start at the first NVRAM data record */ envp = basep; } else { /* Seek to next record */ envp = *cookiep; envp += strlen(envp) + 1; /* key + '\0' */ envp += strlen(envp) + 1; /* value + '\0' */ } /* * Skip entries that have an existing header variable entry that takes * precedence over the NVRAM data value. * * The header's value will be provided when performing header variable * iteration */ while ((size_t)(envp - basep) < io_size && *envp != '\0') { /* Locate corresponding header variable */ hvar = NULL; for (size_t i = 0; i < nitems(bcm->hvars); i++) { if (bcm->hvars[i].envp != envp) continue; hvar = &bcm->hvars[i]; break; } /* If no corresponding hvar entry, or the entry does not take * precedence over this NVRAM value, we can safely return this * value as-is. */ if (hvar == NULL || !hvar->stale) break; /* Seek to next record */ envp += strlen(envp) + 1; /* key + '\0' */ envp += strlen(envp) + 1; /* value + '\0' */ } /* On NVRAM data EOF, try switching to header variables */ if ((size_t)(envp - basep) == io_size || *envp == '\0') { /* Find first valid header variable */ for (size_t i = 0; i < nitems(bcm->hvars); i++) { if (bcm->hvars[i].envp != NULL) continue; *cookiep = &bcm->hvars[i]; return (bcm->hvars[i].name); } /* No header variables */ return (NULL); } *cookiep = __DECONST(void *, envp); return (envp); } static void * bhnd_nvram_bcm_find(struct bhnd_nvram_data *nv, const char *name) { return (bhnd_nvram_data_generic_find(nv, name)); } static int bhnd_nvram_bcm_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2) { struct bhnd_nvram_bcm *bcm; struct bhnd_nvram_bcm_hvar *hvar1, *hvar2; bcm = (struct bhnd_nvram_bcm *)nv; hvar1 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep1); hvar2 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep2); /* Header variables are always ordered below any variables defined * in the BCM data */ if (hvar1 != NULL && hvar2 == NULL) { return (1); /* hvar follows non-hvar */ } else if (hvar1 == NULL && hvar2 != NULL) { return (-1); /* non-hvar precedes hvar */ } /* Otherwise, both cookies are either hvars or non-hvars. We can * safely fall back on pointer order, which will provide a correct * ordering matching the behavior of bhnd_nvram_data_next() for * both cases */ if (cookiep1 < cookiep2) return (-1); if (cookiep1 > cookiep2) return (1); return (0); } static int bhnd_nvram_bcm_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf, size_t *len, bhnd_nvram_type type) { return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type)); } static int bhnd_nvram_bcm_copy_val(struct bhnd_nvram_data *nv, void *cookiep, bhnd_nvram_val **value) { return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value)); } static const void * bhnd_nvram_bcm_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep, size_t *len, bhnd_nvram_type *type) { struct bhnd_nvram_bcm *bcm; struct bhnd_nvram_bcm_hvar *hvar; const char *envp; bcm = (struct bhnd_nvram_bcm *)nv; /* Handle header variables */ if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) { BHND_NV_ASSERT(bhnd_nvram_value_check_aligned(&hvar->value, hvar->len, hvar->type) == 0, ("value misaligned")); *type = hvar->type; *len = hvar->len; return (&hvar->value); } /* Cookie points to key\0value\0 -- get the value address */ BHND_NV_ASSERT(cookiep != NULL, ("NULL cookiep")); envp = cookiep; envp += strlen(envp) + 1; /* key + '\0' */ *len = strlen(envp) + 1; /* value + '\0' */ *type = BHND_NVRAM_TYPE_STRING; return (envp); } static const char * bhnd_nvram_bcm_getvar_name(struct bhnd_nvram_data *nv, void *cookiep) { struct bhnd_nvram_bcm *bcm; struct bhnd_nvram_bcm_hvar *hvar; bcm = (struct bhnd_nvram_bcm *)nv; /* Handle header variables */ if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) { return (hvar->name); } /* Cookie points to key\0value\0 */ return (cookiep); } static int bhnd_nvram_bcm_filter_setvar(struct bhnd_nvram_data *nv, const char *name, bhnd_nvram_val *value, bhnd_nvram_val **result) { bhnd_nvram_val *str; int error; /* Name (trimmed of any path prefix) must be valid */ if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name))) return (EINVAL); /* Value must be bcm-formatted string */ error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt, value, BHND_NVRAM_VAL_DYNAMIC); if (error) return (error); /* Success. Transfer result ownership to the caller. */ *result = str; return (0); } static int bhnd_nvram_bcm_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { /* We permit deletion of any variable */ return (0); } /** * Return the internal BCM data reference for a header-defined variable * with @p name, or NULL if none exists. */ static struct bhnd_nvram_bcm_hvar * bhnd_nvram_bcm_gethdrvar(struct bhnd_nvram_bcm *bcm, const char *name) { for (size_t i = 0; i < nitems(bcm->hvars); i++) { if (strcmp(bcm->hvars[i].name, name) == 0) return (&bcm->hvars[i]); } /* Not found */ return (NULL); } /** * If @p cookiep references a header-defined variable, return the * internal BCM data reference. Otherwise, returns NULL. */ static struct bhnd_nvram_bcm_hvar * bhnd_nvram_bcm_to_hdrvar(struct bhnd_nvram_bcm *bcm, void *cookiep) { #ifdef BHND_NVRAM_INVARIANTS uintptr_t base, ptr; #endif /* If the cookie falls within the hvar array, it's a * header variable cookie */ if (nitems(bcm->hvars) == 0) return (NULL); if (cookiep < (void *)&bcm->hvars[0]) return (NULL); if (cookiep > (void *)&bcm->hvars[nitems(bcm->hvars)-1]) return (NULL); #ifdef BHND_NVRAM_INVARIANTS base = (uintptr_t)bcm->hvars; ptr = (uintptr_t)cookiep; BHND_NV_ASSERT((ptr - base) % sizeof(bcm->hvars[0]) == 0, ("misaligned hvar pointer %p/%p", cookiep, bcm->hvars)); #endif /* INVARIANTS */ return ((struct bhnd_nvram_bcm_hvar *)cookiep); } /** * Return the index of @p hdrvar within @p bcm's backing hvars array. */ static size_t bhnd_nvram_bcm_hdrvar_index(struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_bcm_hvar *hdrvar) { BHND_NV_ASSERT(bhnd_nvram_bcm_to_hdrvar(bcm, (void *)hdrvar) != NULL, ("%p is not a valid hdrvar reference", hdrvar)); return (hdrvar - &bcm->hvars[0]); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c (revision 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c (revision 315866) @@ -1,477 +1,486 @@ /*- * 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" +#include "bhnd_nvram_data_bcmvar.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)", BHND_NVRAM_DATA_CAP_DEVPATHS, 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 initial bytes */ envp_len = bhnd_nv_ummin(sizeof(envp), io_size); if ((error = bhnd_nvram_io_read(io, 0x0, envp, envp_len))) return (error); /* 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); } /* 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 we hit a newline, this is probably BCM-TXT */ if (c == '\n') return (ENXIO); if (c == '\0' && !bhnd_nv_isprint(c)) continue; } /* 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); 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); +} + +static int +bhnd_nvram_bcmraw_getvar_direct(struct bhnd_nvram_io *io, const char *name, + void *buf, size_t *len, bhnd_nvram_type type) +{ + return (bhnd_nvram_bcm_getvar_direct_common(io, name, buf, len, type, + false)); } static int bhnd_nvram_bcmraw_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, size_t *olen) { bhnd_nvram_prop *prop; size_t limit, nbytes; int error; /* Determine output byte limit */ if (outp != NULL) limit = *olen; else limit = 0; nbytes = 0; /* Write all properties */ prop = NULL; while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { const char *name; char *p; size_t prop_limit; size_t name_len, value_len; if (outp == NULL || limit < nbytes) { p = NULL; prop_limit = 0; } else { p = ((char *)outp) + nbytes; prop_limit = limit - nbytes; } /* Fetch and write name + '=' to output */ name = bhnd_nvram_prop_name(prop); name_len = strlen(name) + 1; if (prop_limit > name_len) { memcpy(p, name, name_len - 1); p[name_len - 1] = '='; prop_limit -= name_len; p += name_len; } else { prop_limit = 0; p = NULL; } /* Advance byte count */ if (SIZE_MAX - nbytes < name_len) return (EFTYPE); /* would overflow size_t */ nbytes += name_len; /* Attempt to write NUL-terminated value to output */ value_len = prop_limit; error = bhnd_nvram_prop_encode(prop, p, &value_len, BHND_NVRAM_TYPE_STRING); /* If encoding failed for any reason other than ENOMEM (which * we'll detect and report after encoding all properties), * return immediately */ if (error && error != ENOMEM) { BHND_NV_LOG("error serializing %s to required type " "%s: %d\n", name, bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), error); return (error); } /* Advance byte count */ if (SIZE_MAX - nbytes < value_len) return (EFTYPE); /* would overflow size_t */ nbytes += value_len; } /* Write terminating '\0' */ if (limit > nbytes) *((char *)outp + nbytes) = '\0'; if (nbytes == SIZE_MAX) return (EFTYPE); /* would overflow size_t */ else nbytes++; /* Provide required length */ *olen = nbytes; if (limit < *olen) { if (outp == NULL) return (0); return (ENOMEM); } return (0); } /** * Initialize @p bcm with the provided NVRAM data mapped by @p src. * * @param bcm A newly allocated data instance. */ static int bhnd_nvram_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 bhnd_nvram_plist * bhnd_nvram_bcmraw_options(struct bhnd_nvram_data *nv) { return (NULL); } 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 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 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmvar.h (revision 315866) @@ -1,72 +1,76 @@ /*- * 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; +int bhnd_nvram_bcm_getvar_direct_common(struct bhnd_nvram_io *io, + const char *name, void *outp, size_t *olen, bhnd_nvram_type otype, + bool have_header); + #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 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c (revision 315866) @@ -1,745 +1,965 @@ /*- * 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", BHND_NVRAM_DATA_CAP_DEVPATHS, 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); } + +/** + * Parser states for bhnd_nvram_bcm_getvar_direct_common(). + */ +typedef enum { + BTXT_PARSE_LINE_START, + BTXT_PARSE_KEY, + BTXT_PARSE_KEY_END, + BTXT_PARSE_NEXT_LINE, + BTXT_PARSE_VALUE_START, + BTXT_PARSE_VALUE +} btxt_parse_state; + +static int +bhnd_nvram_btxt_getvar_direct(struct bhnd_nvram_io *io, const char *name, + void *outp, size_t *olen, bhnd_nvram_type otype) +{ + char buf[512]; + btxt_parse_state pstate; + size_t limit, offset; + size_t buflen, bufpos; + size_t namelen, namepos; + size_t vlen; + int error; + + limit = bhnd_nvram_io_getsize(io); + offset = 0; + + /* Loop our parser until we find the requested variable, or hit EOF */ + pstate = BTXT_PARSE_LINE_START; + buflen = 0; + bufpos = 0; + namelen = strlen(name); + namepos = 0; + vlen = 0; + + while ((offset - bufpos) < limit) { + BHND_NV_ASSERT(bufpos <= buflen, + ("buf position invalid (%zu > %zu)", bufpos, buflen)); + BHND_NV_ASSERT(buflen <= sizeof(buf), + ("buf length invalid (%zu > %zu", buflen, sizeof(buf))); + + /* Repopulate our parse buffer? */ + if (buflen - bufpos == 0) { + BHND_NV_ASSERT(offset < limit, ("offset overrun")); + + buflen = bhnd_nv_ummin(sizeof(buf), limit - offset); + bufpos = 0; + + error = bhnd_nvram_io_read(io, offset, buf, buflen); + if (error) + return (error); + + offset += buflen; + } + + switch (pstate) { + case BTXT_PARSE_LINE_START: + BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!")); + + /* Reset name matching position */ + namepos = 0; + + /* Trim any leading whitespace */ + while (bufpos < buflen && bhnd_nv_isspace(buf[bufpos])) + { + bufpos++; + } + + if (bufpos == buflen) { + /* Continue parsing the line */ + pstate = BTXT_PARSE_LINE_START; + } else if (bufpos < buflen && buf[bufpos] == '#') { + /* Comment; skip to next line */ + pstate = BTXT_PARSE_NEXT_LINE; + } else { + /* Start name matching */ + pstate = BTXT_PARSE_KEY; + } + + + break; + + case BTXT_PARSE_KEY: { + size_t navail, nleft; + + nleft = namelen - namepos; + navail = bhnd_nv_ummin(buflen - bufpos, nleft); + + if (strncmp(name+namepos, buf+bufpos, navail) == 0) { + /* Matched */ + namepos += navail; + bufpos += navail; + + if (namepos == namelen) { + /* Matched the full variable; look for + * its trailing delimiter */ + pstate = BTXT_PARSE_KEY_END; + } else { + /* Continue matching the name */ + pstate = BTXT_PARSE_KEY; + } + } else { + /* No match; advance to next entry and restart + * name matching */ + pstate = BTXT_PARSE_NEXT_LINE; + } + + break; + } + + case BTXT_PARSE_KEY_END: + BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!")); + + if (buf[bufpos] == '=') { + /* Key fully matched; advance past '=' and + * parse the value */ + bufpos++; + pstate = BTXT_PARSE_VALUE_START; + } else { + /* No match; advance to next line and restart + * name matching */ + pstate = BTXT_PARSE_NEXT_LINE; + } + + break; + + case BTXT_PARSE_NEXT_LINE: { + const char *p; + + /* Scan for a '\r', '\n', or '\r\n' terminator */ + p = memchr(buf+bufpos, '\n', buflen - bufpos); + if (p == NULL) + p = memchr(buf+bufpos, '\r', buflen - bufpos); + + if (p != NULL) { + /* Found entry terminator; restart name + * matching at next line */ + pstate = BTXT_PARSE_LINE_START; + bufpos = (p - buf); + } else { + /* Consumed full buffer looking for newline; + * force repopulation of the buffer and + * retry */ + pstate = BTXT_PARSE_NEXT_LINE; + bufpos = buflen; + } + + break; + } + + case BTXT_PARSE_VALUE_START: { + const char *p; + + /* Scan for a terminating newline */ + p = memchr(buf+bufpos, '\n', buflen - bufpos); + if (p == NULL) + p = memchr(buf+bufpos, '\r', buflen - bufpos); + + if (p != NULL) { + /* Found entry terminator; parse the value */ + vlen = p - &buf[bufpos]; + pstate = BTXT_PARSE_VALUE; + + } else if (p == NULL && offset == limit) { + /* Hit EOF without a terminating newline; + * treat the entry as implicitly terminated */ + vlen = buflen - bufpos; + pstate = BTXT_PARSE_VALUE; + + } else if (p == NULL && bufpos > 0) { + size_t nread; + + /* Move existing value data to start of + * buffer */ + memmove(buf, buf+bufpos, buflen - bufpos); + buflen = bufpos; + bufpos = 0; + + /* Populate full buffer to allow retry of + * value parsing */ + nread = bhnd_nv_ummin(sizeof(buf) - buflen, + limit - offset); + + error = bhnd_nvram_io_read(io, offset, + buf+buflen, nread); + if (error) + return (error); + + offset += nread; + buflen += nread; + } else { + /* Value exceeds our buffer capacity */ + BHND_NV_LOG("cannot parse value for '%s' " + "(exceeds %zu byte limit)\n", name, + sizeof(buf)); + + return (ENXIO); + } + + break; + } + + case BTXT_PARSE_VALUE: + BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun")); + + /* Trim any trailing whitespace */ + while (vlen > 0 && bhnd_nv_isspace(buf[bufpos+vlen-1])) + vlen--; + + /* Write the value to the caller's buffer */ + return (bhnd_nvram_value_coerce(buf+bufpos, vlen, + BHND_NVRAM_TYPE_STRING, outp, olen, otype)); + } + } + + /* Variable not found */ + return (ENOENT); +} + static int bhnd_nvram_btxt_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, size_t *olen) { bhnd_nvram_prop *prop; size_t limit, nbytes; int error; /* Determine output byte limit */ if (outp != NULL) limit = *olen; else limit = 0; nbytes = 0; /* Write all properties */ prop = NULL; while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { const char *name; char *p; size_t prop_limit; size_t name_len, value_len; if (outp == NULL || limit < nbytes) { p = NULL; prop_limit = 0; } else { p = ((char *)outp) + nbytes; prop_limit = limit - nbytes; } /* Fetch and write 'name=' to output */ name = bhnd_nvram_prop_name(prop); name_len = strlen(name) + 1; if (prop_limit > name_len) { memcpy(p, name, name_len - 1); p[name_len - 1] = '='; prop_limit -= name_len; p += name_len; } else { prop_limit = 0; p = NULL; } /* Advance byte count */ if (SIZE_MAX - nbytes < name_len) return (EFTYPE); /* would overflow size_t */ nbytes += name_len; /* Write NUL-terminated value to output, rewrite NUL as * '\n' record delimiter */ value_len = prop_limit; error = bhnd_nvram_prop_encode(prop, p, &value_len, BHND_NVRAM_TYPE_STRING); if (p != NULL && error == 0) { /* Replace trailing '\0' with newline */ BHND_NV_ASSERT(value_len > 0, ("string length missing " "minimum required trailing NUL")); *(p + (value_len - 1)) = '\n'; } else if (error && error != ENOMEM) { /* If encoding failed for any reason other than ENOMEM * (which we'll detect and report after encoding all * properties), return immediately */ BHND_NV_LOG("error serializing %s to required type " "%s: %d\n", name, bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), error); return (error); } /* Advance byte count */ if (SIZE_MAX - nbytes < value_len) return (EFTYPE); /* would overflow size_t */ nbytes += value_len; } /* Provide required length */ *olen = nbytes; if (limit < *olen) { if (outp == NULL) return (0); return (ENOMEM); } return (0); } /** * 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 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 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom.c (revision 315866) @@ -1,1381 +1,1464 @@ /*- * 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_map.h" #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. */ static const bhnd_sprom_layout *bhnd_nvram_sprom_get_layout(uint8_t sromrev); static int bhnd_nvram_sprom_ident( struct bhnd_nvram_io *io, - const bhnd_sprom_layout **ident, - struct bhnd_nvram_io **shadow); + const bhnd_sprom_layout **ident); static int bhnd_nvram_sprom_write_var( bhnd_sprom_opcode_state *state, bhnd_sprom_opcode_idx_entry *entry, bhnd_nvram_val *value, struct bhnd_nvram_io *io); +static int bhnd_nvram_sprom_read_var( + struct bhnd_sprom_opcode_state *state, + struct bhnd_sprom_opcode_idx_entry *entry, + struct bhnd_nvram_io *io, + union bhnd_nvram_sprom_storage *storage, + bhnd_nvram_val *val); + static int bhnd_nvram_sprom_write_offset( const struct bhnd_nvram_vardefn *var, struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset, uint32_t mask, int8_t shift, uint32_t value); static int bhnd_nvram_sprom_read_offset( const struct bhnd_nvram_vardefn *var, struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset, uint32_t mask, int8_t shift, uint32_t *value); static bool bhnd_sprom_is_external_immutable( const char *name); BHND_NVRAM_DATA_CLASS_DEFN(sprom, "Broadcom SPROM", BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_sprom)) #define SPROM_COOKIE_TO_VID(_cookie) \ (((struct bhnd_sprom_opcode_idx_entry *)(_cookie))->vid) #define SPROM_COOKIE_TO_NVRAM_VAR(_cookie) \ bhnd_nvram_get_vardefn(SPROM_COOKIE_TO_VID(_cookie)) /** * 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 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 bhnd_sprom_layout **ident, struct bhnd_nvram_io **shadow) + const bhnd_sprom_layout **ident) { - struct bhnd_nvram_io *buf; - uint8_t crc; - size_t crc_errors; - size_t sprom_sz_max; - int error; + uint8_t crc; + size_t crc_errors; + size_t nbytes; + 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; + nbytes = 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 bhnd_sprom_layout *layout; - void *ptr; - size_t nbytes, nr; + u_char buf[512]; + size_t nread; 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 = true; + 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 */ + /* + * Read image data and update CRC (errors are reported + * after the signature check) + * + * Layout instances must be ordered from smallest to largest by + * the nvram_map compiler, allowing us to incrementally update + * our CRC. + */ if (nbytes > layout->size) - BHND_NV_PANIC("SPROM layout is defined out-of-order"); + BHND_NV_PANIC("SPROM layout defined out-of-order"); - /* Calculate number of additional bytes to be read */ - nr = layout->size - nbytes; + nread = layout->size - nbytes; - /* Adjust the buffer size and fetch a write pointer */ - if ((error = bhnd_nvram_io_setsize(buf, layout->size))) - goto failed; + while (nread > 0) { + size_t nr; - error = bhnd_nvram_io_write_ptr(buf, nbytes, &ptr, nr, NULL); - if (error) - goto failed; + nr = bhnd_nv_ummin(nread, sizeof(buf)); - /* 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; + if ((error = bhnd_nvram_io_read(io, nbytes, buf, nr))) + return (error); - crc = bhnd_nvram_crc8(ptr, nr, crc); - crc_valid = (crc == BHND_NVRAM_CRC8_VALID); - if (!crc_valid) - crc_errors++; + crc = bhnd_nvram_crc8(buf, 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, + nread -= nr; + nbytes += nr; + } + + /* Read SPROM revision */ + error = bhnd_nvram_io_read(io, layout->srev_offset, &srev, sizeof(srev)); if (error) - goto failed; + return (error); /* 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); + error = bhnd_nvram_sprom_check_magic(io, 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; + return (ENXIO); } 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; + /* No match */ 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); + return (ENXIO); } static int bhnd_nvram_sprom_probe(struct bhnd_nvram_io *io) { const 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))) + if ((error = bhnd_nvram_sprom_ident(io, &layout))) return (error); - /* Clean up the shadow iobuf */ - bhnd_nvram_io_free(shadow); - return (BHND_NVRAM_DATA_PROBE_DEFAULT); } +static int +bhnd_nvram_sprom_getvar_direct(struct bhnd_nvram_io *io, const char *name, + void *buf, size_t *len, bhnd_nvram_type type) +{ + const bhnd_sprom_layout *layout; + bhnd_sprom_opcode_state state; + const struct bhnd_nvram_vardefn *var; + size_t vid; + int error; + /* Look up the variable definition and ID */ + if ((var = bhnd_nvram_find_vardefn(name)) == NULL) + return (ENOENT); + + vid = bhnd_nvram_get_vardefn_id(var); + + /* Identify the SPROM image layout */ + if ((error = bhnd_nvram_sprom_ident(io, &layout))) + return (error); + + /* Initialize SPROM layout interpreter */ + if ((error = bhnd_sprom_opcode_init(&state, layout))) { + BHND_NV_LOG("error initializing opcode state: %d\n", error); + return (ENXIO); + } + + /* Find SPROM layout entry for the requested variable */ + while ((error = bhnd_sprom_opcode_next_var(&state)) == 0) { + bhnd_sprom_opcode_idx_entry entry; + union bhnd_nvram_sprom_storage storage; + bhnd_nvram_val val; + + /* Fetch the variable's entry state */ + if ((error = bhnd_sprom_opcode_init_entry(&state, &entry))) + return (error); + + /* Match against expected VID */ + if (entry.vid != vid) + continue; + + /* Decode variable to a new value instance */ + error = bhnd_nvram_sprom_read_var(&state, &entry, io, &storage, + &val); + if (error) + return (error); + + /* Perform value coercion */ + error = bhnd_nvram_val_encode(&val, buf, len, type); + + /* Clean up */ + bhnd_nvram_val_release(&val); + return (error); + } + + /* Hit EOF without matching the requested variable? */ + if (error == ENOENT) + return (ENOENT); + + /* Some other parse error occured */ + return (error); +} + /** * Return the SPROM layout definition for the given @p sromrev, or NULL if * not found. */ static const bhnd_sprom_layout * bhnd_nvram_sprom_get_layout(uint8_t sromrev) { /* Find matching SPROM layout definition */ for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) { if (bhnd_sprom_layouts[i].rev == sromrev) return (&bhnd_sprom_layouts[i]); } /* Not found */ return (NULL); } /** * Serialize a SPROM variable. * * @param state The SPROM opcode state describing the layout of @p io. * @param entry The variable's SPROM opcode index entry. * @param value The value to encode to @p io as per @p entry. * @param io I/O context to which @p value should be written, or NULL * if no output should be produced. This may be used to validate * values prior to write. * * @retval 0 success * @retval EFTYPE If value coercion from @p value to the type required by * @p entry is unsupported. * @retval ERANGE If value coercion from @p value would overflow * (or underflow) the type required by @p entry. * @retval non-zero If serialization otherwise fails, a regular unix error * code will be returned. */ static int bhnd_nvram_sprom_write_var(bhnd_sprom_opcode_state *state, bhnd_sprom_opcode_idx_entry *entry, bhnd_nvram_val *value, struct bhnd_nvram_io *io) { const struct bhnd_nvram_vardefn *var; uint32_t u32[BHND_SPROM_ARRAY_MAXLEN]; bhnd_nvram_type itype, var_base_type; size_t ipos, ilen, nelem; int error; /* Fetch variable definition and the native element type */ var = bhnd_nvram_get_vardefn(entry->vid); BHND_NV_ASSERT(var != NULL, ("missing variable definition")); var_base_type = bhnd_nvram_base_type(var->type); /* Fetch the element count from the SPROM variable layout definition */ - if ((error = bhnd_sprom_opcode_parse_var(state, entry))) + if ((error = bhnd_sprom_opcode_eval_var(state, entry))) return (error); nelem = state->var.nelem; BHND_NV_ASSERT(nelem <= var->nelem, ("SPROM nelem=%zu exceeds maximum " "NVRAM nelem=%hhu", nelem, var->nelem)); /* Promote the data to a common 32-bit representation */ if (bhnd_nvram_is_signed_type(var_base_type)) itype = BHND_NVRAM_TYPE_INT32_ARRAY; else itype = BHND_NVRAM_TYPE_UINT32_ARRAY; /* Calculate total size of the 32-bit promoted representation */ if ((ilen = bhnd_nvram_value_size(NULL, 0, itype, nelem)) == 0) { /* Variable-width types are unsupported */ BHND_NV_LOG("invalid %s SPROM variable type %d\n", var->name, var->type); return (EFTYPE); } /* The native representation must fit within our scratch array */ if (ilen > sizeof(u32)) { BHND_NV_LOG("error encoding '%s', SPROM_ARRAY_MAXLEN " "incorrect\n", var->name); return (EFTYPE); } /* Initialize our common 32-bit value representation */ if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL) { /* No value provided; can this variable be encoded as missing * by setting all bits to one? */ if (!(var->flags & BHND_NVRAM_VF_IGNALL1)) { BHND_NV_LOG("missing required property: %s\n", var->name); return (EINVAL); } /* Set all bits */ memset(u32, 0xFF, ilen); } else { bhnd_nvram_val bcm_val; const void *var_ptr; bhnd_nvram_type var_type, raw_type; size_t var_len, enc_nelem; /* Try to coerce the value to the native variable format. */ error = bhnd_nvram_val_convert_init(&bcm_val, var->fmt, value, BHND_NVRAM_VAL_DYNAMIC|BHND_NVRAM_VAL_BORROW_DATA); if (error) { BHND_NV_LOG("error converting input type %s to %s " "format\n", bhnd_nvram_type_name(bhnd_nvram_val_type(value)), bhnd_nvram_val_fmt_name(var->fmt)); return (error); } var_ptr = bhnd_nvram_val_bytes(&bcm_val, &var_len, &var_type); /* * Promote to a common 32-bit representation. * * We must use the raw type to interpret the input data as its * underlying integer representation -- otherwise, coercion * would attempt to parse the input as its complex * representation. * * For example, direct CHAR -> UINT32 coercion would attempt to * parse the character as a decimal integer, rather than * promoting the raw UTF8 byte value to a 32-bit value. */ raw_type = bhnd_nvram_raw_type(var_type); error = bhnd_nvram_value_coerce(var_ptr, var_len, raw_type, u32, &ilen, itype); /* Clean up temporary value representation */ bhnd_nvram_val_release(&bcm_val); /* Report coercion failure */ if (error) { BHND_NV_LOG("error promoting %s to %s: %d\n", bhnd_nvram_type_name(var_type), bhnd_nvram_type_name(itype), error); return (error); } /* Encoded element count must match SPROM's definition */ error = bhnd_nvram_value_nelem(u32, ilen, itype, &enc_nelem); if (error) return (error); if (enc_nelem != nelem) { const char *type_name; type_name = bhnd_nvram_type_name(var_base_type); BHND_NV_LOG("invalid %s property value '%s[%zu]': " "required %s[%zu]", var->name, type_name, enc_nelem, type_name, nelem); return (EFTYPE); } } /* * Seek to the start of the variable's SPROM layout definition and * iterate over all bindings. */ if ((error = bhnd_sprom_opcode_seek(state, entry))) { BHND_NV_LOG("variable seek failed: %d\n", error); return (error); } ipos = 0; while ((error = bhnd_sprom_opcode_next_binding(state)) == 0) { bhnd_sprom_opcode_bind *binding; bhnd_sprom_opcode_var *binding_var; size_t offset; uint32_t skip_out_bytes; BHND_NV_ASSERT( state->var_state >= SPROM_OPCODE_VAR_STATE_OPEN, ("invalid var state")); BHND_NV_ASSERT(state->var.have_bind, ("invalid bind state")); binding_var = &state->var; binding = &state->var.bind; /* Calculate output skip bytes for this binding. * * Skip directions are defined in terms of decoding, and * reversed when encoding. */ skip_out_bytes = binding->skip_in; error = bhnd_sprom_opcode_apply_scale(state, &skip_out_bytes); if (error) return (error); /* Bind */ offset = state->offset; for (size_t i = 0; i < binding->count; i++) { if (ipos >= nelem) { BHND_NV_LOG("input skip %u positioned %zu " "beyond nelem %zu\n", binding->skip_out, ipos, nelem); return (EINVAL); } /* Write next offset */ if (io != NULL) { error = bhnd_nvram_sprom_write_offset(var, io, binding_var->base_type, offset, binding_var->mask, binding_var->shift, u32[ipos]); if (error) return (error); } /* Adjust output position; this was already verified to * not overflow/underflow during SPROM opcode * evaluation */ if (binding->skip_in_negative) { offset -= skip_out_bytes; } else { offset += skip_out_bytes; } /* Skip advancing input if additional bindings are * required to fully encode intv */ if (binding->skip_out == 0) continue; /* Advance input 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); return (0); } static int bhnd_nvram_sprom_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, size_t *olen) { bhnd_sprom_opcode_state state; struct bhnd_nvram_io *io; bhnd_nvram_prop *prop; bhnd_sprom_opcode_idx_entry *entry; const bhnd_sprom_layout *layout; size_t limit; uint8_t crc; uint8_t sromrev; int error; limit = *olen; layout = NULL; io = NULL; /* Fetch sromrev property */ if (!bhnd_nvram_plist_contains(props, BHND_NVAR_SROMREV)) { BHND_NV_LOG("missing required property: %s\n", BHND_NVAR_SROMREV); return (EINVAL); } error = bhnd_nvram_plist_get_uint8(props, BHND_NVAR_SROMREV, &sromrev); if (error) { BHND_NV_LOG("error reading sromrev property: %d\n", error); return (EFTYPE); } /* Find SPROM layout definition */ if ((layout = bhnd_nvram_sprom_get_layout(sromrev)) == NULL) { BHND_NV_LOG("unsupported sromrev: %hhu\n", sromrev); return (EFTYPE); } /* Provide required size to caller */ *olen = layout->size; if (outp == NULL) return (0); else if (limit < *olen) return (ENOMEM); /* Initialize SPROM layout interpreter */ if ((error = bhnd_sprom_opcode_init(&state, layout))) { BHND_NV_LOG("error initializing opcode state: %d\n", error); return (ENXIO); } /* Check for unsupported properties */ prop = NULL; while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { const char *name; /* Fetch the corresponding SPROM layout index entry */ name = bhnd_nvram_prop_name(prop); entry = bhnd_sprom_opcode_index_find(&state, name); if (entry == NULL) { BHND_NV_LOG("property '%s' unsupported by sromrev " "%hhu\n", name, layout->rev); error = EINVAL; goto finished; } } /* Zero-initialize output */ memset(outp, 0, *olen); /* Allocate wrapping I/O context for output buffer */ io = bhnd_nvram_ioptr_new(outp, *olen, *olen, BHND_NVRAM_IOPTR_RDWR); if (io == NULL) { error = ENOMEM; goto finished; } /* * Serialize all SPROM variable data. */ entry = NULL; while ((entry = bhnd_sprom_opcode_index_next(&state, entry)) != NULL) { const struct bhnd_nvram_vardefn *var; bhnd_nvram_val *val; var = bhnd_nvram_get_vardefn(entry->vid); BHND_NV_ASSERT(var != NULL, ("missing variable definition")); /* Fetch prop; will be NULL if unavailable */ prop = bhnd_nvram_plist_get_prop(props, var->name); if (prop != NULL) { val = bhnd_nvram_prop_val(prop); } else { val = BHND_NVRAM_VAL_NULL; } /* Attempt to serialize the property value to the appropriate * offset within the output buffer */ error = bhnd_nvram_sprom_write_var(&state, entry, val, io); if (error) { BHND_NV_LOG("error serializing %s to required type " "%s: %d\n", var->name, bhnd_nvram_type_name(var->type), error); /* ENOMEM is reserved for signaling that the output * buffer capacity is insufficient */ if (error == ENOMEM) error = EINVAL; goto finished; } } /* * Write magic value, if any. */ if (!(layout->flags & SPROM_LAYOUT_MAGIC_NONE)) { uint16_t magic; magic = htole16(layout->magic_value); error = bhnd_nvram_io_write(io, layout->magic_offset, &magic, sizeof(magic)); if (error) { BHND_NV_LOG("error writing magic value: %d\n", error); goto finished; } } /* Calculate the CRC over all SPROM data, not including the CRC byte. */ crc = ~bhnd_nvram_crc8(outp, layout->crc_offset, BHND_NVRAM_CRC8_INITIAL); /* Write the checksum. */ error = bhnd_nvram_io_write(io, layout->crc_offset, &crc, sizeof(crc)); if (error) { BHND_NV_LOG("error writing CRC value: %d\n", error); goto finished; } /* * Success! */ error = 0; finished: bhnd_sprom_opcode_fini(&state); if (io != NULL) bhnd_nvram_io_free(io); return (error); } static int bhnd_nvram_sprom_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) { struct bhnd_nvram_sprom *sp; int error; sp = (struct bhnd_nvram_sprom *)nv; /* Identify the SPROM input data */ - if ((error = bhnd_nvram_sprom_ident(io, &sp->layout, &sp->data))) + if ((error = bhnd_nvram_sprom_ident(io, &sp->layout))) + return (error); + + /* Copy SPROM image to our shadow buffer */ + sp->data = bhnd_nvram_iobuf_copy_range(io, 0, sp->layout->size); + if (sp->data == NULL) goto failed; /* Initialize SPROM binding eval state */ if ((error = bhnd_sprom_opcode_init(&sp->state, sp->layout))) goto failed; return (0); failed: if (sp->data != NULL) bhnd_nvram_io_free(sp->data); return (error); } static void bhnd_nvram_sprom_free(struct bhnd_nvram_data *nv) { struct bhnd_nvram_sprom *sp = (struct bhnd_nvram_sprom *)nv; bhnd_sprom_opcode_fini(&sp->state); bhnd_nvram_io_free(sp->data); } 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 bhnd_nvram_plist * bhnd_nvram_sprom_options(struct bhnd_nvram_data *nv) { return (NULL); } 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; bhnd_sprom_opcode_idx_entry *entry; const struct bhnd_nvram_vardefn *var; sp = (struct bhnd_nvram_sprom *)nv; /* Find next index entry that is not disabled by virtue of IGNALL1 */ entry = *cookiep; while ((entry = bhnd_sprom_opcode_index_next(&sp->state, entry))) { /* Update cookiep and fetch variable definition */ *cookiep = entry; var = SPROM_COOKIE_TO_NVRAM_VAR(*cookiep); /* 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); } static void * bhnd_nvram_sprom_find(struct bhnd_nvram_data *nv, const char *name) { struct bhnd_nvram_sprom *sp; bhnd_sprom_opcode_idx_entry *entry; sp = (struct bhnd_nvram_sprom *)nv; entry = bhnd_sprom_opcode_index_find(&sp->state, name); return (entry); } /** * Write @p value of @p type to the SPROM @p data at @p offset, applying * @p mask and @p shift, and OR with the existing data. * * @param var The NVRAM variable definition. * @param data The SPROM data to be modified. * @param type The type to write at @p offset. * @param offset The data offset to be written. * @param mask The mask to be applied to @p value after shifting. * @param shift The shift to be applied to @p value; if positive, a left * shift will be applied, if negative, a right shift (this is the reverse of the * decoding behavior) * @param value The value to be written. The parsed value will be OR'd with the * current contents of @p data at @p offset. */ static int bhnd_nvram_sprom_write_offset(const struct bhnd_nvram_vardefn *var, struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset, uint32_t mask, int8_t shift, uint32_t value) { union bhnd_nvram_sprom_storage scratch; int error; #define NV_WRITE_INT(_widen, _repr, _swap) do { \ /* Narrow the 32-bit representation */ \ scratch._repr[1] = (_widen)value; \ \ /* Shift and mask the new value */ \ if (shift > 0) \ scratch._repr[1] <<= shift; \ else if (shift < 0) \ scratch._repr[1] >>= -shift; \ scratch._repr[1] &= mask; \ \ /* Swap to output byte order */ \ scratch._repr[1] = _swap(scratch._repr[1]); \ \ /* Fetch the current value */ \ error = bhnd_nvram_io_read(data, offset, \ &scratch._repr[0], sizeof(scratch._repr[0])); \ if (error) { \ BHND_NV_LOG("error reading %s SPROM offset " \ "%#zx: %d\n", var->name, offset, error); \ return (EFTYPE); \ } \ \ /* Mask and set our new value's bits in the current \ * value */ \ if (shift >= 0) \ scratch._repr[0] &= ~_swap(mask << shift); \ else if (shift < 0) \ scratch._repr[0] &= ~_swap(mask >> (-shift)); \ scratch._repr[0] |= scratch._repr[1]; \ \ /* Perform write */ \ error = bhnd_nvram_io_write(data, offset, \ &scratch._repr[0], sizeof(scratch._repr[0])); \ if (error) { \ BHND_NV_LOG("error writing %s SPROM offset " \ "%#zx: %d\n", var->name, offset, error); \ return (EFTYPE); \ } \ } while(0) /* Apply mask/shift and widen to a common 32bit representation */ switch (type) { case BHND_NVRAM_TYPE_UINT8: NV_WRITE_INT(uint32_t, u8, ); break; case BHND_NVRAM_TYPE_UINT16: NV_WRITE_INT(uint32_t, u16, htole16); break; case BHND_NVRAM_TYPE_UINT32: NV_WRITE_INT(uint32_t, u32, htole32); break; case BHND_NVRAM_TYPE_INT8: NV_WRITE_INT(int32_t, i8, ); break; case BHND_NVRAM_TYPE_INT16: NV_WRITE_INT(int32_t, i16, htole16); break; case BHND_NVRAM_TYPE_INT32: NV_WRITE_INT(int32_t, i32, htole32); break; case BHND_NVRAM_TYPE_CHAR: NV_WRITE_INT(uint32_t, u8, ); break; default: BHND_NV_LOG("unhandled %s offset type: %d\n", var->name, type); return (EFTYPE); } #undef NV_WRITE_INT return (0); } /** * Read the value of @p type from the SPROM @p data at @p offset, apply @p mask * and @p shift, and OR with the existing @p value. * * @param var The NVRAM variable definition. * @param data The SPROM data to be decoded. * @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(const struct bhnd_nvram_vardefn *var, struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset, uint32_t mask, int8_t shift, uint32_t *value) { union bhnd_nvram_sprom_storage scratch; int error; #define NV_PARSE_INT(_widen, _repr, _swap) do { \ /* Perform read */ \ error = bhnd_nvram_io_read(data, offset, \ &scratch._repr[0], sizeof(scratch._repr[0])); \ if (error) { \ BHND_NV_LOG("error reading %s SPROM offset " \ "%#zx: %d\n", var->name, offset, error); \ return (EFTYPE); \ } \ \ /* Swap to host byte order */ \ scratch._repr[0] = _swap(scratch._repr[0]); \ \ /* Mask and shift the value */ \ scratch._repr[0] &= mask; \ if (shift > 0) { \ scratch. _repr[0] >>= shift; \ } else if (shift < 0) { \ scratch. _repr[0] <<= -shift; \ } \ \ /* Widen to 32-bit representation and OR with current \ * value */ \ (*value) |= (_widen)scratch._repr[0]; \ } while(0) /* Apply mask/shift and widen to a common 32bit representation */ switch (type) { case BHND_NVRAM_TYPE_UINT8: NV_PARSE_INT(uint32_t, u8, ); break; case BHND_NVRAM_TYPE_UINT16: NV_PARSE_INT(uint32_t, u16, le16toh); break; case BHND_NVRAM_TYPE_UINT32: NV_PARSE_INT(uint32_t, u32, le32toh); break; case BHND_NVRAM_TYPE_INT8: NV_PARSE_INT(int32_t, i8, ); break; case BHND_NVRAM_TYPE_INT16: NV_PARSE_INT(int32_t, i16, le16toh); break; case BHND_NVRAM_TYPE_INT32: NV_PARSE_INT(int32_t, i32, le32toh); break; case BHND_NVRAM_TYPE_CHAR: NV_PARSE_INT(uint32_t, u8, ); break; default: BHND_NV_LOG("unhandled %s offset type: %d\n", var->name, type); return (EFTYPE); } #undef NV_PARSE_INT return (0); } /** - * Common variable decoding; fetches and decodes variable to @p val, - * using @p storage for actual data storage. + * Read a SPROM variable value from @p io. * + * @param state The SPROM opcode state describing the layout of @p io. + * @param entry The variable's SPROM opcode index entry. + * @param io The input I/O context. + * @param storage Storage to be used with @p val. + * @param[out] val Value instance to be initialized with the + * parsed variable data. + * * 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, +bhnd_nvram_sprom_read_var(struct bhnd_sprom_opcode_state *state, + struct bhnd_sprom_opcode_idx_entry *entry, struct bhnd_nvram_io *io, union bhnd_nvram_sprom_storage *storage, bhnd_nvram_val *val) { - struct bhnd_nvram_sprom *sp; - bhnd_sprom_opcode_idx_entry *entry; - const struct bhnd_nvram_vardefn *var; union bhnd_nvram_sprom_storage *inp; + const struct bhnd_nvram_vardefn *var; bhnd_nvram_type var_btype; uint32_t 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_VAR(cookiep); - BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep)); + var = bhnd_nvram_get_vardefn(entry->vid); + BHND_NV_ASSERT(var != NULL, ("invalid entry")); /* * 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 = bhnd_sprom_opcode_parse_var(&sp->state, entry))) { + if ((error = bhnd_sprom_opcode_eval_var(state, entry))) { BHND_NV_LOG("variable evaluation failed: %d\n", error); return (error); } - nelem = sp->state.var.nelem; + nelem = 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 = bhnd_sprom_opcode_seek(&sp->state, entry))) { + if ((error = bhnd_sprom_opcode_seek(state, entry))) { BHND_NV_LOG("variable seek failed: %d\n", error); return (error); } ipos = 0; intv = 0x0; if (var->flags & BHND_NVRAM_VF_IGNALL1) all_bits_set = true; else all_bits_set = false; - while ((error = bhnd_sprom_opcode_next_binding(&sp->state)) == 0) { + while ((error = bhnd_sprom_opcode_next_binding(state)) == 0) { bhnd_sprom_opcode_bind *binding; bhnd_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, + state->var_state >= SPROM_OPCODE_VAR_STATE_OPEN, ("invalid var state")); - BHND_NV_ASSERT(sp->state.var.have_bind, ("invalid bind state")); + BHND_NV_ASSERT(state->var.have_bind, ("invalid bind state")); - binding_var = &sp->state.var; - binding = &sp->state.var.bind; + binding_var = &state->var; + binding = &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 = bhnd_sprom_opcode_apply_scale(&sp->state, - &skip_in_bytes); + error = bhnd_sprom_opcode_apply_scale(state, &skip_in_bytes); if (error) return (error); /* Bind */ - offset = sp->state.offset; + offset = 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(var, sp->data, + error = bhnd_nvram_sprom_read_offset(var, io, 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 & 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, sizeof(intv), intv_type, ptr, &nbyte, var_btype); if (error) return (error); /* Clear temporary state */ intv = 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); +} + + +/** + * 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; + bhnd_sprom_opcode_idx_entry *entry; + const struct bhnd_nvram_vardefn *var; + + BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep")); + + sp = (struct bhnd_nvram_sprom *)nv; + entry = cookiep; + + /* Fetch canonical variable definition */ + var = SPROM_COOKIE_TO_NVRAM_VAR(cookiep); + BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep)); + + return (bhnd_nvram_sprom_read_var(&sp->state, entry, sp->data, storage, + val)); } static int bhnd_nvram_sprom_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2) { struct bhnd_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_VAR(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) { struct bhnd_nvram_sprom *sp; const struct bhnd_nvram_vardefn *var; bhnd_sprom_opcode_idx_entry *entry; bhnd_nvram_val *spval; int error; sp = (struct bhnd_nvram_sprom *)nv; /* Is this an externally immutable variable name? */ if (bhnd_sprom_is_external_immutable(name)) return (EINVAL); /* Variable must be defined in our SPROM layout */ if ((entry = bhnd_sprom_opcode_index_find(&sp->state, name)) == NULL) return (ENOENT); var = bhnd_nvram_get_vardefn(entry->vid); BHND_NV_ASSERT(var != NULL, ("missing variable definition")); /* Value must be convertible to the native variable type */ error = bhnd_nvram_val_convert_new(&spval, var->fmt, value, BHND_NVRAM_VAL_DYNAMIC); if (error) return (error); /* Value must be encodeable by our SPROM layout */ error = bhnd_nvram_sprom_write_var(&sp->state, entry, spval, NULL); if (error) { bhnd_nvram_val_release(spval); return (error); } /* Success. Transfer our ownership of the converted value to the * caller */ *result = spval; return (0); } static int bhnd_nvram_sprom_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { struct bhnd_nvram_sprom *sp; const struct bhnd_nvram_vardefn *var; bhnd_sprom_opcode_idx_entry *entry; sp = (struct bhnd_nvram_sprom *)nv; /* Is this an externally immutable variable name? */ if (bhnd_sprom_is_external_immutable(name)) return (EINVAL); /* Variable must be defined in our SPROM layout */ if ((entry = bhnd_sprom_opcode_index_find(&sp->state, name)) == NULL) return (ENOENT); var = bhnd_nvram_get_vardefn(entry->vid); /* Variable must be capable of representing a NULL/deleted value. * * Since SPROM's layout is fixed, this requires IGNALL -- if * all bits are set, an IGNALL variable is treated as unset. */ if (!(var->flags & BHND_NVRAM_VF_IGNALL1)) return (EINVAL); return (0); } /** * Return true if @p name represents a special immutable variable name * (e.g. sromrev) that cannot be updated in an SPROM existing image. * * @param name The name to check. */ static bool bhnd_sprom_is_external_immutable(const char *name) { /* The layout revision is immutable and cannot be changed */ if (strcmp(name, BHND_NVAR_SROMREV) == 0) return (true); return (false); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom_subr.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom_subr.c (revision 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom_subr.c (revision 315866) @@ -1,1366 +1,1389 @@ /*- * 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 #ifdef _KERNEL #include #include #else /* !_KERNEL */ #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_data_spromvar.h" static int bhnd_sprom_opcode_sort_idx(const void *lhs, const void *rhs); static int bhnd_nvram_opcode_idx_vid_compare(const void *key, const void *rhs); static int bhnd_sprom_opcode_reset(bhnd_sprom_opcode_state *state); -static int bhnd_sprom_opcode_next_var(bhnd_sprom_opcode_state *state); static int bhnd_sprom_opcode_set_type(bhnd_sprom_opcode_state *state, bhnd_nvram_type type); static int bhnd_sprom_opcode_set_var(bhnd_sprom_opcode_state *state, size_t vid); static int bhnd_sprom_opcode_clear_var(bhnd_sprom_opcode_state *state); static int bhnd_sprom_opcode_flush_bind(bhnd_sprom_opcode_state *state); static int bhnd_sprom_opcode_read_opval32(bhnd_sprom_opcode_state *state, uint8_t type, uint32_t *opval); static int bhnd_sprom_opcode_step(bhnd_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__) /** * 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. */ int bhnd_sprom_opcode_init(bhnd_sprom_opcode_state *state, const struct bhnd_sprom_layout *layout) { bhnd_sprom_opcode_idx_entry *idx; size_t num_vars, num_idx; int error; idx = NULL; state->layout = layout; state->idx = NULL; state->num_idx = 0; /* Initialize interpretation state */ if ((error = bhnd_sprom_opcode_reset(state))) return (error); /* Allocate and populate our opcode index */ num_idx = state->layout->num_vars; idx = bhnd_nv_calloc(num_idx, sizeof(*idx)); if (idx == NULL) return (ENOMEM); for (num_vars = 0; num_vars < num_idx; num_vars++) { - size_t opcodes; - /* Seek to next entry */ if ((error = bhnd_sprom_opcode_next_var(state))) { SPROM_OP_BAD(state, "error reading expected variable " "entry: %d\n", error); bhnd_nv_free(idx); return (error); } - /* We limit the SPROM index representations to the minimal - * type widths capable of covering all known layouts */ - - /* Save SPROM image offset */ - if (state->offset > UINT16_MAX) { - SPROM_OP_BAD(state, "cannot index large offset %u\n", - state->offset); + /* Record entry state in our index */ + error = bhnd_sprom_opcode_init_entry(state, &idx[num_vars]); + if (error) { + SPROM_OP_BAD(state, "error initializing index for " + "entry: %d\n", error); bhnd_nv_free(idx); - return (ENXIO); + return (error); } - idx[num_vars].offset = state->offset; - - /* Save current variable ID */ - if (state->vid > UINT16_MAX) { - SPROM_OP_BAD(state, "cannot index large vid %zu\n", - state->vid); - bhnd_nv_free(idx); - return (ENXIO); - } - idx[num_vars].vid = state->vid; - - /* Save opcode position */ - opcodes = (state->input - state->layout->bindings); - if (opcodes > UINT16_MAX) { - SPROM_OP_BAD(state, "cannot index large opcode offset " - "%zu\n", opcodes); - bhnd_nv_free(idx); - return (ENXIO); - } - idx[num_vars].opcodes = opcodes; } /* Should have reached end of binding table; next read must return * ENOENT */ if ((error = bhnd_sprom_opcode_next_var(state)) != ENOENT) { BHND_NV_LOG("expected EOF parsing binding table: %d\n", error); bhnd_nv_free(idx); return (ENXIO); } /* Reset interpretation state */ if ((error = bhnd_sprom_opcode_reset(state))) { bhnd_nv_free(idx); return (error); } /* Make index available to opcode state evaluation */ qsort(idx, num_idx, sizeof(idx[0]), bhnd_sprom_opcode_sort_idx); state->idx = idx; state->num_idx = num_idx; 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 bhnd_sprom_opcode_reset(bhnd_sprom_opcode_state *state) { memset(&state->var, 0, sizeof(state->var)); state->input = state->layout->bindings; state->offset = 0; state->vid = 0; state->var_state = SPROM_OPCODE_VAR_STATE_NONE; bit_set(state->revs, state->layout->rev); return (0); } /** * Free any resources associated with @p state. * * @param state An opcode state previously successfully initialized with * bhnd_sprom_opcode_init(). */ void bhnd_sprom_opcode_fini(bhnd_sprom_opcode_state *state) { bhnd_nv_free(state->idx); } /** * Sort function used to prepare our index for querying; sorts * bhnd_sprom_opcode_idx_entry values by variable ID, ascending. */ static int bhnd_sprom_opcode_sort_idx(const void *lhs, const void *rhs) { const bhnd_sprom_opcode_idx_entry *l, *r; l = lhs; r = rhs; if (l->vid < r->vid) return (-1); if (l->vid > r->vid) return (1); return (0); } /** * Binary search comparison function used by bhnd_sprom_opcode_index_find(); * searches bhnd_sprom_opcode_idx_entry values by variable ID, ascending. */ static int bhnd_nvram_opcode_idx_vid_compare(const void *key, const void *rhs) { const bhnd_sprom_opcode_idx_entry *entry; size_t vid; vid = *(const size_t *)key; entry = rhs; if (vid < entry->vid) return (-1); if (vid > entry->vid) return (1); return (0); } /** * Locate an index entry for the variable with @p name, or NULL if not found. * * @param state The opcode state to be queried. * @param name The name to search for. * * @retval non-NULL If @p name is found, its index entry value will be * returned. * @retval NULL If @p name is not found. */ bhnd_sprom_opcode_idx_entry * bhnd_sprom_opcode_index_find(bhnd_sprom_opcode_state *state, const char *name) { const struct bhnd_nvram_vardefn *var; size_t vid; /* 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, state->idx, state->num_idx, sizeof(state->idx[0]), bhnd_nvram_opcode_idx_vid_compare)); } /** * Iterate over all index entries in @p state. * * @param state The opcode state to be iterated. * @param[in,out] prev An entry previously returned by * bhnd_sprom_opcode_index_next(), or a NULL value * to begin iteration. * * @return Returns the next index entry name, or NULL if all entries have * been iterated. */ bhnd_sprom_opcode_idx_entry * bhnd_sprom_opcode_index_next(bhnd_sprom_opcode_state *state, bhnd_sprom_opcode_idx_entry *prev) { size_t idxpos; /* Get next index position */ if (prev == NULL) { idxpos = 0; } else { /* Determine current position */ idxpos = (size_t)(prev - state->idx); BHND_NV_ASSERT(idxpos < state->num_idx, ("invalid index %zu", idxpos)); /* Advance to next entry */ idxpos++; } /* Check for EOF */ if (idxpos == state->num_idx) return (NULL); return (&state->idx[idxpos]); } + /** + * Initialize @p entry with the current variable's opcode state. + * + * @param state The opcode state to be saved. + * @param[out] entry The opcode index entry to be initialized from @p state. + * + * @retval 0 success + * @retval ENXIO if @p state cannot be serialized as an index entry. + */ +int +bhnd_sprom_opcode_init_entry(bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *entry) +{ + size_t opcodes; + + /* We limit the SPROM index representations to the minimal type widths + * capable of covering all known layouts */ + + /* Save SPROM image offset */ + if (state->offset > UINT16_MAX) { + SPROM_OP_BAD(state, "cannot index large offset %u\n", + state->offset); + return (ENXIO); + } + + entry->offset = state->offset; + + /* Save current variable ID */ + if (state->vid > UINT16_MAX) { + SPROM_OP_BAD(state, "cannot index large vid %zu\n", + state->vid); + return (ENXIO); + } + entry->vid = state->vid; + + /* Save opcode position */ + opcodes = (state->input - state->layout->bindings); + if (opcodes > UINT16_MAX) { + SPROM_OP_BAD(state, "cannot index large opcode offset " + "%zu\n", opcodes); + return (ENXIO); + } + entry->opcodes = opcodes; + + return (0); +} + +/** * Reset SPROM opcode evaluation state and seek to the @p entry's position. * * @param state The opcode state to be reset. * @param entry The indexed entry to which we'll seek the opcode state. */ int bhnd_sprom_opcode_seek(bhnd_sprom_opcode_state *state, bhnd_sprom_opcode_idx_entry *entry) { int error; BHND_NV_ASSERT(entry->opcodes < state->layout->bindings_size, ("index entry references invalid opcode position")); /* Reset state */ if ((error = bhnd_sprom_opcode_reset(state))) return (error); /* Seek to the indexed sprom opcode offset */ state->input = state->layout->bindings + entry->opcodes; /* Restore the indexed sprom data offset and VID */ state->offset = entry->offset; /* Restore the indexed sprom variable ID */ if ((error = bhnd_sprom_opcode_set_var(state, entry->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 bhnd_sprom_opcode_set_revs(bhnd_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 = bhnd_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 bhnd_sprom_opcode_set_mask(bhnd_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 bhnd_sprom_opcode_set_shift(bhnd_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 bhnd_sprom_opcode_set_bind(bhnd_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 = bhnd_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 bhnd_sprom_opcode_flush_bind(bhnd_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 = bhnd_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 bhnd_sprom_opcode_set_type(bhnd_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 bhnd_sprom_opcode_clear_var(bhnd_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 bhnd_sprom_opcode_set_nelem(bhnd_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 bhnd_sprom_opcode_set_var(bhnd_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 = bhnd_sprom_opcode_set_type(state, var->type))) return (error); /* Set default array length */ if ((error = bhnd_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 bhnd_sprom_opcode_end_var(bhnd_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. */ int bhnd_sprom_opcode_apply_scale(bhnd_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 bhnd_sprom_opcode_read_opval32(bhnd_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 = bhnd_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 bhnd_sprom_opcode_matches_layout_rev(bhnd_sprom_opcode_state *state) { return (bit_test(state->revs, state->layout->rev)); } /** * When evaluating @p state and @p opcode, rewrite @p opcode based on the * current evaluation state. * * This allows the insertion of implicit opcodes into interpretation of the * opcode stream. * * If @p opcode is rewritten, it should be returned from * bhnd_sprom_opcode_step() instead of the opcode parsed from @p state's opcode * stream. * * If @p opcode remains unmodified, then bhnd_sprom_opcode_step() should * proceed to standard evaluation. */ static int bhnd_sprom_opcode_rewrite_opcode(bhnd_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 = bhnd_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 = bhnd_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 (bhnd_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 bhnd_sprom_opcode_step(bhnd_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 = bhnd_sprom_opcode_flush_bind(state))) return (error); /* Insert local opcode based on current state? */ rewrite = *opcode; if ((error = bhnd_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 (!bhnd_sprom_opcode_matches_layout_rev(state)) continue; return (0); } /* Advance input */ state->input++; switch (op) { case SPROM_OPCODE_VAR_IMM: if ((error = bhnd_sprom_opcode_set_var(state, immd))) return (error); break; case SPROM_OPCODE_VAR_REL_IMM: error = bhnd_sprom_opcode_set_var(state, state->vid + immd); if (error) return (error); break; case SPROM_OPCODE_VAR: error = bhnd_sprom_opcode_read_opval32(state, immd, &val); if (error) return (error); if ((error = bhnd_sprom_opcode_set_var(state, val))) return (error); break; case SPROM_OPCODE_VAR_END: if ((error = bhnd_sprom_opcode_end_var(state))) return (error); break; case SPROM_OPCODE_NELEM: immd = *state->input; if ((error = bhnd_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 = bhnd_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 = bhnd_sprom_opcode_set_bind(state, count, skip_in, skip_in_negative, skip_out); if (error) return (error); break; } case SPROM_OPCODE_REV_IMM: error = bhnd_sprom_opcode_set_revs(state, immd, immd); if (error) 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 = bhnd_sprom_opcode_set_revs(state, rstart, rend); if (error) return (error); /* Advance input */ state->input++; break; } case SPROM_OPCODE_MASK_IMM: if ((error = bhnd_sprom_opcode_set_mask(state, immd))) return (error); break; case SPROM_OPCODE_MASK: error = bhnd_sprom_opcode_read_opval32(state, immd, &val); if (error) return (error); if ((error = bhnd_sprom_opcode_set_mask(state, val))) return (error); break; case SPROM_OPCODE_SHIFT_IMM: error = bhnd_sprom_opcode_set_shift(state, immd * 2); if (error) 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 = bhnd_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 */ error = bhnd_sprom_opcode_apply_scale(state, &val); if (error) 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 = bhnd_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 = bhnd_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 (bhnd_sprom_opcode_matches_layout_rev(state)) return (0); } /* End of opcode stream */ return (ENOENT); } /** * Reset SPROM opcode evaluation state, seek to the @p entry's position, * and perform complete evaluation of the variable's opcodes. * * @param state The opcode state to be to be evaluated. * @param entry The indexed variable location. * * @retval 0 success * @retval non-zero If evaluation fails, a regular unix error code will be * returned. */ int -bhnd_sprom_opcode_parse_var(bhnd_sprom_opcode_state *state, +bhnd_sprom_opcode_eval_var(bhnd_sprom_opcode_state *state, bhnd_sprom_opcode_idx_entry *entry) { uint8_t opcode; int error; /* Seek to entry */ if ((error = bhnd_sprom_opcode_seek(state, entry))) return (error); /* Parse full variable definition */ while ((error = bhnd_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 +int bhnd_sprom_opcode_next_var(bhnd_sprom_opcode_state *state) { uint8_t opcode; int error; /* Step until we hit a variable opcode */ while ((error = bhnd_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. */ int bhnd_sprom_opcode_next_binding(bhnd_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 = bhnd_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_spromvar.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_spromvar.h (revision 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_spromvar.h (revision 315866) @@ -1,174 +1,180 @@ /*- * 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_SPROMVAR_H_ #define _BHND_NVRAM_BHND_NVRAM_SPROMVAR_H_ #ifdef _KERNEL #include #else #include #endif #include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_io.h" /** The maximum number of array elements encoded in a single SPROM variable */ #define BHND_SPROM_ARRAY_MAXLEN 12 typedef struct bhnd_sprom_opcode_state bhnd_sprom_opcode_state; typedef struct bhnd_sprom_opcode_bind bhnd_sprom_opcode_bind; typedef struct bhnd_sprom_opcode_var bhnd_sprom_opcode_var; typedef struct bhnd_sprom_opcode_idx_entry bhnd_sprom_opcode_idx_entry; int bhnd_sprom_opcode_init( bhnd_sprom_opcode_state *state, const bhnd_sprom_layout *layout); void bhnd_sprom_opcode_fini( bhnd_sprom_opcode_state *state); bhnd_sprom_opcode_idx_entry *bhnd_sprom_opcode_index_find( bhnd_sprom_opcode_state *state, const char *name); bhnd_sprom_opcode_idx_entry *bhnd_sprom_opcode_index_next( bhnd_sprom_opcode_state *state, bhnd_sprom_opcode_idx_entry *prev); -int bhnd_sprom_opcode_parse_var( +int bhnd_sprom_opcode_init_entry( bhnd_sprom_opcode_state *state, bhnd_sprom_opcode_idx_entry *entry); +int bhnd_sprom_opcode_eval_var( + bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *entry); + int bhnd_sprom_opcode_seek( bhnd_sprom_opcode_state *state, bhnd_sprom_opcode_idx_entry *entry); +int bhnd_sprom_opcode_next_var( + bhnd_sprom_opcode_state *state); int bhnd_sprom_opcode_next_binding( bhnd_sprom_opcode_state *state); int bhnd_sprom_opcode_apply_scale( bhnd_sprom_opcode_state *state, uint32_t *value); /** * SPROM opcode per-bind evaluation state. */ struct bhnd_sprom_opcode_bind { uint8_t count; uint32_t skip_in; /**< input element skips */ bool skip_in_negative; /**< skip_in should be subtracted */ uint32_t skip_out; /**< output element skip */ }; /** * SPROM opcode per-variable evaluation state. */ struct bhnd_sprom_opcode_var { uint8_t nelem; /**< variable array length */ uint32_t mask; /**< current bind input mask */ int8_t shift; /**< current bind input shift */ bhnd_nvram_type base_type; /**< current bind input type */ uint32_t scale; /**< current scale to apply to scaled encodings */ bhnd_sprom_opcode_bind bind; /**< current bind state */ bool have_bind; /**< if bind state is defined */ size_t bind_total; /**< total count of bind operations performed */ }; /** * SPROM opcode variable definition states. * * Ordered to support inequality comparisons * (e.g. >= SPROM_OPCODE_VAR_STATE_OPEN) */ typedef enum { SPROM_OPCODE_VAR_STATE_NONE = 1, /**< no variable entry available */ SPROM_OPCODE_VAR_STATE_OPEN = 2, /**< currently parsing a variable entry */ SPROM_OPCODE_VAR_STATE_DONE = 3 /**< full variable entry has been parsed */ } bhnd_sprom_opcode_var_state; /** * SPROM opcode evaluation state */ struct bhnd_sprom_opcode_state { const bhnd_sprom_layout *layout; /**< SPROM layout */ bhnd_sprom_opcode_idx_entry *idx; /**< variable index (NULL during initialization) */ size_t num_idx; /**< variable index entry count */ /** Current SPROM revision range */ bitstr_t bit_decl(revs, SPROM_OP_REV_MAX); const uint8_t *input; /**< opcode input position */ /* State preserved across variable definitions */ uint32_t offset; /**< SPROM offset */ size_t vid; /**< Variable ID */ /* State reset after end of each variable definition */ bhnd_sprom_opcode_var var; /**< variable record (if any) */ bhnd_sprom_opcode_var_state var_state; /**< variable record state */ }; /** * SPROM opcode variable index entry */ struct bhnd_sprom_opcode_idx_entry { uint16_t vid; /**< SPROM variable ID */ uint16_t offset; /**< SPROM input offset */ uint16_t opcodes; /**< SPROM opcode offset */ }; /** * SPROM value storage. * * Sufficient for representing the native encoding of any defined SPROM * variable. */ union bhnd_nvram_sprom_storage { uint8_t u8[BHND_SPROM_ARRAY_MAXLEN]; uint16_t u16[BHND_SPROM_ARRAY_MAXLEN]; uint32_t u32[BHND_SPROM_ARRAY_MAXLEN]; int8_t i8[BHND_SPROM_ARRAY_MAXLEN]; int16_t i16[BHND_SPROM_ARRAY_MAXLEN]; int32_t i32[BHND_SPROM_ARRAY_MAXLEN]; char ch[BHND_SPROM_ARRAY_MAXLEN]; }; /** * SPROM data class instance state. */ struct bhnd_nvram_sprom { struct bhnd_nvram_data nv; /**< common instance state */ struct bhnd_nvram_io *data; /**< backing SPROM image */ const bhnd_sprom_layout *layout; /**< layout definition */ bhnd_sprom_opcode_state state; /**< opcode eval state */ }; #endif /* _BHND_NVRAM_BHND_NVRAM_SPROMVAR_H_ */ Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c (revision 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c (revision 315866) @@ -1,813 +1,885 @@ /*- * 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", BHND_NVRAM_DATA_CAP_DEVPATHS, 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); } static int +bhnd_nvram_tlv_getvar_direct(struct bhnd_nvram_io *io, const char *name, + void *buf, size_t *len, bhnd_nvram_type type) +{ + struct bhnd_nvram_tlv_env env; + char data[NVRAM_TLV_ENVP_DATA_MAX_LEN]; + size_t data_len; + const char *key, *value; + size_t keylen, vlen; + size_t namelen; + size_t next, off; + uint8_t tag; + int error; + + namelen = strlen(name); + + /* Iterate over the input looking for the requested variable */ + next = 0; + while (!(error = bhnd_nvram_tlv_next_record(io, &next, &off, &tag))) { + switch (tag) { + case NVRAM_TLV_TYPE_END: + /* Not found */ + return (ENOENT); + + case NVRAM_TLV_TYPE_ENV: + /* Read the record header */ + error = bhnd_nvram_io_read(io, off, &env, sizeof(env)); + if (error) { + BHND_NV_LOG("error reading TLV_ENV record " + "header: %d\n", error); + return (error); + } + + /* Read the record data */ + data_len = NVRAM_TLV_ENVP_DATA_LEN(&env); + error = bhnd_nvram_io_read(io, off + sizeof(env), data, + data_len); + if (error) { + BHND_NV_LOG("error reading TLV_ENV record " + "data: %d\n", error); + return (error); + } + + /* Parse the key=value string */ + error = bhnd_nvram_parse_env(data, data_len, '=', &key, + &keylen, &value, &vlen); + if (error) { + BHND_NV_LOG("error parsing TLV_ENV data: %d\n", + error); + return (error); + } + + /* Match against requested variable name */ + if (keylen == namelen && + strncmp(key, name, namelen) == 0) + { + return (bhnd_nvram_value_coerce(value, vlen, + BHND_NVRAM_TYPE_STRING, buf, len, type)); + } + + break; + + default: + /* Skip unknown tags */ + break; + } + } + + /* Hit I/O error */ + return (error); +} + +static int bhnd_nvram_tlv_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, size_t *olen) { bhnd_nvram_prop *prop; size_t limit, nbytes; int error; /* Determine output byte limit */ if (outp != NULL) limit = *olen; else limit = 0; nbytes = 0; /* Write all properties */ prop = NULL; while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { struct bhnd_nvram_tlv_env env; const char *name; uint8_t *p; size_t name_len, value_len; size_t rec_size; env.hdr.tag = NVRAM_TLV_TYPE_ENV; env.hdr.size = sizeof(env.flags); env.flags = 0x0; /* Fetch name value and add to record length */ name = bhnd_nvram_prop_name(prop); name_len = strlen(name) + 1 /* '=' */; if (UINT8_MAX - env.hdr.size < name_len) { BHND_NV_LOG("%s name exceeds maximum TLV record " "length\n", name); return (EFTYPE); /* would overflow TLV size */ } env.hdr.size += name_len; /* Add string value to record length */ error = bhnd_nvram_prop_encode(prop, NULL, &value_len, BHND_NVRAM_TYPE_STRING); if (error) { BHND_NV_LOG("error serializing %s to required type " "%s: %d\n", name, bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), error); return (error); } if (UINT8_MAX - env.hdr.size < value_len) { BHND_NV_LOG("%s value exceeds maximum TLV record " "length\n", name); return (EFTYPE); /* would overflow TLV size */ } env.hdr.size += value_len; /* Calculate total record size */ rec_size = sizeof(env.hdr) + env.hdr.size; if (SIZE_MAX - nbytes < rec_size) return (EFTYPE); /* would overflow size_t */ /* Calculate our output pointer */ if (nbytes > limit || limit - nbytes < rec_size) { /* buffer is full; cannot write */ p = NULL; } else { p = (uint8_t *)outp + nbytes; } /* Write to output */ if (p != NULL) { memcpy(p, &env, sizeof(env)); p += sizeof(env); memcpy(p, name, name_len - 1); p[name_len - 1] = '='; p += name_len; error = bhnd_nvram_prop_encode(prop, p, &value_len, BHND_NVRAM_TYPE_STRING); if (error) { BHND_NV_LOG("error serializing %s to required " "type %s: %d\n", name, bhnd_nvram_type_name( BHND_NVRAM_TYPE_STRING), error); return (error); } } nbytes += rec_size; } /* Write terminating END record */ if (limit > nbytes) *((uint8_t *)outp + nbytes) = NVRAM_TLV_TYPE_END; if (nbytes == SIZE_MAX) return (EFTYPE); /* would overflow size_t */ nbytes++; /* Provide required length */ *olen = nbytes; if (limit < *olen) { if (outp == NULL) return (0); return (ENOMEM); } return (0); } /** * 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 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; /* 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"); /* 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 315865) +++ head/sys/dev/bhnd/nvram/bhnd_nvram_datavar.h (revision 315866) @@ -1,222 +1,229 @@ /*- * 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_probe() */ +typedef int (bhnd_nvram_data_op_getvar_direct)( + struct bhnd_nvram_io *io, const char *name, + void *outp, size_t *olen, bhnd_nvram_type otype); + /** @see bhnd_nvram_data_serialize() */ typedef int (bhnd_nvram_data_op_serialize)( bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, size_t *olen); /** @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_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 */ uint32_t caps; /**< capabilities (BHND_NVRAM_DATA_CAP_*) */ size_t size; /**< instance size */ bhnd_nvram_data_op_probe *op_probe; + bhnd_nvram_data_op_getvar_direct *op_getvar_direct; bhnd_nvram_data_op_serialize *op_serialize; 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_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, getvar_direct) \ _macro(_cname, serialize) \ _macro(_cname, new) \ _macro(_cname, free) \ _macro(_cname, count) \ _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, _caps, _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), \ .caps = (_caps), \ .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_sprom.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_sprom.c (revision 315865) +++ head/sys/dev/bhnd/nvram/bhnd_sprom.c (revision 315866) @@ -1,226 +1,226 @@ /*- * 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$"); /* * BHND SPROM driver. * * Abstract driver for memory-mapped SPROM devices. */ #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_nvram_if.h" #include "bhnd_nvram_io.h" #include "bhnd_spromvar.h" /** * Default bhnd sprom driver implementation of DEVICE_PROBE(). */ int bhnd_sprom_probe(device_t dev) { device_set_desc(dev, "SPROM/OTP"); /* Refuse wildcard attachments */ return (BUS_PROBE_NOWILDCARD); } /* Default DEVICE_ATTACH() implementation; assumes a zero offset to the * SPROM data */ static int bhnd_sprom_attach_meth(device_t dev) { return (bhnd_sprom_attach(dev, 0)); } /** * BHND SPROM device attach. * * This should be called from DEVICE_ATTACH() with the @p offset to the * SPROM data. * * Assumes SPROM is mapped via SYS_RES_MEMORY resource with RID 0. * * @param dev BHND SPROM device. * @param offset Offset to the SPROM data. */ int bhnd_sprom_attach(device_t dev, bus_size_t offset) { struct bhnd_sprom_softc *sc; struct bhnd_nvram_io *io; struct bhnd_resource *r; bus_size_t r_size, sprom_size; int rid; int error; sc = device_get_softc(dev); sc->dev = dev; io = NULL; /* Allocate SPROM resource */ rid = 0; r = bhnd_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (r == NULL) { device_printf(dev, "failed to allocate resources\n"); return (ENXIO); } /* Determine SPROM size */ r_size = rman_get_size(r->res); if (r_size <= offset || (r_size - offset) > BUS_SPACE_MAXSIZE) { device_printf(dev, "invalid sprom offset\n"); error = ENXIO; goto failed; } sprom_size = r_size - offset; - /* Allocate an I/O context for the SPROM parser. All SPROM reads - * must be 16-bit aligned */ - io = bhnd_nvram_iores_new(r, offset, sprom_size, sizeof(uint16_t)); + /* Allocate an I/O context for the SPROM parser. SPROM reads do not + * appear to require any specific alignment. */ + io = bhnd_nvram_iores_new(r, offset, sprom_size, 1); if (io == NULL) { error = ENXIO; goto failed; } /* Initialize NVRAM data store */ error = bhnd_nvram_store_parse_new(&sc->store, io, &bhnd_nvram_sprom_class); if (error) goto failed; /* Clean up our temporary I/O context and its backing resource */ bhnd_nvram_io_free(io); bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r); return (0); failed: /* Clean up I/O context before releasing its backing resource */ if (io != NULL) bhnd_nvram_io_free(io); bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r); return (error); } /** * Default bhnd_sprom implementation of DEVICE_RESUME(). */ int bhnd_sprom_resume(device_t dev) { return (0); } /** * Default bhnd sprom driver implementation of DEVICE_SUSPEND(). */ int bhnd_sprom_suspend(device_t dev) { return (0); } /** * Default bhnd sprom driver implementation of DEVICE_DETACH(). */ int bhnd_sprom_detach(device_t dev) { struct bhnd_sprom_softc *sc; sc = device_get_softc(dev); bhnd_nvram_store_free(sc->store); return (0); } /** * Default bhnd sprom driver implementation of BHND_NVRAM_GETVAR(). */ static int bhnd_sprom_getvar_method(device_t dev, const char *name, void *buf, size_t *len, bhnd_nvram_type type) { struct bhnd_sprom_softc *sc = device_get_softc(dev); return (bhnd_nvram_store_getvar(sc->store, name, buf, len, type)); } /** * Default bhnd sprom driver implementation of BHND_NVRAM_SETVAR(). */ static int bhnd_sprom_setvar_method(device_t dev, const char *name, const void *buf, size_t len, bhnd_nvram_type type) { struct bhnd_sprom_softc *sc = device_get_softc(dev); return (bhnd_nvram_store_setvar(sc->store, name, buf, len, type)); } static device_method_t bhnd_sprom_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhnd_sprom_probe), DEVMETHOD(device_attach, bhnd_sprom_attach_meth), DEVMETHOD(device_resume, bhnd_sprom_resume), DEVMETHOD(device_suspend, bhnd_sprom_suspend), DEVMETHOD(device_detach, bhnd_sprom_detach), /* NVRAM interface */ DEVMETHOD(bhnd_nvram_getvar, bhnd_sprom_getvar_method), DEVMETHOD(bhnd_nvram_setvar, bhnd_sprom_setvar_method), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd_nvram_store, bhnd_sprom_driver, bhnd_sprom_methods, sizeof(struct bhnd_sprom_softc)); MODULE_VERSION(bhnd_sprom, 1); Index: head/sys/mips/broadcom/bcm_machdep.c =================================================================== --- head/sys/mips/broadcom/bcm_machdep.c (revision 315865) +++ head/sys/mips/broadcom/bcm_machdep.c (revision 315866) @@ -1,544 +1,591 @@ /*- * Copyright (c) 2007 Bruce M. Simpson. * Copyright (c) 2016 Michael Zhilin * 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. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, 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 DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_ddb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bcm_machdep.h" #include "bcm_bmips_exts.h" #ifdef CFE #include +#include #endif #if 0 #define BCM_TRACE(_fmt, ...) printf(_fmt, ##__VA_ARGS__) #else #define BCM_TRACE(_fmt, ...) #endif static int bcm_init_platform_data(struct bcm_platform *bp); static int bcm_find_core(struct bcm_platform *bp, const struct bhnd_core_match *descs, size_t num_descs, struct bhnd_core_info *info, uintptr_t *addr); static int bcm_erom_probe_and_attach(bhnd_erom_class_t **erom_cls, kobj_ops_t erom_ops, bhnd_erom_t *erom, size_t esize, struct bhnd_chipid *cid); extern int *edata; extern int *end; static struct bcm_platform bcm_platform_data; static bool bcm_platform_data_avail = false; +#ifdef CFE +static struct bcm_nvram_iocfe bcm_cfe_nvram; +#endif + static const struct bhnd_core_match bcm_chipc_cores[] = { { BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_CC) }, { BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_4706_CC) }, }; static const struct bhnd_core_match bcm_pmu_cores[] = { { BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_PMU) }, }; struct bcm_platform * bcm_get_platform(void) { if (!bcm_platform_data_avail) panic("platform data not available"); return (&bcm_platform_data); } static bus_addr_t bcm_get_bus_addr(void) { long maddr; if (resource_long_value("bhnd", 0, "maddr", &maddr) == 0) return ((u_long)maddr); return (BHND_DEFAULT_CHIPC_ADDR); } /** * Search the device enumeration table for a core matching @p descs, * * @param bp Platform state containing a valid EROM parser. * @param descs The core match descriptor table. * @param num_descs The number of match descriptors in @p descs. * @param[out] info If non-NULL, will be populated with the core * info. * @param[out] addr If non-NULL, will be populated with the core's * physical register address. */ static int bcm_find_core(struct bcm_platform *bp, const struct bhnd_core_match *descs, size_t num_descs, struct bhnd_core_info *info, uintptr_t *addr) { bhnd_addr_t b_addr; bhnd_size_t b_size; int error; /* Fetch core info */ for (size_t i = 0; i < num_descs; i++) { error = bhnd_erom_lookup_core_addr(&bp->erom.obj, &descs[i], BHND_PORT_DEVICE, 0, 0, info, &b_addr, &b_size); /* Terminate search on first match */ if (error == 0) break; /* Terminate on first error (other than core not found) */ if (error != ENOENT) return (error); /* Continue search ... */ } /* Provide the core's base address */ if (addr != NULL && b_addr > UINTPTR_MAX) { BCM_ERR("core address %#jx overflows native address width\n", (uintmax_t)b_addr); return (ERANGE); } if (addr != NULL) *addr = b_addr; return (0); } /** + * Read a variable directly from NVRAM, decoding as @p type. + * + * @param bp Platform state. + * @param name The raw name of the variable to be fetched, + * including any device path (/pci/1/1/varname) or + * alias prefix (0:varname). + * @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 ENOENT If @p name is not found. + * @retval EFTYPE If the variable data cannot be coerced to @p type. + * @retval ERANGE If value coercion would overflow @p type. + * @retval non-zero If parsing NVRAM otherwise fails, a regular unix error + * code will be returned. + */ +int +bcm_get_nvram(struct bcm_platform *bp, const char *name, void *buf, size_t *len, + bhnd_nvram_type type) +{ + if (bp->nvram_io == NULL || bp->nvram_cls == NULL) + return (ENOENT); + + return (bhnd_nvram_data_getvar_direct(bp->nvram_cls, bp->nvram_io, name, + buf, len, type)); +} + +/** * Probe and attach a bhnd_erom parser instance for the bhnd bus. * * @param[out] erom_cls The probed EROM class. * @param[out] erom_ops The storage to be used when compiling * @p erom_cls. * @param[out] erom The storage to be used when initializing the * static instance of @p erom_cls. * @param esize The total available number of bytes allocated * for @p erom. If this is less than is required * by @p erom_cls ENOMEM will be returned. * @param[out] cid On success, the probed chip identification. */ static int bcm_erom_probe_and_attach(bhnd_erom_class_t **erom_cls, kobj_ops_t erom_ops, bhnd_erom_t *erom, size_t esize, struct bhnd_chipid *cid) { bhnd_erom_class_t **clsp; bus_space_tag_t bst; bus_space_handle_t bsh; bus_addr_t bus_addr; int error, prio, result; bus_addr = bcm_get_bus_addr(); *erom_cls = NULL; prio = 0; bst = mips_bus_space_generic; bsh = BCM_SOC_BSH(bus_addr, 0); SET_FOREACH(clsp, bhnd_erom_class_set) { struct bhnd_chipid pcid; bhnd_erom_class_t *cls; struct kobj_ops kops; cls = *clsp; /* Compile the class' ops table */ kobj_class_compile_static(cls, &kops); /* Probe the bus address */ result = bhnd_erom_probe_static(cls, bst, bsh, bus_addr, NULL, &pcid); /* Drop pointer to stack allocated ops table */ cls->ops = NULL; /* The parser did not match if an error was returned */ if (result > 0) continue; /* Check for a new highest priority match */ if (*erom_cls == NULL || result > prio) { prio = result; *cid = pcid; *erom_cls = cls; } /* Terminate immediately on BUS_PROBE_SPECIFIC */ if (result == BUS_PROBE_SPECIFIC) break; } /* Valid EROM class probed? */ if (*erom_cls == NULL) { BCM_ERR("no erom parser found for root bus at %#jx\n", (uintmax_t)bus_addr); return (ENOENT); } /* Using the provided storage, recompile the erom class ... */ kobj_class_compile_static(*erom_cls, erom_ops); /* ... and initialize the erom parser instance */ bsh = BCM_SOC_BSH(cid->enum_addr, 0); error = bhnd_erom_init_static(*erom_cls, erom, esize, cid, mips_bus_space_generic, bsh); return (error); } /** * Populate platform configuration data. */ static int bcm_init_platform_data(struct bcm_platform *bp) { bool aob, pmu; int error; +#ifdef CFE /* Fetch CFE console handle (if any). Must be initialized before * any calls to printf/early_putc. */ -#ifdef CFE if ((bp->cfe_console = cfe_getstdhandle(CFE_STDHANDLE_CONSOLE)) < 0) bp->cfe_console = -1; -#endif + + /* Probe CFE NVRAM sources */ + bp->nvram_io = &bcm_cfe_nvram.io; + error = bcm_nvram_find_cfedev(&bcm_cfe_nvram, &bp->nvram_cls); + if (error) { + bp->nvram_io = NULL; + bp->nvram_cls = NULL; + } +#endif /* CFE */ /* Probe and attach device table provider, populating our * chip identification */ error = bcm_erom_probe_and_attach(&bp->erom_impl, &bp->erom_ops, &bp->erom.obj, sizeof(bp->erom), &bp->cid); if (error) { BCM_ERR("error attaching erom parser: %d\n", error); return (error); } /* Fetch chipcommon core info */ error = bcm_find_core(bp, bcm_chipc_cores, nitems(bcm_chipc_cores), &bp->cc_id, &bp->cc_addr); if (error) { BCM_ERR("error locating chipc core: %d\n", error); return (error); } /* Fetch chipc capability flags */ bp->cc_caps = BCM_SOC_READ_4(bp->cc_addr, CHIPC_CAPABILITIES); bp->cc_caps_ext = 0x0; if (CHIPC_HWREV_HAS_CAP_EXT(bp->cc_id.hwrev)) bp->cc_caps_ext = BCM_CHIPC_READ_4(bp, CHIPC_CAPABILITIES_EXT); /* Fetch PMU info */ pmu = CHIPC_GET_FLAG(bp->cc_caps, CHIPC_CAP_PMU); aob = CHIPC_GET_FLAG(bp->cc_caps_ext, CHIPC_CAP2_AOB); if (pmu && aob) { /* PMU block mapped to a PMU core on the Always-on-Bus (aob) */ error = bcm_find_core(bp, bcm_pmu_cores, nitems(bcm_pmu_cores), &bp->pmu_id, &bp->pmu_addr); if (error) { BCM_ERR("error locating pmu core: %d\n", error); return (error); } } else if (pmu) { /* PMU block mapped to chipc */ bp->pmu_addr = bp->cc_addr; bp->pmu_id = bp->cc_id; } else { /* No PMU */ bp->pmu_addr = 0x0; memset(&bp->pmu_id, 0, sizeof(bp->pmu_id)); } /* Initialize PMU query state */ if (pmu) { error = bhnd_pmu_query_init(&bp->pmu, NULL, bp->cid, &bcm_pmu_soc_io, bp); if (error) { BCM_ERR("bhnd_pmu_query_init() failed: %d\n", error); return (error); } } bcm_platform_data_avail = true; return (0); } void platform_cpu_init() { /* Nothing special */ } static void mips_init(void) { int i, j; printf("entry: mips_init()\n"); #ifdef CFE /* * Query DRAM memory map from CFE. */ physmem = 0; for (i = 0; i < 10; i += 2) { int result; uint64_t addr, len, type; result = cfe_enummem(i / 2, 0, &addr, &len, &type); if (result < 0) { BCM_TRACE("There is no phys memory for: %d\n", i); phys_avail[i] = phys_avail[i + 1] = 0; break; } if (type != CFE_MI_AVAILABLE) { BCM_TRACE("phys memory is not available: %d\n", i); continue; } phys_avail[i] = addr; if (i == 0 && addr == 0) { /* * If this is the first physical memory segment probed * from CFE, omit the region at the start of physical * memory where the kernel has been loaded. */ phys_avail[i] += MIPS_KSEG0_TO_PHYS(kernel_kseg0_end); } BCM_TRACE("phys memory is available for: %d\n", i); BCM_TRACE(" => addr = %jx\n", addr); BCM_TRACE(" => len = %jd\n", len); phys_avail[i + 1] = addr + len; physmem += len; } BCM_TRACE("Total phys memory is : %ld\n", physmem); realmem = btoc(physmem); #endif for (j = 0; j < i; j++) dump_avail[j] = phys_avail[j]; physmem = realmem; init_param1(); init_param2(physmem); mips_cpu_init(); pmap_bootstrap(); mips_proc0_init(); mutex_init(); kdb_init(); #ifdef KDB if (boothowto & RB_KDB) kdb_enter(KDB_WHY_BOOTFLAGS, "Boot flags requested debugger"); #endif } void platform_reset(void) { struct bcm_platform *bp; bool bcm4785war; printf("bcm::platform_reset()\n"); intr_disable(); #ifdef CFE /* Fall back on CFE if reset requested during platform * data initialization */ if (!bcm_platform_data_avail) { cfe_exit(0, 0); while (1); } #endif bp = bcm_get_platform(); bcm4785war = false; /* Handle BCM4785-specific behavior */ if (bp->cid.chip_id == BHND_CHIPID_BCM4785) { bcm4785war = true; /* Switch to async mode */ bcm_bmips_wr_pllcfg3(BMIPS_BCMCFG_PLLCFG3_SM); } /* Set watchdog (PMU or ChipCommon) */ if (bp->pmu_addr != 0x0) { BCM_PMU_WRITE_4(bp, BHND_PMU_WATCHDOG, 1); } else BCM_CHIPC_WRITE_4(bp, CHIPC_WATCHDOG, 1); /* BCM4785 */ if (bcm4785war) { mips_sync(); __asm __volatile("wait"); } while (1); } void platform_start(__register_t a0, __register_t a1, __register_t a2, __register_t a3) { vm_offset_t kernend; uint64_t platform_counter_freq; int error; /* clear the BSS and SBSS segments */ kernend = (vm_offset_t)&end; memset(&edata, 0, kernend - (vm_offset_t)(&edata)); mips_postboot_fixup(); /* Initialize pcpu stuff */ mips_pcpu0_init(); #ifdef CFE /* * Initialize CFE firmware trampolines. This must be done * before any CFE APIs are called, including writing * to the CFE console. * * CFE passes the following values in registers: * a0: firmware handle * a2: firmware entry point * a3: entry point seal */ if (a3 == CFE_EPTSEAL) cfe_init(a0, a2); #endif /* Init BCM platform data */ if ((error = bcm_init_platform_data(&bcm_platform_data))) panic("bcm_init_platform_data() failed: %d", error); platform_counter_freq = bcm_get_cpufreq(bcm_get_platform()); /* CP0 ticks every two cycles */ mips_timer_early_init(platform_counter_freq / 2); cninit(); mips_init(); mips_timer_init_params(platform_counter_freq, 1); } /* * CFE-based EARLY_PRINTF support. To use, add the following to the kernel * config: * option EARLY_PRINTF * option CFE * device cfe */ #if defined(EARLY_PRINTF) && defined(CFE) static void bcm_cfe_eputc(int c) { unsigned char ch; int handle; ch = (unsigned char) c; /* bcm_get_platform() cannot be used here, as we may be called * from bcm_init_platform_data(). */ if ((handle = bcm_platform_data.cfe_console) < 0) return; if (ch == '\n') early_putc('\r'); while ((cfe_write(handle, &ch, 1)) == 0) continue; } early_putc_t *early_putc = bcm_cfe_eputc; #endif /* EARLY_PRINTF */ Index: head/sys/mips/broadcom/bcm_machdep.h =================================================================== --- head/sys/mips/broadcom/bcm_machdep.h (revision 315865) +++ head/sys/mips/broadcom/bcm_machdep.h (revision 315866) @@ -1,114 +1,123 @@ /*- * 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 _MIPS_BROADCOM_BCM_MACHDEP_H_ #define _MIPS_BROADCOM_BCM_MACHDEP_H_ #include #include #include #include #include +#include "bcm_nvram_cfevar.h" + extern const struct bhnd_pmu_io bcm_pmu_soc_io; struct bcm_platform { struct bhnd_chipid cid; /**< chip id */ struct bhnd_core_info cc_id; /**< chipc core info */ uintptr_t cc_addr; /**< chipc core phys address */ uint32_t cc_caps; /**< chipc capabilities */ uint32_t cc_caps_ext; /**< chipc extended capabilies */ /* On non-AOB devices, the PMU register block is mapped to chipc; * the pmu_id and pmu_addr values will be copied from cc_id * and cc_addr. */ struct bhnd_core_info pmu_id; /**< PMU core info */ uintptr_t pmu_addr; /**< PMU core phys address, or 0x0 if no PMU */ struct bhnd_pmu_query pmu; /**< PMU query instance */ bhnd_erom_class_t *erom_impl; /**< erom parser class */ struct kobj_ops erom_ops; /**< compiled kobj opcache */ union { bhnd_erom_static_t data; bhnd_erom_t obj; } erom; + struct bhnd_nvram_io *nvram_io; /**< NVRAM I/O context, or NULL if unavailable */ + bhnd_nvram_data_class *nvram_cls; /**< NVRAM data class, or NULL if unavailable */ + #ifdef CFE int cfe_console; /**< Console handle, or -1 */ #endif }; struct bcm_platform *bcm_get_platform(void); uint64_t bcm_get_cpufreq(struct bcm_platform *bp); uint64_t bcm_get_sifreq(struct bcm_platform *bp); uint64_t bcm_get_alpfreq(struct bcm_platform *bp); uint64_t bcm_get_ilpfreq(struct bcm_platform *bp); u_int bcm_get_uart_rclk(struct bcm_platform *bp); + +int bcm_get_nvram(struct bcm_platform *bp, + const char *name, void *outp, size_t *olen, + bhnd_nvram_type type); #define BCM_ERR(fmt, ...) \ printf("%s: " fmt, __FUNCTION__, ##__VA_ARGS__) #define BCM_SOC_BSH(_addr, _offset) \ ((bus_space_handle_t)BCM_SOC_ADDR((_addr), (_offset))) #define BCM_SOC_ADDR(_addr, _offset) \ MIPS_PHYS_TO_KSEG1((_addr) + (_offset)) #define BCM_SOC_READ_4(_addr, _offset) \ readl(BCM_SOC_ADDR((_addr), (_offset))) #define BCM_SOC_WRITE_4(_addr, _reg, _val) \ writel(BCM_SOC_ADDR((_addr), (_offset)), (_val)) #define BCM_CORE_ADDR(_bp, _name, _reg) \ BCM_SOC_ADDR(_bp->_name, (_reg)) #define BCM_CORE_READ_4(_bp, _name, _reg) \ readl(BCM_CORE_ADDR(_bp, _name, (_reg))) #define BCM_CORE_WRITE_4(_bp, _name, _reg, _val) \ writel(BCM_CORE_ADDR(_bp, _name, (_reg)), (_val)) #define BCM_CHIPC_READ_4(_bp, _reg) \ BCM_CORE_READ_4(_bp, cc_addr, (_reg)) #define BCM_CHIPC_WRITE_4(_bp, _reg, _val) \ BCM_CORE_WRITE_4(_bp, cc_addr, (_reg), (_val)) #define BCM_PMU_READ_4(_bp, _reg) \ BCM_CORE_READ_4(_bp, pmu_addr, (_reg)) #define BCM_PMU_WRITE_4(_bp, _reg, _val) \ BCM_CORE_WRITE_4(_bp, pmu_addr, (_reg), (_val)) #endif /* _MIPS_BROADCOM_BCM_MACHDEP_H_ */ Index: head/sys/mips/broadcom/bcm_nvram_cfe.c =================================================================== --- head/sys/mips/broadcom/bcm_nvram_cfe.c (revision 315865) +++ head/sys/mips/broadcom/bcm_nvram_cfe.c (revision 315866) @@ -1,525 +1,485 @@ /*- * 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$"); /* * BHND CFE NVRAM driver. * * Provides access to device NVRAM via CFE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include - #include "bhnd_nvram_if.h" +#include "bcm_machdep.h" #include "bcm_nvram_cfevar.h" -/** - * CFE-backed bhnd_nvram_io implementation. - */ -struct bhnd_nvram_iocfe { - struct bhnd_nvram_io io; /**< common I/O instance state */ - - char *dname; /**< CFE device name (borrowed) */ - int fd; /**< CFE file descriptor */ - size_t offset; /**< base offset */ - size_t size; /**< device size */ - bool req_blk_erase; /**< flash blocks must be erased - before writing */ -}; - BHND_NVRAM_IOPS_DEFN(iocfe) #define IOCFE_LOG(_io, _fmt, ...) \ printf("%s/%s: " _fmt, __FUNCTION__, (_io)->dname, ##__VA_ARGS__) -static int bhnd_nvram_iocfe_new(struct bhnd_nvram_io **io, - char *dname); +static int bcm_nvram_iocfe_init(struct bcm_nvram_iocfe *iocfe, + char *dname); -static struct bhnd_nvram_io *bhnd_nvram_find_cfedev(device_t dev, - char **dname, bhnd_nvram_data_class **cls); - /** Known CFE NVRAM device names, in probe order. */ static char *nvram_cfe_devs[] = { "nflash0.nvram", /* NAND */ "nflash1.nvram", "flash0.nvram", "flash1.nvram", }; /** Supported CFE NVRAM formats, in probe order. */ static bhnd_nvram_data_class * const nvram_cfe_fmts[] = { &bhnd_nvram_bcm_class, &bhnd_nvram_tlv_class }; - static int bhnd_nvram_cfe_probe(device_t dev) { - struct bhnd_nvram_io *io; - bhnd_nvram_data_class *cls; - const char *cls_desc; - char *dname; - char *desc; + struct bcm_platform *bp; - /* Locate a usable CFE device */ - io = bhnd_nvram_find_cfedev(dev, &dname, &cls); - if (io == NULL) + /* Fetch platform NVRAM I/O context */ + bp = bcm_get_platform(); + if (bp->nvram_io == NULL) return (ENXIO); - bhnd_nvram_io_free(io); - /* Format the device description */ - cls_desc = bhnd_nvram_data_class_desc(cls); - asprintf(&desc, M_DEVBUF, "%s CFE %s", cls_desc, dname); - if (desc != NULL) { - device_set_desc_copy(dev, desc); - free(desc, M_DEVBUF); - } else { - device_set_desc(dev, cls_desc); - } + KASSERT(bp->nvram_cls != NULL, ("missing NVRAM class")); + /* Set the device description */ + device_set_desc(dev, bhnd_nvram_data_class_desc(bp->nvram_cls)); + /* Refuse wildcard attachments */ return (BUS_PROBE_NOWILDCARD); } static int bhnd_nvram_cfe_attach(device_t dev) { + struct bcm_platform *bp; struct bhnd_nvram_cfe_softc *sc; - bhnd_nvram_data_class *cls; - struct bhnd_nvram_io *io; - char *dname; int error; + bp = bcm_get_platform(); + KASSERT(bp->nvram_io != NULL, ("missing NVRAM I/O context")); + KASSERT(bp->nvram_cls != NULL, ("missing NVRAM class")); + sc = device_get_softc(dev); sc->dev = dev; - /* Locate NVRAM device via CFE */ - io = bhnd_nvram_find_cfedev(dev, &dname, &cls); - if (io == NULL) { - device_printf(dev, "CFE NVRAM device not found\n"); - return (ENXIO); - } - - /* Initialize NVRAM store and free the I/O context */ - error = bhnd_nvram_store_parse_new(&sc->store, io, cls); - bhnd_nvram_io_free(io); + error = bhnd_nvram_store_parse_new(&sc->store, bp->nvram_io, + bp->nvram_cls); if (error) return (error); return (error); } static int bhnd_nvram_cfe_resume(device_t dev) { return (0); } static int bhnd_nvram_cfe_suspend(device_t dev) { return (0); } static int bhnd_nvram_cfe_detach(device_t dev) { struct bhnd_nvram_cfe_softc *sc; sc = device_get_softc(dev); bhnd_nvram_store_free(sc->store); return (0); } static int bhnd_nvram_cfe_getvar(device_t dev, const char *name, void *buf, size_t *len, bhnd_nvram_type type) { struct bhnd_nvram_cfe_softc *sc = device_get_softc(dev); return (bhnd_nvram_store_getvar(sc->store, name, buf, len, type)); } static int bhnd_nvram_cfe_setvar(device_t dev, const char *name, const void *buf, size_t len, bhnd_nvram_type type) { struct bhnd_nvram_cfe_softc *sc = device_get_softc(dev); return (bhnd_nvram_store_setvar(sc->store, name, buf, len, type)); } /** - * Find, open, identify, and return an I/O context mapping our - * CFE NVRAM device. + * Find, open, identify, and initialize an I/O context mapping the CFE NVRAM + * device. * - * @param dev bhnd_nvram_cfe device. - * @param[out] dname On success, the CFE device name. + * @param[out] iocfe On success, an I/O context mapping the CFE NVRAM + * device. * @param[out] cls On success, the identified NVRAM data format * class. * - * @retval non-NULL success. the caller inherits ownership of the returned - * NVRAM I/O context. - * @retval NULL if no usable CFE NVRAM device could be found. + * @retval 0 success. the caller inherits ownership of @p iocfe. + * @retval non-zero if no usable CFE NVRAM device can be found, a standard + * unix error will be returned. */ -static struct bhnd_nvram_io * -bhnd_nvram_find_cfedev(device_t dev, char **dname, bhnd_nvram_data_class **cls) +int +bcm_nvram_find_cfedev(struct bcm_nvram_iocfe *iocfe, + bhnd_nvram_data_class **cls) { - struct bhnd_nvram_io *io; - int devinfo; - int error, result; + char *dname; + int devinfo; + int error, result; for (u_int i = 0; i < nitems(nvram_cfe_fmts); i++) { *cls = nvram_cfe_fmts[i]; for (u_int j = 0; j < nitems(nvram_cfe_devs); j++) { - *dname = nvram_cfe_devs[j]; + dname = nvram_cfe_devs[j]; /* Does the device exist? */ - if ((devinfo = cfe_getdevinfo(*dname)) < 0) { + if ((devinfo = cfe_getdevinfo(dname)) < 0) { if (devinfo != CFE_ERR_DEVNOTFOUND) { - device_printf(dev, "cfe_getdevinfo(%s) " - "failed: %d\n", *dname, devinfo); + BCM_ERR("cfe_getdevinfo(%s) failed: " + "%d\n", dname, devinfo); } continue; } /* Open for reading */ - if ((error = bhnd_nvram_iocfe_new(&io, *dname))) + if ((error = bcm_nvram_iocfe_init(iocfe, dname))) continue; /* Probe */ - result = bhnd_nvram_data_probe(*cls, io); + result = bhnd_nvram_data_probe(*cls, &iocfe->io); if (result <= 0) { /* Found a supporting NVRAM data class */ - return (io); + return (0); } /* Keep searching */ - bhnd_nvram_io_free(io); - io = NULL; + bhnd_nvram_io_free(&iocfe->io); } } - return (NULL); + return (ENODEV); } /** - * Allocate and return a new I/O context backed by a CFE device. + * Initialize a new CFE device-backed I/O context. * - * The caller is responsible for deallocating the returned I/O context via - * bhnd_nvram_io_free(). + * The caller is responsible for releasing all resources held by the returned + * I/O context via bhnd_nvram_io_free(). * - * @param[out] io On success, a valid I/O context for @p dname. - * @param dname The name of the CFE device to be opened for reading. + * @param[out] io On success, will be initialized as an I/O context for + * CFE device @p dname. + * @param dname The name of the CFE device to be opened for reading. * - * @retval 0 success. - * @retval non-zero if opening @p dname otherwise fails, a standard unix error - * will be returned. + * @retval 0 success. + * @retval non-zero if opening @p dname otherwise fails, a standard unix + * error will be returned. */ static int -bhnd_nvram_iocfe_new(struct bhnd_nvram_io **io, char *dname) +bcm_nvram_iocfe_init(struct bcm_nvram_iocfe *iocfe, char *dname) { - struct bhnd_nvram_iocfe *iocfe; nvram_info_t nvram_info; int cerr, devinfo, dtype, rlen; int64_t nv_offset; u_int nv_size; bool req_blk_erase; int error; - iocfe = malloc(sizeof(*iocfe), M_DEVBUF, M_WAITOK); iocfe->io.iops = &bhnd_nvram_iocfe_ops; iocfe->dname = dname; /* Try to open the device */ iocfe->fd = cfe_open(dname); if (iocfe->fd <= 0) { IOCFE_LOG(iocfe, "cfe_open() failed: %d\n", iocfe->fd); - error = ENXIO; - goto failed; + return (ENXIO); } /* Try to fetch device info */ if ((devinfo = cfe_getdevinfo(iocfe->dname)) < 0) { IOCFE_LOG(iocfe, "cfe_getdevinfo() failed: %d\n", devinfo); error = ENXIO; goto failed; } /* Verify device type */ dtype = devinfo & CFE_DEV_MASK; switch (dtype) { case CFE_DEV_FLASH: case CFE_DEV_NVRAM: /* Valid device type */ break; default: IOCFE_LOG(iocfe, "unknown device type: %d\n", dtype); error = ENXIO; goto failed; } /* Try to fetch nvram info from CFE */ cerr = cfe_ioctl(iocfe->fd, IOCTL_NVRAM_GETINFO, (unsigned char *)&nvram_info, sizeof(nvram_info), &rlen, 0); if (cerr == CFE_OK) { /* Sanity check the result; must not be a negative integer */ if (nvram_info.nvram_size < 0 || nvram_info.nvram_offset < 0) { IOCFE_LOG(iocfe, "invalid NVRAM layout (%d/%d)\n", nvram_info.nvram_size, nvram_info.nvram_offset); error = ENXIO; goto failed; } nv_offset = nvram_info.nvram_offset; nv_size = nvram_info.nvram_size; req_blk_erase = (nvram_info.nvram_eraseflg != 0); } else if (cerr != CFE_OK && cerr != CFE_ERR_INV_COMMAND) { IOCFE_LOG(iocfe, "IOCTL_NVRAM_GETINFO failed: %d\n", cerr); error = ENXIO; goto failed; } /* Fall back on flash info. * * This is known to be required on the Asus RT-N53 (CFE 5.70.55.33, * BBP 1.0.37, BCM5358UB0), where IOCTL_NVRAM_GETINFO returns * CFE_ERR_INV_COMMAND. */ if (cerr == CFE_ERR_INV_COMMAND) { flash_info_t fi; cerr = cfe_ioctl(iocfe->fd, IOCTL_FLASH_GETINFO, (unsigned char *)&fi, sizeof(fi), &rlen, 0); if (cerr != CFE_OK) { IOCFE_LOG(iocfe, "IOCTL_FLASH_GETINFO failed %d\n", cerr); error = ENXIO; goto failed; } nv_offset = 0x0; nv_size = fi.flash_size; req_blk_erase = !(fi.flash_flags & FLASH_FLAG_NOERASE); } /* Verify that the full NVRAM layout can be represented via size_t */ if (nv_size > SIZE_MAX || SIZE_MAX - nv_size < nv_offset) { IOCFE_LOG(iocfe, "invalid NVRAM layout (%#x/%#jx)\n", nv_size, (intmax_t)nv_offset); error = ENXIO; goto failed; } iocfe->offset = nv_offset; iocfe->size = nv_size; iocfe->req_blk_erase = req_blk_erase; - *io = &iocfe->io; return (CFE_OK); failed: if (iocfe->fd >= 0) cfe_close(iocfe->fd); - free(iocfe, M_DEVBUF); - - *io = NULL; return (error); } static void bhnd_nvram_iocfe_free(struct bhnd_nvram_io *io) { - struct bhnd_nvram_iocfe *iocfe = (struct bhnd_nvram_iocfe *)io; + struct bcm_nvram_iocfe *iocfe = (struct bcm_nvram_iocfe *)io; + /* CFE I/O instances are statically allocated; we do not need to free + * the instance itself */ cfe_close(iocfe->fd); - free(io, M_DEVBUF); } static size_t bhnd_nvram_iocfe_getsize(struct bhnd_nvram_io *io) { - struct bhnd_nvram_iocfe *iocfe = (struct bhnd_nvram_iocfe *)io; + struct bcm_nvram_iocfe *iocfe = (struct bcm_nvram_iocfe *)io; return (iocfe->size); } static int bhnd_nvram_iocfe_setsize(struct bhnd_nvram_io *io, size_t size) { /* unsupported */ return (ENODEV); } static int bhnd_nvram_iocfe_read_ptr(struct bhnd_nvram_io *io, size_t offset, const void **ptr, size_t nbytes, size_t *navail) { /* unsupported */ return (ENODEV); } static int bhnd_nvram_iocfe_write_ptr(struct bhnd_nvram_io *io, size_t offset, void **ptr, size_t nbytes, size_t *navail) { /* unsupported */ return (ENODEV); } static int bhnd_nvram_iocfe_write(struct bhnd_nvram_io *io, size_t offset, void *buffer, size_t nbytes) { /* unsupported */ return (ENODEV); } static int bhnd_nvram_iocfe_read(struct bhnd_nvram_io *io, size_t offset, void *buffer, size_t nbytes) { - struct bhnd_nvram_iocfe *iocfe; + struct bcm_nvram_iocfe *iocfe; size_t remain; int64_t cfe_offset; int nr, nreq; - iocfe = (struct bhnd_nvram_iocfe *)io; + iocfe = (struct bcm_nvram_iocfe *)io; /* Determine (and validate) the base CFE offset */ #if (SIZE_MAX > INT64_MAX) if (iocfe->offset > INT64_MAX || offset > INT64_MAX) return (ENXIO); #endif if (INT64_MAX - offset < iocfe->offset) return (ENXIO); cfe_offset = iocfe->offset + offset; /* Verify that cfe_offset + nbytes is representable */ if (INT64_MAX - cfe_offset < nbytes) return (ENXIO); /* Perform the read */ for (remain = nbytes; remain > 0;) { void *p; size_t nread; int64_t cfe_noff; nread = (nbytes - remain); cfe_noff = cfe_offset + nread; p = ((uint8_t *)buffer + nread); nreq = ummin(INT_MAX, remain); nr = cfe_readblk(iocfe->fd, cfe_noff, p, nreq); if (nr < 0) { IOCFE_LOG(iocfe, "cfe_readblk() failed: %d\n", nr); return (ENXIO); } /* Check for unexpected short read */ if (nr == 0 && remain > 0) { /* If the request fits entirely within the CFE * device range, we shouldn't hit EOF */ if (remain < iocfe->size && iocfe->size - remain > offset) { IOCFE_LOG(iocfe, "cfe_readblk() returned " "unexpected short read (%d/%d)\n", nr, nreq); return (ENXIO); } } if (nr == 0) break; remain -= nr; } /* Check for short read */ if (remain > 0) return (ENXIO); return (0); } static device_method_t bhnd_nvram_cfe_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhnd_nvram_cfe_probe), DEVMETHOD(device_attach, bhnd_nvram_cfe_attach), DEVMETHOD(device_resume, bhnd_nvram_cfe_resume), DEVMETHOD(device_suspend, bhnd_nvram_cfe_suspend), DEVMETHOD(device_detach, bhnd_nvram_cfe_detach), /* NVRAM interface */ DEVMETHOD(bhnd_nvram_getvar, bhnd_nvram_cfe_getvar), DEVMETHOD(bhnd_nvram_setvar, bhnd_nvram_cfe_setvar), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd_nvram, bhnd_nvram_cfe, bhnd_nvram_cfe_methods, sizeof(struct bhnd_nvram_cfe_softc)); EARLY_DRIVER_MODULE(bhnd_nvram_cfe, nexus, bhnd_nvram_cfe, bhnd_nvram_devclass, NULL, NULL, BUS_PASS_BUS + BUS_PASS_ORDER_EARLY); Index: head/sys/mips/broadcom/bcm_nvram_cfevar.h =================================================================== --- head/sys/mips/broadcom/bcm_nvram_cfevar.h (revision 315865) +++ head/sys/mips/broadcom/bcm_nvram_cfevar.h (revision 315866) @@ -1,47 +1,67 @@ /*- * 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 _MIPS_BROADCOM_BCM_NVRAM_CFE_H_ #define _MIPS_BROADCOM_BCM_NVRAM_CFE_H_ #include #include #include +#include #include + +struct bcm_nvram_iocfe; + +int bcm_nvram_find_cfedev(struct bcm_nvram_iocfe *iocfe, + bhnd_nvram_data_class **cls); + +/** + * CFE-backed bhnd_nvram_io implementation. + */ +struct bcm_nvram_iocfe { + struct bhnd_nvram_io io; /**< common I/O instance state */ + + char *dname; /**< CFE device name (borrowed) */ + int fd; /**< CFE file descriptor */ + size_t offset; /**< base offset */ + size_t size; /**< device size */ + bool req_blk_erase; /**< flash blocks must be erased + before writing */ +}; /** bhnd_nvram_cfe driver instance state. */ struct bhnd_nvram_cfe_softc { device_t dev; struct bhnd_nvram_store *store; /**< nvram store */ }; #endif /* _MIPS_BROADCOM_BCM_NVRAM_CFE_H_ */