/* $NetBSD: flash.c,v 1.14 2017/11/13 17:35:58 jmcneill Exp $ */
/*-
* Copyright (c) 2011 Department of Software Engineering,
* University of Szeged, Hungary
* Copyright (c) 2011 Adam Hoka <ahoka@NetBSD.org>
* Copyright (c) 2010 David Tengeri <dtengeri@inf.u-szeged.hu>
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by the Department of Software Engineering, University of Szeged, Hungary
*
* 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.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, 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 DAMAGE.
*/
/*-
* Framework for storage devices based on Flash technology
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: flash.c,v 1.14 2017/11/13 17:35:58 jmcneill Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/proc.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/kmem.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/atomic.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/malloc.h>
#include <sys/reboot.h>
#include "ioconf.h"
#include <sys/flashio.h>
#include "flash.h"
#ifdef FLASH_DEBUG
int flashdebug = FLASH_DEBUG;
#endif
dev_type_open(flashopen);
dev_type_close(flashclose);
dev_type_read(flashread);
dev_type_write(flashwrite);
dev_type_ioctl(flashioctl);
dev_type_strategy(flashstrategy);
dev_type_dump(flashdump);
int flash_print(void *aux, const char *pnp);
bool flash_shutdown(device_t dev, int how);
int flash_nsectors(struct buf *bp);
int flash_sector(struct buf *bp);
int flash_match(device_t parent, cfdata_t match, void *aux);
void flash_attach(device_t parent, device_t self, void *aux);
int flash_detach(device_t device, int flags);
CFATTACH_DECL_NEW(flash, sizeof(struct flash_softc),
flash_match, flash_attach, flash_detach, NULL);
/**
* Block device's operation
*/
const struct bdevsw flash_bdevsw = {
.d_open = flashopen,
.d_close = flashclose,
.d_strategy = flashstrategy,
.d_ioctl = flashioctl,
.d_dump = flashdump,
.d_psize = nosize,
.d_discard = nodiscard, /* XXX this driver probably wants a discard */
.d_flag = D_DISK | D_MPSAFE
};
/**
* Character device's operations
*/
const struct cdevsw flash_cdevsw = {
.d_open = flashopen,
.d_close = flashclose,
.d_read = flashread,
.d_write = flashwrite,
.d_ioctl = flashioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = D_DISK | D_MPSAFE
};
/* ARGSUSED */
int
flash_match(device_t parent, cfdata_t match, void *aux)
{
/* pseudo device, always attaches */
return 1;
}
/* ARGSUSED */
void
flash_attach(device_t parent, device_t self, void *aux)
{
struct flash_softc * const sc = device_private(self);
struct flash_attach_args * const faa = aux;
char pbuf[2][sizeof("9999 KB")];
sc->sc_dev = self;
sc->sc_parent_dev = parent;
sc->flash_if = faa->flash_if;
sc->sc_partinfo = faa->partinfo;
sc->hw_softc = device_private(parent);
format_bytes(pbuf[0], sizeof(pbuf[0]), sc->sc_partinfo.part_size);
format_bytes(pbuf[1], sizeof(pbuf[1]), sc->flash_if->erasesize);
aprint_naive("\n");
aprint_normal(": partition");
if (sc->sc_partinfo.part_name != NULL)
aprint_normal(" \"%s\"", sc->sc_partinfo.part_name);
aprint_normal(", size %s, offset %#jx",
pbuf[0], (uintmax_t)sc->sc_partinfo.part_offset);
if (sc->sc_partinfo.part_flags & FLASH_PART_READONLY) {
sc->sc_readonly = true;
aprint_normal(", read only");
} else {
sc->sc_readonly = false;
}
aprint_normal("\n");
if (sc->sc_partinfo.part_size == 0) {
aprint_error_dev(self,
"partition size must be larger than 0\n");
return;
}
switch (sc->flash_if->type) {
case FLASH_TYPE_NOR:
aprint_normal_dev(sc->sc_dev,
"erase size %s bytes, write size %d bytes\n",
pbuf[1], sc->flash_if->writesize);
break;
case FLASH_TYPE_NAND:
default:
aprint_normal_dev(sc->sc_dev,
"erase size %s, page size %d bytes, write size %d bytes\n",
pbuf[1], sc->flash_if->page_size,
sc->flash_if->writesize);
break;
}
if (!pmf_device_register1(sc->sc_dev, NULL, NULL, flash_shutdown))
aprint_error_dev(sc->sc_dev,
"couldn't establish power handler\n");
}
int
flash_detach(device_t device, int flags)
{
struct flash_softc * const sc = device_private(device);
pmf_device_deregister(sc->sc_dev);
/* freeing flash_if is our responsibility */
kmem_free(sc->flash_if, sizeof(*sc->flash_if));
return 0;
}
int
flash_print(void *aux, const char *pnp)
{
struct flash_attach_args *arg;
const char *type;
if (pnp != NULL) {
arg = aux;
switch (arg->flash_if->type) {
case FLASH_TYPE_NOR:
type = "NOR";
break;
case FLASH_TYPE_NAND:
type = "NAND";
break;
default:
panic("flash_print: unknown type %d",
arg->flash_if->type);
}
aprint_normal("%s flash at %s", type, pnp);
}
return UNCONF;
}
device_t
flash_attach_mi(struct flash_interface * const flash_if, device_t device)
{
struct flash_attach_args arg;
#ifdef DIAGNOSTIC
if (flash_if == NULL) {
aprint_error("flash_attach_mi: NULL\n");
return 0;
}
#endif
arg.flash_if = flash_if;
return config_found_ia(device, "flashbus", &arg, flash_print);
}
/**
* flash_open - open the character device
* Checks if there is a driver registered to the minor number of the open
* request.
*/
int
flashopen(dev_t dev, int flags, int fmt, lwp_t *l)
{
int unit = minor(dev);
struct flash_softc *sc;
FLDPRINTFN(1, ("flash: opening device unit %d\n", unit));
if ((sc = device_lookup_private(&flash_cd, unit)) == NULL)
return ENXIO;
/* TODO return eperm if want to open for writing a read only dev */
/* reset buffer length */
// sc->sc_cache->fc_len = 0;
return 0;
}
/**
* flash_close - close device
* We don't have to release any resources, so just return 0.
*/
int
flashclose(dev_t dev, int flags, int fmt, lwp_t *l)
{
int unit = minor(dev);
struct flash_softc *sc;
int err;
FLDPRINTFN(1, ("flash: closing flash device unit %d\n", unit));
if ((sc = device_lookup_private(&flash_cd, unit)) == NULL)
return ENXIO;
if (!sc->sc_readonly) {
err = flash_sync(sc->sc_dev);
if (err)
return err;
}
return 0;
}
/**
* flash_read - read from character device
* This function uses the registered driver's read function to read the
* requested length to * a buffer and then moves this buffer to userspace.
*/
int
flashread(dev_t dev, struct uio * const uio, int flag)
{
return physio(flashstrategy, NULL, dev, B_READ, minphys, uio);
}
/**
* flash_write - write to character device
* This function moves the data into a buffer from userspace to kernel space,
* then uses the registered driver's write function to write out the data to
* the media.
*/
int
flashwrite(dev_t dev, struct uio * const uio, int flag)
{
return physio(flashstrategy, NULL, dev, B_WRITE, minphys, uio);
}
void
flashstrategy(struct buf * const bp)
{
struct flash_softc *sc;
const struct flash_interface *flash_if;
const struct flash_partition *part;
int unit, device_blks;
unit = minor(bp->b_dev);
sc = device_lookup_private(&flash_cd, unit);
if (sc == NULL) {
bp->b_error = ENXIO;
goto done;
}
flash_if = sc->flash_if;
part = &sc->sc_partinfo;
/* divider */
KASSERT(flash_if->writesize != 0);
aprint_debug_dev(sc->sc_dev, "flash_strategy()\n");
if (!(bp->b_flags & B_READ) && sc->sc_readonly) {
bp->b_error = EACCES;
goto done;
}
/* check if length is not negative */
if (bp->b_blkno < 0) {
bp->b_error = EINVAL;
goto done;
}
/* zero lenght i/o */
if (bp->b_bcount == 0) {
goto done;
}
device_blks = sc->sc_partinfo.part_size / DEV_BSIZE;
KASSERT(part->part_offset % DEV_BSIZE == 0);
bp->b_rawblkno = bp->b_blkno + (part->part_offset / DEV_BSIZE);
if (bounds_check_with_mediasize(bp, DEV_BSIZE, device_blks) <= 0) {
goto done;
}
bp->b_resid = bp->b_bcount;
flash_if->submit(sc->sc_parent_dev, bp);
return;
done:
bp->b_resid = bp->b_bcount;
biodone(bp);
}
/*
* Handle the ioctl for the device
*/
int
flashioctl(dev_t dev, u_long command, void * const data, int flags, lwp_t *l)
{
struct flash_erase_params *ep;
struct flash_info_params *ip;
struct flash_dump_params *dp;
struct flash_badblock_params *bbp;
struct flash_erase_instruction ei;
struct flash_softc *sc;
int unit, err;
size_t retlen;
flash_off_t offset;
bool bad;
unit = minor(dev);
if ((sc = device_lookup_private(&flash_cd, unit)) == NULL)
return ENXIO;
err = 0;
switch (command) {
case FLASH_ERASE_BLOCK:
/**
* Set up an erase instruction then call the registered
* driver's erase operation.
*/
ep = data;
if (sc->sc_readonly) {
return EACCES;
}
ei.ei_addr = ep->ep_addr;
ei.ei_len = ep->ep_len;
ei.ei_callback = NULL;
err = flash_erase(sc->sc_dev, &ei);
if (err) {
return err;
}
break;
case FLASH_BLOCK_ISBAD:
/**
* Set up an erase instruction then call the registered
* driver's erase operation.
*/
bbp = data;
err = flash_block_isbad(sc->sc_dev, bbp->bbp_addr, &bad);
if (err) {
return err;
}
bbp->bbp_isbad = bad;
break;
case FLASH_BLOCK_MARKBAD:
bbp = data;
err = flash_block_markbad(sc->sc_dev, bbp->bbp_addr);
break;
case FLASH_DUMP:
dp = data;
offset = dp->dp_block * sc->flash_if->erasesize;
FLDPRINTF(("Reading from block: %jd len: %jd\n",
(intmax_t )dp->dp_block, (intmax_t )dp->dp_len));
err = flash_read(sc->sc_parent_dev, offset, dp->dp_len,
&retlen, dp->dp_buf);
if (err)
return err;
if (retlen != dp->dp_len) {
dp->dp_len = -1;
dp->dp_buf = NULL;
}
break;
case FLASH_GET_INFO:
ip = data;
ip->ip_page_size = sc->flash_if->page_size;
ip->ip_erase_size = sc->flash_if->erasesize;
ip->ip_flash_type = sc->flash_if->type;
ip->ip_flash_size = sc->sc_partinfo.part_size;
break;
default:
err = ENODEV;
}
return err;
}
int
flashdump(dev_t dev, daddr_t blkno, void *va, size_t size)
{
return EACCES;
}
bool
flash_shutdown(device_t self, int how)
{
struct flash_softc * const sc = device_private(self);
if ((how & RB_NOSYNC) == 0 && !sc->sc_readonly)
flash_sync(self);
return true;
}
const struct flash_interface *
flash_get_interface(dev_t dev)
{
struct flash_softc *sc;
int unit;
unit = minor(dev);
if ((sc = device_lookup_private(&flash_cd, unit)) == NULL)
return NULL;
return sc->flash_if;
}
const struct flash_softc *
flash_get_softc(dev_t dev)
{
struct flash_softc *sc;
int unit;
unit = minor(dev);
sc = device_lookup_private(&flash_cd, unit);
return sc;
}
device_t
flash_get_device(dev_t dev)
{
struct flash_softc *sc;
int unit;
unit = minor(dev);
sc = device_lookup_private(&flash_cd, unit);
return sc->sc_dev;
}
flash_size_t
flash_get_size(dev_t dev)
{
const struct flash_softc *sc;
sc = flash_get_softc(dev);
return sc->sc_partinfo.part_size;
}
int
flash_erase(device_t self, struct flash_erase_instruction * const ei)
{
struct flash_softc * const sc = device_private(self);
KASSERT(ei != NULL);
struct flash_erase_instruction e = *ei;
if (sc->sc_readonly)
return EACCES;
/* adjust for flash partition */
e.ei_addr += sc->sc_partinfo.part_offset;
/* bounds check for flash partition */
if (e.ei_addr + e.ei_len > sc->sc_partinfo.part_size +
sc->sc_partinfo.part_offset)
return EINVAL;
return sc->flash_if->erase(device_parent(self), &e);
}
int
flash_read(device_t self, flash_off_t offset, size_t len, size_t * const retlen,
uint8_t * const buf)
{
struct flash_softc * const sc = device_private(self);
offset += sc->sc_partinfo.part_offset;
if (offset + len > sc->sc_partinfo.part_size +
sc->sc_partinfo.part_offset)
return EINVAL;
return sc->flash_if->read(device_parent(self),
offset, len, retlen, buf);
}
int
flash_write(device_t self, flash_off_t offset, size_t len,
size_t * const retlen, const uint8_t * const buf)
{
struct flash_softc * const sc = device_private(self);
if (sc->sc_readonly)
return EACCES;
offset += sc->sc_partinfo.part_offset;
if (offset + len > sc->sc_partinfo.part_size +
sc->sc_partinfo.part_offset)
return EINVAL;
return sc->flash_if->write(device_parent(self),
offset, len, retlen, buf);
}
int
flash_block_markbad(device_t self, flash_off_t offset)
{
struct flash_softc * const sc = device_private(self);
if (sc->sc_readonly)
return EACCES;
offset += sc->sc_partinfo.part_offset;
if (offset + sc->flash_if->erasesize >=
sc->sc_partinfo.part_size +
sc->sc_partinfo.part_offset)
return EINVAL;
return sc->flash_if->block_markbad(device_parent(self), offset);
}
int
flash_block_isbad(device_t self, flash_off_t offset, bool * const bad)
{
struct flash_softc * const sc = device_private(self);
offset += sc->sc_partinfo.part_offset;
if (offset + sc->flash_if->erasesize >
sc->sc_partinfo.part_size +
sc->sc_partinfo.part_offset)
return EINVAL;
return sc->flash_if->block_isbad(device_parent(self), offset, bad);
}
int
flash_sync(device_t self)
{
struct flash_softc * const sc = device_private(self);
if (sc->sc_readonly)
return EACCES;
/* noop now TODO: implement */
return 0;
}
MODULE(MODULE_CLASS_DRIVER, flash, NULL);
#ifdef _MODULE
#include "ioconf.c"
#endif
static int
flash_modcmd(modcmd_t cmd, void *opaque)
{
int error = 0;
#ifdef _MODULE
int bmaj = -1, cmaj = -1;
#endif
switch (cmd) {
case MODULE_CMD_INIT:
#ifdef _MODULE
error = config_init_component(cfdriver_ioconf_flash,
cfattach_ioconf_flash, cfdata_ioconf_flash);
if (error)
return error;
error = devsw_attach("flash", &flash_bdevsw, &bmaj,
&flash_cdevsw, &cmaj);
if (error)
config_fini_component(cfdriver_ioconf_flash,
cfattach_ioconf_flash, cfdata_ioconf_flash);
#endif
return error;
case MODULE_CMD_FINI:
#ifdef _MODULE
devsw_detach(&flash_bdevsw, &flash_cdevsw);
error = config_fini_component(cfdriver_ioconf_flash,
cfattach_ioconf_flash, cfdata_ioconf_flash);
#endif
return error;
default:
return ENOTTY;
}
}