[PATCH] Separate io engines into separate loadable objects

Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
diff --git a/engines/fio-engine-sg.c b/engines/fio-engine-sg.c
new file mode 100644
index 0000000..59eea1d
--- /dev/null
+++ b/engines/fio-engine-sg.c
@@ -0,0 +1,324 @@
+/*
+ * scsi generic sg v3 io engine
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/poll.h>
+#include "fio.h"
+#include "os.h"
+
+struct sgio_cmd {
+	unsigned char cdb[10];
+	int nr;
+};
+
+struct sgio_data {
+	struct sgio_cmd *cmds;
+	struct io_u **events;
+	unsigned int bs;
+};
+
+static void sgio_hdr_init(struct sgio_data *sd, struct sg_io_hdr *hdr,
+			  struct io_u *io_u, int fs)
+{
+	struct sgio_cmd *sc = &sd->cmds[io_u->index];
+
+	memset(hdr, 0, sizeof(*hdr));
+	memset(sc->cdb, 0, sizeof(sc->cdb));
+
+	hdr->interface_id = 'S';
+	hdr->cmdp = sc->cdb;
+	hdr->cmd_len = sizeof(sc->cdb);
+	hdr->pack_id = io_u->index;
+	hdr->usr_ptr = io_u;
+
+	if (fs) {
+		hdr->dxferp = io_u->buf;
+		hdr->dxfer_len = io_u->buflen;
+	}
+}
+
+static int fio_sgio_ioctl_getevents(struct thread_data *td, int fio_unused min,
+				    int max, struct timespec fio_unused *t)
+{
+	assert(max <= 1);
+
+	/*
+	 * we can only have one finished io_u for sync io, since the depth
+	 * is always 1
+	 */
+	if (list_empty(&td->io_u_busylist))
+		return 0;
+
+	return 1;
+}
+
+
+static int fio_sgio_getevents(struct thread_data *td, int min, int max,
+			      struct timespec fio_unused *t)
+{
+	struct sgio_data *sd = td->io_ops->data;
+	struct pollfd pfd = { .fd = td->fd, .events = POLLIN };
+	void *buf = malloc(max * sizeof(struct sg_io_hdr));
+	int left = max, ret, events, i, r = 0, fl = 0;
+
+	/*
+	 * don't block for !events
+	 */
+	if (!min) {
+		fl = fcntl(td->fd, F_GETFL);
+		fcntl(td->fd, F_SETFL, fl | O_NONBLOCK);
+	}
+
+	while (left) {
+		do {
+			if (!min)
+				break;
+			poll(&pfd, 1, -1);
+			if (pfd.revents & POLLIN)
+				break;
+		} while (1);
+
+		ret = read(td->fd, buf, left * sizeof(struct sg_io_hdr));
+		if (ret < 0) {
+			if (errno == EAGAIN)
+				break;
+			td_verror(td, errno);
+			r = -1;
+			break;
+		} else if (!ret)
+			break;
+
+		events = ret / sizeof(struct sg_io_hdr);
+		left -= events;
+		r += events;
+
+		for (i = 0; i < events; i++) {
+			struct sg_io_hdr *hdr = (struct sg_io_hdr *) buf + i;
+
+			sd->events[i] = hdr->usr_ptr;
+		}
+	}
+
+	if (!min)
+		fcntl(td->fd, F_SETFL, fl);
+
+	free(buf);
+	return r;
+}
+
+static int fio_sgio_ioctl_doio(struct thread_data *td, struct io_u *io_u)
+{
+	struct sgio_data *sd = td->io_ops->data;
+	struct sg_io_hdr *hdr = &io_u->hdr;
+
+	sd->events[0] = io_u;
+
+	return ioctl(td->fd, SG_IO, hdr);
+}
+
+static int fio_sgio_rw_doio(struct thread_data *td, struct io_u *io_u, int sync)
+{
+	struct sg_io_hdr *hdr = &io_u->hdr;
+	int ret;
+
+	ret = write(td->fd, hdr, sizeof(*hdr));
+	if (ret < 0)
+		return errno;
+
+	if (sync) {
+		ret = read(td->fd, hdr, sizeof(*hdr));
+		if (ret < 0)
+			return errno;
+	}
+
+	return 0;
+}
+
+static int fio_sgio_doio(struct thread_data *td, struct io_u *io_u, int sync)
+{
+	if (td->filetype == FIO_TYPE_BD)
+		return fio_sgio_ioctl_doio(td, io_u);
+
+	return fio_sgio_rw_doio(td, io_u, sync);
+}
+
+static int fio_sgio_sync(struct thread_data *td)
+{
+	struct sgio_data *sd = td->io_ops->data;
+	struct sg_io_hdr *hdr;
+	struct io_u *io_u;
+	int ret;
+
+	io_u = __get_io_u(td);
+	if (!io_u)
+		return ENOMEM;
+
+	hdr = &io_u->hdr;
+	sgio_hdr_init(sd, hdr, io_u, 0);
+	hdr->dxfer_direction = SG_DXFER_NONE;
+
+	hdr->cmdp[0] = 0x35;
+
+	ret = fio_sgio_doio(td, io_u, 1);
+	put_io_u(td, io_u);
+	return ret;
+}
+
+static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u)
+{
+	struct sg_io_hdr *hdr = &io_u->hdr;
+	struct sgio_data *sd = td->io_ops->data;
+	int nr_blocks, lba;
+
+	if (io_u->buflen & (sd->bs - 1)) {
+		log_err("read/write not sector aligned\n");
+		return EINVAL;
+	}
+
+	sgio_hdr_init(sd, hdr, io_u, 1);
+
+	if (io_u->ddir == DDIR_READ) {
+		hdr->dxfer_direction = SG_DXFER_FROM_DEV;
+		hdr->cmdp[0] = 0x28;
+	} else {
+		hdr->dxfer_direction = SG_DXFER_TO_DEV;
+		hdr->cmdp[0] = 0x2a;
+	}
+
+	nr_blocks = io_u->buflen / sd->bs;
+	lba = io_u->offset / sd->bs;
+	hdr->cmdp[2] = (lba >> 24) & 0xff;
+	hdr->cmdp[3] = (lba >> 16) & 0xff;
+	hdr->cmdp[4] = (lba >>  8) & 0xff;
+	hdr->cmdp[5] = lba & 0xff;
+	hdr->cmdp[7] = (nr_blocks >> 8) & 0xff;
+	hdr->cmdp[8] = nr_blocks & 0xff;
+	return 0;
+}
+
+static int fio_sgio_queue(struct thread_data *td, struct io_u *io_u)
+{
+	struct sg_io_hdr *hdr = &io_u->hdr;
+	int ret;
+
+	ret = fio_sgio_doio(td, io_u, 0);
+
+	if (ret < 0)
+		io_u->error = errno;
+	else if (hdr->status) {
+		io_u->resid = hdr->resid;
+		io_u->error = EIO;
+	}
+
+	return io_u->error;
+}
+
+static struct io_u *fio_sgio_event(struct thread_data *td, int event)
+{
+	struct sgio_data *sd = td->io_ops->data;
+
+	return sd->events[event];
+}
+
+static int fio_sgio_get_bs(struct thread_data *td, unsigned int *bs)
+{
+	struct sgio_data *sd = td->io_ops->data;
+	struct io_u *io_u;
+	struct sg_io_hdr *hdr;
+	unsigned char buf[8];
+	int ret;
+
+	io_u = __get_io_u(td);
+	assert(io_u);
+
+	hdr = &io_u->hdr;
+	sgio_hdr_init(sd, hdr, io_u, 0);
+	memset(buf, 0, sizeof(buf));
+
+	hdr->cmdp[0] = 0x25;
+	hdr->dxfer_direction = SG_DXFER_FROM_DEV;
+	hdr->dxferp = buf;
+	hdr->dxfer_len = sizeof(buf);
+
+	ret = fio_sgio_doio(td, io_u, 1);
+	if (ret) {
+		put_io_u(td, io_u);
+		return ret;
+	}
+
+	*bs = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+	put_io_u(td, io_u);
+	return 0;
+}
+
+static void fio_sgio_cleanup(struct thread_data *td)
+{
+	if (td->io_ops->data) {
+		free(td->io_ops->data);
+		td->io_ops->data = NULL;
+	}
+}
+
+static int fio_sgio_init(struct thread_data *td)
+{
+	struct sgio_data *sd;
+	unsigned int bs;
+	int ret;
+
+	sd = malloc(sizeof(*sd));
+	sd->cmds = malloc(td->iodepth * sizeof(struct sgio_cmd));
+	sd->events = malloc(td->iodepth * sizeof(struct io_u *));
+	td->io_ops->data = sd;
+
+	if (td->filetype == FIO_TYPE_BD) {
+		if (ioctl(td->fd, BLKSSZGET, &bs) < 0) {
+			td_verror(td, errno);
+			return 1;
+		}
+	} else if (td->filetype == FIO_TYPE_CHAR) {
+		int version;
+
+		if (ioctl(td->fd, SG_GET_VERSION_NUM, &version) < 0) {
+			td_verror(td, errno);
+			return 1;
+		}
+
+		ret = fio_sgio_get_bs(td, &bs);
+		if (ret)
+			return ret;
+	} else {
+		log_err("ioengine sgio only works on block devices\n");
+		return 1;
+	}
+
+	sd->bs = bs;
+
+	if (td->filetype == FIO_TYPE_BD)
+		td->io_ops->getevents = fio_sgio_ioctl_getevents;
+	else
+		td->io_ops->getevents = fio_sgio_getevents;
+
+	/*
+	 * we want to do it, regardless of whether odirect is set or not
+	 */
+	td->override_sync = 1;
+	return 0;
+}
+
+struct ioengine_ops ioengine = {
+	.name		= "sg",
+	.version	= FIO_IOOPS_VERSION,
+	.init		= fio_sgio_init,
+	.prep		= fio_sgio_prep,
+	.queue		= fio_sgio_queue,
+	.getevents	= fio_sgio_getevents,
+	.event		= fio_sgio_event,
+	.cleanup	= fio_sgio_cleanup,
+	.sync		= fio_sgio_sync,
+	.flags		= FIO_SYNCIO,
+};