mmc_test: collect data and show it via sysfs by demand
authorAndy Shevchenko <andy.shevchenko@gmail.com>
Wed, 1 Sep 2010 06:26:47 +0000 (09:26 +0300)
committerChris Ball <cjb@laptop.org>
Sat, 23 Oct 2010 13:11:13 +0000 (21:11 +0800)
Make it possible to get test results via sysfs.  It helps to do tests
non-interactively.  We have the file created under sysfs already and
can use it to show test results.

Prior to this patch, the "test" file under each card's sysfs node was
write-only, and results were obtained by looking at dmesg.  This patch
improves programmatic access to the test results, making them available by
reading back from the same "test" file:

[root@host mmc0:e624]# echo 6 > test
[root@host mmc0:e624]# cat test
Test 6: 2

[cjb@laptop.org: changelog improvements]
Signed-off-by: Andy Shevchenko <ext-andriy.shevchenko@nokia.com>
Cc: Chris Ball <cjb@laptop.org>
Cc: <linux-mmc@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Chris Ball <cjb@laptop.org>
drivers/mmc/card/mmc_test.c

index 2888fdc..0c8b568 100644 (file)
@@ -17,6 +17,7 @@
 
 #include <linux/scatterlist.h>
 #include <linux/swap.h>                /* For nr_free_buffer_pages() */
+#include <linux/list.h>
 
 #define RESULT_OK              0
 #define RESULT_FAIL            1
@@ -76,6 +77,38 @@ struct mmc_test_area {
        struct scatterlist *sg;
 };
 
+/**
+ * struct mmc_test_transfer_result - transfer results for performance tests.
+ * @link: double-linked list
+ * @count: amount of group of sectors to check
+ * @sectors: amount of sectors to check in one group
+ * @ts: time values of transfer
+ * @rate: calculated transfer rate
+ */
+struct mmc_test_transfer_result {
+       struct list_head link;
+       unsigned int count;
+       unsigned int sectors;
+       struct timespec ts;
+       unsigned int rate;
+};
+
+/**
+ * struct mmc_test_general_result - results for tests.
+ * @link: double-linked list
+ * @card: card under test
+ * @testcase: number of test case
+ * @result: result of test run
+ * @tr_lst: transfer measurements if any as mmc_test_transfer_result
+ */
+struct mmc_test_general_result {
+       struct list_head link;
+       struct mmc_card *card;
+       int testcase;
+       int result;
+       struct list_head tr_lst;
+};
+
 /**
  * struct mmc_test_card - test information.
  * @card: card under test
@@ -83,6 +116,7 @@ struct mmc_test_area {
  * @buffer: transfer buffer
  * @highmem: buffer for highmem tests
  * @area: information for performance tests
+ * @gr: pointer to results of current testcase
  */
 struct mmc_test_card {
        struct mmc_card *card;
@@ -92,7 +126,8 @@ struct mmc_test_card {
 #ifdef CONFIG_HIGHMEM
        struct page     *highmem;
 #endif
-       struct mmc_test_area area;
+       struct mmc_test_area            area;
+       struct mmc_test_general_result  *gr;
 };
 
 /*******************************************************************/
@@ -448,6 +483,30 @@ static unsigned int mmc_test_rate(uint64_t bytes, struct timespec *ts)
        return bytes;
 }
 
+/*
+ * Save transfer results for future usage
+ */
+static void mmc_test_save_transfer_result(struct mmc_test_card *test,
+       unsigned int count, unsigned int sectors, struct timespec ts,
+       unsigned int rate)
+{
+       struct mmc_test_transfer_result *tr;
+
+       if (!test->gr)
+               return;
+
+       tr = kmalloc(sizeof(struct mmc_test_transfer_result), GFP_KERNEL);
+       if (!tr)
+               return;
+
+       tr->count = count;
+       tr->sectors = sectors;
+       tr->ts = ts;
+       tr->rate = rate;
+
+       list_add_tail(&tr->link, &test->gr->tr_lst);
+}
+
 /*
  * Print the transfer rate.
  */
@@ -466,6 +525,8 @@ static void mmc_test_print_rate(struct mmc_test_card *test, uint64_t bytes,
                         mmc_hostname(test->card->host), sectors, sectors >> 1,
                         (sectors == 1 ? ".5" : ""), (unsigned long)ts.tv_sec,
                         (unsigned long)ts.tv_nsec, rate / 1000, rate / 1024);
+
+       mmc_test_save_transfer_result(test, 1, sectors, ts, rate);
 }
 
 /*
@@ -489,6 +550,8 @@ static void mmc_test_print_avg_rate(struct mmc_test_card *test, uint64_t bytes,
                         sectors >> 1, (sectors == 1 ? ".5" : ""),
                         (unsigned long)ts.tv_sec, (unsigned long)ts.tv_nsec,
                         rate / 1000, rate / 1024);
+
+       mmc_test_save_transfer_result(test, count, sectors, ts, rate);
 }
 
 /*
@@ -1940,6 +2003,8 @@ static const struct mmc_test_case mmc_test_cases[] = {
 
 static DEFINE_MUTEX(mmc_test_lock);
 
+static LIST_HEAD(mmc_test_result);
+
 static void mmc_test_run(struct mmc_test_card *test, int testcase)
 {
        int i, ret;
@@ -1950,6 +2015,8 @@ static void mmc_test_run(struct mmc_test_card *test, int testcase)
        mmc_claim_host(test->card->host);
 
        for (i = 0;i < ARRAY_SIZE(mmc_test_cases);i++) {
+               struct mmc_test_general_result *gr;
+
                if (testcase && ((i + 1) != testcase))
                        continue;
 
@@ -1968,6 +2035,25 @@ static void mmc_test_run(struct mmc_test_card *test, int testcase)
                        }
                }
 
+               gr = kzalloc(sizeof(struct mmc_test_general_result),
+                       GFP_KERNEL);
+               if (gr) {
+                       INIT_LIST_HEAD(&gr->tr_lst);
+
+                       /* Assign data what we know already */
+                       gr->card = test->card;
+                       gr->testcase = i;
+
+                       /* Append container to global one */
+                       list_add_tail(&gr->link, &mmc_test_result);
+
+                       /*
+                        * Save the pointer to created container in our private
+                        * structure.
+                        */
+                       test->gr = gr;
+               }
+
                ret = mmc_test_cases[i].run(test);
                switch (ret) {
                case RESULT_OK:
@@ -1993,6 +2079,10 @@ static void mmc_test_run(struct mmc_test_card *test, int testcase)
                                mmc_hostname(test->card->host), ret);
                }
 
+               /* Save the result */
+               if (gr)
+                       gr->result = ret;
+
                if (mmc_test_cases[i].cleanup) {
                        ret = mmc_test_cases[i].cleanup(test);
                        if (ret) {
@@ -2010,13 +2100,80 @@ static void mmc_test_run(struct mmc_test_card *test, int testcase)
                mmc_hostname(test->card->host));
 }
 
+static void mmc_test_free_result(struct mmc_card *card)
+{
+       struct mmc_test_general_result *gr, *grs;
+
+       mutex_lock(&mmc_test_lock);
+
+       list_for_each_entry_safe(gr, grs, &mmc_test_result, link) {
+               struct mmc_test_transfer_result *tr, *trs;
+
+               if (card && gr->card != card)
+                       continue;
+
+               list_for_each_entry_safe(tr, trs, &gr->tr_lst, link) {
+                       list_del(&tr->link);
+                       kfree(tr);
+               }
+
+               list_del(&gr->link);
+               kfree(gr);
+       }
+
+       mutex_unlock(&mmc_test_lock);
+}
+
 static ssize_t mmc_test_show(struct device *dev,
        struct device_attribute *attr, char *buf)
 {
+       struct mmc_card *card = mmc_dev_to_card(dev);
+       struct mmc_test_general_result *gr;
+       char *p = buf;
+       size_t len = PAGE_SIZE;
+       int ret;
+
        mutex_lock(&mmc_test_lock);
+
+       list_for_each_entry(gr, &mmc_test_result, link) {
+               struct mmc_test_transfer_result *tr;
+
+               if (gr->card != card)
+                       continue;
+
+               ret = snprintf(p, len, "Test %d: %d\n", gr->testcase + 1,
+                       gr->result);
+               if (ret < 0)
+                       goto err;
+               if (ret >= len) {
+                       ret = -ENOBUFS;
+                       goto err;
+               }
+               p += ret;
+               len -= ret;
+
+               list_for_each_entry(tr, &gr->tr_lst, link) {
+                       ret = snprintf(p, len, "%u %d %lu.%09lu %u\n",
+                               tr->count, tr->sectors,
+                               (unsigned long)tr->ts.tv_sec,
+                               (unsigned long)tr->ts.tv_nsec,
+                               tr->rate);
+                       if (ret < 0)
+                               goto err;
+                       if (ret >= len) {
+                               ret = -ENOBUFS;
+                               goto err;
+                       }
+                       p += ret;
+                       len -= ret;
+               }
+       }
+
+       ret = PAGE_SIZE - len;
+err:
        mutex_unlock(&mmc_test_lock);
 
-       return 0;
+       return ret;
 }
 
 static ssize_t mmc_test_store(struct device *dev,
@@ -2033,6 +2190,12 @@ static ssize_t mmc_test_store(struct device *dev,
        if (!test)
                return -ENOMEM;
 
+       /*
+        * Remove all test cases associated with given card. Thus we have only
+        * actual data of the last run.
+        */
+       mmc_test_free_result(card);
+
        test->card = card;
 
        test->buffer = kzalloc(BUFFER_SIZE, GFP_KERNEL);
@@ -2079,6 +2242,7 @@ static int mmc_test_probe(struct mmc_card *card)
 
 static void mmc_test_remove(struct mmc_card *card)
 {
+       mmc_test_free_result(card);
        device_remove_file(&card->dev, &dev_attr_test);
 }
 
@@ -2097,6 +2261,9 @@ static int __init mmc_test_init(void)
 
 static void __exit mmc_test_exit(void)
 {
+       /* Clear stalled data if card is still plugged */
+       mmc_test_free_result(NULL);
+
        mmc_unregister_driver(&mmc_driver);
 }