mtd: ioengine
[fio.git] / engines / mtd.c
diff --git a/engines/mtd.c b/engines/mtd.c
new file mode 100644 (file)
index 0000000..db9c539
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * 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;
+}
+
+
+