Changeset View
Changeset View
Standalone View
Standalone View
sys/mips/broadcom/bcm_nvram_cfe.c
- This file was added.
/*- | |||||
* 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 <dev/bhnd/nvram/bhnd_nvram_iovar.h> | |||||
#include "bhnd_nvram_if.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 struct bhnd_nvram_io *bhnd_nvram_find_cfedev(device_t dev, | |||||
char **dname, | |||||
bhnd_nvram_data_class_t **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_t * 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_t *cls; | |||||
const char *cls_desc; | |||||
char *dname; | |||||
char *desc; | |||||
/* Locate a usable CFE device */ | |||||
io = bhnd_nvram_find_cfedev(dev, &dname, &cls); | |||||
if (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); | |||||
} | |||||
/* Refuse wildcard attachments */ | |||||
return (BUS_PROBE_NOWILDCARD); | |||||
} | |||||
static int | |||||
bhnd_nvram_cfe_attach(device_t dev) | |||||
{ | |||||
struct bhnd_nvram_cfe_softc *sc; | |||||
bhnd_nvram_data_class_t *cls; | |||||
struct bhnd_nvram_io *io; | |||||
char *dname; | |||||
int error; | |||||
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); | |||||
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. | |||||
* | |||||
* @param dev bhnd_nvram_cfe device. | |||||
* @param[out] dname On success, the CFE device name. | |||||
* @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. | |||||
*/ | |||||
static struct bhnd_nvram_io * | |||||
bhnd_nvram_find_cfedev(device_t dev, char **dname, | |||||
bhnd_nvram_data_class_t **cls) | |||||
{ | |||||
struct bhnd_nvram_io *io; | |||||
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]; | |||||
/* Does the device exist? */ | |||||
if ((devinfo = cfe_getdevinfo(*dname)) < 0) { | |||||
if (devinfo != CFE_ERR_DEVNOTFOUND) { | |||||
device_printf(dev, "cfe_getdevinfo(%s) " | |||||
"failed: %d\n", *dname, devinfo); | |||||
} | |||||
continue; | |||||
} | |||||
/* Open for reading */ | |||||
if ((error = bhnd_nvram_iocfe_new(&io, *dname))) | |||||
continue; | |||||
/* Probe */ | |||||
result = bhnd_nvram_data_probe(*cls, io); | |||||
if (result <= 0) { | |||||
/* Found a supporting NVRAM data class */ | |||||
return (io); | |||||
} | |||||
/* Keep searching */ | |||||
bhnd_nvram_io_free(io); | |||||
io = NULL; | |||||
} | |||||
} | |||||
return (NULL); | |||||
} | |||||
/** | |||||
* Allocate and return a new I/O context backed by a CFE device. | |||||
* | |||||
* The caller is responsible for deallocating 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. | |||||
* | |||||
* @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) | |||||
{ | |||||
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; | |||||
} | |||||
/* 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; | |||||
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; | |||||
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; | |||||
size_t remain; | |||||
int64_t cfe_offset; | |||||
int nr, nreq; | |||||
iocfe = (struct bhnd_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); |