mirror of
https://github.com/team-infusion-developers/android_kernel_samsung_msm8976.git
synced 2024-11-01 10:33:27 +00:00
c3e06a66f4
Expose "sector_range", which will indicate to the low-level driver unit-tests the size (in sectors, starting from "start_sector") of the address space in which they can perform I/O operations. This user-defined variable can be used to change the address space size from the default 512MiB. Change-Id: I515a6849eb39b78e653f4018993a2c8e64e2a77f Signed-off-by: Lee Susman <lsusman@codeaurora.org>
1300 lines
32 KiB
C
1300 lines
32 KiB
C
/* 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 <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
|
|
|
|
|
|
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");
|