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: <20170411083248.GE15139@linaro.org>
Date:   Tue, 11 Apr 2017 17:32:52 +0900
From:   AKASHI Takahiro <takahiro.akashi@...aro.org>
To:     "Luis R. Rodriguez" <mcgrof@...nel.org>
Cc:     gregkh@...uxfoundation.org, 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,
        dhowells@...hat.com, pjones@...hat.com,
        linux-kernel@...r.kernel.org
Subject: Re: [PATCH v6 3/5] test: add new driver_data load tester

On Wed, Mar 29, 2017 at 08:25:12PM -0700, Luis R. Rodriguez wrote:
> 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>
> ---
>  Documentation/driver-api/firmware/driver_data.rst |   32 +
>  MAINTAINERS                                       |    1 +
>  lib/Kconfig.debug                                 |   12 +
>  lib/Makefile                                      |    1 +
>  lib/test_driver_data.c                            | 1272 +++++++++++++++++++++
>  tools/testing/selftests/firmware/Makefile         |    2 +-
>  tools/testing/selftests/firmware/config           |    1 +
>  tools/testing/selftests/firmware/driver_data.sh   |  996 ++++++++++++++++
>  8 files changed, 2316 insertions(+), 1 deletion(-)
>  create mode 100644 lib/test_driver_data.c
>  create mode 100755 tools/testing/selftests/firmware/driver_data.sh
> 
> diff --git a/Documentation/driver-api/firmware/driver_data.rst b/Documentation/driver-api/firmware/driver_data.rst
> index 08407b7568fe..757c2ffa4ba6 100644
> --- a/Documentation/driver-api/firmware/driver_data.rst
> +++ b/Documentation/driver-api/firmware/driver_data.rst
> @@ -68,6 +68,38 @@ When driver_data_file_request_async() completes you can rest assured all the
>  work for both triggering, and processing the driver data using any of your
>  callbacks has completed.
>  
> +Testing the driver_data API
> +===========================
> +
> +The driver data API has a selftest driver: lib/test_driver_data.c. The
> +test_driver_data enables you to build your tests in userspace by exposing knobs
> +of the exported API in userspace and enabling userspace to configure and
> +trigger a kernel call. This lets us build most possible test cases of
> +the kernel APIs from userspace.
> +
> +The test_driver_data also enables multiple test triggers to be created
> +enabling testing to be done in parallel, one test interface per test case.
> +
> +To test an async call one could do::
> +
> +        echo anything > /lib/firmware/test-driver_data.bin

Your current shell script doesn't search for the firmware in
/lib/firmware unless you explicitly specify $FWPATH.

> +        echo -n 1 >  /sys/devices/virtual/misc/test_driver_data0/config_async
> +        echo -n 1 >  /sys/devices/virtual/misc/test_driver_data0/trigger_config
> +
> +A series of tests have been written to test the driver data API thoroughly.
> +A respective test case is expected to bet written as new features get added.
> +For details of existing tests run::
> +
> +        tools/testing/selftests/firmware/driver_data.sh -l
> +
> +To see all available options::
> +
> +        tools/testing/selftests/firmware/driver_data.sh --help
> +
> +To run a test 0010 case 40 times::
> +
> +        tools/testing/selftests/firmware/driver_data.sh -c 0010 40
> +
>  Tracking development enhancements and ideas
>  ===========================================
>  
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3f025f738600..a0a81c245fb3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5172,6 +5172,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 77fadface4f9..53dfd7db557b 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -1964,6 +1964,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 0f64ef3956bf..d5042ad4dad9 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -50,6 +50,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..11175a3b9f0a
> --- /dev/null
> +++ b/lib/test_driver_data.c
> @@ -0,0 +1,1272 @@
> +/*
> + * 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 copyleft-next (version 0.3.1 or later) as published
> + * at http://copyleft-next.org/.

Is this compatible with GPLv2 for kernel modules?

> + *
> + * 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)
> +{
> +	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)
> +{
> +	struct driver_data_test_device *test_dev = context;
> +	int ret;
> +
> +	ret = config_req_default(test_dev);
> +
> +	return ret;
> +
> +	/* Leave all the error checking for the main caller */
> +}
> +
> +/*
> + * This is the default config->async fallback callback, as a fallback this
> + * then uses a sync request.
> + */
> +static void config_async_req_default_cb(void *context)
> +{
> +	struct driver_data_test_device *test_dev = context;
> +
> +	config_req_default(test_dev);
> +
> +	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)
> +{
> +	struct driver_data_test_device *test_dev = context;
> +
> +	return config_load_data(test_dev, driver_data);
> +}
> +
> +static int trigger_config_sync(struct driver_data_test_device *test_dev)
> +{
> +	struct test_config *config = &test_dev->config;
> +	int ret;
> +	const struct driver_data_req_params req_params_default = {
> +		DRIVER_DATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
> +		.optional = config->optional,
> +		.keep = config->keep,
> +	};
> +	const struct driver_data_req_params req_params_opt_cb = {
> +		DRIVER_DATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
> +		DRIVER_DATA_SYNC_OPT_CB(config_sync_req_default_cb, test_dev),
> +		.optional = config->optional,
> +		.keep = config->keep,
> +	};
> +	const struct driver_data_req_params *req_params;
> +
> +	if (config->enable_opt_cb)
> +		req_params = &req_params_opt_cb;
> +	else
> +		req_params = &req_params_default;
> +
> +	ret = driver_data_request_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)
> +{
> +	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)
> +{
> +	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),
> +		.sync_reqs.mode = config->async ?
> +			DRIVER_DATA_ASYNC : DRIVER_DATA_SYNC,
> +		.optional = config->optional,
> +		.keep = config->keep,
> +	};
> +	const struct driver_data_req_params req_params_opt_cb = {
> +		DRIVER_DATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
> +		DRIVER_DATA_ASYNC_OPT_CB(config_async_req_default_cb, test_dev),
> +		.sync_reqs.mode = config->async ?
> +			DRIVER_DATA_ASYNC : DRIVER_DATA_SYNC,
> +		.optional = config->optional,
> +		.keep = config->keep,
> +	};
> +	const struct driver_data_req_params req_params_api = {
> +		DRIVER_DATA_API_CB(config_async_req_api_cb, test_dev),
> +		.sync_reqs.mode = config->async ?
> +			DRIVER_DATA_ASYNC : DRIVER_DATA_SYNC,
> +		.optional = config->optional,
> +		.keep = config->keep,
> +		DRIVER_DATA_API(config->api_min, config->api_max, config->api_name_postfix),
> +		.uses_api_versioning = config->use_api_versioning,
> +	};
> +	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;
> +	vfree(test_dev);
> +	test_dev = NULL;
> +	driver_data_config_free(test_dev);
> +}
> +
> +void unregister_test_dev_driver_data(struct driver_data_test_device *test_dev)
> +{
> +	wait_for_completion_timeout(&test_dev->request_complete, 5 * HZ);
> +	dev_info(test_dev->dev, "removing interface\n");
> +	misc_deregister(&test_dev->misc_dev);
> +	kfree(&test_dev->misc_dev.name);
> +	free_test_dev_driver_data(test_dev);
> +}
> +
> +struct driver_data_test_device *alloc_test_dev_driver_data(int idx)
> +{
> +	int ret;
> +	struct driver_data_test_device *test_dev;
> +	struct miscdevice *misc_dev;
> +
> +	test_dev = vzalloc(sizeof(struct driver_data_test_device));
> +	if (!test_dev)
> +		goto err_out;
> +
> +	mutex_init(&test_dev->driver_data_mutex);
> +	mutex_init(&test_dev->config_mutex);
> +	mutex_init(&test_dev->trigger_mutex);
> +	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..085fbaec6b3e
> --- /dev/null
> +++ b/tools/testing/selftests/firmware/driver_data.sh
> @@ -0,0 +1,996 @@
> +#!/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="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"

Do you have good reasons for "the number of times" here?
> +
> +# 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"

Do you always want to overwrite the firmware even if user explicitly
provides it?

> +}
> +
> +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]} SUCCESS
> +}
> +
> +driver_data_test_0005a()
> +{
> +	NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> +	driver_data_set_async_defaults
> +	config_set_optional
> +	config_set_name $NAME
> +	config_trigger_want_fail ${FUNCNAME[0]}
> +	# We do this to ensure the default backup callback hasn't
> +	# been called yet
> +	config_file_should_not_match ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0005()
> +{
> +	driver_data_test_0005s
> +	driver_data_test_0005a
> +}
> +
> +driver_data_test_0006s()
> +{
> +	driver_data_set_sync_defaults
> +	config_set_optional
> +	config_trigger ${FUNCNAME[0]}
> +	config_file_should_match ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0006a()
> +{
> +	driver_data_set_async_defaults
> +	config_set_optional
> +	config_trigger ${FUNCNAME[0]}
> +	config_file_should_match ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0006()
> +{
> +	driver_data_test_0006s
> +	driver_data_test_0006a
> +}
> +
> +driver_data_test_0007s()
> +{
> +	driver_data_set_sync_defaults
> +	config_set_keep
> +	config_trigger ${FUNCNAME[0]}
> +	config_file_should_match ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0007a()
> +{
> +	driver_data_set_async_defaults
> +	config_set_keep
> +	config_trigger ${FUNCNAME[0]}
> +	config_file_should_match ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0007()
> +{
> +	driver_data_test_0007s
> +	driver_data_test_0007a
> +}
> +
> +driver_data_test_0008s()
> +{
> +	NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> +	driver_data_set_sync_defaults
> +	config_set_name $NAME
> +	config_set_optional
> +	config_enable_opt_cb
> +	config_trigger ${FUNCNAME[0]}
> +	config_file_should_match_default ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0008a()
> +{
> +	NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> +	driver_data_set_async_defaults
> +	config_set_name $NAME
> +	config_set_optional
> +	config_enable_opt_cb
> +	config_trigger ${FUNCNAME[0]}
> +	config_file_should_match_default ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0008()
> +{
> +	driver_data_test_0008s
> +	driver_data_test_0008a
> +}
> +
> +driver_data_test_0009s()
> +{
> +	NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> +	driver_data_set_sync_defaults
> +	config_set_name $NAME
> +	config_set_keep
> +	config_set_optional
> +	config_enable_opt_cb
> +	config_trigger ${FUNCNAME[0]}
> +	config_file_should_match_default ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0009a()
> +{
> +	NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> +	driver_data_set_async_defaults
> +	config_set_name $NAME
> +	config_set_keep
> +	config_set_optional
> +	config_enable_opt_cb
> +	config_trigger ${FUNCNAME[0]}
> +	config_file_should_match_default ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0009()
> +{
> +	driver_data_test_0009s
> +	driver_data_test_0009a
> +}
> +
> +driver_data_test_0010s()
> +{
> +	NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> +	driver_data_set_sync_defaults
> +	config_set_name $NAME
> +	config_set_default_name $NAME
> +	config_set_keep
> +	config_set_optional
> +	config_enable_opt_cb
> +	config_trigger_want_fail ${FUNCNAME[0]}
> +	config_file_should_not_match ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} -ENOENT
> +}
> +
> +driver_data_test_0010a()
> +{
> +	NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> +	driver_data_set_async_defaults
> +	config_set_name $NAME
> +	config_set_default_name $NAME
> +	config_set_keep
> +	config_set_optional
> +	config_enable_opt_cb
> +	config_trigger_want_fail ${FUNCNAME[0]}
> +	config_file_should_not_match ${FUNCNAME[0]}
> +	config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0010()
> +{
> +	driver_data_test_0010s
> +	driver_data_test_0010a
> +}
> +
> +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]} SUCCESS
> +}
> +
> +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 "    -c      Run test ID once"

               -> -s

> +	echo "    -s      Run test ID x test-count number of times"

               -> -c

If you make the second parameter optional, you don't need
-t nor -s:
        driver_data.sh -c 0004     ; recommended times
        driver_data.sh -c 0004 1   ; only once
        driver_data.sh -c 0004 100 ; as many times as you want

Thanks,
-Takahiro AKASHI

> +	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