NetBSD/sys/dev/flash/flash_io.c

416 lines
10 KiB
C

/* $NetBSD: flash_io.c,v 1.6 2023/04/03 18:03:23 gutteridge Exp $ */
/*-
* Copyright (c) 2011 Department of Software Engineering,
* University of Szeged, Hungary
* Copyright (c) 2011 Adam Hoka <ahoka@NetBSD.org>
* 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.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: flash_io.c,v 1.6 2023/04/03 18:03:23 gutteridge Exp $");
#include <sys/param.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/kthread.h>
#include <sys/mutex.h>
#include <sys/sysctl.h>
#include <dev/flash/flash.h>
#include <dev/flash/flash_io.h>
#ifdef FLASH_DEBUG
extern int flashdebug;
#endif
int flash_cachesync_timeout = 1;
int flash_cachesync_nodenum;
void flash_io_read(struct flash_io *, struct buf *);
void flash_io_write(struct flash_io *, struct buf *);
void flash_io_done(struct flash_io *, struct buf *, int);
int flash_io_cache_write(struct flash_io *, flash_addr_t, struct buf *);
void flash_io_cache_sync(struct flash_io *);
static int
flash_timestamp_diff(struct bintime *bt, struct bintime *b2)
{
struct bintime b1 = *bt;
struct timeval tv;
bintime_sub(&b1, b2);
bintime2timeval(&b1, &tv);
return tvtohz(&tv);
}
static flash_addr_t
flash_io_getblock(struct flash_io *fio, struct buf *bp)
{
flash_off_t block, last;
/* get block number of first byte */
block = bp->b_rawblkno * DEV_BSIZE / fio->fio_if->erasesize;
/* block of the last bite */
last = (bp->b_rawblkno * DEV_BSIZE + bp->b_resid - 1)
/ fio->fio_if->erasesize;
/* spans through multiple blocks, needs special handling */
if (last != block) {
printf("0x%jx -> 0x%jx\n",
bp->b_rawblkno * DEV_BSIZE,
bp->b_rawblkno * DEV_BSIZE + bp->b_resid - 1);
panic("TODO: multiple block write. last: %jd, current: %jd",
(intmax_t )last, (intmax_t )block);
}
return block;
}
int
flash_sync_thread_init(struct flash_io *fio, device_t dev,
struct flash_interface *flash_if)
{
int error;
FLDPRINTF(("starting flash io thread\n"));
fio->fio_dev = dev;
fio->fio_if = flash_if;
fio->fio_data = kmem_alloc(fio->fio_if->erasesize, KM_SLEEP);
mutex_init(&fio->fio_lock, MUTEX_DEFAULT, IPL_NONE);
cv_init(&fio->fio_cv, "flashcv");
error = bufq_alloc(&fio->fio_bufq, "fcfs", BUFQ_SORT_RAWBLOCK);
if (error)
goto err_bufq;
fio->fio_exiting = false;
fio->fio_write_pending = false;
/* arrange to allocate the kthread */
error = kthread_create(PRI_NONE, KTHREAD_MUSTJOIN | KTHREAD_MPSAFE,
NULL, flash_sync_thread, fio, &fio->fio_thread, "flashio");
if (!error)
return 0;
bufq_free(fio->fio_bufq);
err_bufq:
cv_destroy(&fio->fio_cv);
mutex_destroy(&fio->fio_lock);
kmem_free(fio->fio_data, fio->fio_if->erasesize);
return error;
}
void
flash_sync_thread_destroy(struct flash_io *fio)
{
FLDPRINTF(("stopping flash io thread\n"));
mutex_enter(&fio->fio_lock);
fio->fio_exiting = true;
cv_broadcast(&fio->fio_cv);
mutex_exit(&fio->fio_lock);
kthread_join(fio->fio_thread);
kmem_free(fio->fio_data, fio->fio_if->erasesize);
bufq_free(fio->fio_bufq);
mutex_destroy(&fio->fio_lock);
cv_destroy(&fio->fio_cv);
}
int
flash_io_submit(struct flash_io *fio, struct buf *bp)
{
FLDPRINTF(("submitting job to flash io thread: %p\n", bp));
if (__predict_false(fio->fio_exiting)) {
flash_io_done(fio, bp, ENODEV);
return ENODEV;
}
if (BUF_ISREAD(bp)) {
FLDPRINTF(("we have a read job\n"));
mutex_enter(&fio->fio_lock);
if (fio->fio_write_pending)
flash_io_cache_sync(fio);
mutex_exit(&fio->fio_lock);
flash_io_read(fio, bp);
} else {
FLDPRINTF(("we have a write job\n"));
flash_io_write(fio, bp);
}
return 0;
}
int
flash_io_cache_write(struct flash_io *fio, flash_addr_t block, struct buf *bp)
{
size_t retlen;
flash_addr_t base, offset;
int error;
KASSERT(mutex_owned(&fio->fio_lock));
KASSERT(fio->fio_if->erasesize != 0);
base = block * fio->fio_if->erasesize;
offset = bp->b_rawblkno * DEV_BSIZE - base;
FLDPRINTF(("io cache write, offset: %jd\n", (intmax_t )offset));
if (!fio->fio_write_pending) {
fio->fio_block = block;
/*
* fill the cache with data from flash,
* so we dont have to bother with gaps later
*/
FLDPRINTF(("filling buffer from offset %ju\n", (uintmax_t)base));
error = fio->fio_if->read(fio->fio_dev,
base, fio->fio_if->erasesize,
&retlen, fio->fio_data);
FLDPRINTF(("cache filled\n"));
if (error)
return error;
fio->fio_write_pending = true;
/* save creation time for aging */
binuptime(&fio->fio_creation);
}
/* copy data to cache */
memcpy(fio->fio_data + offset, bp->b_data, bp->b_resid);
bufq_put(fio->fio_bufq, bp);
/* update timestamp */
binuptime(&fio->fio_last_write);
return 0;
}
void
flash_io_cache_sync(struct flash_io *fio)
{
struct flash_erase_instruction ei;
struct buf *bp;
size_t retlen;
flash_addr_t base;
int error;
KASSERT(mutex_owned(&fio->fio_lock));
if (!fio->fio_write_pending) {
FLDPRINTF(("trying to sync with an invalid buffer\n"));
return;
}
base = fio->fio_block * fio->fio_if->erasesize;
FLDPRINTF(("eraseing block at 0x%jx\n", (uintmax_t )base));
ei.ei_addr = base;
ei.ei_len = fio->fio_if->erasesize;
ei.ei_callback = NULL;
error = fio->fio_if->erase(fio->fio_dev, &ei);
if (error) {
aprint_error_dev(fio->fio_dev, "cannot erase flash flash!\n");
goto out;
}
FLDPRINTF(("writing %" PRIu32 " bytes to 0x%jx\n",
fio->fio_if->erasesize, (uintmax_t )base));
error = fio->fio_if->write(fio->fio_dev,
base, fio->fio_if->erasesize, &retlen, fio->fio_data);
if (error || retlen != fio->fio_if->erasesize) {
aprint_error_dev(fio->fio_dev, "can't sync write cache: %d\n", error);
goto out;
}
out:
while ((bp = bufq_get(fio->fio_bufq)) != NULL)
flash_io_done(fio, bp, error);
fio->fio_block = -1;
fio->fio_write_pending = false;
}
void
flash_sync_thread(void * arg)
{
struct flash_io *fio = arg;
struct bintime now;
mutex_enter(&fio->fio_lock);
while (!fio->fio_exiting) {
cv_timedwait_sig(&fio->fio_cv, &fio->fio_lock, hz / 4);
if (!fio->fio_write_pending) {
continue;
}
/* see if the cache is older than 3 seconds (safety limit),
* or if we havent touched the cache since more than 1 ms
*/
binuptime(&now);
if (flash_timestamp_diff(&now, &fio->fio_last_write) > hz / 5) {
FLDPRINTF(("syncing write cache after timeout\n"));
flash_io_cache_sync(fio);
} else if (flash_timestamp_diff(&now, &fio->fio_creation)
> 3 * hz) {
aprint_error_dev(fio->fio_dev,
"syncing write cache after 3 sec timeout!\n");
flash_io_cache_sync(fio);
}
}
mutex_exit(&fio->fio_lock);
kthread_exit(0);
}
void
flash_io_read(struct flash_io *fio, struct buf *bp)
{
size_t retlen;
flash_addr_t offset;
int error;
FLDPRINTF(("flash io read\n"));
offset = bp->b_rawblkno * DEV_BSIZE;
error = fio->fio_if->read(fio->fio_dev, offset, bp->b_resid,
&retlen, bp->b_data);
flash_io_done(fio, bp, error);
}
void
flash_io_write(struct flash_io *fio, struct buf *bp)
{
flash_addr_t block;
FLDPRINTF(("flash io write\n"));
block = flash_io_getblock(fio, bp);
FLDPRINTF(("write to block %jd\n", (intmax_t )block));
mutex_enter(&fio->fio_lock);
if (fio->fio_write_pending && fio->fio_block != block) {
FLDPRINTF(("writing to new block, syncing caches\n"));
flash_io_cache_sync(fio);
}
flash_io_cache_write(fio, block, bp);
mutex_exit(&fio->fio_lock);
}
void
flash_io_done(struct flash_io *fio, struct buf *bp, int error)
{
FLDPRINTF(("io done: %p\n", bp));
if (error == 0)
bp->b_resid = 0;
bp->b_error = error;
biodone(bp);
}
static int
sysctl_flash_verify(SYSCTLFN_ARGS)
{
int error, t;
struct sysctlnode node;
node = *rnode;
t = *(int *)rnode->sysctl_data;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (node.sysctl_num == flash_cachesync_nodenum) {
if (t <= 0 || t > 60)
return EINVAL;
} else {
return EINVAL;
}
*(int *)rnode->sysctl_data = t;
return 0;
}
SYSCTL_SETUP(sysctl_flash, "sysctl flash subtree setup")
{
int rc, flash_root_num;
const struct sysctlnode *node;
if ((rc = sysctl_createv(clog, 0, NULL, &node,
CTLFLAG_PERMANENT, CTLTYPE_NODE, "flash",
SYSCTL_DESCR("FLASH driver controls"),
NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0) {
goto error;
}
flash_root_num = node->sysctl_num;
if ((rc = sysctl_createv(clog, 0, NULL, &node,
CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
CTLTYPE_INT, "cache_sync_timeout",
SYSCTL_DESCR("FLASH write cache sync timeout in seconds"),
sysctl_flash_verify, 0, &flash_cachesync_timeout,
0, CTL_HW, flash_root_num, CTL_CREATE,
CTL_EOL)) != 0) {
goto error;
}
flash_cachesync_nodenum = node->sysctl_num;
return;
error:
aprint_error("%s: sysctl_createv failed (rc = %d)\n", __func__, rc);
}