+/*
+ * 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 "../lib/libmtd.h"
+
+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);
+}
+
+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;
+}
+
+
+