summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/ufs/Kconfig9
-rw-r--r--drivers/scsi/ufs/Makefile1
-rw-r--r--drivers/scsi/ufs/ufs_test.c348
-rw-r--r--include/scsi/scsi.h2
4 files changed, 360 insertions, 0 deletions
diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
index 5f4530744e0a..ec72dbdce775 100644
--- a/drivers/scsi/ufs/Kconfig
+++ b/drivers/scsi/ufs/Kconfig
@@ -83,3 +83,12 @@ config SCSI_UFS_QCOM
Select this if you have UFS controller on QCOM chipset.
If unsure, say N.
+
+config SCSI_UFS_TEST
+ tristate "Universal Flash Storage host controller driver unit-tests"
+ depends on SCSI_UFSHCD && IOSCHED_TEST
+ ---help---
+ This adds UFS Host controller unit-test framework.
+ The UFS unit-tests register as a block device test utility to
+ the test-iosched and will be initiated when the test-iosched will
+ be chosen to be the active I/O scheduler. \ No newline at end of file
diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
index 8303bcce7a23..0352e47b8397 100644
--- a/drivers/scsi/ufs/Makefile
+++ b/drivers/scsi/ufs/Makefile
@@ -3,3 +3,4 @@ obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
+obj-$(CONFIG_SCSI_UFS_TEST) += ufs_test.o
diff --git a/drivers/scsi/ufs/ufs_test.c b/drivers/scsi/ufs/ufs_test.c
new file mode 100644
index 000000000000..03c58a4ee2d6
--- /dev/null
+++ b/drivers/scsi/ufs/ufs_test.c
@@ -0,0 +1,348 @@
+/* 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>
+
+#define MODULE_NAME "ufs_test"
+
+#define TEST_MAX_BIOS_PER_REQ 120
+#define LARGE_PRIME_1 1103515367
+#define LARGE_PRIME_2 35757
+#define DEFAULT_NUM_OF_BIOS 2
+
+#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,
+};
+
+struct ufs_test_debug {
+ struct dentry *write_read_test; /* basic test */
+ struct dentry *random_test_seed; /* parameters in utils */
+};
+
+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;
+};
+
+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;
+ 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 requests to Q, first req_id=%d",
+ __func__, 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 ufs_test_debugfs_cleanup(void)
+{
+ debugfs_remove(utd->debug.write_read_test);
+}
+
+static int ufs_test_debugfs_init(void)
+{
+ struct dentry *utils_root, *tests_root;
+
+ 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__);
+ return -EINVAL;
+ }
+
+ 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__);
+ return -ENOMEM;
+ }
+
+ utd->debug.write_read_test = debugfs_create_file("write_read_test",
+ S_IRUGO | S_IWUGO, tests_root,
+ NULL, &write_read_test_ops);
+
+ if (!utd->debug.write_read_test) {
+ debugfs_remove(utd->debug.random_test_seed);
+ test_pr_err("%s: Could not create debugfs write_read_test.",
+ __func__);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+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");
+
diff --git a/include/scsi/scsi.h b/include/scsi/scsi.h
index e0a3398b1547..431175883cf5 100644
--- a/include/scsi/scsi.h
+++ b/include/scsi/scsi.h
@@ -306,4 +306,6 @@ static inline __u32 scsi_to_u32(__u8 *ptr)
return (ptr[0]<<24) + (ptr[1]<<16) + (ptr[2]<<8) + ptr[3];
}
+struct scsi_disk *scsi_disk_get_from_dev(struct device *dev);
+
#endif /* _SCSI_SCSI_H */