671 lines
15 KiB
C
671 lines
15 KiB
C
/* $NetBSD: flash.c,v 1.19 2022/09/25 21:56:12 thorpej 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.19 2022/09/25 21:56:12 thorpej 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/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(device, &arg, flash_print,
|
|
CFARGS(.iattr = "flashbus"));
|
|
}
|
|
|
|
/**
|
|
* 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 length 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 = devsw_attach("flash", &flash_bdevsw, &bmaj,
|
|
&flash_cdevsw, &cmaj);
|
|
if (error)
|
|
return error;
|
|
error = config_init_component(cfdriver_ioconf_flash,
|
|
cfattach_ioconf_flash, cfdata_ioconf_flash);
|
|
if (error)
|
|
devsw_detach(&flash_bdevsw, &flash_cdevsw);
|
|
#endif
|
|
return error;
|
|
case MODULE_CMD_FINI:
|
|
#ifdef _MODULE
|
|
error = config_fini_component(cfdriver_ioconf_flash,
|
|
cfattach_ioconf_flash, cfdata_ioconf_flash);
|
|
devsw_detach(&flash_bdevsw, &flash_cdevsw);
|
|
#endif
|
|
return error;
|
|
default:
|
|
return ENOTTY;
|
|
}
|
|
}
|