#include <sys/cdefs.h>
#ifdef _KERNEL
#include <sys/param.h>
#include <sys/ctype.h>
#include <sys/limits.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#else
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif
#include "bhnd_nvram_private.h"
#include "bhnd_nvram_datavar.h"
#include "bhnd_nvram_data_tlvreg.h"
struct bhnd_nvram_tlv {
struct bhnd_nvram_data nv;
struct bhnd_nvram_io *data;
size_t count;
};
BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", BHND_NVRAM_DATA_CAP_DEVPATHS,
sizeof(struct bhnd_nvram_tlv))
struct bhnd_nvram_tlv_env_hdr {
uint8_t tag;
uint8_t size;
} __packed;
struct bhnd_nvram_tlv_env {
struct bhnd_nvram_tlv_env_hdr hdr;
uint8_t flags;
char envp[];
} __packed;
#define NVRAM_TLV_ENVP_DATA_LEN(_env) \
(((_env)->hdr.size < sizeof((_env)->flags)) ? 0 : \
((_env)->hdr.size - sizeof((_env)->flags)))
#define NVRAM_TLV_ENVP_DATA_MAX_LEN \
(UINT8_MAX - sizeof(uint8_t) )
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);
if (nbytes < sizeof(ident)) {
uint8_t tag;
error = bhnd_nvram_io_read(io, 0x0, &tag, sizeof(tag));
if (error)
return (error);
if (tag == NVRAM_TLV_TYPE_END)
return (BHND_NVRAM_DATA_PROBE_MAYBE);
return (ENXIO);
}
error = bhnd_nvram_io_read(io, 0x0, &ident,
sizeof(ident) + sizeof(ident.envp[0]));
if (error)
return (error);
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");
if (ident.hdr.size < 3)
return (ENXIO);
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);
next = 0;
while (!(error = bhnd_nvram_tlv_next_record(io, &next, &off, &tag))) {
switch (tag) {
case NVRAM_TLV_TYPE_END:
return (ENOENT);
case NVRAM_TLV_TYPE_ENV:
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);
}
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);
}
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);
}
if (keylen == namelen &&
strncmp(key, name, namelen) == 0)
{
return (bhnd_nvram_value_coerce(value, vlen,
BHND_NVRAM_TYPE_STRING, buf, len, type));
}
break;
default:
break;
}
}
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;
if (outp != NULL)
limit = *olen;
else
limit = 0;
nbytes = 0;
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;
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);
}
env.hdr.size += name_len;
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);
}
env.hdr.size += value_len;
rec_size = sizeof(env.hdr) + env.hdr.size;
if (SIZE_MAX - nbytes < rec_size)
return (EFTYPE);
if (nbytes > limit || limit - nbytes < rec_size) {
p = NULL;
} else {
p = (uint8_t *)outp + nbytes;
}
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;
}
if (limit > nbytes)
*((uint8_t *)outp + nbytes) = NVRAM_TLV_TYPE_END;
if (nbytes == SIZE_MAX)
return (EFTYPE);
nbytes++;
*olen = nbytes;
if (limit < *olen) {
if (outp == NULL)
return (0);
return (ENOMEM);
}
return (0);
}
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"));
if ((error = bhnd_nvram_tlv_parse_size(src, &size)))
return (error);
if ((tlv->data = bhnd_nvram_iobuf_copy_range(src, 0x0, size)) == NULL)
return (ENOMEM);
tlv->count = 0;
next = 0;
while ((env = bhnd_nvram_tlv_next_env(tlv, &next, NULL)) != NULL) {
size_t env_len;
size_t name_len;
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);
}
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);
}
*(env->envp + name_len) = '\0';
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;
tlv = (struct bhnd_nvram_tlv *)nv;
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;
if (*cookiep == NULL) {
io_offset = 0x0;
env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep);
} else {
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");
env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep);
}
if (env == NULL)
return (NULL);
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;
if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL)
BHND_NV_PANIC("invalid cookiep: %p", cookiep);
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 = 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;
if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL)
BHND_NV_PANIC("invalid cookiep: %p", cookiep);
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;
if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
return (EINVAL);
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;
error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
value, BHND_NVRAM_VAL_DYNAMIC);
if (error)
return (error);
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;
*result = str;
return (0);
}
static int
bhnd_nvram_tlv_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
{
return (0);
}
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);
if (offset != NULL)
*offset = io_offset;
error = bhnd_nvram_io_read(io, io_offset, tag, sizeof(*tag));
if (error)
return (error);
io_offset++;
if (*tag == NVRAM_TLV_TYPE_END) {
*next = io_offset;
return (0);
}
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;
}
if (parsed_len > io_size || io_size - parsed_len < io_offset) {
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;
return (0);
}
static int
bhnd_nvram_tlv_parse_size(struct bhnd_nvram_io *io, size_t *size)
{
size_t next;
uint8_t tag;
int error;
next = 0x0;
*size = 0x0;
do {
error = bhnd_nvram_tlv_next_record(io, &next, NULL, &tag);
if (error)
return (error);
} while (tag != NVRAM_TLV_TYPE_END);
BHND_NV_ASSERT(next <= bhnd_nvram_io_getsize(io),
("parse returned invalid EOF offset"));
*size = next;
return (0);
}
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;
do {
void *c;
size_t offset;
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);
}
if (tag != NVRAM_TLV_TYPE_ENV)
continue;
c = bhnd_nvram_tlv_to_cookie(tlv, offset);
if (cookiep != NULL)
*cookiep = c;
return (bhnd_nvram_tlv_get_env(tlv, c));
} while (tag != NVRAM_TLV_TYPE_END);
return (NULL);
}
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);
if (io_offset == io_size)
return (NULL);
error = bhnd_nvram_io_write_ptr(tlv->data, io_offset, &ptr,
sizeof(env->hdr), &navail);
if (error) {
BHND_NV_LOG("error mapping record for cookiep: %d\n", error);
return (NULL);
}
env = ptr;
if (env->hdr.tag != NVRAM_TLV_TYPE_ENV) {
BHND_NV_LOG("non-ENV record mapped for %p\n", cookiep);
return (NULL);
}
if (navail < sizeof(struct bhnd_nvram_tlv_env_hdr) + env->hdr.size ||
env->hdr.size == sizeof(env->flags))
{
BHND_NV_LOG("TLV_ENV variable data not mapped for %p\n",
cookiep);
return (NULL);
}
return (env);
}
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));
}
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);
}