- Added a generic bhnd_nvram_parser API, with support for the TLV format used on WGT634U devices, the standard BCM NVRAM format used on most modern devices, and the "board text file" format used on some hardware to supply external NVRAM data at runtime (e.g. via an EFI variable). - Extended the bhnd_bus_if and bhnd_nvram_if interfaces to support both string-based and primitive data type variable access, required for common behavior across both SPROM and NVRAM data sources. - Extended the existing SPROM implementation to support the new string-based NVRAM APIs. - Added an abstract bhnd_nvram driver, implementing the bhnd_nvram_if atop the bhnd_nvram_parser API. - Added a CFE-based bhnd_nvram driver to provide read-only access to NVRAM data on MIPS SoCs, pending implementation of a flash-aware bhnd_nvram driver. Approved by: adrian (mentor) Differential Revision: https://reviews.freebsd.org/D7489
374 lines
9.6 KiB
C
374 lines
9.6 KiB
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>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* BHND CFE NVRAM driver.
|
|
*
|
|
* Provides access to device NVRAM via CFE.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <sys/rman.h>
|
|
#include <machine/resource.h>
|
|
|
|
#include <dev/bhnd/bhnd.h>
|
|
|
|
#include <dev/cfe/cfe_api.h>
|
|
#include <dev/cfe/cfe_error.h>
|
|
#include <dev/cfe/cfe_ioctl.h>
|
|
|
|
#include "bhnd_nvram_if.h"
|
|
|
|
#include "bhnd_nvramvar.h"
|
|
|
|
static int nvram_open_cfedev(device_t dev, char *devname, int fd,
|
|
int64_t *offset, uint32_t *size, bhnd_nvram_format fmt);
|
|
static char *nvram_find_cfedev(device_t dev, int *fd, int64_t *offset,
|
|
uint32_t *size, bhnd_nvram_format *fmt);
|
|
|
|
/** 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. */
|
|
bhnd_nvram_format nvram_cfe_fmts[] = {
|
|
BHND_NVRAM_FMT_BCM,
|
|
BHND_NVRAM_FMT_TLV
|
|
};
|
|
|
|
|
|
static int
|
|
bhnd_nvram_cfe_probe(device_t dev)
|
|
{
|
|
char *devname;
|
|
bhnd_nvram_format fmt;
|
|
int64_t offset;
|
|
uint32_t size;
|
|
int error;
|
|
int fd;
|
|
|
|
/* Defer to default driver implementation */
|
|
if ((error = bhnd_nvram_probe(dev)) > 0)
|
|
return (error);
|
|
|
|
/* Locate a usable CFE device */
|
|
devname = nvram_find_cfedev(dev, &fd, &offset, &size, &fmt);
|
|
if (devname == NULL)
|
|
return (ENXIO);
|
|
cfe_close(fd);
|
|
|
|
switch (fmt) {
|
|
case BHND_NVRAM_FMT_BCM:
|
|
device_set_desc(dev, "Broadcom NVRAM");
|
|
break;
|
|
case BHND_NVRAM_FMT_TLV:
|
|
device_set_desc(dev, "Broadcom WGT634U NVRAM");
|
|
break;
|
|
default:
|
|
device_printf(dev, "unknown NVRAM format: %d\n", fmt);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Refuse wildcard attachments */
|
|
return (BUS_PROBE_NOWILDCARD);
|
|
}
|
|
|
|
|
|
static int
|
|
bhnd_nvram_cfe_attach(device_t dev)
|
|
{
|
|
char *devname;
|
|
unsigned char *buffer;
|
|
bhnd_nvram_format fmt;
|
|
int64_t offset;
|
|
uint32_t size;
|
|
int error;
|
|
int fd;
|
|
|
|
error = 0;
|
|
buffer = NULL;
|
|
fd = CFE_ERR;
|
|
|
|
/* Locate NVRAM device via CFE */
|
|
devname = nvram_find_cfedev(dev, &fd, &offset, &size, &fmt);
|
|
if (devname == NULL) {
|
|
device_printf(dev, "CFE NVRAM device not found\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Copy out NVRAM buffer */
|
|
buffer = malloc(size, M_TEMP, M_NOWAIT);
|
|
if (buffer == NULL)
|
|
return (ENOMEM);
|
|
|
|
for (size_t remain = size; remain > 0;) {
|
|
int nr, req;
|
|
|
|
req = ulmin(INT_MAX, remain);
|
|
nr = cfe_readblk(fd, size-remain, buffer+(size-remain),
|
|
req);
|
|
if (nr < 0) {
|
|
device_printf(dev, "%s: cfe_readblk() failed: %d\n",
|
|
devname, fd);
|
|
|
|
error = ENXIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
remain -= nr;
|
|
|
|
if (nr == 0 && remain > 0) {
|
|
device_printf(dev, "%s: cfe_readblk() unexpected EOF: "
|
|
"%zu of %zu pending\n", devname, remain, size);
|
|
|
|
error = ENXIO;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
device_printf(dev, "CFE %s (%#jx+%#jx)\n", devname, (uintmax_t)offset,
|
|
(uintmax_t)size);
|
|
|
|
/* Delegate to default driver implementation */
|
|
error = bhnd_nvram_attach(dev, buffer, size, fmt);
|
|
|
|
cleanup:
|
|
if (buffer != NULL)
|
|
free(buffer, M_TEMP);
|
|
|
|
if (fd >= 0)
|
|
cfe_close(fd);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/**
|
|
* Identify and open a CFE NVRAM device.
|
|
*
|
|
* @param dev bhnd_nvram_cfe device.
|
|
* @param devname The name of the CFE device to be probed.
|
|
* @param fd An open CFE file descriptor for @p devname.
|
|
* @param[out] offset On success, the NVRAM data offset within @p @fd.
|
|
* @param[out] size On success, maximum the NVRAM data size within @p fd.
|
|
* @param fmt The expected NVRAM data format for this device.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If probing @p devname fails, a regular unix
|
|
* error code will be returned.
|
|
*/
|
|
static int
|
|
nvram_open_cfedev(device_t dev, char *devname, int fd, int64_t *offset,
|
|
uint32_t *size, bhnd_nvram_format fmt)
|
|
{
|
|
union bhnd_nvram_ident ident;
|
|
nvram_info_t nvram_info;
|
|
int cerr, devinfo, dtype, rlen;
|
|
int error;
|
|
|
|
/* Try to fetch device info */
|
|
if ((devinfo = cfe_getdevinfo(devname)) == CFE_ERR_DEVNOTFOUND)
|
|
return (ENODEV);
|
|
|
|
if (devinfo < 0) {
|
|
device_printf(dev, "cfe_getdevinfo() failed: %d",
|
|
devinfo);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Verify device type */
|
|
dtype = devinfo & CFE_DEV_MASK;
|
|
switch (dtype) {
|
|
case CFE_DEV_FLASH:
|
|
case CFE_DEV_NVRAM:
|
|
/* Valid device type */
|
|
break;
|
|
default:
|
|
device_printf(dev, "%s: unknown device type %d\n",
|
|
devname, dtype);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Try to fetch nvram info from CFE */
|
|
cerr = cfe_ioctl(fd, IOCTL_NVRAM_GETINFO, (unsigned char *)&nvram_info,
|
|
sizeof(nvram_info), &rlen, 0);
|
|
if (cerr != CFE_OK && cerr != CFE_ERR_INV_COMMAND) {
|
|
device_printf(dev, "%s: IOCTL_NVRAM_GETINFO failed: %d\n",
|
|
devname, cerr);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* 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(fd, IOCTL_FLASH_GETINFO, (unsigned char *)&fi,
|
|
sizeof(fi), &rlen, 0);
|
|
|
|
if (cerr != CFE_OK) {
|
|
device_printf(dev, "%s: IOCTL_FLASH_GETINFO failed: "
|
|
"%d\n", devname, cerr);
|
|
return (ENXIO);
|
|
}
|
|
|
|
nvram_info.nvram_eraseflg =
|
|
!(fi.flash_flags & FLASH_FLAG_NOERASE);
|
|
nvram_info.nvram_offset = 0x0;
|
|
nvram_info.nvram_size = fi.flash_size;
|
|
}
|
|
|
|
/* Try to read NVRAM header/format identification */
|
|
cerr = cfe_readblk(fd, 0, (unsigned char *)&ident, sizeof(ident));
|
|
if (cerr < 0) {
|
|
device_printf(dev, "%s: cfe_readblk() failed: %d\n",
|
|
devname, cerr);
|
|
return (ENXIO);
|
|
} else if (cerr == 0) {
|
|
/* EOF */
|
|
return (ENODEV);
|
|
} else if (cerr != sizeof(ident)) {
|
|
device_printf(dev, "%s: cfe_readblk() short read: %d\n",
|
|
devname, cerr);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Verify expected format */
|
|
if ((error = bhnd_nvram_parser_identify(&ident, fmt)))
|
|
return (error);
|
|
|
|
/* Provide offset and size */
|
|
switch (fmt) {
|
|
case BHND_NVRAM_FMT_TLV:
|
|
/* No size field is available; must assume the NVRAM data
|
|
* consumes up to the full CFE NVRAM range */
|
|
*offset = nvram_info.nvram_offset;
|
|
*size = nvram_info.nvram_size;
|
|
break;
|
|
case BHND_NVRAM_FMT_BCM:
|
|
if (ident.bcm.size > nvram_info.nvram_size) {
|
|
device_printf(dev, "%s: NVRAM size %#x overruns %#x "
|
|
"device limit\n", devname, ident.bcm.size,
|
|
nvram_info.nvram_size);
|
|
return (ENODEV);
|
|
}
|
|
|
|
*offset = nvram_info.nvram_offset;
|
|
*size = ident.bcm.size;
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Find (and open) a CFE NVRAM device.
|
|
*
|
|
* @param dev bhnd_nvram_cfe device.
|
|
* @param[out] fd On success, a valid CFE file descriptor. The callee
|
|
* is responsible for closing this file descriptor via
|
|
* cfe_close().
|
|
* @param[out] offset On success, the NVRAM data offset within @p @fd.
|
|
* @param[out] size On success, maximum the NVRAM data size within @p fd.
|
|
* @param fmt The expected NVRAM data format for this device.
|
|
*
|
|
* @return On success, the opened CFE device's name will be returned. On
|
|
* error, returns NULL.
|
|
*/
|
|
static char *
|
|
nvram_find_cfedev(device_t dev, int *fd, int64_t *offset,
|
|
uint32_t *size, bhnd_nvram_format *fmt)
|
|
{
|
|
char *devname;
|
|
int error;
|
|
|
|
for (u_int i = 0; i < nitems(nvram_cfe_fmts); i++) {
|
|
*fmt = nvram_cfe_fmts[i];
|
|
|
|
for (u_int j = 0; j < nitems(nvram_cfe_devs); j++) {
|
|
devname = nvram_cfe_devs[j];
|
|
|
|
/* Open for reading */
|
|
*fd = cfe_open(devname);
|
|
if (*fd == CFE_ERR_DEVNOTFOUND) {
|
|
continue;
|
|
} else if (*fd < 0) {
|
|
device_printf(dev, "%s: cfe_open() failed: "
|
|
"%d\n", devname, *fd);
|
|
continue;
|
|
}
|
|
|
|
/* Probe */
|
|
error = nvram_open_cfedev(dev, devname, *fd, offset,
|
|
size, *fmt);
|
|
if (error == 0)
|
|
return (devname);
|
|
|
|
/* Keep searching */
|
|
devname = NULL;
|
|
cfe_close(*fd);
|
|
}
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
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_END
|
|
};
|
|
|
|
DEFINE_CLASS_1(bhnd_nvram, bhnd_nvram_cfe, bhnd_nvram_cfe_methods,
|
|
sizeof(struct bhnd_nvram_softc), bhnd_nvram_driver);
|
|
EARLY_DRIVER_MODULE(bhnd_nvram_cfe, nexus, bhnd_nvram_cfe,
|
|
bhnd_nvram_devclass, NULL, NULL, BUS_PASS_BUS + BUS_PASS_ORDER_EARLY);
|