1/*- 2 * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org> 3 * Copyright (c) 2017 The FreeBSD Foundation 4 * All rights reserved. 5 * 6 * Portions of this software were developed by Landon Fuller 7 * under sponsorship from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer, 14 * without modification. 15 * 2. Redistributions in binary form must reproduce at minimum a disclaimer 16 * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any 17 * redistribution must be conditioned upon including a substantially 18 * similar Disclaimer requirement for further binary redistribution. 19 * 20 * NO WARRANTY 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY 24 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 25 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, 26 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 29 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 31 * THE POSSIBILITY OF SUCH DAMAGES. 32 */ 33 34#include <sys/cdefs.h> 35__FBSDID("$FreeBSD$"); 36 37/* 38 * BHND CFE NVRAM driver. 39 * 40 * Provides access to device NVRAM via CFE. 41 */ 42 43#include <sys/param.h> 44#include <sys/kernel.h> 45#include <sys/bus.h> 46#include <sys/limits.h> 47#include <sys/malloc.h> 48#include <sys/module.h> 49#include <sys/systm.h> 50 51#include <machine/bus.h> 52#include <sys/rman.h> 53#include <machine/resource.h> 54 55#include <dev/bhnd/bhnd.h> 56 57#include <dev/cfe/cfe_api.h> 58#include <dev/cfe/cfe_error.h> 59#include <dev/cfe/cfe_ioctl.h> 60 61#include "bhnd_nvram_if.h" 62 63#include "bcm_machdep.h" 64#include "bcm_nvram_cfevar.h" 65 66BHND_NVRAM_IOPS_DEFN(iocfe) 67 68#define IOCFE_LOG(_io, _fmt, ...) \ 69 printf("%s/%s: " _fmt, __FUNCTION__, (_io)->dname, ##__VA_ARGS__) 70 71static int bcm_nvram_iocfe_init(struct bcm_nvram_iocfe *iocfe, 72 char *dname); 73 74/** Known CFE NVRAM device names, in probe order. */ 75static char *nvram_cfe_devs[] = { 76 "nflash0.nvram", /* NAND */ 77 "nflash1.nvram", 78 "flash0.nvram", 79 "flash1.nvram", 80}; 81 82/** Supported CFE NVRAM formats, in probe order. */ 83static bhnd_nvram_data_class * const nvram_cfe_fmts[] = { 84 &bhnd_nvram_bcm_class, 85 &bhnd_nvram_tlv_class 86}; 87 88static int 89bhnd_nvram_cfe_probe(device_t dev) 90{ 91 struct bcm_platform *bp; 92 93 /* Fetch platform NVRAM I/O context */ 94 bp = bcm_get_platform(); 95 if (bp->nvram_io == NULL) 96 return (ENXIO); 97 98 KASSERT(bp->nvram_cls != NULL, ("missing NVRAM class")); 99 100 /* Set the device description */ 101 device_set_desc(dev, bhnd_nvram_data_class_desc(bp->nvram_cls)); 102 103 /* Refuse wildcard attachments */ 104 return (BUS_PROBE_NOWILDCARD); 105} 106 107 108static int 109bhnd_nvram_cfe_attach(device_t dev) 110{ 111 struct bcm_platform *bp; 112 struct bhnd_nvram_cfe_softc *sc; 113 int error; 114 115 bp = bcm_get_platform(); 116 KASSERT(bp->nvram_io != NULL, ("missing NVRAM I/O context")); 117 KASSERT(bp->nvram_cls != NULL, ("missing NVRAM class")); 118 119 sc = device_get_softc(dev); 120 sc->dev = dev; 121 122 error = bhnd_nvram_store_parse_new(&sc->store, bp->nvram_io, 123 bp->nvram_cls); 124 if (error) 125 return (error); 126 127 error = bhnd_service_registry_add(&bp->services, dev, 128 BHND_SERVICE_NVRAM, 0); 129 if (error) { 130 bhnd_nvram_store_free(sc->store); 131 return (error); 132 } 133 134 return (error); 135} 136 137static int 138bhnd_nvram_cfe_resume(device_t dev) 139{ 140 return (0); 141} 142 143static int 144bhnd_nvram_cfe_suspend(device_t dev) 145{ 146 return (0); 147} 148 149static int 150bhnd_nvram_cfe_detach(device_t dev) 151{ 152 struct bcm_platform *bp; 153 struct bhnd_nvram_cfe_softc *sc; 154 int error; 155 156 bp = bcm_get_platform(); 157 sc = device_get_softc(dev); 158 159 error = bhnd_service_registry_remove(&bp->services, dev, 160 BHND_SERVICE_ANY); 161 if (error) 162 return (error); 163 164 bhnd_nvram_store_free(sc->store); 165 166 return (0); 167} 168 169static int 170bhnd_nvram_cfe_getvar(device_t dev, const char *name, void *buf, size_t *len, 171 bhnd_nvram_type type) 172{ 173 struct bhnd_nvram_cfe_softc *sc = device_get_softc(dev); 174 175 return (bhnd_nvram_store_getvar(sc->store, name, buf, len, type)); 176} 177 178static int 179bhnd_nvram_cfe_setvar(device_t dev, const char *name, const void *buf, 180 size_t len, bhnd_nvram_type type) 181{ 182 struct bhnd_nvram_cfe_softc *sc = device_get_softc(dev); 183 184 return (bhnd_nvram_store_setvar(sc->store, name, buf, len, type)); 185} 186 187/** 188 * Find, open, identify, and initialize an I/O context mapping the CFE NVRAM 189 * device. 190 * 191 * @param[out] iocfe On success, an I/O context mapping the CFE NVRAM 192 * device. 193 * @param[out] cls On success, the identified NVRAM data format 194 * class. 195 * 196 * @retval 0 success. the caller inherits ownership of @p iocfe. 197 * @retval non-zero if no usable CFE NVRAM device can be found, a standard 198 * unix error will be returned. 199 */ 200int 201bcm_nvram_find_cfedev(struct bcm_nvram_iocfe *iocfe, 202 bhnd_nvram_data_class **cls) 203{ 204 char *dname; 205 int devinfo; 206 int error, result; 207 208 for (u_int i = 0; i < nitems(nvram_cfe_fmts); i++) { 209 *cls = nvram_cfe_fmts[i]; 210 211 for (u_int j = 0; j < nitems(nvram_cfe_devs); j++) { 212 dname = nvram_cfe_devs[j]; 213 214 /* Does the device exist? */ 215 if ((devinfo = cfe_getdevinfo(dname)) < 0) { 216 if (devinfo != CFE_ERR_DEVNOTFOUND) { 217 BCM_ERR("cfe_getdevinfo(%s) failed: " 218 "%d\n", dname, devinfo); 219 } 220 221 continue; 222 } 223 224 /* Open for reading */ 225 if ((error = bcm_nvram_iocfe_init(iocfe, dname))) 226 continue; 227 228 /* Probe */ 229 result = bhnd_nvram_data_probe(*cls, &iocfe->io); 230 if (result <= 0) { 231 /* Found a supporting NVRAM data class */ 232 return (0); 233 } 234 235 /* Keep searching */ 236 bhnd_nvram_io_free(&iocfe->io); 237 } 238 } 239 240 return (ENODEV); 241} 242 243 244/** 245 * Initialize a new CFE device-backed I/O context. 246 * 247 * The caller is responsible for releasing all resources held by the returned 248 * I/O context via bhnd_nvram_io_free(). 249 * 250 * @param[out] io On success, will be initialized as an I/O context for 251 * CFE device @p dname. 252 * @param dname The name of the CFE device to be opened for reading. 253 * 254 * @retval 0 success. 255 * @retval non-zero if opening @p dname otherwise fails, a standard unix 256 * error will be returned. 257 */ 258static int 259bcm_nvram_iocfe_init(struct bcm_nvram_iocfe *iocfe, char *dname) 260{ 261 nvram_info_t nvram_info; 262 int cerr, devinfo, dtype, rlen; 263 int64_t nv_offset; 264 u_int nv_size; 265 bool req_blk_erase; 266 int error; 267 268 iocfe->io.iops = &bhnd_nvram_iocfe_ops; 269 iocfe->dname = dname; 270 271 /* Try to open the device */ 272 iocfe->fd = cfe_open(dname); 273 if (iocfe->fd <= 0) { 274 IOCFE_LOG(iocfe, "cfe_open() failed: %d\n", iocfe->fd); 275 276 return (ENXIO); 277 } 278 279 /* Try to fetch device info */ 280 if ((devinfo = cfe_getdevinfo(iocfe->dname)) < 0) { 281 IOCFE_LOG(iocfe, "cfe_getdevinfo() failed: %d\n", devinfo); 282 error = ENXIO; 283 goto failed; 284 } 285 286 /* Verify device type */ 287 dtype = devinfo & CFE_DEV_MASK; 288 switch (dtype) { 289 case CFE_DEV_FLASH: 290 case CFE_DEV_NVRAM: 291 /* Valid device type */ 292 break; 293 default: 294 IOCFE_LOG(iocfe, "unknown device type: %d\n", dtype); 295 error = ENXIO; 296 goto failed; 297 } 298 299 /* Try to fetch nvram info from CFE */ 300 cerr = cfe_ioctl(iocfe->fd, IOCTL_NVRAM_GETINFO, 301 (unsigned char *)&nvram_info, sizeof(nvram_info), &rlen, 0); 302 if (cerr == CFE_OK) { 303 /* Sanity check the result; must not be a negative integer */ 304 if (nvram_info.nvram_size < 0 || 305 nvram_info.nvram_offset < 0) 306 { 307 IOCFE_LOG(iocfe, "invalid NVRAM layout (%d/%d)\n", 308 nvram_info.nvram_size, nvram_info.nvram_offset); 309 error = ENXIO; 310 goto failed; 311 } 312 313 nv_offset = nvram_info.nvram_offset; 314 nv_size = nvram_info.nvram_size; 315 req_blk_erase = (nvram_info.nvram_eraseflg != 0); 316 } else if (cerr != CFE_OK && cerr != CFE_ERR_INV_COMMAND) { 317 IOCFE_LOG(iocfe, "IOCTL_NVRAM_GETINFO failed: %d\n", cerr); 318 error = ENXIO; 319 goto failed; 320 } 321 322 /* Fall back on flash info. 323 * 324 * This is known to be required on the Asus RT-N53 (CFE 5.70.55.33, 325 * BBP 1.0.37, BCM5358UB0), where IOCTL_NVRAM_GETINFO returns 326 * CFE_ERR_INV_COMMAND. 327 */ 328 if (cerr == CFE_ERR_INV_COMMAND) { 329 flash_info_t fi; 330 331 cerr = cfe_ioctl(iocfe->fd, IOCTL_FLASH_GETINFO, 332 (unsigned char *)&fi, sizeof(fi), &rlen, 0); 333 334 if (cerr != CFE_OK) { 335 IOCFE_LOG(iocfe, "IOCTL_FLASH_GETINFO failed %d\n", 336 cerr); 337 error = ENXIO; 338 goto failed; 339 } 340 341 nv_offset = 0x0; 342 nv_size = fi.flash_size; 343 req_blk_erase = !(fi.flash_flags & FLASH_FLAG_NOERASE); 344 } 345 346 347 /* Verify that the full NVRAM layout can be represented via size_t */ 348 if (nv_size > SIZE_MAX || SIZE_MAX - nv_size < nv_offset) { 349 IOCFE_LOG(iocfe, "invalid NVRAM layout (%#x/%#jx)\n", 350 nv_size, (intmax_t)nv_offset); 351 error = ENXIO; 352 goto failed; 353 } 354 355 iocfe->offset = nv_offset; 356 iocfe->size = nv_size; 357 iocfe->req_blk_erase = req_blk_erase; 358 359 return (CFE_OK); 360 361failed: 362 if (iocfe->fd >= 0) 363 cfe_close(iocfe->fd); 364 365 return (error); 366} 367 368static void 369bhnd_nvram_iocfe_free(struct bhnd_nvram_io *io) 370{ 371 struct bcm_nvram_iocfe *iocfe = (struct bcm_nvram_iocfe *)io; 372 373 /* CFE I/O instances are statically allocated; we do not need to free 374 * the instance itself */ 375 cfe_close(iocfe->fd); 376} 377 378static size_t 379bhnd_nvram_iocfe_getsize(struct bhnd_nvram_io *io) 380{ 381 struct bcm_nvram_iocfe *iocfe = (struct bcm_nvram_iocfe *)io; 382 return (iocfe->size); 383} 384 385static int 386bhnd_nvram_iocfe_setsize(struct bhnd_nvram_io *io, size_t size) 387{ 388 /* unsupported */ 389 return (ENODEV); 390} 391 392static int 393bhnd_nvram_iocfe_read_ptr(struct bhnd_nvram_io *io, size_t offset, 394 const void **ptr, size_t nbytes, size_t *navail) 395{ 396 /* unsupported */ 397 return (ENODEV); 398} 399 400static int 401bhnd_nvram_iocfe_write_ptr(struct bhnd_nvram_io *io, size_t offset, 402 void **ptr, size_t nbytes, size_t *navail) 403{ 404 /* unsupported */ 405 return (ENODEV); 406} 407 408static int 409bhnd_nvram_iocfe_write(struct bhnd_nvram_io *io, size_t offset, void *buffer, 410 size_t nbytes) 411{ 412 /* unsupported */ 413 return (ENODEV); 414} 415 416static int 417bhnd_nvram_iocfe_read(struct bhnd_nvram_io *io, size_t offset, void *buffer, 418 size_t nbytes) 419{ 420 struct bcm_nvram_iocfe *iocfe; 421 size_t remain; 422 int64_t cfe_offset; 423 int nr, nreq; 424 425 iocfe = (struct bcm_nvram_iocfe *)io; 426 427 /* Determine (and validate) the base CFE offset */ 428#if (SIZE_MAX > INT64_MAX) 429 if (iocfe->offset > INT64_MAX || offset > INT64_MAX) 430 return (ENXIO); 431#endif 432 433 if (INT64_MAX - offset < iocfe->offset) 434 return (ENXIO); 435 436 cfe_offset = iocfe->offset + offset; 437 438 /* Verify that cfe_offset + nbytes is representable */ 439 if (INT64_MAX - cfe_offset < nbytes) 440 return (ENXIO); 441 442 /* Perform the read */ 443 for (remain = nbytes; remain > 0;) { 444 void *p; 445 size_t nread; 446 int64_t cfe_noff; 447 448 nread = (nbytes - remain); 449 cfe_noff = cfe_offset + nread; 450 p = ((uint8_t *)buffer + nread); 451 nreq = ummin(INT_MAX, remain); 452 453 nr = cfe_readblk(iocfe->fd, cfe_noff, p, nreq); 454 if (nr < 0) { 455 IOCFE_LOG(iocfe, "cfe_readblk() failed: %d\n", nr); 456 return (ENXIO); 457 } 458 459 /* Check for unexpected short read */ 460 if (nr == 0 && remain > 0) { 461 /* If the request fits entirely within the CFE 462 * device range, we shouldn't hit EOF */ 463 if (remain < iocfe->size && 464 iocfe->size - remain > offset) 465 { 466 IOCFE_LOG(iocfe, "cfe_readblk() returned " 467 "unexpected short read (%d/%d)\n", nr, 468 nreq); 469 return (ENXIO); 470 } 471 } 472 473 if (nr == 0) 474 break; 475 476 remain -= nr; 477 } 478 479 /* Check for short read */ 480 if (remain > 0) 481 return (ENXIO); 482 483 return (0); 484} 485 486static device_method_t bhnd_nvram_cfe_methods[] = { 487 /* Device interface */ 488 DEVMETHOD(device_probe, bhnd_nvram_cfe_probe), 489 DEVMETHOD(device_attach, bhnd_nvram_cfe_attach), 490 DEVMETHOD(device_resume, bhnd_nvram_cfe_resume), 491 DEVMETHOD(device_suspend, bhnd_nvram_cfe_suspend), 492 DEVMETHOD(device_detach, bhnd_nvram_cfe_detach), 493 494 /* NVRAM interface */ 495 DEVMETHOD(bhnd_nvram_getvar, bhnd_nvram_cfe_getvar), 496 DEVMETHOD(bhnd_nvram_setvar, bhnd_nvram_cfe_setvar), 497 498 DEVMETHOD_END 499}; 500 501DEFINE_CLASS_0(bhnd_nvram, bhnd_nvram_cfe, bhnd_nvram_cfe_methods, 502 sizeof(struct bhnd_nvram_cfe_softc)); 503EARLY_DRIVER_MODULE(bhnd_nvram_cfe, nexus, bhnd_nvram_cfe, 504 bhnd_nvram_devclass, NULL, NULL, BUS_PASS_BUS + BUS_PASS_ORDER_EARLY); 505