The test scheduler allows testing a block device by dispatching
specific requests according to the test case and declare PASS/FAIL
according to the requests completion error code

Signed-off-by: Maya Erez <me...@codeaurora.org>
---
 Documentation/block/test-iosched.txt |   39 ++
 block/Kconfig.iosched                |   11 +
 block/Makefile                       |    1 +
 block/blk-core.c                     |    3 +-
 block/test-iosched.c                 | 1038 ++++++++++++++++++++++++++++++++++
 include/linux/test-iosched.h         |  233 ++++++++
 6 files changed, 1323 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/block/test-iosched.txt
 create mode 100644 block/test-iosched.c
 create mode 100644 include/linux/test-iosched.h

diff --git a/Documentation/block/test-iosched.txt 
b/Documentation/block/test-iosched.txt
new file mode 100644
index 0000000..75d8134
--- /dev/null
+++ b/Documentation/block/test-iosched.txt
@@ -0,0 +1,39 @@
+Test IO scheduler
+==================
+
+The test scheduler allows testing a block device by dispatching
+specific requests according to the test case and declare PASS/FAIL
+according to the requests completion error code.
+
+The test IO scheduler implements the no-op scheduler operations, and uses
+them in order to dispatch the non-test requests when no test is running.
+This will allow to keep a normal FS operation in parallel to the test
+capability.
+The test IO scheduler keeps two different queues, one for real-world requests
+(inserted by the FS) and the other for the test requests.
+The test IO scheduler chooses the queue for dispatch requests according to the
+test state (IDLE/RUNNING).
+
+the test IO scheduler is compiled by default as a dynamic module and enabled
+only if CONFIG_DEBUG_FS is defined.
+
+Each block device test utility that would like to use the test-iosched test
+services, should register as a blk_dev_test_type and supply an init and exit
+callbacks. Those callback are called upon selection (or removal) of the
+test-iosched as the active scheduler. From that point the block device test
+can start a test and supply its own callbacks for preparing, running, result
+checking and cleanup of the test.
+
+Each test is exposed via debugfs and can be triggered by writing to
+the debugfs file. In order to add a new test one should expose a new debugfs
+file for the new test.
+
+Selecting IO schedulers
+-----------------------
+Refer to Documentation/block/switching-sched.txt for information on
+selecting an io scheduler on a per-device basis.
+
+
+May 10 2012, maya Erez <me...@codeaurora.org>
+
+
diff --git a/block/Kconfig.iosched b/block/Kconfig.iosched
index 421bef9..af3d6a3 100644
--- a/block/Kconfig.iosched
+++ b/block/Kconfig.iosched
@@ -12,6 +12,17 @@ config IOSCHED_NOOP
          that do their own scheduling and require only minimal assistance from
          the kernel.
 
+config IOSCHED_TEST
+       tristate "Test I/O scheduler"
+       depends on DEBUG_FS
+       default m
+       ---help---
+         The test I/O scheduler is a duplicate of the noop scheduler with
+         addition of test utlity.
+         It allows testing a block device by dispatching specific requests
+         according to the test case and declare PASS/FAIL according to the
+         requests completion error code.
+
 config IOSCHED_DEADLINE
        tristate "Deadline I/O scheduler"
        default y
diff --git a/block/Makefile b/block/Makefile
index 39b76ba..436b220 100644
--- a/block/Makefile
+++ b/block/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_BLK_DEV_THROTTLING)      += blk-throttle.o
 obj-$(CONFIG_IOSCHED_NOOP)     += noop-iosched.o
 obj-$(CONFIG_IOSCHED_DEADLINE) += deadline-iosched.o
 obj-$(CONFIG_IOSCHED_CFQ)      += cfq-iosched.o
+obj-$(CONFIG_IOSCHED_TEST)     += test-iosched.o
 
 obj-$(CONFIG_BLOCK_COMPAT)     += compat_ioctl.o
 obj-$(CONFIG_BLK_DEV_INTEGRITY)        += blk-integrity.o
diff --git a/block/blk-core.c b/block/blk-core.c
index c3b17c3..6fe111e 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -1085,8 +1085,6 @@ struct request *blk_get_request(struct request_queue *q, 
int rw, gfp_t gfp_mask)
 {
        struct request *rq;
 
-       BUG_ON(rw != READ && rw != WRITE);
-
        spin_lock_irq(q->queue_lock);
        if (gfp_mask & __GFP_WAIT)
                rq = get_request_wait(q, rw, NULL);
@@ -1419,6 +1417,7 @@ void init_request_from_bio(struct request *req, struct 
bio *bio)
        req->ioprio = bio_prio(bio);
        blk_rq_bio_prep(req->q, req, bio);
 }
+EXPORT_SYMBOL(init_request_from_bio);
 
 void blk_queue_bio(struct request_queue *q, struct bio *bio)
 {
diff --git a/block/test-iosched.c b/block/test-iosched.c
new file mode 100644
index 0000000..d3d10d3
--- /dev/null
+++ b/block/test-iosched.c
@@ -0,0 +1,1038 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * The test scheduler allows to test the block device by dispatching
+ * specific requests according to the test case and declare PASS/FAIL
+ * according to the requests completion error code.
+ * Each test is exposed via debugfs and can be triggered by writing to
+ * the debugfs file.
+ *
+ */
+
+/* elevator test iosched */
+#include <linux/blkdev.h>
+#include <linux/elevator.h>
+#include <linux/bio.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/debugfs.h>
+#include <linux/test-iosched.h>
+#include <linux/delay.h>
+#include "blk.h"
+
+#define MODULE_NAME "test-iosched"
+#define WR_RD_START_REQ_ID 1234
+#define UNIQUE_START_REQ_ID 5678
+#define TIMEOUT_TIMER_MS 40000
+#define TEST_MAX_TESTCASE_ROUNDS 15
+
+#define test_pr_debug(fmt, args...) pr_debug("%s: "fmt"\n", MODULE_NAME, args)
+#define test_pr_info(fmt, args...) pr_info("%s: "fmt"\n", MODULE_NAME, args)
+#define test_pr_err(fmt, args...) pr_err("%s: "fmt"\n", MODULE_NAME, args)
+
+static DEFINE_SPINLOCK(blk_dev_test_list_lock);
+static LIST_HEAD(blk_dev_test_list);
+static struct test_data *ptd;
+
+/* Get the request after `test_rq' in the test requests list */
+static struct test_request *
+latter_test_request(struct request_queue *q,
+                                struct test_request *test_rq)
+{
+       struct test_data *td = q->elevator->elevator_data;
+
+       if (test_rq->queuelist.next == &td->test_queue)
+               return NULL;
+       return list_entry(test_rq->queuelist.next, struct test_request,
+                         queuelist);
+}
+
+/**
+ * test_iosched_get_req_queue() - returns the request queue
+ * served by the scheduler
+ */
+struct request_queue *test_iosched_get_req_queue(void)
+{
+       if (!ptd)
+               return NULL;
+
+       return ptd->req_q;
+}
+EXPORT_SYMBOL(test_iosched_get_req_queue);
+
+/**
+ * test_iosched_mark_test_completion() - Wakeup the debugfs
+ * thread, waiting on the test completion
+ */
+void test_iosched_mark_test_completion(void)
+{
+       if (!ptd)
+               return;
+
+       ptd->test_state = TEST_COMPLETED;
+       wake_up(&ptd->wait_q);
+}
+EXPORT_SYMBOL(test_iosched_mark_test_completion);
+
+/* Check if all the queued test requests were completed */
+static void check_test_completion(void)
+{
+       struct test_request *test_rq;
+       struct request *rq;
+
+       list_for_each_entry(test_rq, &ptd->test_queue, queuelist) {
+               rq = test_rq->rq;
+               if (!test_rq->req_completed)
+                       return;
+       }
+
+       test_pr_info("%s: Test is completed", __func__);
+
+       test_iosched_mark_test_completion();
+}
+
+/*
+ * A callback to be called per bio completion.
+ * Frees the bio memory.
+ */
+static void end_test_bio(struct bio *bio, int err)
+{
+       if (err)
+               clear_bit(BIO_UPTODATE, &bio->bi_flags);
+
+       bio_put(bio);
+}
+
+/*
+ * A callback to be called per request completion.
+ * the request memory is not freed here, will be freed later after the test
+ * results checking.
+ */
+static void end_test_req(struct request *rq, int err)
+{
+       struct test_request *test_rq;
+
+       test_rq = (struct test_request *)rq->elv.priv[0];
+       BUG_ON(!test_rq);
+
+       test_pr_info("%s: request %d completed, err=%d",
+              __func__, test_rq->req_id, err);
+
+       test_rq->req_completed = true;
+       test_rq->req_result = err;
+
+       check_test_completion();
+}
+
+/**
+ * test_iosched_add_unique_test_req() - Create and queue a non
+ * read/write request (such as FLUSH/DISCRAD/SANITIZE).
+ * @is_err_expcted:    A flag to indicate if this request
+ *                     should succeed or not
+ * @req_unique:                The type of request to add
+ * @start_sec:         start address of the first bio
+ * @nr_sects:          number of sectors in the request
+ * @end_req_io:                specific completion callback. When not
+ *                     set, the defaulcallback will be used
+ */
+int test_iosched_add_unique_test_req(int is_err_expcted,
+                       enum req_unique_type req_unique,
+                       int start_sec, int nr_sects, rq_end_io_fn *end_req_io)
+{
+       struct bio *bio;
+       struct request *rq;
+       int rw_flags;
+       struct test_request *test_rq;
+
+       if (!ptd)
+               return -ENODEV;
+
+       bio = bio_alloc(GFP_KERNEL, 0);
+       if (!bio) {
+               test_pr_err("%s: Failed to allocate a bio", __func__);
+               return -ENODEV;
+       }
+       bio_get(bio);
+       bio->bi_end_io = end_test_bio;
+
+       switch (req_unique) {
+       case REQ_UNIQUE_FLUSH:
+               bio->bi_rw = WRITE_FLUSH;
+               break;
+       case REQ_UNIQUE_DISCARD:
+               bio->bi_rw = REQ_WRITE | REQ_DISCARD;
+               bio->bi_size = nr_sects << 9;
+               bio->bi_sector = start_sec;
+               break;
+       default:
+               test_pr_err("%s: Invalid request type %d", __func__,
+                           req_unique);
+               bio_put(bio);
+               return -ENODEV;
+       }
+
+       rw_flags = bio_data_dir(bio);
+       if (bio->bi_rw & REQ_SYNC)
+               rw_flags |= REQ_SYNC;
+
+       rq = blk_get_request(ptd->req_q, rw_flags, GFP_KERNEL);
+       if (!rq) {
+               test_pr_err("%s: Failed to allocate a request", __func__);
+               bio_put(bio);
+               return -ENODEV;
+       }
+
+       init_request_from_bio(rq, bio);
+       if (end_req_io)
+               rq->end_io = end_req_io;
+       else
+               rq->end_io = end_test_req;
+
+       test_rq = kzalloc(sizeof(struct test_request), GFP_KERNEL);
+       if (!test_rq) {
+               test_pr_err("%s: Failed to allocate a test request", __func__);
+               bio_put(bio);
+               blk_put_request(rq);
+               return -ENODEV;
+       }
+       test_rq->req_completed = false;
+       test_rq->req_result = -EINVAL;
+       test_rq->rq = rq;
+       test_rq->is_err_expected = is_err_expcted;
+       rq->elv.priv[0] = (void *)test_rq;
+       test_rq->req_id = ptd->unique_next_req_id++;
+
+       test_pr_debug(
+               "%s: added request %d to the test requests list, type = %d",
+               __func__, test_rq->req_id, req_unique);
+
+       list_add_tail(&test_rq->queuelist, &ptd->test_queue);
+
+       return 0;
+}
+EXPORT_SYMBOL(test_iosched_add_unique_test_req);
+
+/*
+ * Get a pattern to be filled in the request data buffer.
+ * If the pattern used is (-1) the buffer will be filled with sequential
+ * numbers
+ */
+static void fill_buf_with_pattern(int *buf, int num_bytes, int pattern)
+{
+       int i = 0;
+       int num_of_dwords = num_bytes/sizeof(int);
+
+       if (pattern == TEST_NO_PATTERN)
+               return;
+
+       /* num_bytes should be aligned to sizeof(int) */
+       BUG_ON((num_bytes % sizeof(int)) != 0);
+
+       if (pattern == TEST_PATTERN_SEQUENTIAL) {
+               for (i = 0; i < num_of_dwords; i++)
+                       buf[i] = i;
+       } else {
+               for (i = 0; i < num_of_dwords; i++)
+                       buf[i] = pattern;
+       }
+}
+
+/**
+ * test_iosched_add_wr_rd_test_req() - Create and queue a
+ * read/write request.
+ * @is_err_expcted:    A flag to indicate if this request
+ *                     should succeed or not
+ * @direction:         READ/WRITE
+ * @start_sec:         start address of the first bio
+ * @num_bios:          number of BIOs to be allocated for the
+ *                     request
+ * @pattern:           A pattern, to be written into the write
+ *                     requests data buffer. In case of READ
+ *                     request, the given pattern is kept as
+ *                     the expected pattern. The expected
+ *                     pattern will be compared in the test
+ *                     check result function. If no comparisson
+ *                     is required, set pattern to
+ *                     TEST_NO_PATTERN.
+ * @end_req_io:                specific completion callback. When not
+ *                     set,the default callback will be used
+ *
+ * This function allocates the test request and the block
+ * request and calls blk_rq_map_kern which allocates the
+ * required BIO. The allocated test request and the block
+ * request memory is freed at the end of the test and the
+ * allocated BIO memory is freed by end_test_bio.
+ */
+int test_iosched_add_wr_rd_test_req(int is_err_expcted,
+                     int direction, int start_sec,
+                     int num_bios, int pattern, rq_end_io_fn *end_req_io)
+{
+       struct request *rq = NULL;
+       struct test_request *test_rq = NULL;
+       int rw_flags = 0;
+       int buf_size = 0;
+       int ret = 0, i = 0;
+       unsigned int *bio_ptr = NULL;
+       struct bio *bio = NULL;
+
+       if (!ptd)
+               return -ENODEV;
+
+       rw_flags = direction;
+
+       rq = blk_get_request(ptd->req_q, rw_flags, GFP_KERNEL);
+       if (!rq) {
+               test_pr_err("%s: Failed to allocate a request", __func__);
+               return -ENODEV;
+       }
+
+       test_rq = kzalloc(sizeof(struct test_request), GFP_KERNEL);
+       if (!test_rq) {
+               test_pr_err("%s: Failed to allocate test request", __func__);
+               blk_put_request(rq);
+               return -ENODEV;
+       }
+
+       buf_size = sizeof(unsigned int) * BIO_U32_SIZE * num_bios;
+       test_rq->bios_buffer = kzalloc(buf_size, GFP_KERNEL);
+       if (!test_rq->bios_buffer) {
+               test_pr_err("%s: Failed to allocate the data buf", __func__);
+               goto err;
+       }
+       test_rq->buf_size = buf_size;
+
+       if (direction == WRITE)
+               fill_buf_with_pattern(test_rq->bios_buffer,
+                                                  buf_size, pattern);
+       test_rq->wr_rd_data_pattern = pattern;
+
+       bio_ptr = test_rq->bios_buffer;
+       for (i = 0; i < num_bios; ++i) {
+               ret = blk_rq_map_kern(ptd->req_q, rq,
+                                     (void *)bio_ptr,
+                                     sizeof(unsigned int)*BIO_U32_SIZE,
+                                     GFP_KERNEL);
+               if (ret) {
+                       test_pr_err("%s: blk_rq_map_kern returned error %d",
+                                   __func__, ret);
+                       goto err;
+               }
+               bio_ptr += BIO_U32_SIZE;
+       }
+
+       if (end_req_io)
+               rq->end_io = end_req_io;
+       else
+               rq->end_io = end_test_req;
+       rq->__sector = start_sec;
+       rq->cmd_type |= REQ_TYPE_FS;
+
+       if (rq->bio) {
+               rq->bio->bi_sector = start_sec;
+               rq->bio->bi_end_io = end_test_bio;
+               bio = rq->bio;
+               while ((bio = bio->bi_next) != NULL)
+                       bio->bi_end_io = end_test_bio;
+       }
+
+       ptd->num_of_write_bios += num_bios;
+       test_rq->req_id = ptd->wr_rd_next_req_id++;
+
+       test_rq->req_completed = false;
+       test_rq->req_result = -EINVAL;
+       test_rq->rq = rq;
+       test_rq->is_err_expected = is_err_expcted;
+       rq->elv.priv[0] = (void *)test_rq;
+
+       test_pr_debug(
+               "%s: added request %d to the test requests list, buf_size=%d",
+               __func__, test_rq->req_id, buf_size);
+
+       list_add_tail(&test_rq->queuelist, &ptd->test_queue);
+
+       return 0;
+err:
+       blk_put_request(rq);
+       kfree(test_rq->bios_buffer);
+       return -ENODEV;
+}
+EXPORT_SYMBOL(test_iosched_add_wr_rd_test_req);
+
+/* Converts the testcase number into a string */
+static char *get_test_case_str(struct test_data *td)
+{
+       if (td->test_info.get_test_case_str_fn)
+               return td->test_info.get_test_case_str_fn(td);
+
+       return "Unknown testcase";
+}
+
+/*
+ * Verify that the test request data buffer includes the expected
+ * pattern
+ */
+static int compare_buffer_to_pattern(struct test_request *test_rq)
+{
+       int i = 0;
+       int num_of_dwords = test_rq->buf_size/sizeof(int);
+
+       /* num_bytes should be aligned to sizeof(int) */
+       BUG_ON((test_rq->buf_size % sizeof(int)) != 0);
+       BUG_ON(test_rq->bios_buffer == NULL);
+
+       if (test_rq->wr_rd_data_pattern == TEST_NO_PATTERN)
+               return 0;
+
+       if (test_rq->wr_rd_data_pattern == TEST_PATTERN_SEQUENTIAL) {
+               for (i = 0; i < num_of_dwords; i++) {
+                       if (test_rq->bios_buffer[i] != i) {
+                               test_pr_err(
+                                       "%s: wrong pattern 0x%x in index %d",
+                                       __func__, test_rq->bios_buffer[i], i);
+                               return -EINVAL;
+                       }
+               }
+       } else {
+               for (i = 0; i < num_of_dwords; i++) {
+                       if (test_rq->bios_buffer[i] !=
+                           test_rq->wr_rd_data_pattern) {
+                               test_pr_err(
+                                       "%s: wrong pattern 0x%x in index %d",
+                                       __func__, test_rq->bios_buffer[i], i);
+                               return -EINVAL;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * Determine if the test passed or failed.
+ * The function checks the test request completion value and calls
+ * check_testcase_result for result checking that are specific
+ * to a test case.
+ */
+static int check_test_result(struct test_data *td)
+{
+       struct test_request *test_rq;
+       struct request *rq;
+       int res = 0;
+       static int run;
+
+       list_for_each_entry(test_rq, &ptd->test_queue, queuelist) {
+               rq = test_rq->rq;
+               if (!test_rq->req_completed) {
+                       test_pr_err("%s: rq %d not completed", __func__,
+                                   test_rq->req_id);
+                       res = -EINVAL;
+                       goto err;
+               }
+
+               if ((test_rq->req_result < 0) && !test_rq->is_err_expected) {
+                       test_pr_err(
+                               "%s: rq %d completed with err, not as expected",
+                               __func__, test_rq->req_id);
+                       res = -EINVAL;
+                       goto err;
+               }
+               if ((test_rq->req_result == 0) && test_rq->is_err_expected) {
+                       test_pr_err("%s: rq %d succeeded, not as expected",
+                                   __func__, test_rq->req_id);
+                       res = -EINVAL;
+                       goto err;
+               }
+               if (rq_data_dir(test_rq->rq) == READ) {
+                       res = compare_buffer_to_pattern(test_rq);
+                       if (res) {
+                               test_pr_err("%s: read pattern not as expected",
+                                           __func__);
+                               res = -EINVAL;
+                               goto err;
+                       }
+               }
+       }
+
+       if (td->test_info.check_test_result_fn) {
+               res = td->test_info.check_test_result_fn(td);
+               if (res)
+                       goto err;
+       }
+
+       test_pr_info("%s: %s, run# %03d, PASSED",
+                           __func__, get_test_case_str(td), ++run);
+       td->test_result = TEST_PASSED;
+
+       return 0;
+err:
+       test_pr_err("%s: %s, run# %03d, FAILED",
+                   __func__, get_test_case_str(td), ++run);
+       td->test_result = TEST_FAILED;
+       return res;
+}
+
+/* Create and queue the required requests according to the test case */
+static int prepare_test(struct test_data *td)
+{
+       int ret = 0;
+
+       if (td->test_info.prepare_test_fn) {
+               ret = td->test_info.prepare_test_fn(td);
+               return ret;
+       }
+
+       return 0;
+}
+
+/* Run the test */
+static int run_test(struct test_data *td)
+{
+       int ret = 0;
+
+       if (td->test_info.run_test_fn) {
+               ret = td->test_info.run_test_fn(td);
+               return ret;
+       }
+
+       /*
+        * Set the next_req pointer to the first request in the test requests
+        * list
+        */
+       if (!list_empty(&td->test_queue))
+               td->next_req = list_entry(td->test_queue.next,
+                                         struct test_request, queuelist);
+       __blk_run_queue(td->req_q);
+
+       return 0;
+}
+
+/* Free the allocated test requests, their requests and BIOs buffer */
+static void free_test_requests(struct test_data *td)
+{
+       struct test_request *test_rq;
+       struct bio *bio;
+
+       while (!list_empty(&td->test_queue)) {
+               test_rq = list_entry(td->test_queue.next, struct test_request,
+                                    queuelist);
+               list_del_init(&test_rq->queuelist);
+               /*
+                * If the request was not completed we need to free its BIOs
+                * and remove it from the packed list
+                */
+               if (!test_rq->req_completed) {
+                       test_pr_info(
+                               "%s: Freeing memory of an uncompleted request",
+                               __func__);
+                       list_del_init(&test_rq->rq->queuelist);
+                       while ((bio = test_rq->rq->bio) != NULL) {
+                               test_rq->rq->bio = bio->bi_next;
+                               bio_put(bio);
+                       }
+               }
+               blk_put_request(test_rq->rq);
+               kfree(test_rq->bios_buffer);
+               kfree(test_rq);
+       }
+}
+
+/*
+ * Do post test operations.
+ * Free the allocated test requests, their requests and BIOs buffer.
+ */
+static int post_test(struct test_data *td)
+{
+       int ret = 0;
+
+       if (td->test_info.post_test_fn)
+               ret = td->test_info.post_test_fn(td);
+
+       ptd->next_req = NULL;
+
+       free_test_requests(td);
+
+       ptd->test_info.testcase = 0;
+       ptd->test_state = TEST_IDLE;
+
+       return ret;
+}
+
+/*
+ * The timer verifies that the test will be completed even if we don't get
+ * the completion callback for all the requests.
+ */
+static void test_timeout_handler(unsigned long data)
+{
+       struct test_data *td = (struct test_data *)data;
+
+       test_pr_info("%s: TIMEOUT timer expired", __func__);
+       td->test_state = TEST_COMPLETED;
+       wake_up(&td->wait_q);
+       return;
+}
+
+static unsigned int get_timeout_msec(struct test_data *td)
+{
+       if (td->test_info.timeout_msec)
+               return td->test_info.timeout_msec;
+       else
+               return TIMEOUT_TIMER_MS;
+}
+
+/**
+ * test_iosched_start_test() - Prepares and runs the test.
+ * @t_info:    the current test testcase and callbacks
+ *             functions
+ *
+ * The function also checks the test result upon test completion
+ */
+int test_iosched_start_test(struct test_info *t_info)
+{
+       int ret = 0;
+       unsigned timeout_msec;
+       int counter = 0;
+       char *test_name = NULL;
+
+       if (!ptd)
+               return -ENODEV;
+
+       if (!t_info) {
+               ptd->test_result = TEST_FAILED;
+               return -EINVAL;
+       }
+
+       do {
+               if (ptd->ignore_round)
+                       /*
+                        * We ignored the last run due to FS write requests.
+                        * Sleep to allow those requests to be issued
+                        */
+                       msleep(2000);
+
+               spin_lock(&ptd->lock);
+
+               if (ptd->test_state != TEST_IDLE) {
+                       test_pr_info(
+                               "%s: Another test is running, try again later",
+                               __func__);
+                       spin_unlock(&ptd->lock);
+                       return -EBUSY;
+               }
+
+               if (ptd->start_sector == 0) {
+                       test_pr_err("%s: Invalid start sector", __func__);
+                       ptd->test_result = TEST_FAILED;
+                       spin_unlock(&ptd->lock);
+                       return -EINVAL;
+               }
+
+               memcpy(&ptd->test_info, t_info, sizeof(struct test_info));
+
+               ptd->next_req = NULL;
+               ptd->test_result = TEST_NO_RESULT;
+               ptd->num_of_write_bios = 0;
+
+               ptd->unique_next_req_id = UNIQUE_START_REQ_ID;
+               ptd->wr_rd_next_req_id = WR_RD_START_REQ_ID;
+
+               ptd->ignore_round = false;
+               ptd->fs_wr_reqs_during_test = false;
+
+               ptd->test_state = TEST_RUNNING;
+
+               spin_unlock(&ptd->lock);
+
+               timeout_msec = get_timeout_msec(ptd);
+               mod_timer(&ptd->timeout_timer, jiffies +
+                         msecs_to_jiffies(timeout_msec));
+
+               if (ptd->test_info.get_test_case_str_fn)
+                       test_name = ptd->test_info.get_test_case_str_fn(ptd);
+               else
+                       test_name = "Unknown testcase";
+               test_pr_info("%s: Starting test %s\n", __func__, test_name);
+
+               ret = prepare_test(ptd);
+               if (ret) {
+                       test_pr_err("%s: failed to prepare the test\n",
+                                   __func__);
+                       goto error;
+               }
+
+               ret = run_test(ptd);
+               if (ret) {
+                       test_pr_err("%s: failed to run the test\n", __func__);
+                       goto error;
+               }
+
+               test_pr_info("%s: Waiting for the test completion", __func__);
+
+               wait_event(ptd->wait_q, ptd->test_state == TEST_COMPLETED);
+               del_timer_sync(&ptd->timeout_timer);
+
+               ret = check_test_result(ptd);
+               if (ret) {
+                       test_pr_err("%s: check_test_result failed\n",
+                                   __func__);
+                       goto error;
+               }
+
+               ret = post_test(ptd);
+               if (ret) {
+                       test_pr_err("%s: post_test failed\n", __func__);
+                       goto error;
+               }
+
+               /*
+                * Wakeup the queue thread to fetch FS requests that might got
+                * postponded due to the test
+                */
+               __blk_run_queue(ptd->req_q);
+
+               if (ptd->ignore_round)
+                       test_pr_info(
+                       "%s: Round canceled (Got wr reqs in the middle)",
+                       __func__);
+
+               if (++counter == TEST_MAX_TESTCASE_ROUNDS) {
+                       test_pr_info("%s: Too many rounds, did not succeed...",
+                            __func__);
+                       ptd->test_result = TEST_FAILED;
+               }
+
+       } while ((ptd->ignore_round) && (counter < TEST_MAX_TESTCASE_ROUNDS));
+
+       if (ptd->test_result == TEST_PASSED)
+               return 0;
+       else
+               return -EINVAL;
+
+error:
+       post_test(ptd);
+       ptd->test_result = TEST_FAILED;
+       return ret;
+}
+EXPORT_SYMBOL(test_iosched_start_test);
+
+/**
+ * test_iosched_register() - register a block device test
+ * utility.
+ * @bdt:       the block device test type to register
+ */
+void test_iosched_register(struct blk_dev_test_type *bdt)
+{
+       spin_lock(&blk_dev_test_list_lock);
+       list_add_tail(&bdt->list, &blk_dev_test_list);
+       spin_unlock(&blk_dev_test_list_lock);
+}
+EXPORT_SYMBOL_GPL(test_iosched_register);
+
+/**
+ * test_iosched_unregister() - unregister a block device test
+ * utility.
+ * @bdt:       the block device test type to unregister
+ */
+void test_iosched_unregister(struct blk_dev_test_type *bdt)
+{
+       spin_lock(&blk_dev_test_list_lock);
+       list_del_init(&bdt->list);
+       spin_unlock(&blk_dev_test_list_lock);
+}
+EXPORT_SYMBOL_GPL(test_iosched_unregister);
+
+/**
+ * test_iosched_set_test_result() - Set the test
+ * result(PASS/FAIL)
+ * @test_result:       the test result
+ */
+void test_iosched_set_test_result(int test_result)
+{
+       if (!ptd)
+               return;
+
+       ptd->test_result = test_result;
+}
+EXPORT_SYMBOL(test_iosched_set_test_result);
+
+
+/**
+ * test_iosched_set_ignore_round() - Set the ignore_round flag
+ * @ignore_round:      A flag to indicate if this test round
+ * should be ignored and re-run
+ */
+void test_iosched_set_ignore_round(bool ignore_round)
+{
+       if (!ptd)
+               return;
+
+       ptd->ignore_round = ignore_round;
+}
+EXPORT_SYMBOL(test_iosched_set_ignore_round);
+
+/**
+ * test_iosched_get_debugfs_tests_root() - returns the root
+ * debugfs directory for the test_iosched tests
+ */
+struct dentry *test_iosched_get_debugfs_tests_root(void)
+{
+       if (!ptd)
+               return NULL;
+
+       return ptd->debug.debug_tests_root;
+}
+EXPORT_SYMBOL(test_iosched_get_debugfs_tests_root);
+
+/**
+ * test_iosched_get_debugfs_utils_root() - returns the root
+ * debugfs directory for the test_iosched utils
+ */
+struct dentry *test_iosched_get_debugfs_utils_root(void)
+{
+       if (!ptd)
+               return NULL;
+
+       return ptd->debug.debug_utils_root;
+}
+EXPORT_SYMBOL(test_iosched_get_debugfs_utils_root);
+
+static int test_debugfs_init(struct test_data *td)
+{
+       td->debug.debug_root = debugfs_create_dir("test-iosched", NULL);
+       if (!td->debug.debug_root)
+               return -ENOENT;
+
+       td->debug.debug_tests_root = debugfs_create_dir("tests",
+                                                       td->debug.debug_root);
+       if (!td->debug.debug_tests_root)
+               goto err;
+
+       td->debug.debug_utils_root = debugfs_create_dir("utils",
+                                                       td->debug.debug_root);
+       if (!td->debug.debug_utils_root)
+               goto err;
+
+       td->debug.debug_test_result = debugfs_create_u32(
+                                       "test_result",
+                                       S_IRUGO | S_IWUGO,
+                                       td->debug.debug_utils_root,
+                                       &td->test_result);
+       if (!td->debug.debug_test_result)
+               goto err;
+
+       td->debug.start_sector = debugfs_create_u32(
+                                       "start_sector",
+                                       S_IRUGO | S_IWUGO,
+                                       td->debug.debug_utils_root,
+                                       &td->start_sector);
+       if (!td->debug.start_sector)
+               goto err;
+
+       return 0;
+
+err:
+       debugfs_remove_recursive(td->debug.debug_root);
+       return -ENOENT;
+}
+
+static void test_debugfs_cleanup(struct test_data *td)
+{
+       debugfs_remove_recursive(td->debug.debug_root);
+}
+
+static void print_req(struct request *req)
+{
+       struct bio *bio;
+       struct test_request *test_rq;
+
+       if (!req)
+               return;
+
+       test_rq = (struct test_request *)req->elv.priv[0];
+
+       if (test_rq) {
+               test_pr_debug("%s: Dispatch request %d: __sector=0x%lx",
+                      __func__, test_rq->req_id, (unsigned long)req->__sector);
+               test_pr_debug("%s: nr_phys_segments=%d, num_of_sectors=%d",
+                      __func__, req->nr_phys_segments, blk_rq_sectors(req));
+               bio = req->bio;
+               test_pr_debug("%s: bio: bi_size=%d, bi_sector=0x%lx",
+                             __func__, bio->bi_size,
+                             (unsigned long)bio->bi_sector);
+               while ((bio = bio->bi_next) != NULL) {
+                       test_pr_debug("%s: bio: bi_size=%d, bi_sector=0x%lx",
+                                     __func__, bio->bi_size,
+                                     (unsigned long)bio->bi_sector);
+               }
+       }
+}
+
+static void test_merged_requests(struct request_queue *q,
+                        struct request *rq, struct request *next)
+{
+       list_del_init(&next->queuelist);
+}
+
+/*
+ * Dispatch a test request in case there is a running test Otherwise, dispatch
+ * a request that was queued by the FS to keep the card functional.
+ */
+static int test_dispatch_requests(struct request_queue *q, int force)
+{
+       struct test_data *td = q->elevator->elevator_data;
+       struct request *rq = NULL;
+
+       switch (td->test_state) {
+       case TEST_IDLE:
+               if (!list_empty(&td->queue)) {
+                       rq = list_entry(td->queue.next, struct request,
+                                       queuelist);
+                       list_del_init(&rq->queuelist);
+                       elv_dispatch_sort(q, rq);
+                       return 1;
+               }
+               break;
+       case TEST_RUNNING:
+               if (td->next_req) {
+                       rq = td->next_req->rq;
+                       td->next_req =
+                               latter_test_request(td->req_q, td->next_req);
+                       if (!rq)
+                               return 0;
+                       print_req(rq);
+                       elv_dispatch_sort(q, rq);
+                       return 1;
+               }
+               break;
+       case TEST_COMPLETED:
+       default:
+               return 0;
+       }
+
+       return 0;
+}
+
+static void test_add_request(struct request_queue *q, struct request *rq)
+{
+       struct test_data *td = q->elevator->elevator_data;
+
+       list_add_tail(&rq->queuelist, &td->queue);
+
+       /*
+        * The write requests can be followed by a FLUSH request that might
+        * cause unexpected results of the test.
+        */
+       if ((rq_data_dir(rq) == WRITE) && (td->test_state == TEST_RUNNING)) {
+               test_pr_debug("%s: got WRITE req in the middle of the test",
+                       __func__);
+               td->fs_wr_reqs_during_test = true;
+       }
+}
+
+static struct request *
+test_former_request(struct request_queue *q, struct request *rq)
+{
+       struct test_data *td = q->elevator->elevator_data;
+
+       if (rq->queuelist.prev == &td->queue)
+               return NULL;
+       return list_entry(rq->queuelist.prev, struct request, queuelist);
+}
+
+static struct request *
+test_latter_request(struct request_queue *q, struct request *rq)
+{
+       struct test_data *td = q->elevator->elevator_data;
+
+       if (rq->queuelist.next == &td->queue)
+               return NULL;
+       return list_entry(rq->queuelist.next, struct request, queuelist);
+}
+
+static int test_init_queue(struct request_queue *q)
+{
+       struct blk_dev_test_type *__bdt;
+
+       ptd = kmalloc_node(sizeof(struct test_data), GFP_KERNEL,
+                            q->node);
+       if (!ptd) {
+               test_pr_err("%s: failed to allocate test data", __func__);
+               return -ENODEV;
+       }
+       memset((void *)ptd, 0, sizeof(struct test_data));
+       INIT_LIST_HEAD(&ptd->queue);
+       INIT_LIST_HEAD(&ptd->test_queue);
+       init_waitqueue_head(&ptd->wait_q);
+       ptd->req_q = q;
+       q->elevator->elevator_data = ptd;
+
+       setup_timer(&ptd->timeout_timer, test_timeout_handler,
+                   (unsigned long)ptd);
+
+       spin_lock_init(&ptd->lock);
+
+       if (test_debugfs_init(ptd)) {
+               test_pr_err("%s: Failed to create debugfs files", __func__);
+               return -ENODEV;
+       }
+
+       list_for_each_entry(__bdt, &blk_dev_test_list, list)
+               __bdt->init_fn();
+
+       return 0;
+}
+
+static void test_exit_queue(struct elevator_queue *e)
+{
+       struct test_data *td = e->elevator_data;
+       struct blk_dev_test_type *__bdt;
+
+       BUG_ON(!list_empty(&td->queue));
+
+       list_for_each_entry(__bdt, &blk_dev_test_list, list)
+               __bdt->exit_fn();
+
+       test_debugfs_cleanup(td);
+
+       kfree(td);
+}
+
+static struct elevator_type elevator_test_iosched = {
+       .ops = {
+               .elevator_merge_req_fn = test_merged_requests,
+               .elevator_dispatch_fn = test_dispatch_requests,
+               .elevator_add_req_fn = test_add_request,
+               .elevator_former_req_fn = test_former_request,
+               .elevator_latter_req_fn = test_latter_request,
+               .elevator_init_fn = test_init_queue,
+               .elevator_exit_fn = test_exit_queue,
+       },
+       .elevator_name = "test-iosched",
+       .elevator_owner = THIS_MODULE,
+};
+
+static int __init test_init(void)
+{
+       elv_register(&elevator_test_iosched);
+
+       return 0;
+}
+
+static void __exit test_exit(void)
+{
+       elv_unregister(&elevator_test_iosched);
+}
+
+module_init(test_init);
+module_exit(test_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Test IO scheduler");
diff --git a/include/linux/test-iosched.h b/include/linux/test-iosched.h
new file mode 100644
index 0000000..8054409
--- /dev/null
+++ b/include/linux/test-iosched.h
@@ -0,0 +1,233 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * The test scheduler allows to test the block device by dispatching
+ * specific requests according to the test case and declare PASS/FAIL
+ * according to the requests completion error code.
+ * Each test is exposed via debugfs and can be triggered by writing to
+ * the debugfs file.
+ *
+ */
+
+#ifndef _LINUX_TEST_IOSCHED_H
+#define _LINUX_TEST_IOSCHED_H
+
+/*
+ * Patterns definitions for read/write requests data
+ */
+#define TEST_PATTERN_SEQUENTIAL        -1
+#define TEST_PATTERN_5A                0x5A5A5A5A
+#define TEST_PATTERN_FF                0xFFFFFFFF
+#define TEST_NO_PATTERN                0xDEADBEEF
+#define BIO_U32_SIZE 1024
+
+struct test_data;
+
+typedef int (prepare_test_fn) (struct test_data *);
+typedef int (run_test_fn) (struct test_data *);
+typedef int (check_test_result_fn) (struct test_data *);
+typedef int (post_test_fn) (struct test_data *);
+typedef char* (get_test_case_str_fn) (struct test_data *);
+typedef void (blk_dev_test_init_fn) (void);
+typedef void (blk_dev_test_exit_fn) (void);
+
+/**
+ * enum test_state - defines the state of the test
+ */
+enum test_state {
+       TEST_IDLE,
+       TEST_RUNNING,
+       TEST_COMPLETED,
+};
+
+/**
+ * enum test_results - defines the success orfailure of the test
+ */
+enum test_results {
+       TEST_NO_RESULT,
+       TEST_FAILED,
+       TEST_PASSED,
+       TEST_NOT_SUPPORTED,
+};
+
+/**
+ * enum req_unique_type - defines a unique request type
+ */
+enum req_unique_type {
+       REQ_UNIQUE_NONE,
+       REQ_UNIQUE_DISCARD,
+       REQ_UNIQUE_FLUSH,
+};
+
+/**
+ * struct test_debug - debugfs directories
+ * @debug_root:                The test-iosched debugfs root directory
+ * @debug_utils_root:  test-iosched debugfs utils root
+ *                     directory
+ * @debug_tests_root:  test-iosched debugfs tests root
+ *                     directory
+ * @debug_test_result: Exposes the test result to the user
+ *                     space
+ * @start_sector:      The start sector for read/write requests
+ */
+struct test_debug {
+       struct dentry *debug_root;
+       struct dentry *debug_utils_root;
+       struct dentry *debug_tests_root;
+       struct dentry *debug_test_result;
+       struct dentry *start_sector;
+};
+
+/**
+ * struct test_request - defines a test request
+ * @queuelist:         The test requests list
+ * @bios_buffer:       Write/read requests data buffer
+ * @buf_size:          Write/read requests data buffer size (in
+ *                     bytes)
+ * @rq:                        A block request, to be dispatched
+ * @req_completed:     A flag to indicate if the request was
+ *                     completed
+ * @req_result:                Keeps the error code received in the
+ *                     request completion callback
+ * @is_err_expected:   A flag to indicate if the request should
+ *                     fail
+ * @wr_rd_data_pattern:        A pattern written to the write data
+ *                     buffer. Can be used in read requests to
+ *                     verify the data
+ * @req_id:            A unique ID to identify a test request
+ *                     to ease the debugging of the test cases
+ */
+struct test_request {
+       struct list_head queuelist;
+       unsigned int *bios_buffer;
+       int buf_size;
+       struct request *rq;
+       bool req_completed;
+       int req_result;
+       int is_err_expected;
+       int wr_rd_data_pattern;
+       int req_id;
+};
+
+/**
+ * struct test_info - specific test information
+ * @testcase:          The current running test case
+ * @timeout_msec:      Test specific test timeout
+ * @buf_size:          Write/read requests data buffer size (in
+ *                     bytes)
+ * @prepare_test_fn:   Test specific test preparation callback
+ * @run_test_fn:       Test specific test running callback
+ * @check_test_result_fn: Test specific test result checking
+ *                     callback
+ * @get_test_case_str_fn: Test specific function to get the test name
+ * @data:              Test specific private data
+ */
+struct test_info {
+       int testcase;
+       unsigned timeout_msec;
+       prepare_test_fn *prepare_test_fn;
+       run_test_fn *run_test_fn;
+       check_test_result_fn *check_test_result_fn;
+       post_test_fn *post_test_fn;
+       get_test_case_str_fn *get_test_case_str_fn;
+       void *data;
+};
+
+/**
+ * struct blk_dev_test_type - identifies block device test
+ * @list:      list head pointer
+ * @init_fn:   block device test init callback
+ * @exit_fn:   block device test exit callback
+ */
+struct blk_dev_test_type {
+       struct list_head list;
+       blk_dev_test_init_fn *init_fn;
+       blk_dev_test_exit_fn *exit_fn;
+};
+
+/**
+ * struct test_data - global test iosched data
+ * @queue:             The test IO scheduler requests list
+ * @test_queue:                The test requests list
+ * @next_req:          Points to the next request to be
+ *                     dispatched from the test requests list
+ * @wait_q:            A wait queue for waiting for the test
+ *                     requests completion
+ * @test_state:                Indicates if there is a running test.
+ *                     Used for dispatch function
+ * @test_result:       Indicates if the test passed or failed
+ * @debug:             The test debugfs entries
+ * @req_q:             The block layer request queue
+ * @num_of_write_bios: The number of write BIOs added to the test requests.
+ *                     Used to calcualte the sector number of
+ *                     new BIOs.
+ * @start_sector:      The address of the first sector that can
+ *                     be accessed by the test
+ * @timeout_timer:     A timer to verify test completion in
+ *                     case of non-completed requests
+ * @wr_rd_next_req_id: A unique ID to identify WRITE/READ
+ *                     request to ease the debugging of the
+ *                     test cases
+ * @unique_next_req_id:        A unique ID to identify
+ *                     FLUSH/DISCARD/SANITIZE request to ease
+ *                     the debugging of the test cases
+ * @lock:              A lock to verify running a single test
+ *                     at a time
+ * @test_info:         A specific test data to be set by the
+ *                     test invokation function
+ * @ignore_round:      A boolean variable indicating that a
+ *                     test round was disturbed by an external
+ *                     flush request, therefore disqualifying
+ *                     the results
+ */
+struct test_data {
+       struct list_head queue;
+       struct list_head test_queue;
+       struct test_request *next_req;
+       wait_queue_head_t wait_q;
+       enum test_state test_state;
+       enum test_results test_result;
+       struct test_debug debug;
+       struct request_queue *req_q;
+       int num_of_write_bios;
+       u32 start_sector;
+       struct timer_list timeout_timer;
+       int wr_rd_next_req_id;
+       int unique_next_req_id;
+       spinlock_t lock;
+       struct test_info test_info;
+       bool fs_wr_reqs_during_test;
+       bool ignore_round;
+};
+
+extern int test_iosched_start_test(struct test_info *t_info);
+extern void test_iosched_mark_test_completion(void);
+extern int test_iosched_add_unique_test_req(int is_err_expcted,
+               enum req_unique_type req_unique,
+               int start_sec, int nr_sects, rq_end_io_fn *end_req_io);
+extern int test_iosched_add_wr_rd_test_req(int is_err_expcted,
+             int direction, int start_sec,
+             int num_bios, int pattern, rq_end_io_fn *end_req_io);
+
+extern struct dentry *test_iosched_get_debugfs_tests_root(void);
+extern struct dentry *test_iosched_get_debugfs_utils_root(void);
+
+extern struct request_queue *test_iosched_get_req_queue(void);
+
+extern void test_iosched_set_test_result(int);
+
+void test_iosched_set_ignore_round(bool ignore_round);
+
+void test_iosched_register(struct blk_dev_test_type *bdt);
+
+void test_iosched_unregister(struct blk_dev_test_type *bdt);
+
+#endif /* _LINUX_TEST_IOSCHED_H */
-- 
1.7.3.3
-- 
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to