From f0e8866cef8de2158ae97c64e8ab671be1ac8afc Mon Sep 17 00:00:00 2001 From: Dolev Raviv Date: Sun, 6 Oct 2013 16:07:52 +0200 Subject: scsi: ufs: random parallel read and write test Tests stability on scenarios of multiple threads running random reads and random writes. The test introduces the infrastructure for more complex multi-threaded tests. Change-Id: I3775f61c65d90d093e4a5b9a9a3e6724f68b5185 Signed-off-by: Dolev Raviv --- drivers/scsi/ufs/ufs_test.c | 226 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/ufs/ufs_test.c b/drivers/scsi/ufs/ufs_test.c index 687cb60b76f6..c33c0026434e 100644 --- a/drivers/scsi/ufs/ufs_test.c +++ b/drivers/scsi/ufs/ufs_test.c @@ -28,11 +28,14 @@ #define MODULE_NAME "ufs_test" #define TEST_MAX_BIOS_PER_REQ 16 +#define TEST_MAX_SECTOR_RANGE (10*1024*1024) /* 5GB */ #define LARGE_PRIME_1 1103515367 #define LARGE_PRIME_2 35757 #define DEFAULT_NUM_OF_BIOS 2 #define LONG_SEQUENTIAL_MIXED_TIMOUT_MS 100000 -#define THREADS_COMPLETION_TIMOUT 10000 /* 10 sec */ +#define THREADS_COMPLETION_TIMOUT msecs_to_jiffies(10000) /* 10 sec */ +#define MAX_PARALLEL_QUERIES 33 +#define RANDOM_REQUEST_THREADS 4 /* the amount of requests that will be inserted */ #define LONG_SEQ_TEST_NUM_REQS 256 @@ -46,7 +49,8 @@ /* and calculate the MiB value fraction */ #define LONG_TEST_SIZE_FRACTION(x) (BYTE_TO_MB_x_10(x) - \ (LONG_TEST_SIZE_INTEGER(x) * 10)) -#define MAX_PARALLEL_QUERIES 33 +/* translation mask from sectors to block */ +#define SECTOR_TO_BLOCK_MASK 0x7 #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) @@ -86,6 +90,7 @@ enum ufs_test_testcases { UFS_TEST_LONG_SEQUENTIAL_WRITE, UFS_TEST_LONG_SEQUENTIAL_MIXED, + UFS_TEST_PARALLEL_READ_AND_WRITE, NUM_TESTS, }; @@ -158,6 +163,46 @@ static int ufs_test_add_test(struct ufs_test_data *utd, return ret; } +/** + * struct test_scenario - keeps scenario data that creates unique pattern + * @td: per test reference + * @direction: pattern initial direction + * @toggle_direction: every toggle_direction requests switch direction for one + * request + * @total_req: number of request to issue + * @rnd_req: should request issue to random LBA with random size + * @run_q: the maximum number of request to hold in queue (before run_queue()) + */ +struct test_scenario { + struct test_data *td; + int direction; + int toggle_direction; + int total_req; + bool rnd_req; + int run_q; +}; + +enum scenario_id { + /* scenarios for parallel read and write test */ + SCEN_RANDOM_READ_50, + SCEN_RANDOM_WRITE_50, + SCEN_RANDOM_MAX, +}; + +static struct test_scenario test_scenario[SCEN_RANDOM_MAX] = { + {NULL, READ, 0, 50, true, 5}, /* SCEN_RANDOM_READ_50 */ + {NULL, WRITE, 0, 50, true, 5}, /* SCEN_RANDOM_WRITE_50 */ +}; + +static +struct test_scenario *get_scenario(struct test_data *td, enum scenario_id id) +{ + struct test_scenario *ret = &test_scenario[id]; + + ret->td = td; + return ret; +} + static char *ufs_test_get_test_case_str(struct test_data *td) { if (!td) { @@ -181,6 +226,9 @@ static char *ufs_test_get_test_case_str(struct test_data *td) case UFS_TEST_LONG_SEQUENTIAL_MIXED: return "UFS long sequential mixed test"; break; + case UFS_TEST_PARALLEL_READ_AND_WRITE: + return "UFS parallel read and write test"; + break; default: return "Unknown test"; } @@ -201,6 +249,37 @@ static unsigned int ufs_test_pseudo_random_seed(unsigned int *seed_number, return (ret > min_val ? ret : min_val); } +/** + * pseudo_rnd_sector_and_size - provides random sector and size for test request + * @seed: random seed + * @min_start_sector: minimum lba + * @start_sector: pointer for output start sector + * @num_of_bios: pointer for output number of bios + * + * Note that for UFS sector number has to be aligned with block size. Since + * scsi will send the block number as the LBA. + */ +static void pseudo_rnd_sector_and_size(unsigned int *seed, + unsigned int min_start_sector, + unsigned int *start_sector, + unsigned int *num_of_bios) +{ + unsigned int max_sec = min_start_sector + TEST_MAX_SECTOR_RANGE; + do { + *start_sector = ufs_test_pseudo_random_seed(seed, 1, max_sec); + *num_of_bios = ufs_test_pseudo_random_seed(seed, + 1, TEST_MAX_BIOS_PER_REQ); + if (!(*num_of_bios)) + *num_of_bios = 1; + } while ((*start_sector < min_start_sector) || + (*start_sector + (*num_of_bios * BIO_U32_SIZE * 4)) > max_sec); + /* + * The test-iosched API is working with sectors 512b, while UFS LBA + * is in blocks (4096). Thus the last 3 bits has to be cleared. + */ + *start_sector &= ~SECTOR_TO_BLOCK_MASK; +} + static void ufs_test_pseudo_rnd_size(unsigned int *seed, unsigned int *num_of_bios) { @@ -257,6 +336,14 @@ static int ufs_test_show(struct seq_file *file, int test_case) "Parallel to write requests with the same LBA and size.\n" "NOTE: The test requires a long timeout.\n"; break; + case UFS_TEST_PARALLEL_READ_AND_WRITE: + test_description = "\nufs_test_parallel_read_and_write\n" + "=========\n" + "Description:\n" + "This test initiate two threads. Each thread is issuing " + "multiple random requests. One thread will issue only read " + "requests, while the other will only issue write requests.\n"; + break; default: test_description = "Unknown test"; } @@ -451,6 +538,91 @@ static void ufs_test_random_async_query(void *data, async_cookie_t cookie) ufs_test_thread_complete(ret); } +static void scenario_free_end_io_fn(struct request *rq, int err) +{ + struct test_request *test_rq; + struct test_data *ptd = test_get_test_data(); + + BUG_ON(!rq); + test_rq = (struct test_request *)rq->elv.priv[0]; + BUG_ON(!test_rq); + + spin_lock_irq(&ptd->lock); + ptd->dispatched_count--; + list_del_init(&test_rq->queuelist); + __blk_put_request(ptd->req_q, test_rq->rq); + spin_unlock_irq(&ptd->lock); + + kfree(test_rq->bios_buffer); + kfree(test_rq); + + if (err) + test_pr_err("%s: request %d completed, err=%d", __func__, + test_rq->req_id, err); + + check_test_completion(); +} + +static bool ufs_test_multi_thread_completion(void) +{ + return atomic_read(&utd->outstanding_threads) <= 0; +} + +/** + * ufs_test_do_toggling() - decides whether toggling is needed + * toggle_factor - iteration to toggle + * iteration - what is the current iteration + */ +static inline int ufs_test_toggle_direction(int toggle_factor, int iteration) +{ + if (toggle_factor) + return 1; + return iteration % toggle_factor; +} + +static void ufs_test_run_scenario(void *data, async_cookie_t cookie) +{ + struct test_scenario *ts = (struct test_scenario *)data; + int ret = 0, i, start_sec; + + BUG_ON(!ts); + start_sec = ts->td->start_sector; + + for (i = 0; i < ts->total_req; i++) { + int num_bios = DEFAULT_NUM_OF_BIOS; + int direction; + + if (ufs_test_toggle_direction(ts->toggle_direction, i)) + direction = (ts->direction == WRITE) ? READ : WRITE; + else + direction = ts->direction; + + /* use randomly generated requests */ + if (ts->rnd_req && utd->random_test_seed != 0) + pseudo_rnd_sector_and_size(&utd->random_test_seed, + ts->td->start_sector, &start_sec, &num_bios); + + ret = test_iosched_add_wr_rd_test_req(0, direction, start_sec, + num_bios, TEST_PATTERN_5A, + scenario_free_end_io_fn); + if (ret) { + test_pr_err("%s: failed to create request" , __func__); + break; + } + + /* + * We want to run the queue every run_q requests, or, + * when the requests pool is exhausted + */ + if (ts->td->test_count >= QUEUE_MAX_REQUESTS || + (ts->run_q && !(i % ts->run_q))) + blk_run_queue(ts->td->req_q); + } + + blk_run_queue(ts->td->req_q); + ufs_test_thread_complete(ret); +} + static int ufs_test_run_multi_query_test(struct test_data *td) { int i; @@ -480,6 +652,45 @@ static int ufs_test_run_multi_query_test(struct test_data *td) return 0; } +static int ufs_test_run_parallel_read_and_write_test(struct test_data *td) +{ + struct test_scenario *read_data, *write_data; + int i; + bool changed_seed = false; + + read_data = get_scenario(td, SCEN_RANDOM_READ_50); + write_data = get_scenario(td, SCEN_RANDOM_WRITE_50); + + /* allow randomness even if user forgot */ + if (utd->random_test_seed <= 0) { + changed_seed = true; + utd->random_test_seed = 1; + } + + atomic_set(&utd->outstanding_threads, 0); + utd->fail_threads = 0; + init_completion(&utd->outstanding_complete); + + for (i = 0; i < (RANDOM_REQUEST_THREADS % 2); i++) { + async_schedule(ufs_test_run_scenario, read_data); + async_schedule(ufs_test_run_scenario, write_data); + atomic_add(2, &utd->outstanding_threads); + } + + if (!wait_for_completion_timeout(&utd->outstanding_complete, + THREADS_COMPLETION_TIMOUT)) { + test_pr_err("%s: Multi-thread test timed-out %d threads left", + __func__, atomic_read(&utd->outstanding_threads)); + } + check_test_completion(); + + /* clear random seed if changed */ + if (changed_seed) + utd->random_test_seed = 0; + + return 0; +} + static void long_seq_test_free_end_io_fn(struct request *rq, int err) { struct test_request *test_rq; @@ -678,6 +889,7 @@ static ssize_t ufs_test_write(struct file *file, const char __user *buf, utd->test_info.get_test_case_str_fn = ufs_test_get_test_case_str; utd->test_info.testcase = test_case; utd->test_info.get_rq_disk_fn = ufs_test_get_rq_disk; + utd->test_info.check_test_result_fn = ufs_test_check_result; utd->test_stage = DEFAULT; switch (test_case) { @@ -702,6 +914,12 @@ static ssize_t ufs_test_write(struct file *file, const char __user *buf, utd->test_info.post_test_fn = long_seq_test_calc_throughput; utd->test_info.check_test_result_fn = ufs_test_check_result; break; + case UFS_TEST_PARALLEL_READ_AND_WRITE: + utd->test_info.run_test_fn = + ufs_test_run_parallel_read_and_write_test; + utd->test_info.check_test_completion_fn = + ufs_test_multi_thread_completion; + break; default: test_pr_err("%s: Unknown test-case: %d", __func__, test_case); WARN_ON(true); @@ -733,6 +951,7 @@ TEST_OPS(multi_query, MULTI_QUERY); TEST_OPS(long_sequential_read, LONG_SEQUENTIAL_READ); TEST_OPS(long_sequential_write, LONG_SEQUENTIAL_WRITE); TEST_OPS(long_sequential_mixed, LONG_SEQUENTIAL_MIXED); +TEST_OPS(parallel_read_and_write, PARALLEL_READ_AND_WRITE); static void ufs_test_debugfs_cleanup(void) { @@ -784,6 +1003,9 @@ static int ufs_test_debugfs_init(void) if (ret) goto exit_err; add_test(utd, multi_query, MULTI_QUERY); + if (ret) + goto exit_err; + add_test(utd, parallel_read_and_write, PARALLEL_READ_AND_WRITE); if (ret) goto exit_err; -- cgit v1.2.3