416 lines
10 KiB
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);
|
|
}
|