This adds a load tester driver test_driver_data a for the new extensible
driver_data loader API, part of firmware_class. This test driver enables
you to build your tests in userspace by exposing knobs of the exported
API to userspace and enables a trigger action to mimic a one time use
of the kernel API. This gives us the flexibility to build test case from
userspace with less kernel changes.

Signed-off-by: Luis R. Rodriguez <mcg...@kernel.org>
---
 Documentation/driver-api/firmware/driver_data.rst |   32 +
 MAINTAINERS                                       |    1 +
 lib/Kconfig.debug                                 |   12 +
 lib/Makefile                                      |    1 +
 lib/test_driver_data.c                            | 1039 +++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile         |    2 +-
 tools/testing/selftests/firmware/config           |    1 +
 tools/testing/selftests/firmware/driver_data.sh   |  826 ++++++++++++++++
 8 files changed, 1913 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_driver_data.c
 create mode 100755 tools/testing/selftests/firmware/driver_data.sh

diff --git a/Documentation/driver-api/firmware/driver_data.rst 
b/Documentation/driver-api/firmware/driver_data.rst
index 08407b7568fe..757c2ffa4ba6 100644
--- a/Documentation/driver-api/firmware/driver_data.rst
+++ b/Documentation/driver-api/firmware/driver_data.rst
@@ -68,6 +68,38 @@ When driver_data_file_request_async() completes you can rest 
assured all the
 work for both triggering, and processing the driver data using any of your
 callbacks has completed.
 
+Testing the driver_data API
+===========================
+
+The driver data API has a selftest driver: lib/test_driver_data.c. The
+test_driver_data enables you to build your tests in userspace by exposing knobs
+of the exported API in userspace and enabling userspace to configure and
+trigger a kernel call. This lets us build most possible test cases of
+the kernel APIs from userspace.
+
+The test_driver_data also enables multiple test triggers to be created
+enabling testing to be done in parallel, one test interface per test case.
+
+To test an async call one could do::
+
+        echo anything > /lib/firmware/test-driver_data.bin
+        echo -n 1 >  /sys/devices/virtual/misc/test_driver_data0/config_async
+        echo -n 1 >  /sys/devices/virtual/misc/test_driver_data0/trigger_config
+
+A series of tests have been written to test the driver data API thoroughly.
+A respective test case is expected to bet written as new features get added.
+For details of existing tests run::
+
+        tools/testing/selftests/firmware/driver_data.sh -l
+
+To see all available options::
+
+        tools/testing/selftests/firmware/driver_data.sh --help
+
+To run a test 0010 case 40 times::
+
+        tools/testing/selftests/firmware/driver_data.sh -c 0010 40
+
 Tracking development enhancements and ideas
 ===========================================
 
diff --git a/MAINTAINERS b/MAINTAINERS
index f1dd84bb21ed..96449804ec9d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5072,6 +5072,7 @@ L:        linux-kernel@vger.kernel.org
 S:     Maintained
 F:     Documentation/firmware_class/
 F:     drivers/base/firmware*.c
+F:     lib/test_driver_data.c
 F:     include/linux/firmware.h
 F:     include/linux/driver_data.h
 
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 64c03b07ad2f..68495327ca49 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1975,6 +1975,18 @@ config TEST_FIRMWARE
 
          If unsure, say N.
 
+config TEST_DRIVER_DATA
+       tristate "Test driver data loading via driver_data APIs"
+       default n
+       depends on FW_LOADER
+       help
+         This builds the "test_driver_data" module that creates a userspace
+         interface for testing driver data loading using the driver_data API.
+         This can be used to control the triggering of driver data loading
+         without needing an actual real device.
+
+         If unsure, say N.
+
 config TEST_UDELAY
        tristate "udelay test driver"
        default n
diff --git a/lib/Makefile b/lib/Makefile
index fbbe2e2dba8f..79eecd0ce6fb 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -46,6 +46,7 @@ obj-y += kstrtox.o
 obj-$(CONFIG_TEST_BPF) += test_bpf.o
 obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o
 obj-$(CONFIG_TEST_HASH) += test_hash.o test_siphash.o
+obj-$(CONFIG_TEST_DRIVER_DATA) += test_driver_data.o
 obj-$(CONFIG_TEST_KASAN) += test_kasan.o
 obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o
 obj-$(CONFIG_TEST_LKM) += test_module.o
diff --git a/lib/test_driver_data.c b/lib/test_driver_data.c
new file mode 100644
index 000000000000..0c2670b356aa
--- /dev/null
+++ b/lib/test_driver_data.c
@@ -0,0 +1,1039 @@
+/*
+ * Driver data test interface
+ *
+ * Copyright (C) 2017 Luis R. Rodriguez <mcg...@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of copyleft-next (version 0.3.1 or later) as published
+ * at http://copyleft-next.org/.
+ *
+ * This module provides an interface to trigger and test the driver data API
+ * through a series of configurations and a few triggers. This driver
+ * lacks any extra dependencies, and will not normally be loaded by the
+ * system unless explicitly requested by name. You can also build this
+ * driver into your kernel.
+ *
+ * Although all configurations are already written for and will be supported
+ * for this test driver, ideally we should strive to see what mechanisms we
+ * can put in place to instead automatically generate this sort of test
+ * interface, test cases, and infer results. Its a simple enough interface that
+ * should hopefully enable more exploring in this area.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/completion.h>
+#include <linux/driver_data.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/async.h>
+#include <linux/delay.h>
+#include <linux/vmalloc.h>
+
+/* Used for the fallback default to test against */
+#define TEST_DRIVER_DATA "test-driver_data.bin"
+
+/*
+ * For device allocation / registration
+ */
+static DEFINE_MUTEX(reg_dev_mutex);
+static LIST_HEAD(reg_test_devs);
+
+/*
+ * num_test_devs actually represents the *next* ID of the next
+ * device we will allow to create.
+ */
+int num_test_devs;
+
+/**
+ * test_config - represents configuration for the driver_data API
+ *
+ * @name: the name of the primary driver_data file to look for
+ * @default_name: a fallback example, used to test the optional callback
+ *     mechanism.
+ * @async: true if you want to trigger an async request. This will use
+ *     driver_data_request_async(). If false the synchronous call will
+ *     be used, driver_data_request().
+ * @optional: whether or not the driver_data is optional refer to the
+ *     struct driver_data_reg_params @optional field for more information.
+ * @keep: whether or not we wish to free the driver_data on our own, refer to
+ *     the struct driver_data_req_params @keep field for more information.
+ * @enable_opt_cb: whether or not the optional callback should be set
+ *     on a trigger. There is no equivalent setting on the struct
+ *     driver_data_req_params as this is implementation specific, and in
+ *     in driver_data API its explicit if you had defined an optional call
+ *     back for your descriptor with either DRIVER_DATA_SYNC_OPT_CB() or
+ *     DRIVER_DATA_ASYNC_OPT_CB(). Since the params are in a const we have
+ *     no option but to use a flag and two const structs to decide which
+ *     one we should use.
+ * @test_result: a test may use this to collect the result from the call
+ *     of the driver_data_request_async() or driver_data_request() calls used
+ *     in their tests. Note that for async calls this typically will be a
+ *     successful result (0) unless of course you've used bogus parameters, or
+ *     the system is out of memory. Tests against the callbacks can only be
+ *     implementation specific, so we don't test for that for now but it may
+ *     make sense to build tests cases against a series of semantically
+ *     similar family of callbacks that generally represents usage in the
+ *     kernel. Synchronous calls return bogus error checks against the
+ *     parameters as well, but also return the result of the work from the
+ *     callbacks. You can therefore rely on sync calls if you really want to
+ *     test for the callback results as well. Errors you can expect:
+ *
+ *     API specific:
+ *
+ *     0:              success for sync, for async it means request was sent
+ *     -EINVAL:        invalid parameters or request
+ *     -ENOENT:        files not found
+ *
+ *     System environment:
+ *
+ *     -ENOMEM:        memory pressure on system
+ *     -ENODEV:        out of number of devices to test
+ *
+ * The ordering of elements in this struct must match the exact order of the
+ * elements in the ATTRIBUTE_GROUPS(test_dev_config), this is done to know
+ * what corresponding field each device attribute configuration entry maps
+ * to what struct member on test_alloc_dev_attrs().
+ */
+struct test_config {
+       char *name;
+       char *default_name;
+       bool async;
+       bool optional;
+       bool keep;
+       bool enable_opt_cb;
+
+       int test_result;
+};
+
+/**
+ * test_driver_data_private - private device driver driver_data representation
+ *
+ * @size: size of the data copied, in bytes
+ * @data: the actual data we copied over from driver_data
+ * @written: true if a callback managed to copy data over to the device
+ *     successfully. Since different callbacks are used for this purpose
+ *     having the data written does not necessarily mean a test case
+ *     completed successfully. Each tests case has its own specific
+ *     goals.
+ *
+ * Private representation of buffer where we put the device system data.
+ */
+struct test_driver_data_private {
+       size_t size;
+       u8 *data;
+       bool written;
+};
+
+/**
+ * driver_data_test_device - test device to help test driver_data
+ *
+ * @dev_idx: unique ID for test device
+ * @config: this keeps the device's own configuration. Instead of creating
+ *     different triggers for all possible test cases we can think of in
+ *     kernel, we expose a set possible device attributes for tuning the
+ *     driver_data API and we to let you tune them in userspace. We then just
+ *     provide one trigger.
+ * @test_driver_data: internal private representation of a storage area
+ *     a driver might typically use to stuff firmware / driver_data.
+ * @misc_dev: we use a misc device under the hood
+ * @dev: pointer to misc_dev's own struct device
+ * @driver_data_mutex: for access into the @driver_data, the fake storage
+ *     location for the system data we copy.
+ * @config_mutex:
+ * @trigger_mutex: all triggers are mutually exclusive when testing. To help
+ *     enable testing you can create a different device, each device has its
+ *     own set of protections, mimicking real devices.
+ * list: needed to be part of the reg_test_devs
+ */
+struct driver_data_test_device {
+       int dev_idx;
+       struct test_config config;
+       struct test_driver_data_private test_driver_data;
+       struct miscdevice misc_dev;
+       struct device *dev;
+
+       struct mutex driver_data_mutex;
+       struct mutex config_mutex;
+       struct mutex trigger_mutex;
+       struct list_head list;
+};
+
+static struct miscdevice *dev_to_misc_dev(struct device *dev)
+{
+       return dev_get_drvdata(dev);
+}
+
+static struct driver_data_test_device *
+misc_dev_to_test_dev(struct miscdevice *misc_dev)
+{
+       return container_of(misc_dev, struct driver_data_test_device, misc_dev);
+}
+
+static struct driver_data_test_device *dev_to_test_dev(struct device *dev)
+{
+       struct miscdevice *misc_dev;
+
+       misc_dev = dev_to_misc_dev(dev);
+
+       return misc_dev_to_test_dev(misc_dev);
+}
+
+static ssize_t test_fw_misc_read(struct file *f, char __user *buf,
+                                size_t size, loff_t *offset)
+{
+       struct miscdevice *misc_dev = f->private_data;
+       struct driver_data_test_device *test_dev =
+               misc_dev_to_test_dev(misc_dev);
+       struct test_driver_data_private *test_driver_data =
+               &test_dev->test_driver_data;
+       ssize_t ret = 0;
+
+       mutex_lock(&test_dev->driver_data_mutex);
+       if (test_driver_data->written)
+               ret = simple_read_from_buffer(buf, size, offset,
+                                             test_driver_data->data,
+                                             test_driver_data->size);
+       mutex_unlock(&test_dev->driver_data_mutex);
+
+       return ret;
+}
+
+static const struct file_operations test_fw_fops = {
+       .owner          = THIS_MODULE,
+       .read           = test_fw_misc_read,
+};
+
+static
+void free_test_driver_data(struct test_driver_data_private *test_driver_data)
+{
+       kfree(test_driver_data->data);
+       test_driver_data->data = NULL;
+       test_driver_data->size = 0;
+       test_driver_data->written = false;
+}
+
+static int test_load_driver_data(struct driver_data_test_device *test_dev,
+                                const struct driver_data *driver_data)
+{
+       struct test_driver_data_private *test_driver_data =
+               &test_dev->test_driver_data;
+       int ret = 0;
+
+       if (!driver_data)
+               return -ENOENT;
+
+       mutex_lock(&test_dev->driver_data_mutex);
+
+       free_test_driver_data(test_driver_data);
+
+       test_driver_data->data = kzalloc(driver_data->size, GFP_KERNEL);
+       if (!test_driver_data->data) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       memcpy(test_driver_data->data, driver_data->data, driver_data->size);
+       test_driver_data->size = driver_data->size;
+       test_driver_data->written = true;
+
+       dev_info(test_dev->dev, "loaded: %zu\n", test_driver_data->size);
+
+out:
+       mutex_unlock(&test_dev->driver_data_mutex);
+
+       return ret;
+}
+
+static int sync_found_cb(void *context, const struct driver_data *driver_data)
+{
+       struct driver_data_test_device *test_dev = context;
+       int ret;
+
+       ret = test_load_driver_data(test_dev, driver_data);
+       if (ret)
+               dev_info(test_dev->dev,
+                        "unable to write driver_data: %d\n", ret);
+       return ret;
+}
+
+static ssize_t config_show(struct device *dev,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+       int len = 0;
+
+       mutex_lock(&test_dev->config_mutex);
+
+       len += snprintf(buf, PAGE_SIZE,
+                       "Custom trigger configuration for: %s\n",
+                       dev_name(dev));
+
+       if (config->default_name)
+               len += snprintf(buf+len, PAGE_SIZE,
+                               "default name:\t%s\n",
+                               config->default_name);
+       else
+               len += snprintf(buf+len, PAGE_SIZE,
+                               "default name:\tEMTPY\n");
+
+       if (config->name)
+               len += snprintf(buf+len, PAGE_SIZE,
+                               "name:\t\t%s\n", config->name);
+       else
+               len += snprintf(buf+len, PAGE_SIZE,
+                               "name:\t\tEMPTY\n");
+
+       len += snprintf(buf+len, PAGE_SIZE,
+                       "type:\t\t%s\n",
+                       config->async ? "async" : "sync");
+       len += snprintf(buf+len, PAGE_SIZE,
+                       "optional:\t%s\n",
+                       config->optional ? "true" : "false");
+       len += snprintf(buf+len, PAGE_SIZE,
+                       "enable_opt_cb:\t%s\n",
+                       config->enable_opt_cb ? "true" : "false");
+       len += snprintf(buf+len, PAGE_SIZE,
+                       "keep:\t\t%s\n",
+                       config->keep ? "true" : "false");
+
+       mutex_unlock(&test_dev->config_mutex);
+
+       return len;
+}
+static DEVICE_ATTR_RO(config);
+
+static int config_load_data(struct driver_data_test_device *test_dev,
+                           const struct driver_data *driver_data)
+{
+       struct test_config *config = &test_dev->config;
+       int ret;
+
+       ret = test_load_driver_data(test_dev, driver_data);
+       if (ret) {
+               if (!config->optional)
+                       dev_info(test_dev->dev,
+                                "unable to write driver_data\n");
+       }
+       if (config->keep) {
+               release_driver_data(driver_data);
+               driver_data = NULL;
+       }
+
+       return ret;
+}
+
+static int config_req_default(struct driver_data_test_device *test_dev)
+{
+       struct test_config *config = &test_dev->config;
+       int ret;
+       /*
+        * Note: we don't chain config->optional here, we make this
+        * fallback file a requirement. It doesn't make much sense to test
+        * chaining further as the optional callback is implementation
+        * specific, by testing it once we test it for any possible
+        * chains. We provide this as an example of what people can do
+        * and use a default non-optional fallback.
+        */
+       const struct driver_data_req_params req_params = {
+               DRIVER_DATA_DEFAULT_SYNC(sync_found_cb, test_dev),
+       };
+
+       if (config->async)
+               dev_info(test_dev->dev,
+                        "loading default fallback '%s' using sync request 
now\n",
+                        config->default_name);
+       else
+               dev_info(test_dev->dev,
+                        "loading default fallback '%s'\n",
+                        config->default_name);
+
+       ret = driver_data_request(config->default_name,
+                             &req_params, test_dev->dev);
+       if (ret)
+               dev_info(test_dev->dev,
+                        "load of default '%s' failed: %d\n",
+                        config->default_name, ret);
+
+       return ret;
+}
+
+/*
+ * This is the default sync fallback callback, as a fallback this
+ * then uses a sync request.
+ */
+static int config_sync_req_default_cb(void *context)
+{
+       struct driver_data_test_device *test_dev = context;
+       int ret;
+
+       ret = config_req_default(test_dev);
+
+       return ret;
+
+       /* Leave all the error checking for the main caller */
+}
+
+/*
+ * This is the default config->async fallback callback, as a fallback this
+ * then uses a sync request.
+ */
+static void config_async_req_default_cb(void *context)
+{
+       struct driver_data_test_device *test_dev = context;
+
+       config_req_default(test_dev);
+
+       /* Leave all the error checking for the main caller */
+}
+
+static int config_sync_req_cb(void *context,
+                             const struct driver_data *driver_data)
+{
+       struct driver_data_test_device *test_dev = context;
+
+       return config_load_data(test_dev, driver_data);
+}
+
+static int trigger_config_sync(struct driver_data_test_device *test_dev)
+{
+       struct test_config *config = &test_dev->config;
+       int ret;
+       const struct driver_data_req_params req_params_default = {
+               DRIVER_DATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+               .optional = config->optional,
+               .keep = config->keep,
+       };
+       const struct driver_data_req_params req_params_opt_cb = {
+               DRIVER_DATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+               DRIVER_DATA_SYNC_OPT_CB(config_sync_req_default_cb, test_dev),
+               .optional = config->optional,
+               .keep = config->keep,
+       };
+       const struct driver_data_req_params *req_params;
+
+       if (config->enable_opt_cb)
+               req_params = &req_params_opt_cb;
+       else
+               req_params = &req_params_default;
+
+       ret = driver_data_request(config->name, req_params, test_dev->dev);
+       if (ret)
+               dev_err(test_dev->dev, "sync load of '%s' failed: %d\n",
+                       config->name, ret);
+
+       return ret;
+}
+
+static void config_async_req_cb(const struct driver_data *driver_data,
+                               void *context)
+{
+       struct driver_data_test_device *test_dev = context;
+
+       config_load_data(test_dev, driver_data);
+}
+
+static int trigger_config_async(struct driver_data_test_device *test_dev)
+{
+       struct test_config *config = &test_dev->config;
+       int ret;
+       const struct driver_data_req_params req_params_default = {
+               DRIVER_DATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+               .sync_reqs.mode = config->async ?
+                       DRIVER_DATA_ASYNC : DRIVER_DATA_SYNC,
+               .optional = config->optional,
+               .keep = config->keep,
+       };
+       const struct driver_data_req_params req_params_opt_cb = {
+               DRIVER_DATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+               DRIVER_DATA_ASYNC_OPT_CB(config_async_req_default_cb, test_dev),
+               .sync_reqs.mode = config->async ?
+                       DRIVER_DATA_ASYNC : DRIVER_DATA_SYNC,
+               .optional = config->optional,
+               .keep = config->keep,
+       };
+       const struct driver_data_req_params *req_params;
+       async_cookie_t async_cookie;
+
+       if (config->enable_opt_cb)
+               req_params = &req_params_opt_cb;
+       else
+               req_params = &req_params_default;
+
+       ret = driver_data_request_async(config->name, req_params,
+                                  test_dev->dev, &async_cookie);
+       if (ret) {
+               dev_err(test_dev->dev, "async load of '%s' failed: %d\n",
+                       config->name, ret);
+               goto out;
+       }
+
+       driver_data_synchronize_request(async_cookie);
+out:
+       return ret;
+}
+
+static ssize_t
+trigger_config_store(struct device *dev,
+                    struct device_attribute *attr,
+                    const char *buf, size_t count)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_driver_data_private *test_driver_data =
+               &test_dev->test_driver_data;
+       struct test_config *config = &test_dev->config;
+       int ret;
+
+       mutex_lock(&test_dev->trigger_mutex);
+       mutex_lock(&test_dev->config_mutex);
+
+       dev_info(dev, "loading '%s'\n", config->name);
+
+       if (config->async)
+               ret = trigger_config_async(test_dev);
+       else
+               ret = trigger_config_sync(test_dev);
+
+       config->test_result = ret;
+
+       if (ret)
+               goto out;
+
+       if (test_driver_data->written) {
+               dev_info(dev, "loaded: %zu\n", test_driver_data->size);
+               ret = count;
+       } else {
+               dev_err(dev, "failed to load firmware\n");
+               ret = -ENODEV;
+       }
+
+out:
+       mutex_unlock(&test_dev->config_mutex);
+       mutex_unlock(&test_dev->trigger_mutex);
+
+       return ret;
+}
+static DEVICE_ATTR_WO(trigger_config);
+
+/*
+ * XXX: move to kstrncpy() once merged.
+ *
+ * Users should use kfree_const() when freeing these.
+ */
+static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp)
+{
+       *dst = kstrndup(name, count, gfp);
+       if (!*dst)
+               return -ENOSPC;
+       return count;
+}
+
+static void __driver_data_config_free(struct test_config *config)
+{
+       kfree_const(config->name);
+       config->name = NULL;
+       kfree_const(config->default_name);
+       config->default_name = NULL;
+}
+
+static void driver_data_config_free(struct driver_data_test_device *test_dev)
+{
+       struct test_config *config = &test_dev->config;
+
+       mutex_lock(&test_dev->config_mutex);
+       __driver_data_config_free(config);
+       mutex_unlock(&test_dev->config_mutex);
+}
+
+static int __driver_data_config_init(struct test_config *config)
+{
+       int ret;
+
+       ret = __kstrncpy(&config->name, TEST_DRIVER_DATA,
+                        strlen(TEST_DRIVER_DATA), GFP_KERNEL);
+       if (ret < 0)
+               goto out;
+
+       ret = __kstrncpy(&config->default_name, TEST_DRIVER_DATA,
+                        strlen(TEST_DRIVER_DATA), GFP_KERNEL);
+       if (ret < 0)
+               goto out;
+
+       config->async = false;
+       config->optional = false;
+       config->keep = false;
+       config->enable_opt_cb = false;
+       config->test_result = 0;
+
+       return 0;
+
+out:
+       __driver_data_config_free(config);
+       return ret;
+}
+
+int driver_data_config_init(struct driver_data_test_device *test_dev)
+{
+       struct test_config *config = &test_dev->config;
+       int ret;
+
+       mutex_lock(&test_dev->config_mutex);
+       ret = __driver_data_config_init(config);
+       mutex_unlock(&test_dev->config_mutex);
+
+       return ret;
+}
+
+static ssize_t config_name_store(struct device *dev,
+                                struct device_attribute *attr,
+                                const char *buf, size_t count)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+       int ret;
+
+       mutex_lock(&test_dev->config_mutex);
+       kfree_const(config->name);
+       ret = __kstrncpy(&config->name, buf, count, GFP_KERNEL);
+       mutex_unlock(&test_dev->config_mutex);
+
+       return ret;
+}
+
+/*
+ * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE.
+ */
+static ssize_t config_test_show_str(struct mutex *config_mutex,
+                                   char *dst,
+                                   char *src)
+{
+       int len;
+
+       mutex_lock(config_mutex);
+       len = snprintf(dst, PAGE_SIZE, "%s\n", src);
+       mutex_unlock(config_mutex);
+
+       return len;
+}
+
+static ssize_t config_name_show(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return config_test_show_str(&test_dev->config_mutex, buf,
+                                   config->name);
+}
+static DEVICE_ATTR(config_name, 0644, config_name_show, config_name_store);
+
+static ssize_t config_default_name_store(struct device *dev,
+                                        struct device_attribute *attr,
+                                        const char *buf, size_t count)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+       int ret;
+
+       mutex_lock(&test_dev->config_mutex);
+       kfree_const(config->default_name);
+       ret = __kstrncpy(&config->default_name, buf, count, GFP_KERNEL);
+       mutex_unlock(&test_dev->config_mutex);
+
+       return ret;
+}
+
+static ssize_t config_default_name_show(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return config_test_show_str(&test_dev->config_mutex, buf,
+                                   config->default_name);
+}
+static DEVICE_ATTR(config_default_name, 0644, config_default_name_show,
+                  config_default_name_store);
+
+static ssize_t reset_store(struct device *dev,
+                          struct device_attribute *attr,
+                          const char *buf, size_t count)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+       int ret;
+
+       mutex_lock(&test_dev->trigger_mutex);
+
+       mutex_lock(&test_dev->driver_data_mutex);
+       free_test_driver_data(&test_dev->test_driver_data);
+       mutex_unlock(&test_dev->driver_data_mutex);
+
+       mutex_lock(&test_dev->config_mutex);
+
+       __driver_data_config_free(config);
+
+       ret = __driver_data_config_init(config);
+       if (ret < 0) {
+               ret = -ENOMEM;
+               dev_err(dev, "could not alloc settings for config trigger: 
%d\n",
+                      ret);
+               goto out;
+       }
+
+       dev_info(dev, "reset\n");
+       ret = count;
+
+out:
+       mutex_unlock(&test_dev->config_mutex);
+       mutex_unlock(&test_dev->trigger_mutex);
+
+       return ret;
+}
+static DEVICE_ATTR_WO(reset);
+
+/*
+ * XXX: consider a soluton to generalize drivers to specify their own
+ * mutex, adding it to dev core after this gets merged. This may not
+ * be important for once-in-a-while system tuning parameters, but if
+ * we want to enable fuzz testing, this is really important.
+ *
+ * It may make sense to just have a "struct device configuration mutex"
+ * for these sorts of things, although there is difficulty in that we'd
+ * need dynamically allocated attributes for that. Its the same reason
+ * why we ended up not using the provided standard device attribute
+ * bool, int interfaces.
+ */
+
+static int test_dev_config_update_bool(struct driver_data_test_device 
*test_dev,
+                                      const char *buf, size_t size,
+                                      bool *config)
+{
+       int ret;
+
+       mutex_lock(&test_dev->config_mutex);
+       if (strtobool(buf, config) < 0)
+               ret = -EINVAL;
+       else
+               ret = size;
+       mutex_unlock(&test_dev->config_mutex);
+
+       return ret;
+}
+
+static ssize_t
+test_dev_config_show_bool(struct driver_data_test_device *test_dev,
+                         char *buf,
+                         bool config)
+{
+       bool val;
+
+       mutex_lock(&test_dev->config_mutex);
+       val = config;
+       mutex_unlock(&test_dev->config_mutex);
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static int test_dev_config_update_int(struct driver_data_test_device *test_dev,
+                                     const char *buf, size_t size,
+                                     int *config)
+{
+       int ret;
+       long new;
+
+       ret = kstrtol(buf, 10, &new);
+       if (ret)
+               return ret;
+
+       if (new > INT_MAX || new < INT_MIN)
+               return -EINVAL;
+
+       mutex_lock(&test_dev->config_mutex);
+       *(int *)config = new;
+       mutex_unlock(&test_dev->config_mutex);
+
+       /* Always return full write size even if we didn't consume all */
+       return size;
+}
+
+static
+ssize_t test_dev_config_show_int(struct driver_data_test_device *test_dev,
+                                char *buf,
+                                int config)
+{
+       int val;
+
+       mutex_lock(&test_dev->config_mutex);
+       val = config;
+       mutex_unlock(&test_dev->config_mutex);
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t config_async_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t count)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_update_bool(test_dev, buf, count,
+                                          &config->async);
+}
+
+static ssize_t config_async_show(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_show_bool(test_dev, buf, config->async);
+}
+static DEVICE_ATTR(config_async, 0644, config_async_show, config_async_store);
+
+static ssize_t config_optional_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_update_bool(test_dev, buf, count,
+                                          &config->optional);
+}
+
+static ssize_t config_optional_show(struct device *dev,
+                                   struct device_attribute *attr,
+                                   char *buf)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_show_bool(test_dev, buf, config->optional);
+}
+static DEVICE_ATTR(config_optional, 0644, config_optional_show,
+                  config_optional_store);
+
+static ssize_t config_keep_store(struct device *dev,
+                                struct device_attribute *attr,
+                                const char *buf, size_t count)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_update_bool(test_dev, buf, count,
+                                          &config->keep);
+}
+
+static ssize_t config_keep_show(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_show_bool(test_dev, buf, config->keep);
+}
+static DEVICE_ATTR(config_keep, 0644, config_keep_show, config_keep_store);
+
+static ssize_t config_enable_opt_cb_store(struct device *dev,
+                                         struct device_attribute *attr,
+                                         const char *buf, size_t count)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_update_bool(test_dev, buf, count,
+                                          &config->enable_opt_cb);
+}
+
+static ssize_t config_enable_opt_cb_show(struct device *dev,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_show_bool(test_dev, buf,
+                                        config->enable_opt_cb);
+}
+static DEVICE_ATTR(config_enable_opt_cb, 0644,
+                  config_enable_opt_cb_show,
+                  config_enable_opt_cb_store);
+
+static ssize_t test_result_store(struct device *dev,
+                                struct device_attribute *attr,
+                                const char *buf, size_t count)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_update_int(test_dev, buf, count,
+                                         &config->test_result);
+}
+
+static ssize_t test_result_show(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf)
+{
+       struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+       struct test_config *config = &test_dev->config;
+
+       return test_dev_config_show_int(test_dev, buf, config->test_result);
+}
+static DEVICE_ATTR(test_result, 0644, test_result_show, test_result_store);
+
+#define TEST_DRIVER_DATA_DEV_ATTR(name)                &dev_attr_##name.attr
+
+static struct attribute *test_dev_attrs[] = {
+       TEST_DRIVER_DATA_DEV_ATTR(trigger_config),
+       TEST_DRIVER_DATA_DEV_ATTR(config),
+       TEST_DRIVER_DATA_DEV_ATTR(reset),
+
+       TEST_DRIVER_DATA_DEV_ATTR(config_name),
+       TEST_DRIVER_DATA_DEV_ATTR(config_default_name),
+       TEST_DRIVER_DATA_DEV_ATTR(config_async),
+       TEST_DRIVER_DATA_DEV_ATTR(config_optional),
+       TEST_DRIVER_DATA_DEV_ATTR(config_keep),
+       TEST_DRIVER_DATA_DEV_ATTR(config_enable_opt_cb),
+       TEST_DRIVER_DATA_DEV_ATTR(test_result),
+
+       NULL,
+};
+
+ATTRIBUTE_GROUPS(test_dev);
+
+void free_test_dev_driver_data(struct driver_data_test_device *test_dev)
+{
+       kfree_const(test_dev->misc_dev.name);
+       test_dev->misc_dev.name = NULL;
+       vfree(test_dev);
+       test_dev = NULL;
+       driver_data_config_free(test_dev);
+}
+
+void unregister_test_dev_driver_data(struct driver_data_test_device *test_dev)
+{
+       dev_info(test_dev->dev, "removing interface\n");
+       misc_deregister(&test_dev->misc_dev);
+       kfree(&test_dev->misc_dev.name);
+       free_test_dev_driver_data(test_dev);
+}
+
+struct driver_data_test_device *alloc_test_dev_driver_data(int idx)
+{
+       int ret;
+       struct driver_data_test_device *test_dev;
+       struct miscdevice *misc_dev;
+
+       test_dev = vzalloc(sizeof(struct driver_data_test_device));
+       if (!test_dev)
+               goto err_out;
+
+       mutex_init(&test_dev->driver_data_mutex);
+       mutex_init(&test_dev->config_mutex);
+       mutex_init(&test_dev->trigger_mutex);
+
+       ret = driver_data_config_init(test_dev);
+       if (ret < 0)
+               goto err_out_free;
+
+       test_dev->dev_idx = idx;
+       misc_dev = &test_dev->misc_dev;
+
+       misc_dev->minor = MISC_DYNAMIC_MINOR;
+       misc_dev->name = kasprintf(GFP_KERNEL, "test_driver_data%d", idx);
+       if (!misc_dev->name)
+               goto err_out_free_config;
+
+       misc_dev->fops = &test_fw_fops;
+       misc_dev->groups = test_dev_groups;
+
+       return test_dev;
+
+err_out_free_config:
+       __driver_data_config_free(&test_dev->config);
+err_out_free:
+       kfree(test_dev);
+err_out:
+       return NULL;
+}
+
+static int register_test_dev_driver_data(void)
+{
+       struct driver_data_test_device *test_dev = NULL;
+       int ret = -ENODEV;
+
+       mutex_lock(&reg_dev_mutex);
+
+       /* int should suffice for number of devices, test for wrap */
+       if (unlikely(num_test_devs + 1) < 0) {
+               pr_err("reached limit of number of test devices\n");
+               goto out;
+       }
+
+       test_dev = alloc_test_dev_driver_data(num_test_devs);
+       if (!test_dev) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       ret = misc_register(&test_dev->misc_dev);
+       if (ret) {
+               pr_err("could not register misc device: %d\n", ret);
+               free_test_dev_driver_data(test_dev);
+               goto out;
+       }
+
+       test_dev->dev = test_dev->misc_dev.this_device;
+       list_add_tail(&test_dev->list, &reg_test_devs);
+       dev_info(test_dev->dev, "interface ready\n");
+
+       num_test_devs++;
+
+out:
+       mutex_unlock(&reg_dev_mutex);
+
+       return ret;
+}
+
+static int __init test_driver_data_init(void)
+{
+       int ret;
+
+       ret = register_test_dev_driver_data();
+       if (ret)
+               pr_err("Cannot add first test driver_data device\n");
+
+       return ret;
+}
+late_initcall(test_driver_data_init);
+
+static void __exit test_driver_data_exit(void)
+{
+       struct driver_data_test_device *test_dev, *tmp;
+
+       mutex_lock(&reg_dev_mutex);
+       list_for_each_entry_safe(test_dev, tmp, &reg_test_devs, list) {
+               list_del(&test_dev->list);
+               unregister_test_dev_driver_data(test_dev);
+       }
+       mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_driver_data_exit);
+
+MODULE_AUTHOR("Luis R. Rodriguez <mcg...@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/tools/testing/selftests/firmware/Makefile 
b/tools/testing/selftests/firmware/Makefile
index 1894d625af2d..c9bf6c44435f 100644
--- a/tools/testing/selftests/firmware/Makefile
+++ b/tools/testing/selftests/firmware/Makefile
@@ -3,7 +3,7 @@
 # No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
 all:
 
-TEST_PROGS := fw_filesystem.sh fw_fallback.sh
+TEST_PROGS := fw_filesystem.sh fw_fallback.sh driver_data.sh
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/firmware/config 
b/tools/testing/selftests/firmware/config
index c8137f70e291..0f1a299f9270 100644
--- a/tools/testing/selftests/firmware/config
+++ b/tools/testing/selftests/firmware/config
@@ -1 +1,2 @@
 CONFIG_TEST_FIRMWARE=y
+CONFIG_TEST_DRIVER_DATA=y
diff --git a/tools/testing/selftests/firmware/driver_data.sh 
b/tools/testing/selftests/firmware/driver_data.sh
new file mode 100755
index 000000000000..7884cda3dd16
--- /dev/null
+++ b/tools/testing/selftests/firmware/driver_data.sh
@@ -0,0 +1,826 @@
+#!/bin/bash
+# Copyright (C) 2016 Luis R. Rodriguez <mcg...@kernel.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of copyleft-next (version 0.3.1 or later) as published
+# at http://copyleft-next.org/.
+
+# This performs a series tests against firmware_class to excercise the
+# firmware_class driver with focus only on the extensible driver data API.
+#
+# To make this test self contained, and not pollute your distribution
+# firmware install paths, we reset the custom load directory to a
+# temporary location.
+
+set -e
+
+TEST_NAME="driver_data"
+TEST_DRIVER="test_${TEST_NAME}"
+TEST_DIR=$(dirname $0)
+
+# This represents
+#
+# TEST_ID:TEST_COUNT:ENABLED
+#
+# TEST_ID: is the test id number
+# TEST_COUNT: number of times we should run the test
+# ENABLED: 1 if enabled, 0 otherwise
+#
+# Once these are enabled please leave them as-is. Write your own test,
+# we have tons of space.
+ALL_TESTS="0001:3:1"
+ALL_TESTS="$ALL_TESTS 0002:3:1"
+ALL_TESTS="$ALL_TESTS 0003:3:1"
+ALL_TESTS="$ALL_TESTS 0004:10:1"
+ALL_TESTS="$ALL_TESTS 0005:10:1"
+ALL_TESTS="$ALL_TESTS 0006:10:1"
+ALL_TESTS="$ALL_TESTS 0007:10:1"
+ALL_TESTS="$ALL_TESTS 0008:10:1"
+ALL_TESTS="$ALL_TESTS 0009:10:1"
+ALL_TESTS="$ALL_TESTS 0010:10:1"
+
+test_modprobe()
+{
+       if [ ! -d $DIR ]; then
+               echo "$0: $DIR not present" >&2
+               echo "You must have the following enabled in your kernel:" >&2
+               cat $TEST_DIR/config >&2
+               exit 1
+       fi
+}
+
+function allow_user_defaults()
+{
+       if [ -z $DIR ]; then
+               DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"
+       fi
+
+       if [ -z $DEFAULT_NUM_TESTS ]; then
+               DEFAULT_NUM_TESTS=50
+       fi
+
+       if [ -z $FW_SYSFSPATH ]; then
+               FW_SYSFSPATH="/sys/module/firmware_class/parameters/path"
+       fi
+
+       if [ -z $OLD_FWPATH ]; then
+               OLD_FWPATH=$(cat $FW_SYSFSPATH)
+       fi
+
+       if [ -z $FWPATH]; then
+               FWPATH=$(mktemp -d)
+       fi
+
+       if [ -z $DEFAULT_DRIVER_DATA ]; then
+               DEFAULT_DRIVER_DATA="test-driver_data.bin"
+       fi
+
+       if [ -z $FW ]; then
+               FW="$FWPATH/$DEFAULT_DRIVER_DATA"
+       fi
+
+       # Set the kernel search path.
+       echo -n "$FWPATH" > $FW_SYSFSPATH
+
+       # This is an unlikely real-world firmware content. :)
+       echo "ABCD0123" >"$FW"
+
+       NAME=$(basename "$FW")
+}
+
+test_reqs()
+{
+       if ! which diff 2> /dev/null > /dev/null; then
+               echo "$0: You need diff installed"
+               exit 1
+       fi
+
+       uid=$(id -u)
+       if [ $uid -ne 0 ]; then
+               echo $msg must be run as root >&2
+               exit 0
+       fi
+}
+
+function load_req_mod()
+{
+       trap "test_modprobe" EXIT
+
+       if [ ! -d $DIR ]; then
+               modprobe $TEST_DRIVER
+       fi
+}
+
+test_finish()
+{
+       echo -n "$OLD_PATH" >/sys/module/firmware_class/parameters/path
+       rm -f "$FW"
+       rmdir "$FWPATH"
+}
+
+errno_name_to_val()
+{
+       case "$1" in
+       SUCCESS)
+               echo 0;;
+       -EPERM)
+               echo -1;;
+       -ENOENT)
+               echo -2;;
+       -EINVAL)
+               echo -22;;
+       -ERR_ANY)
+               echo -123456;;
+       *)
+               echo invalid;;
+       esac
+}
+
+errno_val_to_name()
+       case "$1" in
+       0)
+               echo SUCCESS;;
+       -1)
+               echo -EPERM;;
+       -2)
+               echo -ENOENT;;
+       -22)
+               echo -EINVAL;;
+       -123456)
+               echo -ERR_ANY;;
+       *)
+               echo invalid;;
+       esac
+
+config_set_async()
+{
+       if ! echo -n 1 >$DIR/config_async ; then
+               echo "$0: Unable to set to async" >&2
+               exit 1
+       fi
+}
+
+config_disable_async()
+{
+       if ! echo -n 0 >$DIR/config_async ; then
+               echo "$0: Unable to set to sync" >&2
+               exit 1
+       fi
+}
+
+config_set_optional()
+{
+       if ! echo -n 1 >$DIR/config_optional ; then
+               echo "$0: Unable to set to optional" >&2
+               exit 1
+       fi
+}
+
+config_disable_optional()
+{
+       if ! echo -n 0 >$DIR/config_optional ; then
+               echo "$0: Unable to disable optional" >&2
+               exit 1
+       fi
+}
+
+config_set_keep()
+{
+       if ! echo -n 1 >$DIR/config_keep; then
+               echo "$0: Unable to set to keep" >&2
+               exit 1
+       fi
+}
+
+config_disable_keep()
+{
+       if ! echo -n 0 >$DIR/config_keep; then
+               echo "$0: Unable to disable keep option" >&2
+               exit 1
+       fi
+}
+
+config_enable_opt_cb()
+{
+       if ! echo -n 1 >$DIR/config_enable_opt_cb; then
+               echo "$0: Unable to set to optional" >&2
+               exit 1
+       fi
+}
+
+config_disable_opt_cb()
+{
+       if ! echo -n 0 >$DIR/config_enable_opt_cb; then
+               echo "$0: Unable to disable keep option" >&2
+               exit 1
+       fi
+}
+
+# For special characters use printf directly,
+# refer to driver_data_test_0001
+config_set_name()
+{
+       if ! echo -n $1 >$DIR/config_name; then
+               echo "$0: Unable to set name" >&2
+               exit 1
+       fi
+}
+
+config_get_name()
+{
+       cat $DIR/config_name
+}
+
+# For special characters use printf directly,
+# refer to driver_data_test_0001
+config_set_default_name()
+{
+       if ! echo -n $1 >$DIR/config_default_name; then
+               echo "$0: Unable to set default_name" >&2
+               exit 1
+       fi
+}
+
+config_get_default_name()
+{
+       cat $DIR/config_default_name
+}
+
+config_get_test_result()
+{
+       cat $DIR/test_result
+}
+
+config_reset()
+{
+       if ! echo -n "1" >"$DIR"/reset; then
+               echo "$0: reset shuld have worked" >&2
+               exit 1
+       fi
+}
+
+config_show_config()
+{
+       echo "----------------------------------------------------"
+       cat "$DIR"/config
+       echo "----------------------------------------------------"
+}
+
+config_trigger()
+{
+       if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then
+               echo "$1: FAIL - loading should have worked" >&2
+               config_show_config >&2
+               exit 1
+       fi
+       echo "$1: OK! - loading driver_data"
+}
+
+config_trigger_want_fail()
+{
+       if echo "1" > $DIR/trigger_config 2>/dev/null; then
+               echo "$1: FAIL - loading was expected to fail" >&2
+               config_show_config >&2
+               exit 1
+       fi
+       echo "$1: OK! - loading failed as expected"
+}
+
+config_file_should_match()
+{
+       FILE=$(config_get_name)
+       # On this one we expect the file to exist so leave stderr in
+       if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > 
/dev/null; then
+               echo "$1: FAIL - file $FILE did not match contents in 
/dev/test_driver_data0" >&2
+               config_show_config >&2
+               exit 1
+       fi
+       echo "$1: OK! - $FILE == /dev/test_driver_data0"
+}
+
+config_file_should_match_default()
+{
+       FILE=$(config_get_default_name)
+       # On this one we expect the file to exist so leave stderr in
+       if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > 
/dev/null; then
+               echo "$1: FAIL - file $FILE did not match contents in 
/dev/test_driver_data0" >&2
+               config_show_config >&2
+               exit 1
+       fi
+       echo "$1: OK! - $FILE == /dev/test_driver_data0"
+}
+
+config_file_should_not_match()
+{
+       FILE=$(config_get_name)
+       # File may not exist, so skip those error messages as well
+       if $(diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null) 2> 
/dev/null ; then
+               echo "$1: FAIL - file $FILE was not expected to match 
/dev/null" >&2
+               config_show_config >&2
+               exit 1
+       fi
+       echo "$1: OK! - $FILE != /dev/test_driver_data0"
+}
+
+config_default_file_should_match()
+{
+       FILE=$(config_get_default_name)
+       diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null
+       if ! $? ; then
+               echo "$1: FAIL - file $FILE expected to match 
/dev/test_driver_data0" >&2
+               config_show_config >&2
+               exit 1
+       fi
+       echo "$1: OK! [file integrity matches]"
+}
+
+config_default_file_should_not_match()
+{
+       FILE=$(config_get_default_name)
+       diff -q FWPATH/$FILE /dev/test_driver_data0 2> /dev/null
+       if $? 2> /dev/null ; then
+               echo "$1: FAIL - file $FILE was not expected to match 
test_driver_data0" >&2
+               config_show_config >&2
+               exit 1
+       fi
+       echo "$1: OK!"
+}
+
+config_expect_result()
+{
+       RC=$(config_get_test_result)
+       RC_NAME=$(errno_val_to_name $RC)
+
+       ERRNO_NAME=$2
+       ERRNO=$(errno_name_to_val $ERRNO_NAME)
+
+       if [[ $ERRNO_NAME = "-ERR_ANY" ]]; then
+               if [[ $RC -ge 0 ]]; then
+                       echo "$1: FAIL, test expects $ERRNO_NAME - got $RC_NAME 
($RC)" >&2
+                       config_show_config >&2
+                       exit 1
+               fi
+       elif [[ $RC != $ERRNO ]]; then
+               echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got 
$RC_NAME ($RC)" >&2
+               config_show_config >&2
+               exit 1
+       fi
+       echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME"
+}
+
+driver_data_set_sync_defaults()
+{
+       config_reset
+}
+
+driver_data_set_async_defaults()
+{
+       config_reset
+       config_set_async
+}
+
+driver_data_test_0001s()
+{
+       NAME='\000'
+
+       driver_data_set_sync_defaults
+       config_set_name $NAME
+       printf '\000' >"$DIR"/config_name
+       config_trigger_want_fail ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+driver_data_test_0001a()
+{
+       NAME='\000'
+
+       driver_data_set_async_defaults
+       printf '\000' >"$DIR"/config_name
+       config_trigger_want_fail ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+driver_data_test_0001()
+{
+       driver_data_test_0001s
+       driver_data_test_0001a
+}
+
+driver_data_test_0002s()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_sync_defaults
+       config_set_name ${FUNCNAME[0]}
+       config_trigger_want_fail ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+driver_data_test_0002a()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_async_defaults
+       config_set_name $NAME
+       config_trigger_want_fail ${FUNCNAME[0]}
+       # This may seem odd to expect success on a bogus
+       # file but remember this is an async call, the actual
+       # error handling is managed by the async callbacks.
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0002()
+{
+       driver_data_test_0002s
+       driver_data_test_0002a
+}
+
+driver_data_test_0003()
+{
+       config_reset
+       config_file_should_not_match ${FUNCNAME[0]}
+}
+
+driver_data_test_0004s()
+{
+       driver_data_set_sync_defaults
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0004a()
+{
+       driver_data_set_async_defaults
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0004()
+{
+       driver_data_test_0004s
+       driver_data_test_0004a
+}
+
+driver_data_test_0005s()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_sync_defaults
+       config_set_optional
+       config_set_name $NAME
+       config_trigger_want_fail ${FUNCNAME[0]}
+       # We do this to ensure the default backup callback hasn't
+       # been called yet
+       config_file_should_not_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0005a()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_async_defaults
+       config_set_optional
+       config_set_name $NAME
+       config_trigger_want_fail ${FUNCNAME[0]}
+       # We do this to ensure the default backup callback hasn't
+       # been called yet
+       config_file_should_not_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0005()
+{
+       driver_data_test_0005s
+       driver_data_test_0005a
+}
+
+driver_data_test_0006s()
+{
+       driver_data_set_sync_defaults
+       config_set_optional
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0006a()
+{
+       driver_data_set_async_defaults
+       config_set_optional
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0006()
+{
+       driver_data_test_0006s
+       driver_data_test_0006a
+}
+
+driver_data_test_0007s()
+{
+       driver_data_set_sync_defaults
+       config_set_keep
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0007a()
+{
+       driver_data_set_async_defaults
+       config_set_keep
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0007()
+{
+       driver_data_test_0007s
+       driver_data_test_0007a
+}
+
+driver_data_test_0008s()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_sync_defaults
+       config_set_name $NAME
+       config_set_optional
+       config_enable_opt_cb
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match_default ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0008a()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_async_defaults
+       config_set_name $NAME
+       config_set_optional
+       config_enable_opt_cb
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match_default ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0008()
+{
+       driver_data_test_0008s
+       driver_data_test_0008a
+}
+
+driver_data_test_0009s()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_sync_defaults
+       config_set_name $NAME
+       config_set_keep
+       config_set_optional
+       config_enable_opt_cb
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match_default ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0009a()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_async_defaults
+       config_set_name $NAME
+       config_set_keep
+       config_set_optional
+       config_enable_opt_cb
+       config_trigger ${FUNCNAME[0]}
+       config_file_should_match_default ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0009()
+{
+       driver_data_test_0009s
+       driver_data_test_0009a
+}
+
+driver_data_test_0010s()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_sync_defaults
+       config_set_name $NAME
+       config_set_default_name $NAME
+       config_set_keep
+       config_set_optional
+       config_enable_opt_cb
+       config_trigger_want_fail ${FUNCNAME[0]}
+       config_file_should_not_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+driver_data_test_0010a()
+{
+       NAME="nope-$DEFAULT_DRIVER_DATA"
+
+       driver_data_set_async_defaults
+       config_set_name $NAME
+       config_set_default_name $NAME
+       config_set_keep
+       config_set_optional
+       config_enable_opt_cb
+       config_trigger_want_fail ${FUNCNAME[0]}
+       config_file_should_not_match ${FUNCNAME[0]}
+       config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0010()
+{
+       driver_data_test_0010s
+       driver_data_test_0010a
+}
+
+list_tests()
+{
+       echo "Test ID list:"
+       echo
+       echo "TEST_ID x NUM_TEST"
+       echo "TEST_ID:   Test ID"
+       echo "NUM_TESTS: Number of recommended times to run the test"
+       echo
+       echo "0001 x $(get_test_count 0001) - Empty string should be ignored"
+       echo "0002 x $(get_test_count 0002) - Files that do not exist should be 
ignored"
+       echo "0003 x $(get_test_count 0003) - Verify test_driver_data0 has 
nothing loaded upon reset"
+       echo "0004 x $(get_test_count 0004) - Simple sync and async loader"
+       echo "0005 x $(get_test_count 0005) - Verify optional loading is not 
fatal"
+       echo "0006 x $(get_test_count 0006) - Verify optional loading enables 
loading"
+       echo "0007 x $(get_test_count 0007) - Verify keep works"
+       echo "0008 x $(get_test_count 0008) - Verify optional callback works"
+       echo "0009 x $(get_test_count 0009) - Verify optional callback works, 
keep"
+       echo "0010 x $(get_test_count 0010) - Verify when fallback file is not 
present"
+       echo "0011 x $(get_test_count 0011) - Verify requesting module after 
driver data works"
+}
+
+test_reqs
+
+usage()
+{
+       NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .)
+       let NUM_TESTS=$NUM_TESTS+1
+       MAX_TEST=$(printf "%04d\n" $NUM_TESTS)
+       echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |"
+       echo "           [ -s <4-number-digit> ] | [ -c <4-number-digit> <test- 
count>"
+       echo "           [ all ] [ -h | --help ] [ -l ]"
+       echo ""
+       echo "Valid tests: 0001-$MAX_TEST"
+       echo ""
+       echo "    all     Runs all tests (default)"
+       echo "    -t      Run test ID the number amount of times is recommended"
+       echo "    -w      Watch test ID run until it runs into an error"
+       echo "    -c      Run test ID once"
+       echo "    -s      Run test ID x test-count number of times"
+       echo "    -l      List all test ID list"
+       echo " -h|--help  Help"
+       echo
+       echo "If an error every occurs execution will immediately terminate."
+       echo "If you are adding a new test try using -w <test-ID> first to"
+       echo "make sure the test passes a series of tests."
+       echo
+       echo Example uses:
+       echo
+       echo "$TEST_NAME.sh            -- executes all tests"
+       echo "$TEST_NAME.sh -t 0008    -- Executes test ID 0008 number of times 
is recomended"
+       echo "$TEST_NAME.sh -w 0008    -- Watch test ID 0008 run until an error 
occurs"
+       echo "$TEST_NAME.sh -s 0008    -- Run test ID 0008 once"
+       echo "$TEST_NAME.sh -c 0008 3  -- Run test ID 0008 three times"
+       echo
+       list_tests
+       exit 1
+}
+
+function test_num()
+{
+       re='^[0-9]+$'
+       if ! [[ $1 =~ $re ]]; then
+               usage
+       fi
+}
+
+function get_test_count()
+{
+       test_num $1
+       TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
+       LAST_TWO=${TEST_DATA#*:*}
+       echo ${LAST_TWO%:*}
+}
+
+function get_test_enabled()
+{
+       test_num $1
+       TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
+       echo ${TEST_DATA#*:*:}
+}
+
+function run_all_tests()
+{
+       for i in $ALL_TESTS ; do
+               TEST_ID=${i%:*:*}
+               ENABLED=$(get_test_enabled $TEST_ID)
+               TEST_COUNT=$(get_test_count $TEST_ID)
+               if [[ $ENABLED -eq "1" ]]; then
+                       test_case $TEST_ID $TEST_COUNT
+               fi
+       done
+}
+
+function watch_log()
+{
+       if [ $# -ne 3 ]; then
+               clear
+       fi
+       date
+       echo "Running test: $2 - run #$1"
+}
+
+function watch_case()
+{
+       i=0
+       while [ 1 ]; do
+
+               if [ $# -eq 1 ]; then
+                       test_num $1
+                       watch_log $i ${TEST_NAME}_test_$1
+                       ${TEST_NAME}_test_$1
+               else
+                       watch_log $i all
+                       run_all_tests
+               fi
+               let i=$i+1
+       done
+}
+
+function test_case()
+{
+       NUM_TESTS=$DEFAULT_NUM_TESTS
+       if [ $# -eq 2 ]; then
+               NUM_TESTS=$2
+       fi
+
+       i=0
+       while [ $i -lt $NUM_TESTS ]; do
+               test_num $1
+               watch_log $i ${TEST_NAME}_test_$1 noclear
+               RUN_TEST=${TEST_NAME}_test_$1
+               $RUN_TEST
+               let i=$i+1
+       done
+}
+
+function parse_args()
+{
+       if [ $# -eq 0 ]; then
+               run_all_tests
+       else
+               if [[ "$1" = "all" ]]; then
+                       run_all_tests
+               elif [[ "$1" = "-w" ]]; then
+                       shift
+                       watch_case $@
+               elif [[ "$1" = "-t" ]]; then
+                       shift
+                       test_num $1
+                       test_case $1 $(get_test_count $1)
+               elif [[ "$1" = "-c" ]]; then
+                       shift
+                       test_num $1
+                       test_num $2
+                       test_case $1 $2
+               elif [[ "$1" = "-s" ]]; then
+                       shift
+                       test_case $1 1
+               elif [[ "$1" = "-l" ]]; then
+                       list_tests
+               elif [[ "$1" = "-h" || "$1" = "--help" ]]; then
+                       usage
+               else
+                       usage
+               fi
+       fi
+}
+
+test_reqs
+allow_user_defaults
+load_req_mod
+
+trap "test_finish" EXIT
+
+parse_args $@
+
+exit 0
-- 
2.11.0

Reply via email to