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 for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20170112150244.12700-3-mcgrof@kernel.org>
Date:   Thu, 12 Jan 2017 07:02:43 -0800
From:   "Luis R. Rodriguez" <mcgrof@...nel.org>
To:     gregkh@...uxfoundation.org, ming.lei@...onical.com
Cc:     bp@...en8.de, wagi@...om.org, teg@...m.no, mchehab@....samsung.com,
        zajec5@...il.com, linux-kernel@...r.kernel.org,
        markivx@...eaurora.org, stephen.boyd@...aro.org,
        broonie@...nel.org, zohar@...ux.vnet.ibm.com, tiwai@...e.de,
        johannes@...solutions.net, chunkeey@...glemail.com,
        hauke@...ke-m.de, jwboyer@...oraproject.org,
        dmitry.torokhov@...il.com, dwmw2@...radead.org, jslaby@...e.com,
        torvalds@...ux-foundation.org, luto@...capital.net,
        fengguang.wu@...el.com, rpurdie@...ys.net,
        j.anaszewski@...sung.com, Abhay_Salunke@...l.com,
        Julia.Lawall@...6.fr, Gilles.Muller@...6.fr, nicolas.palix@...g.fr,
        dhowells@...hat.com, bjorn.andersson@...aro.org,
        arend.vanspriel@...adcom.com, kvalo@...eaurora.org,
        "Luis R. Rodriguez" <mcgrof@...nel.org>
Subject: [PATCH v4 2/3] test: add new drvdata loader tester

This adds a load tester driver test_drvdata a for the new
extensible drvdata loader APIs, part of firmware_class.
Since the fallback mechanisms are currenlty completely
ignored by the drvdata API the testing is much easier to do.

Contrary to the firmware_class tester which adds in-kernel
code for each and every single test it can think of for each
type of request, this enables you to build your tests in userspace
by exposing knobs of the exported API to userspace of the
options available in the API and then lets the trigger kick a one
time kernel API use. This lets us build any possible test case
in userspace.

The test driver also enables multiple test triggers
to be created enabling further testing to be done through
separate threads in parallel.

Both these facts should should not only help testing the
drvdata API in as many ways as possible as efficiently
as possible, but it also paves the way to later strive to
see how it might be even possible to automatically generate
test API drivers for exported symbols in the future. The
exported symbols being the test cases and attributes exposed
in userspace consisting of device attributes, the target test
driver being the desired output driver.

v5:
o 0-day build complaint lock fix on lib/test_drvdata.c:9991
o expand testdrv.sh to support a series of different options
  to help with testing
o use snprintf() using PAGE_SIZE
o add a common helper config_test_show_str()
o drop test_dev_get_name() in favor for kasprintf()
o use vzalloc() instead of vmalloc()
o bike shedding sysdata/drvdata

Signed-off-by: Luis R. Rodriguez <mcgrof@...nel.org>
---
 lib/Kconfig.debug                           |   12 +
 lib/Makefile                                |    1 +
 lib/test_drvdata.c                          | 1033 +++++++++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile   |    2 +-
 tools/testing/selftests/firmware/config     |    1 +
 tools/testing/selftests/firmware/drvdata.sh |  827 +++++++++++++++++++++
 6 files changed, 1875 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_drvdata.c
 create mode 100755 tools/testing/selftests/firmware/drvdata.sh

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index b06848a104e6..88d807eb1efe 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1930,6 +1930,18 @@ config TEST_FIRMWARE
 
 	  If unsure, say N.
 
+config TEST_DRVDATA
+	tristate "Test driver data loading via drvdata APIs"
+	default n
+	depends on FW_LOADER
+	help
+	  This builds the "test_drvdata" module that creates a userspace
+	  interface for testing driver data loading using the drvdata 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 bc4073a8cd08..d456f59e6ae0 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -45,6 +45,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
+obj-$(CONFIG_TEST_DRVDATA) += test_drvdata.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_drvdata.c b/lib/test_drvdata.c
new file mode 100644
index 000000000000..a924ed7d85b3
--- /dev/null
+++ b/lib/test_drvdata.c
@@ -0,0 +1,1033 @@
+/*
+ * Driver data test interface
+ *
+ * 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 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/drvdata.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_DRVDATA "test-drvdata.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 drvdata API
+ *
+ * @name: the name of the primary drvdata 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
+ * 	drvdata_request_async(). If false the synchronous call will
+ * 	be used, drvdata_request().
+ * @optional: whether or not the drvdata is optional refer to the
+ *	struct drvdata_reg_params @optional field for more information.
+ * @keep: whether or not we wish to free the drvdata on our own, refer to
+ *	the struct drvdata_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
+ *	drvdata_req_params as this is implementation specific, and in
+ *	in drvdata API its explicit if you had defined an optional call
+ *	back for your descriptor with either DRVDATA_SYNC_OPT_CB() or
+ *	DRVDATA_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 drvdata_request_async() or drvdata_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_drvdata_private - private device driver drvdata representation
+ *
+ * @size: size of the data copied, in bytes
+ * @data: the actual data we copied over from drvdata
+ * @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_drvdata_private {
+	size_t size;
+	u8 *data;
+	bool written;
+};
+
+/**
+ * drvdata_test_device - test device to help test drvdata
+ *
+ * @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
+ *	drvdata API and we to let you tune them in userspace. We then just
+ *	provide one trigger.
+ * @test_drvdata: internal private representation of a storage area
+ *	a driver might typically use to stuff firmware / drvdata.
+ * @misc_dev: we use a misc device under the hood
+ * @dev: pointer to misc_dev's own struct device
+ * @drvdata_mutex: for access into the @drvdata, 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 drvdata_test_device {
+	int dev_idx;
+	struct test_config config;
+	struct test_drvdata_private test_drvdata;
+	struct miscdevice misc_dev;
+	struct device *dev;
+
+	struct mutex drvdata_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 drvdata_test_device *misc_dev_to_test_dev(struct miscdevice *misc_dev)
+{
+	return container_of(misc_dev, struct drvdata_test_device, misc_dev);
+}
+
+static struct drvdata_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 drvdata_test_device *test_dev = misc_dev_to_test_dev(misc_dev);
+	struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
+	ssize_t ret = 0;
+
+	mutex_lock(&test_dev->drvdata_mutex);
+	if (test_drvdata->written)
+		ret = simple_read_from_buffer(buf, size, offset,
+					      test_drvdata->data,
+					      test_drvdata->size);
+	mutex_unlock(&test_dev->drvdata_mutex);
+
+	return ret;
+}
+
+static const struct file_operations test_fw_fops = {
+	.owner          = THIS_MODULE,
+	.read           = test_fw_misc_read,
+};
+
+static void free_test_drvdata(struct test_drvdata_private *test_drvdata)
+{
+	kfree(test_drvdata->data);
+	test_drvdata->data = NULL;
+	test_drvdata->size = 0;
+	test_drvdata->written = false;
+}
+
+static int test_load_drvdata(struct drvdata_test_device *test_dev,
+			     const struct drvdata *drvdata)
+{
+	struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
+	int ret = 0;
+
+	if (!drvdata)
+		return -ENOENT;
+
+	mutex_lock(&test_dev->drvdata_mutex);
+
+	free_test_drvdata(test_drvdata);
+
+	test_drvdata->data = kzalloc(drvdata->size, GFP_KERNEL);
+	if (!test_drvdata->data) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	memcpy(test_drvdata->data, drvdata->data, drvdata->size);
+	test_drvdata->size = drvdata->size;
+	test_drvdata->written = true;
+
+	dev_info(test_dev->dev, "loaded: %zu\n", test_drvdata->size);
+
+out:
+	mutex_unlock(&test_dev->drvdata_mutex);
+
+	return ret;
+}
+
+static int sync_found_cb(void *context, const struct drvdata *drvdata)
+{
+	struct drvdata_test_device *test_dev = context;
+	int ret;
+
+	ret = test_load_drvdata(test_dev, drvdata);
+	if (ret)
+		dev_info(test_dev->dev, "unable to write drvdata: %d\n", ret);
+	return ret;
+}
+
+static ssize_t config_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	struct drvdata_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 drvdata_test_device *test_dev,
+			    const struct drvdata *drvdata)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	ret = test_load_drvdata(test_dev, drvdata);
+	if (ret) {
+		if (!config->optional)
+			dev_info(test_dev->dev, "unable to write drvdata\n");
+	}
+	if (config->keep) {
+		release_drvdata(drvdata);
+		drvdata = NULL;
+	}
+	return ret;
+}
+
+static int config_req_default(struct drvdata_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 drvdata_req_params req_params = {
+		DRVDATA_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 = drvdata_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 drvdata_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 drvdata_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 drvdata *drvdata)
+{
+	struct drvdata_test_device *test_dev = context;
+
+	return config_load_data(test_dev, drvdata);
+}
+
+static int trigger_config_sync(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+	const struct drvdata_req_params req_params_default = {
+		DRVDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params req_params_opt_cb = {
+		DRVDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+		DRVDATA_SYNC_OPT_CB(config_sync_req_default_cb, test_dev),
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params *req_params;
+
+	if (config->enable_opt_cb)
+		req_params = &req_params_opt_cb;
+	else
+		req_params= &req_params_default;
+
+	ret = drvdata_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 drvdata *drvdata,
+				void *context)
+{
+	struct drvdata_test_device *test_dev = context;
+	config_load_data(test_dev, drvdata);
+}
+
+static int trigger_config_async(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+	const struct drvdata_req_params req_params_default = {
+		DRVDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+		.sync_reqs.mode = config->async ?
+			DRVDATA_ASYNC : DRVDATA_SYNC,
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params req_params_opt_cb = {
+		DRVDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+		DRVDATA_ASYNC_OPT_CB(config_async_req_default_cb, test_dev),
+		.sync_reqs.mode = config->async ?
+			DRVDATA_ASYNC : DRVDATA_SYNC,
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_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 = drvdata_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;
+	}
+
+	drvdata_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 drvdata_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
+	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_drvdata->written) {
+		dev_info(dev, "loaded: %zu\n", test_drvdata->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 int config_copy_name(struct test_config *config,
+			    const char *name,
+			    size_t count)
+{
+	return __kstrncpy(&config->name, name, count, GFP_KERNEL);
+}
+
+static int config_copy_default_name(struct test_config *config,
+				    const char *name,
+				    size_t count)
+{
+	return __kstrncpy(&config->default_name, name, count, GFP_KERNEL);
+}
+
+static void __drvdata_config_free(struct test_config *config)
+{
+	kfree_const(config->name);
+	config->name = NULL;
+	kfree_const(config->default_name);
+	config->default_name = NULL;
+}
+
+static void drvdata_config_free(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+
+	mutex_lock(&test_dev->config_mutex);
+	__drvdata_config_free(config);
+	mutex_unlock(&test_dev->config_mutex);
+}
+
+static int __drvdata_config_init(struct test_config *config)
+{
+	int ret;
+
+	ret = config_copy_name(config, TEST_DRVDATA, strlen(TEST_DRVDATA));
+	if (ret < 0)
+		goto out;
+
+	ret = config_copy_default_name(config, TEST_DRVDATA,
+				       strlen(TEST_DRVDATA));
+	if (ret < 0)
+		goto out;
+
+	config->async = false;
+	config->optional = false;
+	config->keep = false;
+	config->enable_opt_cb = false;
+	config->test_result = 0;
+
+out:
+	return ret;
+}
+
+int drvdata_config_init(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	ret = __drvdata_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 drvdata_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	ret = config_copy_name(config, buf, count);
+	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 drvdata_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 drvdata_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	ret = config_copy_default_name(config, buf, count);
+	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 drvdata_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 drvdata_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->drvdata_mutex);
+	free_test_drvdata(&test_dev->test_drvdata);
+	mutex_unlock(&test_dev->drvdata_mutex);
+
+	mutex_lock(&test_dev->config_mutex);
+
+	__drvdata_config_free(config);
+
+	ret = __drvdata_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 drvdata_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 drvdata_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 drvdata_test_device *test_dev,
+				      const char *buf, size_t size,
+				      int *config)
+{
+	char *end;
+	long new = simple_strtol(buf, &end, 0);
+	if (end == buf || 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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 DRVDATA_DEV_ATTR(name)		&dev_attr_##name.attr
+
+static struct attribute *test_dev_attrs[] = {
+	DRVDATA_DEV_ATTR(trigger_config),
+	DRVDATA_DEV_ATTR(config),
+	DRVDATA_DEV_ATTR(reset),
+
+	DRVDATA_DEV_ATTR(config_name),
+	DRVDATA_DEV_ATTR(config_default_name),
+	DRVDATA_DEV_ATTR(config_async),
+	DRVDATA_DEV_ATTR(config_optional),
+	DRVDATA_DEV_ATTR(config_keep),
+	DRVDATA_DEV_ATTR(config_enable_opt_cb),
+	DRVDATA_DEV_ATTR(test_result),
+
+	NULL,
+};
+
+ATTRIBUTE_GROUPS(test_dev);
+
+void free_test_dev_drvdata(struct drvdata_test_device *test_dev)
+{
+	kfree_const(test_dev->misc_dev.name);
+	test_dev->misc_dev.name = NULL;
+	vfree(test_dev);
+	test_dev = NULL;
+	drvdata_config_free(test_dev);
+}
+
+void unregister_test_dev_drvdata(struct drvdata_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_drvdata(test_dev);
+}
+
+struct drvdata_test_device *alloc_test_dev_drvdata(int idx)
+{
+	int ret;
+	struct drvdata_test_device *test_dev;
+	struct miscdevice *misc_dev;
+
+	test_dev = vzalloc(sizeof(struct drvdata_test_device));
+	if (!test_dev) {
+		pr_err("Cannot alloc test_dev\n");
+		goto err_out;
+	}
+
+	mutex_init(&test_dev->drvdata_mutex);
+	mutex_init(&test_dev->config_mutex);
+	mutex_init(&test_dev->trigger_mutex);
+
+	ret = drvdata_config_init(test_dev);
+	if (ret < 0) {
+		pr_err("Cannot alloc drvdata_config_init()\n");
+		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_drvdata%d", idx);
+	if (!misc_dev->name) {
+		pr_err("Cannot alloc misc_dev->name\n");
+		goto err_out_free_config;
+	}
+	misc_dev->fops = &test_fw_fops;
+	misc_dev->groups = test_dev_groups;
+
+	return test_dev;
+
+err_out_free_config:
+	__drvdata_config_free(&test_dev->config);
+err_out_free:
+	kfree(test_dev);
+err_out:
+	return NULL;
+}
+
+static int register_test_dev_drvdata(void)
+{
+	struct drvdata_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_drvdata(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_drvdata(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_drvdata_init(void)
+{
+	int ret;
+
+	ret = register_test_dev_drvdata();
+	if (ret)
+		pr_err("Cannot add first test drvdata device\n");
+
+	return ret;
+}
+late_initcall(test_drvdata_init);
+
+static void __exit test_drvdata_exit(void)
+{
+	struct drvdata_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_drvdata(test_dev);
+	}
+	mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_drvdata_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 9bf82234855b..7d4f6808bf1c 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_userhelper.sh
+TEST_PROGS := fw_filesystem.sh fw_userhelper.sh drvdata.sh
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config
index c8137f70e291..0a07fccd8850 100644
--- a/tools/testing/selftests/firmware/config
+++ b/tools/testing/selftests/firmware/config
@@ -1 +1,2 @@
 CONFIG_TEST_FIRMWARE=y
+CONFIG_TEST_DRVDATA=y
diff --git a/tools/testing/selftests/firmware/drvdata.sh b/tools/testing/selftests/firmware/drvdata.sh
new file mode 100755
index 000000000000..aae23e61e3a4
--- /dev/null
+++ b/tools/testing/selftests/firmware/drvdata.sh
@@ -0,0 +1,827 @@
+#!/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 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="drvdata"
+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_DRVDATA ]; then
+		DEFAULT_DRVDATA="test-drvdata.bin"
+	fi
+
+	if [ -z $FW ]; then
+		FW="$FWPATH/$DEFAULT_DRVDATA"
+	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 drvdata_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 drvdata_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 drvdata"
+}
+
+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_drvdata0 > /dev/null) > /dev/null; then
+		echo "$1: FAIL - file $FILE did not match contents in /dev/test_drvdata0" >&2
+		config_show_config >&2
+		exit 1
+	fi
+	echo "$1: OK! - $FILE == /dev/test_drvdata0"
+}
+
+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_drvdata0 > /dev/null) > /dev/null; then
+		echo "$1: FAIL - file $FILE did not match contents in /dev/test_drvdata0" >&2
+		config_show_config >&2
+		exit 1
+	fi
+	echo "$1: OK! - $FILE == /dev/test_drvdata0"
+}
+
+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_drvdata0 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_drvdata0"
+}
+
+config_default_file_should_match()
+{
+	FILE=$(config_get_default_name)
+	diff -q $FWPATH/$FILE /dev/test_drvdata0 2> /dev/null
+	if ! $? ; then
+		echo "$1: FAIL - file $FILE expected to match /dev/test_drvdata0" >&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_drvdata0 2> /dev/null
+	if $? 2> /dev/null ; then
+		echo "$1: FAIL - file $FILE was not expected to match test_drvdata0" >&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"
+}
+
+drvdata_set_sync_defaults()
+{
+	config_reset
+}
+
+drvdata_set_async_defaults()
+{
+	config_reset
+	config_set_async
+}
+
+drvdata_test_0001s()
+{
+	NAME='\000'
+
+	drvdata_set_sync_defaults
+	config_set_name $NAME
+	printf '\000' >"$DIR"/config_name
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+drvdata_test_0001a()
+{
+	NAME='\000'
+
+	drvdata_set_async_defaults
+	printf '\000' >"$DIR"/config_name
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+drvdata_test_0001()
+{
+	drvdata_test_0001s
+	drvdata_test_0001a
+}
+
+drvdata_test_0002s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_set_sync_defaults
+	config_set_name ${FUNCNAME[0]}
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+drvdata_test_0002a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0002()
+{
+	#drvdata_test_0002s
+	drvdata_test_0002a
+}
+
+drvdata_test_0003()
+{
+	config_reset
+	config_file_should_not_match ${FUNCNAME[0]}
+}
+
+drvdata_test_0004s()
+{
+	TEST="drvdata_test_0004s"
+
+	drvdata_set_sync_defaults
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0004a()
+{
+	drvdata_set_async_defaults
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0004()
+{
+	drvdata_test_0004s
+	drvdata_test_0004a
+}
+
+drvdata_test_0005s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0005a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0005()
+{
+	drvdata_test_0005s
+	drvdata_test_0005a
+}
+
+drvdata_test_0006s()
+{
+	drvdata_set_sync_defaults
+	config_set_optional
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0006a()
+{
+	drvdata_set_async_defaults
+	config_set_optional
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0006()
+{
+	drvdata_test_0006s
+	drvdata_test_0006a
+}
+
+drvdata_test_0007s()
+{
+	drvdata_set_sync_defaults
+	config_set_keep
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0007a()
+{
+	drvdata_set_async_defaults
+	config_set_keep
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0007()
+{
+	drvdata_test_0007s
+	drvdata_test_0007a
+}
+
+drvdata_test_0008s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0008a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0008()
+{
+	drvdata_test_0008s
+	drvdata_test_0008a
+}
+
+drvdata_test_0009s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0009a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0009()
+{
+	drvdata_test_0009s
+	drvdata_test_0009a
+}
+
+drvdata_test_0010s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0010a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0010()
+{
+	drvdata_test_0010s
+	drvdata_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_drvdata0 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"
+}
+
+test_reqs
+
+usage()
+{
+	NUM_TESTS=$(grep -o ':' <<<"$ALL_TESTS" | grep -c .)
+	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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ