| /* Copyright (c) 2013, 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. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/blkdev.h> |
| #include <linux/debugfs.h> |
| #include <linux/test-iosched.h> |
| #include <scsi/scsi_device.h> |
| #include <scsi/scsi_cmnd.h> |
| #include <../sd.h> |
| #include <linux/delay.h> |
| |
| #define MODULE_NAME "ufs_test" |
| |
| #define TEST_MAX_BIOS_PER_REQ 16 |
| #define LARGE_PRIME_1 1103515367 |
| #define LARGE_PRIME_2 35757 |
| #define DEFAULT_NUM_OF_BIOS 2 |
| |
| /* the amount of requests that will be inserted */ |
| #define LONG_SEQ_TEST_NUM_REQS 256 |
| /* request queue limitation is 128 requests, and we leave 10 spare requests */ |
| #define QUEUE_MAX_REQUESTS 118 |
| #define MB_MSEC_RATIO_APPROXIMATION ((1024 * 1024) / 1000) |
| /* actual number of MiB in test multiplied by 10, for single digit precision*/ |
| #define BYTE_TO_MB_x_10(x) ((x * 10) / (1024 * 1024)) |
| /* extract integer value */ |
| #define LONG_TEST_SIZE_INTEGER(x) (BYTE_TO_MB_x_10(x) / 10) |
| /* 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 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) |
| |
| enum ufs_test_testcases { |
| UFS_TEST_WRITE_READ_TEST, |
| |
| TEST_LONG_SEQUENTIAL_READ, |
| TEST_LONG_SEQUENTIAL_WRITE, |
| }; |
| |
| struct ufs_test_debug { |
| struct dentry *write_read_test; /* basic test */ |
| struct dentry *random_test_seed; /* parameters in utils */ |
| struct dentry *long_sequential_read_test; |
| struct dentry *long_sequential_write_test; |
| }; |
| |
| struct ufs_test_data { |
| /* Data structure for debugfs dentrys */ |
| struct ufs_test_debug debug; |
| /* |
| * Data structure containing individual test information, including |
| * self-defined specific data |
| */ |
| struct test_info test_info; |
| /* device test */ |
| struct blk_dev_test_type bdt; |
| /* A wait queue for OPs to complete */ |
| wait_queue_head_t wait_q; |
| /* a flag for read compleation */ |
| bool read_completed; |
| /* a flag for write compleation */ |
| bool write_completed; |
| /* |
| * To determine the number of r/w bios. When seed = 0, random is |
| * disabled and 2 BIOs are written. |
| */ |
| unsigned int random_test_seed; |
| /* A counter for the number of test requests completed */ |
| unsigned int completed_req_count; |
| }; |
| |
| static struct ufs_test_data *utd; |
| |
| static bool message_repeat; |
| |
| static char *ufs_test_get_test_case_str(struct test_data *td) |
| { |
| if (!td) { |
| test_pr_err("%s: NULL td", __func__); |
| return NULL; |
| } |
| |
| switch (td->test_info.testcase) { |
| case UFS_TEST_WRITE_READ_TEST: |
| return "UFS write read test"; |
| break; |
| case TEST_LONG_SEQUENTIAL_READ: |
| return "UFS long sequential read test"; |
| break; |
| case TEST_LONG_SEQUENTIAL_WRITE: |
| return "UFS long sequential write test"; |
| break; |
| default: |
| return "Unknown test"; |
| } |
| } |
| |
| static unsigned int ufs_test_pseudo_random_seed(unsigned int *seed_number, |
| unsigned int min_val, unsigned int max_val) |
| { |
| int ret = 0; |
| |
| if (!seed_number) |
| return 0; |
| |
| *seed_number = ((unsigned int) (((unsigned long) *seed_number |
| * (unsigned long) LARGE_PRIME_1) + LARGE_PRIME_2)); |
| ret = (unsigned int) ((*seed_number) % max_val); |
| |
| return (ret > min_val ? ret : min_val); |
| } |
| |
| static void ufs_test_pseudo_rnd_size(unsigned int *seed, |
| unsigned int *num_of_bios) |
| { |
| *num_of_bios = ufs_test_pseudo_random_seed(seed, 1, |
| TEST_MAX_BIOS_PER_REQ); |
| if (!(*num_of_bios)) |
| *num_of_bios = DEFAULT_NUM_OF_BIOS; |
| } |
| |
| static void ufs_test_write_read_test_end_io_fn(struct request *rq, int err) |
| { |
| struct test_request *test_rq = (struct test_request *)rq->elv.priv[0]; |
| BUG_ON(!test_rq); |
| |
| test_rq->req_completed = 1; |
| test_rq->req_result = err; |
| |
| test_pr_info("%s: request %d completed, err=%d", |
| __func__, test_rq->req_id, err); |
| |
| utd->write_completed = true; |
| wake_up(&utd->wait_q); |
| } |
| |
| static struct gendisk *ufs_test_get_rq_disk(void) |
| { |
| struct request_queue *req_q = test_iosched_get_req_queue(); |
| struct scsi_device *sd; |
| struct device *dev; |
| struct scsi_disk *sdkp; |
| struct gendisk *gd; |
| |
| if (!req_q) { |
| test_pr_info("%s: Could not fetch request_queue", __func__); |
| gd = NULL; |
| goto exit; |
| } |
| |
| sd = (struct scsi_device *)req_q->queuedata; |
| |
| dev = &sd->sdev_gendev; |
| sdkp = scsi_disk_get_from_dev(dev); |
| if (!sdkp) { |
| test_pr_info("%s: Could not fatch scsi disk", __func__); |
| gd = NULL; |
| goto exit; |
| } |
| |
| gd = sdkp->disk; |
| exit: |
| return gd; |
| } |
| |
| static int ufs_test_run_write_read_test(struct test_data *td) |
| { |
| int ret = 0; |
| unsigned int start_sec; |
| unsigned int num_bios; |
| struct request_queue *q = td->req_q; |
| |
| |
| start_sec = td->start_sector + sizeof(int) * BIO_U32_SIZE |
| * td->num_of_write_bios; |
| if (utd->random_test_seed != 0) |
| ufs_test_pseudo_rnd_size(&utd->random_test_seed, &num_bios); |
| else |
| num_bios = DEFAULT_NUM_OF_BIOS; |
| |
| /* Adding a write request */ |
| test_pr_info( |
| "%s: Adding a write request with %d bios to Q, req_id=%d" |
| , __func__, num_bios, td->wr_rd_next_req_id); |
| |
| utd->write_completed = false; |
| ret = test_iosched_add_wr_rd_test_req(0, WRITE, start_sec, |
| num_bios, TEST_PATTERN_5A, |
| ufs_test_write_read_test_end_io_fn); |
| |
| if (ret) { |
| test_pr_err("%s: failed to add a write request", __func__); |
| return ret; |
| } |
| |
| /* waiting for the write request to finish */ |
| blk_run_queue(q); |
| wait_event(utd->wait_q, utd->write_completed); |
| |
| /* Adding a read request*/ |
| test_pr_info("%s: Adding a read request to Q", __func__); |
| |
| ret = test_iosched_add_wr_rd_test_req(0, READ, start_sec, |
| num_bios, TEST_PATTERN_5A, NULL); |
| |
| if (ret) { |
| test_pr_err("%s: failed to add a read request", __func__); |
| return ret; |
| } |
| |
| blk_run_queue(q); |
| return ret; |
| } |
| |
| static |
| int ufs_test_write_read_test_open_cb(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| message_repeat = 1; |
| test_pr_info("%s:UFS test initialized", __func__); |
| return 0; |
| } |
| |
| static ssize_t ufs_test_write_read_test_write_cb(struct file *file, |
| const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| int ret = 0; |
| int i; |
| int number; |
| |
| sscanf(buf, "%d", &number); |
| |
| if (number <= 0) |
| number = 1; |
| |
| test_pr_info("%s:the test will run for %d iterations.", |
| __func__, number); |
| memset(&utd->test_info, 0, sizeof(struct test_info)); |
| |
| /* Initializing test */ |
| utd->test_info.data = utd; |
| utd->test_info.get_test_case_str_fn = ufs_test_get_test_case_str; |
| utd->test_info.testcase = UFS_TEST_WRITE_READ_TEST; |
| utd->test_info.get_rq_disk_fn = ufs_test_get_rq_disk; |
| utd->test_info.run_test_fn = ufs_test_run_write_read_test; |
| |
| /* Running the test multiple times */ |
| for (i = 0; i < number; ++i) { |
| ret = test_iosched_start_test(&utd->test_info); |
| if (ret) { |
| test_pr_err("%s: Test failed.", __func__); |
| return ret; |
| } |
| } |
| |
| test_pr_info("%s: Completed all the ufs test iterations.", __func__); |
| |
| return count; |
| } |
| |
| static ssize_t ufs_test_write_read_test_read_cb(struct file *file, |
| char __user *buffer, size_t count, loff_t *offset) |
| { |
| memset((void *) buffer, 0, count); |
| |
| snprintf(buffer, count, "\nThis is a UFS write-read test for debug.\n"); |
| |
| if (message_repeat == 1) { |
| message_repeat = 0; |
| return strnlen(buffer, count); |
| } else |
| return 0; |
| } |
| |
| const struct file_operations write_read_test_ops = { |
| .open = ufs_test_write_read_test_open_cb, |
| .write = ufs_test_write_read_test_write_cb, |
| .read = ufs_test_write_read_test_read_cb, |
| }; |
| |
| static void long_seq_test_free_end_io_fn(struct request *rq, int err) |
| { |
| struct test_request *test_rq; |
| struct test_data *ptd = test_get_test_data(); |
| |
| if (rq) |
| test_rq = (struct test_request *)rq->elv.priv[0]; |
| else { |
| test_pr_err("%s: error: NULL request", __func__); |
| return; |
| } |
| |
| 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); |
| utd->completed_req_count++; |
| |
| test_pr_err("%s: request %d completed, err=%d", |
| __func__, test_rq->req_id, err); |
| |
| check_test_completion(); |
| |
| } |
| |
| static int run_long_seq_test(struct test_data *td) |
| { |
| int ret = 0; |
| int direction; |
| static unsigned int inserted_requests; |
| |
| BUG_ON(!td); |
| td->test_count = 0; |
| utd->completed_req_count = 0; |
| inserted_requests = 0; |
| |
| if (td->test_info.testcase == TEST_LONG_SEQUENTIAL_READ) |
| direction = READ; |
| else |
| direction = WRITE; |
| |
| test_pr_info("%s: Adding %d requests, first req_id=%d", |
| __func__, LONG_SEQ_TEST_NUM_REQS, |
| td->wr_rd_next_req_id); |
| |
| do { |
| /* |
| * since our requests come from a pool containing 128 |
| * requests, we don't want to exhaust this quantity, |
| * therefore we add up to QUEUE_MAX_REQUESTS (which |
| * includes a safety margin) and then call the mmc layer |
| * to fetch them |
| */ |
| if (td->test_count >= QUEUE_MAX_REQUESTS) { |
| blk_run_queue(td->req_q); |
| continue; |
| } |
| |
| ret = test_iosched_add_wr_rd_test_req(0, direction, |
| td->start_sector, TEST_MAX_BIOS_PER_REQ, |
| TEST_PATTERN_5A, |
| long_seq_test_free_end_io_fn); |
| if (ret) { |
| test_pr_err("%s: failed to create request" , __func__); |
| break; |
| } |
| inserted_requests++; |
| td->test_info.test_byte_count += |
| (TEST_MAX_BIOS_PER_REQ * sizeof(unsigned int) * |
| BIO_U32_SIZE); |
| |
| } while (inserted_requests < LONG_SEQ_TEST_NUM_REQS); |
| |
| /* in this case the queue will not run in the above loop */ |
| if (LONG_SEQ_TEST_NUM_REQS < QUEUE_MAX_REQUESTS) |
| blk_run_queue(td->req_q); |
| |
| return ret; |
| } |
| |
| |
| void long_seq_test_calc_throughput(unsigned long mtime, |
| unsigned long byte_count) |
| { |
| unsigned long fraction, integer; |
| |
| test_pr_info("%s: time is %lu msec, size is %lu.%lu MiB", |
| __func__, mtime, LONG_TEST_SIZE_INTEGER(byte_count), |
| LONG_TEST_SIZE_FRACTION(byte_count)); |
| |
| /* we first multiply in order not to lose precision */ |
| mtime *= MB_MSEC_RATIO_APPROXIMATION; |
| /* divide values to get a MiB/sec integer value with one |
| digit of precision |
| */ |
| fraction = integer = (byte_count * 10) / mtime; |
| integer /= 10; |
| /* and calculate the MiB value fraction */ |
| fraction -= integer * 10; |
| |
| test_pr_info("%s: Throughput: %lu.%lu MiB/sec\n", |
| __func__, integer, fraction); |
| } |
| |
| static ssize_t long_sequential_read_test_write(struct file *file, |
| const char __user *buf, |
| size_t count, |
| loff_t *ppos) |
| { |
| int ret = 0; |
| int i = 0; |
| int number = -1; |
| unsigned long mtime, byte_count; |
| |
| test_pr_info("%s: -- UFS Long Sequential Read TEST --", __func__); |
| |
| sscanf(buf, "%d", &number); |
| |
| if (number <= 0) |
| number = 1; |
| |
| memset(&utd->test_info, 0, sizeof(struct test_info)); |
| |
| utd->test_info.data = utd; |
| utd->test_info.get_rq_disk_fn = ufs_test_get_rq_disk; |
| utd->test_info.run_test_fn = run_long_seq_test; |
| utd->test_info.get_test_case_str_fn = ufs_test_get_test_case_str; |
| utd->test_info.testcase = TEST_LONG_SEQUENTIAL_READ; |
| |
| for (i = 0 ; i < number ; ++i) { |
| test_pr_info("%s: Cycle # %d / %d", __func__, i+1, number); |
| test_pr_info("%s: ====================", __func__); |
| |
| ret = test_iosched_start_test(&utd->test_info); |
| if (ret) |
| break; |
| |
| mtime = ktime_to_ms(utd->test_info.test_duration); |
| byte_count = utd->test_info.test_byte_count; |
| |
| long_seq_test_calc_throughput(mtime, byte_count); |
| |
| /* Allow FS requests to be dispatched */ |
| msleep(1000); |
| } |
| |
| return count; |
| } |
| |
| static ssize_t long_sequential_read_test_read(struct file *file, |
| char __user *buffer, |
| size_t count, |
| loff_t *offset) |
| { |
| memset((void *)buffer, 0, count); |
| |
| snprintf(buffer, count, |
| "\nufs_long_sequential_read_test\n" |
| "=========\n" |
| "Description:\n" |
| "This test runs the following scenarios\n" |
| "- Long Sequential Read Test: this test measures read " |
| "throughput at the driver level by sequentially reading many " |
| "large requests.\n"); |
| |
| if (message_repeat == 1) { |
| message_repeat = 0; |
| return strnlen(buffer, count); |
| } else |
| return 0; |
| } |
| |
| static bool message_repeat; |
| static int test_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| message_repeat = 1; |
| return 0; |
| } |
| |
| const struct file_operations long_sequential_read_test_ops = { |
| .open = test_open, |
| .write = long_sequential_read_test_write, |
| .read = long_sequential_read_test_read, |
| }; |
| |
| static ssize_t long_sequential_write_test_write(struct file *file, |
| const char __user *buf, |
| size_t count, |
| loff_t *ppos) |
| { |
| int ret = 0; |
| int i = 0; |
| int number = -1; |
| unsigned long mtime, byte_count; |
| |
| test_pr_info("%s: -- UFS Long Sequential Write TEST --", __func__); |
| |
| sscanf(buf, "%d", &number); |
| |
| if (number <= 0) |
| number = 1; |
| |
| memset(&utd->test_info, 0, sizeof(struct test_info)); |
| |
| utd->test_info.data = utd; |
| utd->test_info.get_rq_disk_fn = ufs_test_get_rq_disk; |
| utd->test_info.get_test_case_str_fn = ufs_test_get_test_case_str; |
| utd->test_info.run_test_fn = run_long_seq_test; |
| utd->test_info.testcase = TEST_LONG_SEQUENTIAL_WRITE; |
| |
| for (i = 0 ; i < number ; ++i) { |
| test_pr_info("%s: Cycle # %d / %d", __func__, i+1, number); |
| test_pr_info("%s: ====================", __func__); |
| |
| utd->test_info.test_byte_count = 0; |
| ret = test_iosched_start_test(&utd->test_info); |
| if (ret) |
| break; |
| |
| mtime = ktime_to_ms(utd->test_info.test_duration); |
| byte_count = utd->test_info.test_byte_count; |
| |
| long_seq_test_calc_throughput(mtime, byte_count); |
| |
| /* Allow FS requests to be dispatched */ |
| msleep(1000); |
| } |
| |
| return count; |
| } |
| |
| static ssize_t long_sequential_write_test_read(struct file *file, |
| char __user *buffer, |
| size_t count, |
| loff_t *offset) |
| { |
| memset((void *)buffer, 0, count); |
| |
| snprintf(buffer, count, |
| "\nufs_long_sequential_write_test\n" |
| "=========\n" |
| "Description:\n" |
| "This test runs the following scenarios\n" |
| "- Long Sequential Write Test: this test measures write " |
| "throughput at the driver level by sequentially writing many " |
| "large requests\n"); |
| |
| if (message_repeat == 1) { |
| message_repeat = 0; |
| return strnlen(buffer, count); |
| } else |
| return 0; |
| } |
| |
| const struct file_operations long_sequential_write_test_ops = { |
| .open = test_open, |
| .write = long_sequential_write_test_write, |
| .read = long_sequential_write_test_read, |
| }; |
| |
| static void ufs_test_debugfs_cleanup(void) |
| { |
| debugfs_remove_recursive(test_iosched_get_debugfs_tests_root()); |
| } |
| |
| static int ufs_test_debugfs_init(void) |
| { |
| struct dentry *utils_root, *tests_root; |
| int ret = 0; |
| |
| utils_root = test_iosched_get_debugfs_utils_root(); |
| tests_root = test_iosched_get_debugfs_tests_root(); |
| |
| if (!utils_root || !tests_root) { |
| test_pr_err("%s: Failed to create debugfs root.", __func__); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| utd->debug.random_test_seed = debugfs_create_u32("random_test_seed", |
| S_IRUGO | S_IWUGO, utils_root, &utd->random_test_seed); |
| |
| if (!utd->debug.random_test_seed) { |
| test_pr_err("%s: Could not create debugfs random_test_seed.", |
| __func__); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| utd->debug.write_read_test = debugfs_create_file("ufs_write_read_test", |
| S_IRUGO | S_IWUGO, tests_root, |
| NULL, &write_read_test_ops); |
| |
| if (!utd->debug.write_read_test) { |
| ret = -ENOMEM; |
| goto exit_err; |
| } |
| |
| utd->debug.long_sequential_read_test = debugfs_create_file( |
| "ufs_long_sequential_read_test", |
| S_IRUGO | S_IWUGO, |
| tests_root, |
| NULL, |
| &long_sequential_read_test_ops); |
| |
| if (!utd->debug.long_sequential_read_test) { |
| ret = -ENOMEM; |
| goto exit_err; |
| } |
| |
| utd->debug.long_sequential_write_test = debugfs_create_file( |
| "ufs_long_sequential_write_test", |
| S_IRUGO | S_IWUGO, |
| tests_root, |
| NULL, |
| &long_sequential_write_test_ops); |
| |
| if (!utd->debug.long_sequential_write_test) { |
| ret = -ENOMEM; |
| goto exit_err; |
| } |
| |
| goto exit; |
| |
| exit_err: |
| debugfs_remove_recursive(tests_root); |
| exit: |
| return ret; |
| } |
| |
| static void ufs_test_probe(void) |
| { |
| ufs_test_debugfs_init(); |
| } |
| |
| static void ufs_test_remove(void) |
| { |
| ufs_test_debugfs_cleanup(); |
| } |
| |
| int __init ufs_test_init(void) |
| { |
| utd = kzalloc(sizeof(struct ufs_test_data), GFP_KERNEL); |
| if (!utd) { |
| test_pr_err("%s: failed to allocate ufs_test_data", __func__); |
| return -ENODEV; |
| } |
| |
| init_waitqueue_head(&utd->wait_q); |
| utd->bdt.init_fn = ufs_test_probe; |
| utd->bdt.exit_fn = ufs_test_remove; |
| INIT_LIST_HEAD(&utd->bdt.list); |
| test_iosched_register(&utd->bdt); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(ufs_test_init); |
| |
| static void __exit ufs_test_exit(void) |
| { |
| test_iosched_unregister(&utd->bdt); |
| kfree(utd); |
| } |
| module_init(ufs_test_init) |
| ; |
| module_exit(ufs_test_exit) |
| ; |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("UFC test"); |
| |