lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20170519191040.25165-4-mcgrof@kernel.org>
Date:   Fri, 19 May 2017 12:10:38 -0700
From:   "Luis R. Rodriguez" <mcgrof@...nel.org>
To:     gregkh@...uxfoundation.org
Cc:     wagi@...om.org, dwmw2@...radead.org, rafal@...ecki.pl,
        arend.vanspriel@...adcom.com, rjw@...ysocki.net,
        yi1.li@...ux.intel.com, atull@...nsource.altera.com,
        moritz.fischer@...us.com, pmladek@...e.com,
        johannes.berg@...el.com, emmanuel.grumbach@...el.com,
        luciano.coelho@...el.com, kvalo@...eaurora.org, luto@...nel.org,
        torvalds@...ux-foundation.org, keescook@...omium.org,
        takahiro.akashi@...aro.org, dhowells@...hat.com, pjones@...hat.com,
        hdegoede@...hat.com, alan@...ux.intel.com, tytso@....edu,
        linux-kernel@...r.kernel.org,
        "Luis R. Rodriguez" <mcgrof@...nel.org>
Subject: [PATCH v8 3/5] test: add new driver_data load tester

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 <mcgrof@...nel.org>
---
 MAINTAINERS                                     |    1 +
 lib/Kconfig.debug                               |   12 +
 lib/Makefile                                    |    1 +
 lib/test_driver_data.c                          | 1278 +++++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile       |    2 +-
 tools/testing/selftests/firmware/config         |    1 +
 tools/testing/selftests/firmware/driver_data.sh | 1002 ++++++++++++++++++
 7 files changed, 2296 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_driver_data.c
 create mode 100755 tools/testing/selftests/firmware/driver_data.sh

diff --git a/MAINTAINERS b/MAINTAINERS
index 148d032e9401..a44bdda2ffa9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5232,6 +5232,7 @@ L:	linux-kernel@...r.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 f6aece3b8098..4198995ca3f7 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1782,6 +1782,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 07fbe6a75692..5a0dcca7097d 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -47,6 +47,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_LIST_SORT) += test_list_sort.o
diff --git a/lib/test_driver_data.c b/lib/test_driver_data.c
new file mode 100644
index 000000000000..422ea6289396
--- /dev/null
+++ b/lib/test_driver_data.c
@@ -0,0 +1,1278 @@
+/*
+ * Driver data test interface
+ *
+ * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@...nel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or at your option) any
+ * later version; or, when distributed separately from the Linux kernel or
+ * incorporated into other software packages, subject to the following license:
+ *
+ * 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_sync().
+ * @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.
+ * @use_api_versioning: use the driver data API versioning support. This
+ *	currenlty implies you are using an async test.
+ * @api_min: API min version to use for the test.
+ * @api_max: API max version to use for the test.
+ * @api_name_postfix: API name postfix
+ * @test_result: a test may use this to collect the result from the call
+ *	of the driver_data_request_async() or driver_data_request_sync() 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;
+	bool use_api_versioning;
+	u8 api_min;
+	u8 api_max;
+	char *api_name_postfix;
+
+	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;
+	u8 api;
+	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
+ * @api_found_calls: number of calls a fetch for a driver was found. We use
+ *	for internal use on the api callback.
+ * @driver_data_mutex: for access into the @driver_data, the fake storage
+ *	location for the system data we copy.
+ * @config_mutex: used to protect configuration changes
+ * @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.
+ * @request_complete: used to help the driver inform itself when async
+ * 	callbacks complete.
+ * 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;
+
+	u8 api_found_calls;
+
+	struct mutex driver_data_mutex;
+	struct mutex config_mutex;
+	struct mutex trigger_mutex;
+	struct completion request_complete;
+	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->api = 0;
+	test_driver_data->written = false;
+}
+
+static int test_load_driver_data(struct driver_data_test_device *test_dev,
+				 const struct firmware *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;
+	test_driver_data->api = driver_data->api;
+
+	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 firmware *driver_data,
+			 int unused_error)
+{
+	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,
+			"use_api_versioning:\t%s\n",
+			config->use_api_versioning ? "true" : "false");
+	len += snprintf(buf+len, PAGE_SIZE,
+			"api_min:\t%u\n", config->api_min);
+	len += snprintf(buf+len, PAGE_SIZE,
+			"api_max:\t%u\n", config->api_max);
+	if (config->api_name_postfix)
+		len += snprintf(buf+len, PAGE_SIZE,
+				"api_name_postfix:\t\t%s\n", config->api_name_postfix);
+	else
+		len += snprintf(buf+len, PAGE_SIZE,
+				"api_name_postfix:\t\tEMPTY\n");
+	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 firmware *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_firmware(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_sync(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, int unused_error)
+{
+	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, int unused_error)
+{
+	struct driver_data_test_device *test_dev = context;
+
+	config_req_default(test_dev);
+
+	complete(&test_dev->request_complete);
+	/* Leave all the error checking for the main caller */
+}
+
+static int config_sync_req_cb(void *context,
+			      const struct firmware *driver_data,
+			      int unused_error)
+{
+	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_REQS(config_sync_req_cb, test_dev,
+					      (config->optional ?
+					       DRIVER_DATA_REQ_OPTIONAL : 0) |
+					      (config->keep ?
+					       DRIVER_DATA_REQ_KEEP : 0)),
+	};
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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_sync(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 firmware *driver_data,
+				void *context, int unused_error)
+{
+	struct driver_data_test_device *test_dev = context;
+
+	config_load_data(test_dev, driver_data);
+	complete(&test_dev->request_complete);
+}
+
+static int config_async_req_api_cb(const struct firmware *driver_data,
+				   void *context,
+				   int unused_error)
+{
+	struct driver_data_test_device *test_dev = context;
+	/*
+	 * This drivers may process a file and determine it does not
+	 * like it, so it wants us to try again, to do this it returns
+	 * -EAGAIN. We mimick this behaviour by not liking odd numbered
+	 * api files, so we know to expect only success on even numbered
+	 * apis.
+	 */
+	if (driver_data && (driver_data->api % 2 == 1)) {
+		pr_info("File api %u found but we purposely ignore it\n",
+			driver_data->api);
+		return -EAGAIN;
+	}
+
+	config_load_data(test_dev, driver_data);
+
+	/*
+	 * If the file was found we let our stupid driver emulator thing
+	 * fake holding the driver data. If the file was not found just
+	 * bail immediately.
+	 */
+	if (driver_data)
+		pr_info("File with api %u found!\n", driver_data->api);
+
+	complete(&test_dev->request_complete);
+
+	return 0;
+}
+
+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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	const struct driver_data_req_params req_params_api = {
+		DRIVER_DATA_API_CB(config_async_req_api_cb, test_dev),
+		DRIVER_DATA_API(config->api_min, config->api_max, config->api_name_postfix),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0) |
+			(config->use_api_versioning ? DRIVER_DATA_REQ_USE_API_VERSIONING : 0),
+	};
+	const struct driver_data_req_params *req_params;
+
+	if (config->enable_opt_cb)
+		req_params = &req_params_opt_cb;
+	else if (config->use_api_versioning)
+		req_params = &req_params_api;
+	else
+		req_params = &req_params_default;
+
+	test_dev->api_found_calls = 0;
+	ret = driver_data_request_async(config->name, req_params,
+					test_dev->dev);
+	if (ret) {
+		dev_err(test_dev->dev, "async load of '%s' failed: %d\n",
+			config->name, ret);
+		goto out;
+	}
+
+	/*
+	 * Without waiting for completion we'd return before the async callback
+	 * completes, and any testing analysis done on the results would be
+	 * bogus. We could have used async cookies to avoid having drivers
+	 * avoid adding their own completions and initializing them.
+	 * We have decided its best to keep with the old way of doing things to
+	 * keep things compatible. Deal with it.
+	 */
+	wait_for_completion_timeout(&test_dev->request_complete, 5 * HZ);
+
+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;
+	kfree_const(config->api_name_postfix);
+	config->api_name_postfix = 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->use_api_versioning = false;
+	config->api_min = 0;
+	config->api_max = 0;
+	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);
+	reinit_completion(&test_dev->request_complete);
+	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 int test_dev_config_update_u8(struct driver_data_test_device *test_dev,
+				     const char *buf, size_t size,
+				     u8 *config)
+{
+	int ret;
+	long new;
+
+	ret = kstrtol(buf, 10, &new);
+	if (ret)
+		return ret;
+
+	if (new > U8_MAX)
+		return -EINVAL;
+
+	mutex_lock(&test_dev->config_mutex);
+	*(u8 *)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_u8(struct driver_data_test_device *test_dev,
+				char *buf,
+				u8 config)
+{
+	u8 val;
+
+	mutex_lock(&test_dev->config_mutex);
+	val = config;
+	mutex_unlock(&test_dev->config_mutex);
+
+	return snprintf(buf, PAGE_SIZE, "%u\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 config_use_api_versioning_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->use_api_versioning);
+}
+
+static ssize_t config_use_api_versioning_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->use_api_versioning);
+}
+static DEVICE_ATTR(config_use_api_versioning, 0644,
+		   config_use_api_versioning_show,
+		   config_use_api_versioning_store);
+
+static ssize_t config_api_min_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_u8(test_dev, buf, count,
+					 &config->api_min);
+}
+
+static ssize_t config_api_min_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_u8(test_dev, buf, config->api_min);
+}
+static DEVICE_ATTR(config_api_min, 0644, config_api_min_show, config_api_min_store);
+
+static ssize_t config_api_max_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_u8(test_dev, buf, count,
+					 &config->api_max);
+}
+
+static ssize_t config_api_max_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_u8(test_dev, buf, config->api_max);
+}
+static DEVICE_ATTR(config_api_max, 0644, config_api_max_show, config_api_max_store);
+
+static ssize_t config_api_name_postfix_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->api_name_postfix);
+	ret = __kstrncpy(&config->api_name_postfix, buf, count, GFP_KERNEL);
+	mutex_unlock(&test_dev->config_mutex);
+
+	return ret;
+}
+
+static ssize_t config_api_name_postfix_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->api_name_postfix);
+}
+static DEVICE_ATTR(config_api_name_postfix, 0644, config_api_name_postfix_show,
+		   config_api_name_postfix_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_use_api_versioning),
+	TEST_DRIVER_DATA_DEV_ATTR(config_enable_opt_cb),
+	TEST_DRIVER_DATA_DEV_ATTR(config_api_min),
+	TEST_DRIVER_DATA_DEV_ATTR(config_api_max),
+	TEST_DRIVER_DATA_DEV_ATTR(config_api_name_postfix),
+	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;
+	driver_data_config_free(test_dev);
+	vfree(test_dev);
+}
+
+void unregister_test_dev_driver_data(struct driver_data_test_device *test_dev)
+{
+	wait_for_completion_timeout(&test_dev->request_complete, 5 * HZ);
+	dev_info(test_dev->dev, "removing interface\n");
+	misc_deregister(&test_dev->misc_dev);
+	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);
+	init_completion(&test_dev->request_complete);
+
+	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 <mcgrof@...nel.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..bfd0436cce47
--- /dev/null
+++ b/tools/testing/selftests/firmware/driver_data.sh
@@ -0,0 +1,1002 @@
+#!/bin/bash
+# Copyright (C) 2016 Luis R. Rodriguez <mcgrof@...nel.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or at your option) any
+# later version; or, when distributed separately from the Linux kernel or
+# incorporated into other software packages, subject to the following license:
+#
+# 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"
+ALL_TESTS="$ALL_TESTS 0011:10:1"
+ALL_TESTS="$ALL_TESTS 0012:1:1"
+ALL_TESTS="$ALL_TESTS 0013:1:1"
+
+# Not yet sure how to automate suspend test well yet.  For now we expect a
+# manual run. If using qemu you can resume a guest using something like the
+# following on the monitor pts.
+# system_wakeupakeup | socat - /dev/pts/7,raw,echo=0,crnl
+#ALL_TESTS="$ALL_TESTS 0014:0: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 $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
+		config_reset
+		DEFAULT_DRIVER_DATA=$(config_get_name)
+	fi
+
+	if [ -z $FW ]; then
+		FW="$FWPATH/$DEFAULT_DRIVER_DATA"
+	fi
+
+	if [ -z $SYS_STATE_PATH ]; then
+		SYS_STATE_PATH="/sys/power/state"
+	fi
+
+	# Set the kernel search path.
+	echo -n "$FWPATH" > $FW_SYSFSPATH
+
+	# This is an unlikely real-world firmware content. :)
+	echo "ABCD0123" >"$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 [ -z $DIR ]; then
+		DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"
+	fi
+
+	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_enable_api_versioning()
+{
+	if ! echo -n 1 >$DIR/config_use_api_versioning; then
+		echo "$0: Unable to set use_api_versioning option" >&2
+		exit 1
+	fi
+}
+
+config_set_api_name_postfix()
+{
+	if ! echo -n $1 >$DIR/config_api_name_postfix; then
+		echo "$0: Unable to set use_api_versioning option" >&2
+		exit 1
+	fi
+}
+
+config_set_api_min()
+{
+	if ! echo -n $1 >$DIR/config_api_min; then
+		echo "$0: Unable to set config_api_min option" >&2
+		exit 1
+	fi
+}
+
+config_set_api_max()
+{
+	if ! echo -n $1 >$DIR/config_api_max; then
+		echo "$0: Unable to set config_api_max option" >&2
+		exit 1
+	fi
+}
+
+config_add_api_file()
+{
+	TMP_FW="$FWPATH/$1"
+	echo "ABCD0123" >"$TMP_FW"
+}
+
+config_rm_api_file()
+{
+	TMP_FW="$FWPATH/$1"
+	rm -f $TMP_FW
+}
+
+# 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
+}
+
+trigger_release_driver_data()
+{
+	if ! echo -n "1" >"$DIR"/trigger_release_driver_data; then
+		echo "$0: release driver data 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)
+	if [ ! -z $2 ]; then
+		FILE=$2
+	fi
+	# 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
+}
+
+set_system_state()
+{
+	STATE="mem"
+	if [ ! -z $2 ]; then
+		STATE=$2
+	fi
+	echo $STATE > $SYS_STATE_PATH
+}
+
+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 $NAME
+	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]} -ENOENT
+}
+
+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
+}
+
+driver_data_test_0011a()
+{
+	driver_data_set_async_defaults
+	config_set_keep
+	config_enable_api_versioning
+
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_file_should_not_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+driver_data_test_0011()
+{
+	driver_data_test_0011a
+}
+
+driver_data_test_0012a()
+{
+	driver_data_set_async_defaults
+	NAME_PREFIX="driver_data_test_0012a_"
+	TARGET_API="4"
+	NAME_POSTFIX=".bin"
+	NAME="${NAME_PREFIX}${TARGET_API}${NAME_POSTFIX}"
+
+	config_set_name $NAME_PREFIX
+	config_set_keep
+	config_enable_api_versioning
+	config_set_api_name_postfix ".bin"
+	config_set_api_min 3
+	config_set_api_max 18
+
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_file_should_not_match ${FUNCNAME[0]} $NAME
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0012()
+{
+	driver_data_test_0012a
+}
+
+driver_data_test_0013a()
+{
+	driver_data_set_async_defaults
+	NAME_PREFIX="driver_data_test_0013a_"
+	TARGET_API="4"
+	NAME_POSTFIX=".bin"
+	NAME="${NAME_PREFIX}${TARGET_API}${NAME_POSTFIX}"
+
+	config_set_name $NAME_PREFIX
+	config_set_keep
+	config_enable_api_versioning
+	config_set_api_name_postfix $NAME_POSTFIX
+	config_set_api_min 3
+	config_set_api_max 18
+	config_add_api_file $NAME
+
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]} $NAME
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+	config_rm_api_file $NAME
+}
+
+driver_data_test_0013()
+{
+	driver_data_test_0013a
+}
+
+driver_data_test_0014a()
+{
+	driver_data_set_async_defaults
+	NAME_PREFIX="driver_data_test_0013a_"
+	TARGET_API="4"
+	NAME_POSTFIX=".bin"
+	NAME="${NAME_PREFIX}${TARGET_API}${NAME_POSTFIX}"
+
+	config_set_name $NAME_PREFIX
+	config_set_keep
+	config_enable_api_versioning
+	config_set_api_name_postfix $NAME_POSTFIX
+	config_set_api_min 3
+	config_set_api_max 18
+	config_add_api_file $NAME
+
+	config_trigger ${FUNCNAME[0]}
+
+	# suspend to memory
+	set_system_state mem
+
+	config_file_should_match ${FUNCNAME[0]} $NAME
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+	config_rm_api_file $NAME
+}
+
+driver_data_test_0014()
+{
+	driver_data_test_0014a
+}
+
+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 api setup will fail on invalid values"
+	echo "0012 x $(get_test_count 0012) - Verify api call wills will hunt for files, ignore file"
+	echo "0013 x $(get_test_count 0013) - Verify api call works"
+	echo "0014 x $(get_test_count 0013) - Verify api call works with suspend + resume"
+}
+
+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 "    -s      Run test ID once"
+	echo "    -c      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
+load_req_mod
+allow_user_defaults
+
+trap "test_finish" EXIT
+
+parse_args $@
+
+exit 0
-- 
2.11.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ