root/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c
/*-
 * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
 * 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 <sys/cdefs.h>
#ifdef _KERNEL

#include <sys/param.h>
#include <sys/ctype.h>
#include <sys/malloc.h>
#include <sys/systm.h>

#else /* !_KERNEL */

#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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 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 the 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);
}