209 lines
4.7 KiB
C
209 lines
4.7 KiB
C
/*
|
|
* 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;
|
|
}
|
|
|
|
|
|
|