blob: 3c22a1b15be99032b53d1bb70a27cf1109811cde [file] [log] [blame]
/*
* MTD engine
*
* IO engine that reads/writes from MTD character devices.
*
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <mtd/mtd-user.h>
#include "../fio.h"
#include "../verify.h"
#include "../oslib/libmtd.h"
static libmtd_t desc;
struct fio_mtd_data {
struct mtd_dev_info info;
};
static int fio_mtd_maybe_mark_bad(struct thread_data *td,
struct fio_mtd_data *fmd,
struct io_u *io_u, int eb)
{
int ret;
if (errno == EIO) {
ret = mtd_mark_bad(&fmd->info, io_u->file->fd, eb);
if (ret != 0) {
io_u->error = errno;
td_verror(td, errno, "mtd_mark_bad");
return -1;
}
}
return 0;
}
static int fio_mtd_is_bad(struct thread_data *td,
struct fio_mtd_data *fmd,
struct io_u *io_u, int eb)
{
int ret = mtd_is_bad(&fmd->info, io_u->file->fd, eb);
if (ret == -1) {
io_u->error = errno;
td_verror(td, errno, "mtd_is_bad");
} else if (ret == 1)
io_u->error = EIO; /* Silent failure--don't flood stderr */
return ret;
}
static int fio_mtd_queue(struct thread_data *td, struct io_u *io_u)
{
struct fio_file *f = io_u->file;
struct fio_mtd_data *fmd = FILE_ENG_DATA(f);
int local_offs = 0;
int ret;
fio_ro_check(td, io_u);
/*
* Errors tend to pertain to particular erase blocks, so divide up
* I/O to erase block size.
* If an error is encountered, log it and keep going onto the next
* block because the error probably just pertains to that block.
* TODO(dehrenberg): Divide up reads and writes into page-sized
* operations to get more fine-grained information about errors.
*/
while (local_offs < io_u->buflen) {
int eb = (io_u->offset + local_offs) / fmd->info.eb_size;
int eb_offs = (io_u->offset + local_offs) % fmd->info.eb_size;
/* The length is the smaller of the length remaining in the
* buffer and the distance to the end of the erase block */
int len = min((int)io_u->buflen - local_offs,
(int)fmd->info.eb_size - eb_offs);
char *buf = ((char *)io_u->buf) + local_offs;
if (td->o.skip_bad) {
ret = fio_mtd_is_bad(td, fmd, io_u, eb);
if (ret == -1)
break;
else if (ret == 1)
goto next;
}
if (io_u->ddir == DDIR_READ) {
ret = mtd_read(&fmd->info, f->fd, eb, eb_offs, buf, len);
if (ret != 0) {
io_u->error = errno;
td_verror(td, errno, "mtd_read");
if (fio_mtd_maybe_mark_bad(td, fmd, io_u, eb))
break;
}
} else if (io_u->ddir == DDIR_WRITE) {
ret = mtd_write(desc, &fmd->info, f->fd, eb,
eb_offs, buf, len, NULL, 0, 0);
if (ret != 0) {
io_u->error = errno;
td_verror(td, errno, "mtd_write");
if (fio_mtd_maybe_mark_bad(td, fmd, io_u, eb))
break;
}
} else if (io_u->ddir == DDIR_TRIM) {
if (eb_offs != 0 || len != fmd->info.eb_size) {
io_u->error = EINVAL;
td_verror(td, EINVAL,
"trim on MTD must be erase block-aligned");
}
ret = mtd_erase(desc, &fmd->info, f->fd, eb);
if (ret != 0) {
io_u->error = errno;
td_verror(td, errno, "mtd_erase");
if (fio_mtd_maybe_mark_bad(td, fmd, io_u, eb))
break;
}
} else {
io_u->error = ENOTSUP;
td_verror(td, io_u->error, "operation not supported on mtd");
}
next:
local_offs += len;
}
return FIO_Q_COMPLETED;
}
static int fio_mtd_open_file(struct thread_data *td, struct fio_file *f)
{
struct fio_mtd_data *fmd;
int ret;
ret = generic_open_file(td, f);
if (ret)
return ret;
fmd = calloc(1, sizeof(*fmd));
if (!fmd)
goto err_close;
ret = mtd_get_dev_info(desc, f->file_name, &fmd->info);
if (ret != 0) {
td_verror(td, errno, "mtd_get_dev_info");
goto err_free;
}
FILE_SET_ENG_DATA(f, fmd);
return 0;
err_free:
free(fmd);
err_close:
{
int fio_unused __ret;
__ret = generic_close_file(td, f);
return 1;
}
}
static int fio_mtd_close_file(struct thread_data *td, struct fio_file *f)
{
struct fio_mtd_data *fmd = FILE_ENG_DATA(f);
FILE_SET_ENG_DATA(f, NULL);
free(fmd);
return generic_close_file(td, f);
}
static int fio_mtd_get_file_size(struct thread_data *td, struct fio_file *f)
{
struct mtd_dev_info info;
int ret = mtd_get_dev_info(desc, f->file_name, &info);
if (ret != 0) {
td_verror(td, errno, "mtd_get_dev_info");
return errno;
}
f->real_file_size = info.size;
return 0;
}
static struct ioengine_ops ioengine = {
.name = "mtd",
.version = FIO_IOOPS_VERSION,
.queue = fio_mtd_queue,
.open_file = fio_mtd_open_file,
.close_file = fio_mtd_close_file,
.get_file_size = fio_mtd_get_file_size,
.flags = FIO_SYNCIO | FIO_NOEXTEND,
};
static void fio_init fio_mtd_register(void)
{
desc = libmtd_open();
register_ioengine(&ioengine);
}
static void fio_exit fio_mtd_unregister(void)
{
unregister_ioengine(&ioengine);
libmtd_close(desc);
desc = NULL;
}