/* Copyright (c) 2012-2015, The Linux Foundation. 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. * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt"\n" /* elevator test iosched */ #include #include #include #include #include #include #include #include #include #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 static DEFINE_MUTEX(blk_dev_test_list_lock); static LIST_HEAD(blk_dev_test_list); /** * test_iosched_mark_test_completion() - Wakeup the debugfs * thread, waiting on the test completion */ void test_iosched_mark_test_completion(struct test_iosched *tios) { if (!tios) return; pr_info("%s: mark test is completed, test_count=%d, ", __func__, tios->test_count); pr_info("%s: urgent_count=%d, reinsert_count=%d,", __func__, tios->urgent_count, tios->reinsert_count); tios->test_state = TEST_COMPLETED; wake_up(&tios->wait_q); } EXPORT_SYMBOL(test_iosched_mark_test_completion); /** * check_test_completion() - Check if all the queued test * requests were completed */ void check_test_completion(struct test_iosched *tios) { struct test_request *test_rq; if (!tios) goto exit; if (tios->test_info.check_test_completion_fn && !tios->test_info.check_test_completion_fn(tios)) goto exit; list_for_each_entry(test_rq, &tios->dispatched_queue, queuelist) if (!test_rq->req_completed) goto exit; if (!list_empty(&tios->test_queue) || !list_empty(&tios->reinsert_queue) || !list_empty(&tios->urgent_queue)) { pr_info("%s: Test still not completed,", __func__); pr_info("%s: test_count=%d, reinsert_count=%d", __func__, tios->test_count, tios->reinsert_count); pr_info("%s: dispatched_count=%d, urgent_count=%d", __func__, tios->dispatched_count, tios->urgent_count); goto exit; } tios->test_info.test_duration = ktime_sub(ktime_get(), tios->test_info.test_duration); test_iosched_mark_test_completion(tios); exit: return; } EXPORT_SYMBOL(check_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); } void test_iosched_free_test_req_data_buffer(struct test_request *test_rq) { int i; if (!test_rq) return; for (i = 0; i < BLK_MAX_SEGMENTS; i++) if (test_rq->bios_buffer[i]) { free_page((unsigned long)test_rq->bios_buffer[i]); test_rq->bios_buffer[i] = NULL; } } EXPORT_SYMBOL(test_iosched_free_test_req_data_buffer); /* * 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; struct test_iosched *tios = rq->q->elevator->elevator_data; test_rq = (struct test_request *)rq->elv.priv[0]; BUG_ON(!test_rq); pr_debug("%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(tios); } /** * 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(struct test_iosched *tios, 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 (!tios) return -ENODEV; bio = bio_alloc(GFP_KERNEL, 0); if (!bio) { 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: 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(tios->req_q, rw_flags, GFP_KERNEL); if (!rq) { 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) { 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 = tios->unique_next_req_id++; pr_debug( "%s: added request %d to the test requests list, type = %d", __func__, test_rq->req_id, req_unique); spin_lock_irq(tios->req_q->queue_lock); list_add_tail(&test_rq->queuelist, &tios->test_queue); tios->test_count++; spin_unlock_irq(tios->req_q->queue_lock); 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_create_test_req() - Create 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. */ struct test_request *test_iosched_create_test_req( struct test_iosched *tios, int is_err_expcted, int direction, int start_sec, int num_bios, int pattern, rq_end_io_fn *end_req_io) { struct request *rq; struct test_request *test_rq; struct bio *bio = NULL; int i; int ret; if (!tios) return NULL; rq = blk_get_request(tios->req_q, direction, GFP_KERNEL); if (!rq) { pr_err("%s: Failed to allocate a request", __func__); return NULL; } test_rq = kzalloc(sizeof(struct test_request), GFP_KERNEL); if (!test_rq) { pr_err("%s: Failed to allocate test request", __func__); goto err; } test_rq->buf_size = TEST_BIO_SIZE * num_bios; test_rq->wr_rd_data_pattern = pattern; for (i = 0; i < num_bios; i++) { test_rq->bios_buffer[i] = (void *)get_zeroed_page(GFP_KERNEL); if (!test_rq->bios_buffer[i]) { pr_err("%s: failed to kmap page for bio #%d/%d\n", __func__, i, num_bios); goto free_bios; } ret = blk_rq_map_kern(tios->req_q, rq, test_rq->bios_buffer[i], TEST_BIO_SIZE, GFP_KERNEL); if (ret) { pr_err("%s: blk_rq_map_kern returned error %d", __func__, ret); goto free_bios; } if (direction == WRITE) fill_buf_with_pattern(test_rq->bios_buffer[i], TEST_BIO_SIZE, pattern); } 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; rq->cmd_flags |= REQ_SORTED; rq->cmd_flags &= ~REQ_IO_STAT; 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; } tios->num_of_write_bios += num_bios; test_rq->req_id = tios->wr_rd_next_req_id++; test_rq->req_completed = false; test_rq->req_result = -EINVAL; test_rq->rq = rq; if (tios->test_info.get_rq_disk_fn) test_rq->rq->rq_disk = tios->test_info.get_rq_disk_fn(tios); test_rq->is_err_expected = is_err_expcted; rq->elv.priv[0] = (void *)test_rq; return test_rq; free_bios: test_iosched_free_test_req_data_buffer(test_rq); kfree(test_rq); err: blk_put_request(rq); return NULL; } EXPORT_SYMBOL(test_iosched_create_test_req); /** * 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. Upon success the new request is added to the * test_queue. 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(struct test_iosched *tios, int is_err_expcted, int direction, int start_sec, int num_bios, int pattern, rq_end_io_fn *end_req_io) { struct test_request *test_rq = NULL; test_rq = test_iosched_create_test_req(tios, is_err_expcted, direction, start_sec, num_bios, pattern, end_req_io); if (test_rq) { spin_lock_irq(tios->req_q->queue_lock); list_add_tail(&test_rq->queuelist, &tios->test_queue); tios->test_count++; spin_unlock_irq(tios->req_q->queue_lock); return 0; } 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_iosched *tios) { if (tios->test_info.get_test_case_str_fn) return tios->test_info.get_test_case_str_fn( tios->test_info.testcase); return "Unknown testcase"; } /* * Verify that the test request data buffer includes the expected * pattern */ int compare_buffer_to_pattern(struct test_request *test_rq) { int i; int j; unsigned int *buf; /* 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; for (i = 0; i < test_rq->buf_size / TEST_BIO_SIZE; i++) { buf = test_rq->bios_buffer[i]; for (j = 0; j < TEST_BIO_SIZE / sizeof(int); j++) if ((test_rq->wr_rd_data_pattern == TEST_PATTERN_SEQUENTIAL && buf[j] != j) || (test_rq->wr_rd_data_pattern != TEST_PATTERN_SEQUENTIAL && buf[j] != test_rq->wr_rd_data_pattern)) { pr_err("%s: wrong pattern 0x%x in index %d", __func__, buf[j], j); return -EINVAL; } } return 0; } EXPORT_SYMBOL(compare_buffer_to_pattern); /* * 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_iosched *tios) { struct test_request *trq; int res = 0; static int run; list_for_each_entry(trq, &tios->dispatched_queue, queuelist) { if (!trq->rq) { pr_info("%s: req_id %d is contains empty req", __func__, trq->req_id); continue; } if (!trq->req_completed) { pr_err("%s: rq %d not completed", __func__, trq->req_id); res = -EINVAL; goto err; } if ((trq->req_result < 0) && !trq->is_err_expected) { pr_err( "%s: rq %d completed with err, not as expected", __func__, trq->req_id); res = -EINVAL; goto err; } if ((trq->req_result == 0) && trq->is_err_expected) { pr_err("%s: rq %d succeeded, not as expected", __func__, trq->req_id); res = -EINVAL; goto err; } if (rq_data_dir(trq->rq) == READ) { res = compare_buffer_to_pattern(trq); if (res) { pr_err("%s: read pattern not as expected", __func__); res = -EINVAL; goto err; } } } if (tios->test_info.check_test_result_fn) { res = tios->test_info.check_test_result_fn( tios); if (res) goto err; } pr_info("%s: %s, run# %03d, PASSED", __func__, get_test_case_str(tios), ++run); tios->test_result = TEST_PASSED; return 0; err: pr_err("%s: %s, run# %03d, FAILED", __func__, get_test_case_str(tios), ++run); tios->test_result = TEST_FAILED; return res; } /* Create and queue the required requests according to the test case */ static int prepare_test(struct test_iosched *tios) { int ret = 0; if (tios->test_info.prepare_test_fn) { ret = tios->test_info.prepare_test_fn(tios); return ret; } return 0; } /* Run the test */ static int run_test(struct test_iosched *tios) { int ret = 0; if (tios->test_info.run_test_fn) { ret = tios->test_info.run_test_fn(tios); return ret; } blk_run_queue(tios->req_q); return 0; } /* * free_test_queue() - Free all allocated test requests in the given test_queue: * free their requests and BIOs buffer * @test_queue the test queue to be freed */ static void free_test_queue(struct list_head *test_queue) { struct test_request *test_rq; struct bio *bio; while (!list_empty(test_queue)) { test_rq = list_entry(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) { 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); test_iosched_free_test_req_data_buffer(test_rq); kfree(test_rq); } } /* * free_test_requests() - Free all allocated test requests in * all test queues in given test_data. * @td The test_data struct whos test requests will be * freed. */ static void free_test_requests(struct test_iosched *tios) { if (!tios) return; if (tios->urgent_count) { free_test_queue(&tios->urgent_queue); tios->urgent_count = 0; } if (tios->test_count) { free_test_queue(&tios->test_queue); tios->test_count = 0; } if (tios->dispatched_count) { free_test_queue(&tios->dispatched_queue); tios->dispatched_count = 0; } if (tios->reinsert_count) { free_test_queue(&tios->reinsert_queue); tios->reinsert_count = 0; } } /* * post_test() - Do post test operations. Free the allocated * test requests, their requests and BIOs buffer. * @td The test_data struct for the test that has * ended. */ static int post_test(struct test_iosched *tios) { int ret = 0; if (tios->test_info.post_test_fn) ret = tios->test_info.post_test_fn(tios); tios->test_info.testcase = 0; tios->test_state = TEST_IDLE; free_test_requests(tios); return ret; } static unsigned int get_timeout_msec(struct test_iosched *tios) { if (tios->test_info.timeout_msec) return tios->test_info.timeout_msec; return TIMEOUT_TIMER_MS; } /** * test_iosched_start_test() - Prepares and runs the test. * The members test_duration and test_byte_count of the input * parameter t_info are modified by this function. * @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_iosched *tios, struct test_info *t_info) { int ret = 0; unsigned long timeout; int counter = 0; char *test_name = NULL; if (!tios) return -ENODEV; if (!t_info) { tios->test_result = TEST_FAILED; return -EINVAL; } timeout = msecs_to_jiffies(get_timeout_msec(tios)); do { if (tios->ignore_round) /* * We ignored the last run due to FS write requests. * Sleep to allow those requests to be issued */ msleep(2000); spin_lock(&tios->lock); if (tios->test_state != TEST_IDLE) { pr_info( "%s: Another test is running, try again later", __func__); spin_unlock(&tios->lock); return -EBUSY; } if (tios->start_sector == 0) { pr_err("%s: Invalid start sector", __func__); tios->test_result = TEST_FAILED; spin_unlock(&tios->lock); return -EINVAL; } memcpy(&tios->test_info, t_info, sizeof(*t_info)); tios->test_result = TEST_NO_RESULT; tios->num_of_write_bios = 0; tios->unique_next_req_id = UNIQUE_START_REQ_ID; tios->wr_rd_next_req_id = WR_RD_START_REQ_ID; tios->ignore_round = false; tios->fs_wr_reqs_during_test = false; tios->test_state = TEST_RUNNING; spin_unlock(&tios->lock); /* * Give an already dispatch request from * FS a chanse to complete */ msleep(2000); if (tios->test_info.get_test_case_str_fn) test_name = tios->test_info.get_test_case_str_fn( tios->test_info.testcase); else test_name = "Unknown testcase"; pr_info("%s: Starting test %s", __func__, test_name); ret = prepare_test(tios); if (ret) { pr_err("%s: failed to prepare the test", __func__); goto error; } tios->test_info.test_duration = ktime_get(); ret = run_test(tios); if (ret) { pr_err("%s: failed to run the test", __func__); goto error; } pr_info("%s: Waiting for the test completion", __func__); ret = wait_event_interruptible_timeout(tios->wait_q, (tios->test_state == TEST_COMPLETED), timeout); if (ret <= 0) { tios->test_state = TEST_COMPLETED; if (!ret) pr_info("%s: Test timeout\n", __func__); else pr_err("%s: Test error=%d\n", __func__, ret); goto error; } memcpy(t_info, &tios->test_info, sizeof(*t_info)); ret = check_test_result(tios); if (ret) { pr_err("%s: check_test_result failed", __func__); goto error; } ret = post_test(tios); if (ret) { pr_err("%s: post_test failed", __func__); goto error; } /* * Wakeup the queue thread to fetch FS requests that might got * postponded due to the test */ blk_run_queue(tios->req_q); if (tios->ignore_round) pr_info( "%s: Round canceled (Got wr reqs in the middle)", __func__); if (++counter == TEST_MAX_TESTCASE_ROUNDS) { pr_info("%s: Too many rounds, did not succeed...", __func__); tios->test_result = TEST_FAILED; } } while ((tios->ignore_round) && (counter < TEST_MAX_TESTCASE_ROUNDS)); if (tios->test_result == TEST_PASSED) return 0; else return -EINVAL; error: post_test(tios); tios->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) { if (!bdt) return; mutex_lock(&blk_dev_test_list_lock); list_add_tail(&bdt->list, &blk_dev_test_list); mutex_unlock(&blk_dev_test_list_lock); } EXPORT_SYMBOL(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) { if (!bdt) return; mutex_lock(&blk_dev_test_list_lock); list_del_init(&bdt->list); mutex_unlock(&blk_dev_test_list_lock); } EXPORT_SYMBOL(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(struct test_iosched *tios, int test_result) { if (!tios) return; tios->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(struct test_iosched *tios, bool ignore_round) { if (!tios) return; tios->ignore_round = ignore_round; } EXPORT_SYMBOL(test_iosched_set_ignore_round); static int test_debugfs_init(struct test_iosched *tios) { char name[2*BDEVNAME_SIZE]; snprintf(name, 2*BDEVNAME_SIZE - 1, "%s-%s", "test-iosched", tios->req_q->kobj.parent->name); pr_debug("%s: creating test-iosched instance %s\n", __func__, name); tios->debug.debug_root = debugfs_create_dir(name, NULL); if (!tios->debug.debug_root) return -ENOENT; tios->debug.debug_tests_root = debugfs_create_dir("tests", tios->debug.debug_root); if (!tios->debug.debug_tests_root) goto err; tios->debug.debug_utils_root = debugfs_create_dir("utils", tios->debug.debug_root); if (!tios->debug.debug_utils_root) goto err; tios->debug.debug_test_result = debugfs_create_u32( "test_result", S_IRUGO | S_IWUGO, tios->debug.debug_utils_root, &tios->test_result); if (!tios->debug.debug_test_result) goto err; tios->debug.start_sector = debugfs_create_u32( "start_sector", S_IRUGO | S_IWUGO, tios->debug.debug_utils_root, &tios->start_sector); if (!tios->debug.start_sector) goto err; tios->debug.sector_range = debugfs_create_u32( "sector_range", S_IRUGO | S_IWUGO, tios->debug.debug_utils_root, &tios->sector_range); if (!tios->debug.sector_range) goto err; return 0; err: debugfs_remove_recursive(tios->debug.debug_root); return -ENOENT; } static void test_debugfs_cleanup(struct test_iosched *tios) { debugfs_remove_recursive(tios->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) { pr_debug("%s: Dispatch request %d: __sector=0x%lx", __func__, test_rq->req_id, (unsigned long)req->__sector); pr_debug("%s: nr_phys_segments=%d, num_of_sectors=%d", __func__, req->nr_phys_segments, blk_rq_sectors(req)); bio = req->bio; 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) { 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); } /* * test_dispatch_from(): Dispatch request from @queue to the @dispatched_queue. * Also update the dispatched_count counter. */ static int test_dispatch_from(struct request_queue *q, struct list_head *queue, unsigned int *count) { struct test_request *test_rq; struct request *rq; int ret = 0; struct test_iosched *tios = q->elevator->elevator_data; unsigned long flags; if (!tios) goto err; spin_lock_irqsave(&tios->lock, flags); if (!list_empty(queue)) { test_rq = list_entry(queue->next, struct test_request, queuelist); rq = test_rq->rq; if (!rq) { pr_err("%s: null request,return", __func__); spin_unlock_irqrestore(&tios->lock, flags); goto err; } list_move_tail(&test_rq->queuelist, &tios->dispatched_queue); tios->dispatched_count++; (*count)--; spin_unlock_irqrestore(&tios->lock, flags); print_req(rq); elv_dispatch_sort(q, rq); tios->test_info.test_byte_count += test_rq->buf_size; ret = 1; goto err; } spin_unlock_irqrestore(&tios->lock, flags); err: return ret; } /* * 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_iosched *tios = q->elevator->elevator_data; struct request *rq = NULL; int ret = 0; switch (tios->test_state) { case TEST_IDLE: if (!list_empty(&tios->queue)) { rq = list_entry(tios->queue.next, struct request, queuelist); list_del_init(&rq->queuelist); elv_dispatch_sort(q, rq); ret = 1; goto exit; } break; case TEST_RUNNING: if (test_dispatch_from(q, &tios->urgent_queue, &tios->urgent_count)) { pr_debug("%s: Dispatched from urgent_count=%d", __func__, tios->urgent_count); ret = 1; goto exit; } if (test_dispatch_from(q, &tios->reinsert_queue, &tios->reinsert_count)) { pr_debug("%s: Dispatched from reinsert_count=%d", __func__, tios->reinsert_count); ret = 1; goto exit; } if (test_dispatch_from(q, &tios->test_queue, &tios->test_count)) { pr_debug("%s: Dispatched from test_count=%d", __func__, tios->test_count); ret = 1; goto exit; } break; case TEST_COMPLETED: default: break; } exit: return ret; } static void test_add_request(struct request_queue *q, struct request *rq) { struct test_iosched *tios = q->elevator->elevator_data; list_add_tail(&rq->queuelist, &tios->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 && tios->test_state == TEST_RUNNING) { pr_debug("%s: got WRITE req in the middle of the test", __func__); tios->fs_wr_reqs_during_test = true; } } static struct request * test_former_request(struct request_queue *q, struct request *rq) { struct test_iosched *tios = q->elevator->elevator_data; if (rq->queuelist.prev == &tios->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_iosched *tios = q->elevator->elevator_data; if (rq->queuelist.next == &tios->queue) return NULL; return list_entry(rq->queuelist.next, struct request, queuelist); } static int test_init_queue(struct request_queue *q, struct elevator_type *e) { struct blk_dev_test_type *__bdt; struct elevator_queue *eq; struct test_iosched *tios; const char *blk_dev_name; int ret; bool found = false; eq = elevator_alloc(q, e); if (!eq) return -ENOMEM; tios = kzalloc_node(sizeof(*tios), GFP_KERNEL, q->node); if (!tios) { pr_err("%s: failed to allocate test iosched\n", __func__); ret = -ENOMEM; goto free_kobj; } eq->elevator_data = tios; INIT_LIST_HEAD(&tios->queue); INIT_LIST_HEAD(&tios->test_queue); INIT_LIST_HEAD(&tios->dispatched_queue); INIT_LIST_HEAD(&tios->reinsert_queue); INIT_LIST_HEAD(&tios->urgent_queue); init_waitqueue_head(&tios->wait_q); tios->req_q = q; spin_lock_init(&tios->lock); ret = test_debugfs_init(tios); if (ret) { pr_err("%s: Failed to create debugfs files, ret=%d", __func__, ret); goto free_mem; } blk_dev_name = q->kobj.parent->name; /* Traverse the block device test list and init matches */ mutex_lock(&blk_dev_test_list_lock); list_for_each_entry(__bdt, &blk_dev_test_list, list) { pr_debug("%s: checking if %s is a match to device %s\n", __func__, __bdt->type_prefix, blk_dev_name); if (!strnstr(blk_dev_name, __bdt->type_prefix, strlen(__bdt->type_prefix))) continue; pr_debug("%s: found the match!\n", __func__); found = true; break; } mutex_unlock(&blk_dev_test_list_lock); /* No match found */ if (!found) { pr_err("%s: No matching block device test utility found\n", __func__); ret = -ENODEV; goto free_debugfs; } else { ret = __bdt->init_fn(tios); if (ret) { pr_err("%s: failed to init block device test, ret=%d\n", __func__, ret); goto free_debugfs; } } spin_lock_irq(q->queue_lock); q->elevator = eq; spin_unlock_irq(q->queue_lock); return 0; free_debugfs: test_debugfs_cleanup(tios); free_mem: kfree(tios); free_kobj: kobject_put(&eq->kobj); return ret; } static void test_exit_queue(struct elevator_queue *e) { struct test_iosched *tios = e->elevator_data; struct blk_dev_test_type *__bdt; BUG_ON(!list_empty(&tios->queue)); list_for_each_entry(__bdt, &blk_dev_test_list, list) __bdt->exit_fn(tios); test_debugfs_cleanup(tios); kfree(tios); } static bool test_urgent_pending(struct request_queue *q) { struct test_iosched *tios = q->elevator->elevator_data; return !list_empty(&tios->urgent_queue); } /** * test_iosched_add_urgent_req() - Add an urgent test_request. * First mark the request as urgent, then add it to the * urgent_queue test queue. * @test_rq: pointer to the urgent test_request to be * added. * */ void test_iosched_add_urgent_req(struct test_iosched *tios, struct test_request *test_rq) { if (!tios) return; spin_lock_irq(&tios->lock); test_rq->rq->cmd_flags |= REQ_URGENT; list_add_tail(&test_rq->queuelist, &tios->urgent_queue); tios->urgent_count++; spin_unlock_irq(&tios->lock); } EXPORT_SYMBOL(test_iosched_add_urgent_req); /** * test_reinsert_req() - Moves the @rq request from * @dispatched_queue into @reinsert_queue. * The @rq must be in @dispatched_queue * @q: request queue * @rq: request to be inserted * * */ static int test_reinsert_req(struct request_queue *q, struct request *rq) { struct test_iosched *tios = q->elevator->elevator_data; struct test_request *trq; int ret = -EINVAL; if (!tios) goto exit; if (list_empty(&tios->dispatched_queue)) { pr_err("%s: dispatched_queue is empty", __func__); goto exit; } list_for_each_entry(trq, &tios->dispatched_queue, queuelist) { if (trq->rq == rq) { list_move(&trq->queuelist, &tios->reinsert_queue); tios->dispatched_count--; tios->reinsert_count++; ret = 0; break; } } exit: return ret; } 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_is_urgent_fn = test_urgent_pending, .elevator_reinsert_req_fn = test_reinsert_req, }, .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");