lkml.org 
[lkml]   [2012]   [Jun]   [12]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH v4 1/1] block: Add test-iosched scheduler
    Date
    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 <merez@codeaurora.org>

    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 <merez@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 5639a3d..54ea4da 100644
    --- a/block/blk-core.c
    +++ b/block/blk-core.c
    @@ -1072,8 +1072,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);
    @@ -1406,6 +1404,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..e4b89d2
    --- /dev/null
    +++ b/block/test-iosched.c
    @@ -0,0 +1,1015 @@
    +
    +/* 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 = 1;
    + 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 = 0;
    + test_rq->req_result = -1;
    + 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 = 0;
    + test_rq->req_result = -1;
    + 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;
    + while (!list_empty(&td->test_queue)) {
    + test_rq = list_entry(td->test_queue.next, struct test_request,
    + queuelist);
    + list_del_init(&test_rq->queuelist);
    + 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->test_info.testcase = 0;
    + ptd->test_state = TEST_IDLE;
    +
    + free_test_requests(td);
    +
    + 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__);
    + return -EINVAL;
    + }
    +
    + if (ptd->start_sector == 0) {
    + test_pr_err("%s: Invalid start sector\n. "
    + "User should set the start sector before "
    + "running the test using this command:\n"
    + "echo <START_SECTOR> > "
    + "/<path-to-debugfs>/test-iosched/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->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);
    +
    + if (!ptd->ignore_round) {
    + ret = check_test_result(ptd);
    + if (ret) {
    + test_pr_err("%s: check_test_result failed\n",
    + __func__);
    + goto error;
    + }
    + } else {
    + test_pr_info(
    + "%s: Round canceled (Got wr reqs in the middle)",
    + __func__);
    + }
    +
    + 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 (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:
    + ptd->test_result = TEST_FAILED;
    + ptd->test_info.testcase = 0;
    + post_test(ptd);
    + 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_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: "
    + "__data_len=0x%x, __sector=0x%lx, atomic_flags=0x%lx, "
    + "cmd_flags=0x%x, cmd_len=0x%x, cmd_type=0x%x, "
    + "nr_phys_segments=%d, num_of_sectors=%d",
    + __func__, test_rq->req_id, req->__data_len,
    + (unsigned long)req->__sector,
    + req->atomic_flags, req->cmd_flags, req->cmd_len,
    + req->cmd_type, 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);
    +
    + if (ptd->test_info.ignore_run_due_to_fs_wr_reqs) {
    + if ((rq_data_dir(rq) == WRITE) &&
    + (td->test_state == TEST_RUNNING)) {
    + test_pr_info(
    + "%s: got WRITE req in the middle of the test",
    + __func__);
    + td->ignore_round = 1;
    + }
    + }
    +}
    +
    +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");
    +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..c2db5a9
    --- /dev/null
    +++ b/include/linux/test-iosched.h
    @@ -0,0 +1,218 @@
    +#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)
    + * @ignore_run_due_to_fs_wr_reqs: A flag to indicate if the test
    + * should be re-run in case we get FS write
    + * requests in theh middle of the test. The
    + * write requests can be followed by a
    + * FLUSH request that might cause
    + * unexpected results of the test.
    + * @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;
    + bool ignore_run_due_to_fs_wr_reqs;
    + 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;
    + int 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_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.


    \
     
     \ /
      Last update: 2012-06-12 11:42    [W:0.097 / U:60.088 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site