[PATCH v8] test: add new driver_data load tester

Luis R. Rodriguez
2017-05-15T18:23:57Z

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@kernel.org>
---

Greg,

This v8 fixes a test driver unload issue, and a configuration typo spotted by
AKASHI. Its the only issue discovered during review on the v7 series of the
driver data API, and the other series it depends on, other than the
clarification on license, which I've asked Linus about on the old copyleft-next
thread, just to be sure.

FWIW I have rebased my 2 pending firmware series on top of linux-next tag,
next-20170515 (based on v4.12-rc1) and I did not find any conflicts when doing
so, so I don't expect the merge window to have created any issues on my two
pending firmware series. In any case my 2 pending firmware series are available
on my linux-next tree branch 20170515-driver-data [0].

[0] https://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux-next.git/log/?h=20170515-driver-data

MAINTAINERS | 1 +
lib/Kconfig.debug | 12 +
lib/Makefile | 1 +
lib/test_driver_data.c | 1270 +++++++++++++++++++++++
tools/testing/selftests/firmware/Makefile | 2 +-
tools/testing/selftests/firmware/config | 1 +
tools/testing/selftests/firmware/driver_data.sh | 996 ++++++++++++++++++
7 files changed, 2282 insertions(+), 1 deletion(-)
create mode 100644 lib/test_driver_data.c
create mode 100755 tools/testing/selftests/firmware/driver_data.sh

diff --git a/MAINTAINERS b/MAINTAINERS
index 214585cddab0..1231267ad830 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5226,6 +5226,7 @@ L: linux-kernel@vger.kernel.org
S: Maintained
F: Documentation/firmware_class/
F: drivers/base/firmware*.c
+F: lib/test_driver_data.c
F: include/linux/firmware.h
F: include/linux/driver_data.h

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 122825555822..6704541f4bd0 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 0166fbc0fa81..4be5e119a98c 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..a3dc67b6cf34
--- /dev/null
+++ b/lib/test_driver_data.c
@@ -0,0 +1,1270 @@
+/*
+ * Driver data test interface
+ *
+ * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of copyleft-next (version 0.3.1 or later) as published
+ * at http://copyleft-next.org/.
+ *
+ * This module provides an interface to trigger and test the driver data API
+ * through a series of configurations and a few triggers. This driver
+ * lacks any extra dependencies, and will not normally be loaded by the
+ * system unless explicitly requested by name. You can also build this
+ * driver into your kernel.
+ *
+ * Although all configurations are already written for and will be supported
+ * for this test driver, ideally we should strive to see what mechanisms we
+ * can put in place to instead automatically generate this sort of test
+ * interface, test cases, and infer results. Its a simple enough interface that
+ * should hopefully enable more exploring in this area.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/completion.h>
+#include <linux/driver_data.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/async.h>
+#include <linux/delay.h>
+#include <linux/vmalloc.h>
+
+/* Used for the fallback default to test against */
+#define TEST_DRIVER_DATA "test-driver_data.bin"
+
+/*
+ * For device allocation / registration
+ */
+static DEFINE_MUTEX(reg_dev_mutex);
+static LIST_HEAD(reg_test_devs);
+
+/*
+ * num_test_devs actually represents the *next* ID of the next
+ * device we will allow to create.
+ */
+int num_test_devs;
+
+/**
+ * test_config - represents configuration for the driver_data API
+ *
+ * @name: the name of the primary driver_data file to look for
+ * @default_name: a fallback example, used to test the optional callback
+ * mechanism.
+ * @async: true if you want to trigger an async request. This will use
+ * driver_data_request_async(). If false the synchronous call will
+ * be used, driver_data_request_sync().
+ * @optional: whether or not the driver_data is optional refer to the
+ * struct driver_data_reg_params @optional field for more information.
+ * @keep: whether or not we wish to free the driver_data on our own, refer to
+ * the struct driver_data_req_params @keep field for more information.
+ * @enable_opt_cb: whether or not the optional callback should be set
+ * on a trigger. There is no equivalent setting on the struct
+ * driver_data_req_params as this is implementation specific, and in
+ * in driver_data API its explicit if you had defined an optional call
+ * back for your descriptor with either DRIVER_DATA_SYNC_OPT_CB() or
+ * DRIVER_DATA_ASYNC_OPT_CB(). Since the params are in a const we have
+ * no option but to use a flag and two const structs to decide which
+ * one we should use.
+ * @use_api_versioning: use the driver data API versioning support. This
+ * currenlty implies you are using an async test.
+ * @api_min: API min version to use for the test.
+ * @api_max: API max version to use for the test.
+ * @api_name_postfix: API name postfix
+ * @test_result: a test may use this to collect the result from the call
+ * of the driver_data_request_async() or driver_data_request_sync() calls
+ * used in their tests. Note that for async calls this typically will be a
+ * successful result (0) unless of course you've used bogus parameters, or
+ * the system is out of memory. Tests against the callbacks can only be
+ * implementation specific, so we don't test for that for now but it may
+ * make sense to build tests cases against a series of semantically
+ * similar family of callbacks that generally represents usage in the
+ * kernel. Synchronous calls return bogus error checks against the
+ * parameters as well, but also return the result of the work from the
+ * callbacks. You can therefore rely on sync calls if you really want to
+ * test for the callback results as well. Errors you can expect:
+ *
+ * API specific:
+ *
+ * 0: success for sync, for async it means request was sent
+ * -EINVAL: invalid parameters or request
+ * -ENOENT: files not found
+ *
+ * System environment:
+ *
+ * -ENOMEM: memory pressure on system
+ * -ENODEV: out of number of devices to test
+ *
+ * The ordering of elements in this struct must match the exact order of the
+ * elements in the ATTRIBUTE_GROUPS(test_dev_config), this is done to know
+ * what corresponding field each device attribute configuration entry maps
+ * to what struct member on test_alloc_dev_attrs().
+ */
+struct test_config {
+ char *name;
+ char *default_name;
+ bool async;
+ bool optional;
+ bool keep;
+ bool enable_opt_cb;
+ bool use_api_versioning;
+ u8 api_min;
+ u8 api_max;
+ char *api_name_postfix;
+
+ int test_result;
+};
+
+/**
+ * test_driver_data_private - private device driver driver_data representation
+ *
+ * @size: size of the data copied, in bytes
+ * @data: the actual data we copied over from driver_data
+ * @written: true if a callback managed to copy data over to the device
+ * successfully. Since different callbacks are used for this purpose
+ * having the data written does not necessarily mean a test case
+ * completed successfully. Each tests case has its own specific
+ * goals.
+ *
+ * Private representation of buffer where we put the device system data.
+ */
+struct test_driver_data_private {
+ size_t size;
+ u8 *data;
+ u8 api;
+ bool written;
+};
+
+/**
+ * driver_data_test_device - test device to help test driver_data
+ *
+ * @dev_idx: unique ID for test device
+ * @config: this keeps the device's own configuration. Instead of creating
+ * different triggers for all possible test cases we can think of in
+ * kernel, we expose a set possible device attributes for tuning the
+ * driver_data API and we to let you tune them in userspace. We then just
+ * provide one trigger.
+ * @test_driver_data: internal private representation of a storage area
+ * a driver might typically use to stuff firmware / driver_data.
+ * @misc_dev: we use a misc device under the hood
+ * @dev: pointer to misc_dev's own struct device
+ * @api_found_calls: number of calls a fetch for a driver was found. We use
+ * for internal use on the api callback.
+ * @driver_data_mutex: for access into the @driver_data, the fake storage
+ * location for the system data we copy.
+ * @config_mutex: used to protect configuration changes
+ * @trigger_mutex: all triggers are mutually exclusive when testing. To help
+ * enable testing you can create a different device, each device has its
+ * own set of protections, mimicking real devices.
+ * @request_complete: used to help the driver inform itself when async
+ * callbacks complete.
+ * list: needed to be part of the reg_test_devs
+ */
+struct driver_data_test_device {
+ int dev_idx;
+ struct test_config config;
+ struct test_driver_data_private test_driver_data;
+ struct miscdevice misc_dev;
+ struct device *dev;
+
+ u8 api_found_calls;
+
+ struct mutex driver_data_mutex;
+ struct mutex config_mutex;
+ struct mutex trigger_mutex;
+ struct completion request_complete;
+ struct list_head list;
+};
+
+static struct miscdevice *dev_to_misc_dev(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static struct driver_data_test_device *
+misc_dev_to_test_dev(struct miscdevice *misc_dev)
+{
+ return container_of(misc_dev, struct driver_data_test_device, misc_dev);
+}
+
+static struct driver_data_test_device *dev_to_test_dev(struct device *dev)
+{
+ struct miscdevice *misc_dev;
+
+ misc_dev = dev_to_misc_dev(dev);
+
+ return misc_dev_to_test_dev(misc_dev);
+}
+
+static ssize_t test_fw_misc_read(struct file *f, char __user *buf,
+ size_t size, loff_t *offset)
+{
+ struct miscdevice *misc_dev = f->private_data;
+ struct driver_data_test_device *test_dev =
+ misc_dev_to_test_dev(misc_dev);
+ struct test_driver_data_private *test_driver_data =
+ &test_dev->test_driver_data;
+ ssize_t ret = 0;
+
+ mutex_lock(&test_dev->driver_data_mutex);
+ if (test_driver_data->written)
+ ret = simple_read_from_buffer(buf, size, offset,
+ test_driver_data->data,
+ test_driver_data->size);
+ mutex_unlock(&test_dev->driver_data_mutex);
+
+ return ret;
+}
+
+static const struct file_operations test_fw_fops = {
+ .owner = THIS_MODULE,
+ .read = test_fw_misc_read,
+};
+
+static
+void free_test_driver_data(struct test_driver_data_private *test_driver_data)
+{
+ kfree(test_driver_data->data);
+ test_driver_data->data = NULL;
+ test_driver_data->size = 0;
+ test_driver_data->api = 0;
+ test_driver_data->written = false;
+}
+
+static int test_load_driver_data(struct driver_data_test_device *test_dev,
+ const struct firmware *driver_data)
+{
+ struct test_driver_data_private *test_driver_data =
+ &test_dev->test_driver_data;
+ int ret = 0;
+
+ if (!driver_data)
+ return -ENOENT;
+
+ mutex_lock(&test_dev->driver_data_mutex);
+
+ free_test_driver_data(test_driver_data);
+
+ test_driver_data->data = kzalloc(driver_data->size, GFP_KERNEL);
+ if (!test_driver_data->data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(test_driver_data->data, driver_data->data, driver_data->size);
+ test_driver_data->size = driver_data->size;
+ test_driver_data->written = true;
+ test_driver_data->api = driver_data->api;
+
+ dev_info(test_dev->dev, "loaded: %zu\n", test_driver_data->size);
+
+out:
+ mutex_unlock(&test_dev->driver_data_mutex);
+
+ return ret;
+}
+
+static int sync_found_cb(void *context, const struct firmware *driver_data,
+ int unused_error)
+{
+ struct driver_data_test_device *test_dev = context;
+ int ret;
+
+ ret = test_load_driver_data(test_dev, driver_data);
+ if (ret)
+ dev_info(test_dev->dev,
+ "unable to write driver_data: %d\n", ret);
+ return ret;
+}
+
+static ssize_t config_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+ int len = 0;
+
+ mutex_lock(&test_dev->config_mutex);
+
+ len += snprintf(buf, PAGE_SIZE,
+ "Custom trigger configuration for: %s\n",
+ dev_name(dev));
+
+ if (config->default_name)
+ len += snprintf(buf+len, PAGE_SIZE,
+ "default name:\t%s\n",
+ config->default_name);
+ else
+ len += snprintf(buf+len, PAGE_SIZE,
+ "default name:\tEMTPY\n");
+
+ if (config->name)
+ len += snprintf(buf+len, PAGE_SIZE,
+ "name:\t\t%s\n", config->name);
+ else
+ len += snprintf(buf+len, PAGE_SIZE,
+ "name:\t\tEMPTY\n");
+
+ len += snprintf(buf+len, PAGE_SIZE,
+ "type:\t\t%s\n",
+ config->async ? "async" : "sync");
+ len += snprintf(buf+len, PAGE_SIZE,
+ "optional:\t%s\n",
+ config->optional ? "true" : "false");
+ len += snprintf(buf+len, PAGE_SIZE,
+ "enable_opt_cb:\t%s\n",
+ config->enable_opt_cb ? "true" : "false");
+ len += snprintf(buf+len, PAGE_SIZE,
+ "use_api_versioning:\t%s\n",
+ config->use_api_versioning ? "true" : "false");
+ len += snprintf(buf+len, PAGE_SIZE,
+ "api_min:\t%u\n", config->api_min);
+ len += snprintf(buf+len, PAGE_SIZE,
+ "api_max:\t%u\n", config->api_max);
+ if (config->api_name_postfix)
+ len += snprintf(buf+len, PAGE_SIZE,
+ "api_name_postfix:\t\t%s\n", config->api_name_postfix);
+ else
+ len += snprintf(buf+len, PAGE_SIZE,
+ "api_name_postfix:\t\tEMPTY\n");
+ len += snprintf(buf+len, PAGE_SIZE,
+ "keep:\t\t%s\n",
+ config->keep ? "true" : "false");
+
+ mutex_unlock(&test_dev->config_mutex);
+
+ return len;
+}
+static DEVICE_ATTR_RO(config);
+
+static int config_load_data(struct driver_data_test_device *test_dev,
+ const struct firmware *driver_data)
+{
+ struct test_config *config = &test_dev->config;
+ int ret;
+
+ ret = test_load_driver_data(test_dev, driver_data);
+ if (ret) {
+ if (!config->optional)
+ dev_info(test_dev->dev,
+ "unable to write driver_data\n");
+ }
+ if (config->keep) {
+ release_firmware(driver_data);
+ driver_data = NULL;
+ }
+
+ return ret;
+}
+
+static int config_req_default(struct driver_data_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+ int ret;
+ /*
+ * Note: we don't chain config->optional here, we make this
+ * fallback file a requirement. It doesn't make much sense to test
+ * chaining further as the optional callback is implementation
+ * specific, by testing it once we test it for any possible
+ * chains. We provide this as an example of what people can do
+ * and use a default non-optional fallback.
+ */
+ const struct driver_data_req_params req_params = {
+ DRIVER_DATA_DEFAULT_SYNC(sync_found_cb, test_dev),
+ };
+
+ if (config->async)
+ dev_info(test_dev->dev,
+ "loading default fallback '%s' using sync request now\n",
+ config->default_name);
+ else
+ dev_info(test_dev->dev,
+ "loading default fallback '%s'\n",
+ config->default_name);
+
+ ret = driver_data_request_sync(config->default_name,
+ &req_params, test_dev->dev);
+ if (ret)
+ dev_info(test_dev->dev,
+ "load of default '%s' failed: %d\n",
+ config->default_name, ret);
+
+ return ret;
+}
+
+/*
+ * This is the default sync fallback callback, as a fallback this
+ * then uses a sync request.
+ */
+static int config_sync_req_default_cb(void *context, int unused_error)
+{
+ struct driver_data_test_device *test_dev = context;
+ int ret;
+
+ ret = config_req_default(test_dev);
+
+ return ret;
+
+ /* Leave all the error checking for the main caller */
+}
+
+/*
+ * This is the default config->async fallback callback, as a fallback this
+ * then uses a sync request.
+ */
+static void config_async_req_default_cb(void *context, int unused_error)
+{
+ struct driver_data_test_device *test_dev = context;
+
+ config_req_default(test_dev);
+
+ complete(&test_dev->request_complete);
+ /* Leave all the error checking for the main caller */
+}
+
+static int config_sync_req_cb(void *context,
+ const struct firmware *driver_data,
+ int unused_error)
+{
+ struct driver_data_test_device *test_dev = context;
+
+ return config_load_data(test_dev, driver_data);
+}
+
+static int trigger_config_sync(struct driver_data_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+ int ret;
+ const struct driver_data_req_params req_params_default = {
+ DRIVER_DATA_DEFAULT_SYNC_REQS(config_sync_req_cb, test_dev,
+ (config->optional ?
+ DRIVER_DATA_REQ_OPTIONAL : 0) |
+ (config->keep ?
+ DRIVER_DATA_REQ_KEEP : 0)),
+ };
+ const struct driver_data_req_params req_params_opt_cb = {
+ DRIVER_DATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+ DRIVER_DATA_SYNC_OPT_CB(config_sync_req_default_cb,
+ test_dev),
+ .reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+ (config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+ };
+ const struct driver_data_req_params *req_params;
+
+ if (config->enable_opt_cb)
+ req_params = &req_params_opt_cb;
+ else
+ req_params = &req_params_default;
+
+ ret = driver_data_request_sync(config->name, req_params, test_dev->dev);
+ if (ret)
+ dev_err(test_dev->dev, "sync load of '%s' failed: %d\n",
+ config->name, ret);
+
+ return ret;
+}
+
+static void config_async_req_cb(const struct firmware *driver_data,
+ void *context, int unused_error)
+{
+ struct driver_data_test_device *test_dev = context;
+
+ config_load_data(test_dev, driver_data);
+ complete(&test_dev->request_complete);
+}
+
+static int config_async_req_api_cb(const struct firmware *driver_data,
+ void *context,
+ int unused_error)
+{
+ struct driver_data_test_device *test_dev = context;
+ /*
+ * This drivers may process a file and determine it does not
+ * like it, so it wants us to try again, to do this it returns
+ * -EAGAIN. We mimick this behaviour by not liking odd numbered
+ * api files, so we know to expect only success on even numbered
+ * apis.
+ */
+ if (driver_data && (driver_data->api % 2 == 1)) {
+ pr_info("File api %u found but we purposely ignore it\n",
+ driver_data->api);
+ return -EAGAIN;
+ }
+
+ config_load_data(test_dev, driver_data);
+
+ /*
+ * If the file was found we let our stupid driver emulator thing
+ * fake holding the driver data. If the file was not found just
+ * bail immediately.
+ */
+ if (driver_data)
+ pr_info("File with api %u found!\n", driver_data->api);
+
+ complete(&test_dev->request_complete);
+
+ return 0;
+}
+
+static int trigger_config_async(struct driver_data_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+ int ret;
+ const struct driver_data_req_params req_params_default = {
+ DRIVER_DATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+ .reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+ (config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+ };
+ const struct driver_data_req_params req_params_opt_cb = {
+ DRIVER_DATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+ DRIVER_DATA_ASYNC_OPT_CB(config_async_req_default_cb, test_dev),
+ .reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+ (config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+ };
+ const struct driver_data_req_params req_params_api = {
+ DRIVER_DATA_API_CB(config_async_req_api_cb, test_dev),
+ DRIVER_DATA_API(config->api_min, config->api_max, config->api_name_postfix),
+ .reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+ (config->keep ? DRIVER_DATA_REQ_KEEP : 0) |
+ (config->use_api_versioning ? DRIVER_DATA_REQ_USE_API_VERSIONING : 0),
+ };
+ const struct driver_data_req_params *req_params;
+
+ if (config->enable_opt_cb)
+ req_params = &req_params_opt_cb;
+ else if (config->use_api_versioning)
+ req_params = &req_params_api;
+ else
+ req_params = &req_params_default;
+
+ test_dev->api_found_calls = 0;
+ ret = driver_data_request_async(config->name, req_params,
+ test_dev->dev);
+ if (ret) {
+ dev_err(test_dev->dev, "async load of '%s' failed: %d\n",
+ config->name, ret);
+ goto out;
+ }
+
+ /*
+ * Without waiting for completion we'd return before the async callback
+ * completes, and any testing analysis done on the results would be
+ * bogus. We could have used async cookies to avoid having drivers
+ * avoid adding their own completions and initializing them.
+ * We have decided its best to keep with the old way of doing things to
+ * keep things compatible. Deal with it.
+ */
+ wait_for_completion_timeout(&test_dev->request_complete, 5 * HZ);
+
+out:
+ return ret;
+}
+
+static ssize_t
+trigger_config_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_driver_data_private *test_driver_data =
+ &test_dev->test_driver_data;
+ struct test_config *config = &test_dev->config;
+ int ret;
+
+ mutex_lock(&test_dev->trigger_mutex);
+ mutex_lock(&test_dev->config_mutex);
+
+ dev_info(dev, "loading '%s'\n", config->name);
+
+ if (config->async)
+ ret = trigger_config_async(test_dev);
+ else
+ ret = trigger_config_sync(test_dev);
+
+ config->test_result = ret;
+
+ if (ret)
+ goto out;
+
+ if (test_driver_data->written) {
+ dev_info(dev, "loaded: %zu\n", test_driver_data->size);
+ ret = count;
+ } else {
+ dev_err(dev, "failed to load firmware\n");
+ ret = -ENODEV;
+ }
+
+out:
+ mutex_unlock(&test_dev->config_mutex);
+ mutex_unlock(&test_dev->trigger_mutex);
+
+ return ret;
+}
+static DEVICE_ATTR_WO(trigger_config);
+
+/*
+ * XXX: move to kstrncpy() once merged.
+ *
+ * Users should use kfree_const() when freeing these.
+ */
+static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp)
+{
+ *dst = kstrndup(name, count, gfp);
+ if (!*dst)
+ return -ENOSPC;
+ return count;
+}
+
+static void __driver_data_config_free(struct test_config *config)
+{
+ kfree_const(config->name);
+ config->name = NULL;
+ kfree_const(config->default_name);
+ config->default_name = NULL;
+ kfree_const(config->api_name_postfix);
+ config->api_name_postfix = NULL;
+}
+
+static void driver_data_config_free(struct driver_data_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+
+ mutex_lock(&test_dev->config_mutex);
+ __driver_data_config_free(config);
+ mutex_unlock(&test_dev->config_mutex);
+}
+
+static int __driver_data_config_init(struct test_config *config)
+{
+ int ret;
+
+ ret = __kstrncpy(&config->name, TEST_DRIVER_DATA,
+ strlen(TEST_DRIVER_DATA), GFP_KERNEL);
+ if (ret < 0)
+ goto out;
+
+ ret = __kstrncpy(&config->default_name, TEST_DRIVER_DATA,
+ strlen(TEST_DRIVER_DATA), GFP_KERNEL);
+ if (ret < 0)
+ goto out;
+
+ config->async = false;
+ config->optional = false;
+ config->keep = false;
+ config->enable_opt_cb = false;
+ config->use_api_versioning = false;
+ config->api_min = 0;
+ config->api_max = 0;
+ config->test_result = 0;
+
+ return 0;
+
+out:
+ __driver_data_config_free(config);
+ return ret;
+}
+
+int driver_data_config_init(struct driver_data_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+ int ret;
+
+ mutex_lock(&test_dev->config_mutex);
+ ret = __driver_data_config_init(config);
+ mutex_unlock(&test_dev->config_mutex);
+
+ return ret;
+}
+
+static ssize_t config_name_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+ int ret;
+
+ mutex_lock(&test_dev->config_mutex);
+ kfree_const(config->name);
+ ret = __kstrncpy(&config->name, buf, count, GFP_KERNEL);
+ mutex_unlock(&test_dev->config_mutex);
+
+ return ret;
+}
+
+/*
+ * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE.
+ */
+static ssize_t config_test_show_str(struct mutex *config_mutex,
+ char *dst,
+ char *src)
+{
+ int len;
+
+ mutex_lock(config_mutex);
+ len = snprintf(dst, PAGE_SIZE, "%s\n", src);
+ mutex_unlock(config_mutex);
+
+ return len;
+}
+
+static ssize_t config_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return config_test_show_str(&test_dev->config_mutex, buf,
+ config->name);
+}
+static DEVICE_ATTR(config_name, 0644, config_name_show, config_name_store);
+
+static ssize_t config_default_name_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+ int ret;
+
+ mutex_lock(&test_dev->config_mutex);
+ kfree_const(config->default_name);
+ ret = __kstrncpy(&config->default_name, buf, count, GFP_KERNEL);
+ mutex_unlock(&test_dev->config_mutex);
+
+ return ret;
+}
+
+static ssize_t config_default_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return config_test_show_str(&test_dev->config_mutex, buf,
+ config->default_name);
+}
+static DEVICE_ATTR(config_default_name, 0644, config_default_name_show,
+ config_default_name_store);
+
+static ssize_t reset_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+ int ret;
+
+ mutex_lock(&test_dev->trigger_mutex);
+
+ mutex_lock(&test_dev->driver_data_mutex);
+ free_test_driver_data(&test_dev->test_driver_data);
+ reinit_completion(&test_dev->request_complete);
+ mutex_unlock(&test_dev->driver_data_mutex);
+
+ mutex_lock(&test_dev->config_mutex);
+
+ __driver_data_config_free(config);
+
+ ret = __driver_data_config_init(config);
+ if (ret < 0) {
+ ret = -ENOMEM;
+ dev_err(dev, "could not alloc settings for config trigger: %d\n",
+ ret);
+ goto out;
+ }
+
+ dev_info(dev, "reset\n");
+ ret = count;
+
+out:
+ mutex_unlock(&test_dev->config_mutex);
+ mutex_unlock(&test_dev->trigger_mutex);
+
+ return ret;
+}
+static DEVICE_ATTR_WO(reset);
+
+/*
+ * XXX: consider a soluton to generalize drivers to specify their own
+ * mutex, adding it to dev core after this gets merged. This may not
+ * be important for once-in-a-while system tuning parameters, but if
+ * we want to enable fuzz testing, this is really important.
+ *
+ * It may make sense to just have a "struct device configuration mutex"
+ * for these sorts of things, although there is difficulty in that we'd
+ * need dynamically allocated attributes for that. Its the same reason
+ * why we ended up not using the provided standard device attribute
+ * bool, int interfaces.
+ */
+
+static int test_dev_config_update_bool(struct driver_data_test_device *test_dev,
+ const char *buf, size_t size,
+ bool *config)
+{
+ int ret;
+
+ mutex_lock(&test_dev->config_mutex);
+ if (strtobool(buf, config) < 0)
+ ret = -EINVAL;
+ else
+ ret = size;
+ mutex_unlock(&test_dev->config_mutex);
+
+ return ret;
+}
+
+static ssize_t
+test_dev_config_show_bool(struct driver_data_test_device *test_dev,
+ char *buf,
+ bool config)
+{
+ bool val;
+
+ mutex_lock(&test_dev->config_mutex);
+ val = config;
+ mutex_unlock(&test_dev->config_mutex);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static int test_dev_config_update_int(struct driver_data_test_device *test_dev,
+ const char *buf, size_t size,
+ int *config)
+{
+ int ret;
+ long new;
+
+ ret = kstrtol(buf, 10, &new);
+ if (ret)
+ return ret;
+
+ if (new > INT_MAX || new < INT_MIN)
+ return -EINVAL;
+
+ mutex_lock(&test_dev->config_mutex);
+ *(int *)config = new;
+ mutex_unlock(&test_dev->config_mutex);
+
+ /* Always return full write size even if we didn't consume all */
+ return size;
+}
+
+static
+ssize_t test_dev_config_show_int(struct driver_data_test_device *test_dev,
+ char *buf,
+ int config)
+{
+ int val;
+
+ mutex_lock(&test_dev->config_mutex);
+ val = config;
+ mutex_unlock(&test_dev->config_mutex);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static int test_dev_config_update_u8(struct driver_data_test_device *test_dev,
+ const char *buf, size_t size,
+ u8 *config)
+{
+ int ret;
+ long new;
+
+ ret = kstrtol(buf, 10, &new);
+ if (ret)
+ return ret;
+
+ if (new > U8_MAX)
+ return -EINVAL;
+
+ mutex_lock(&test_dev->config_mutex);
+ *(u8 *)config = new;
+ mutex_unlock(&test_dev->config_mutex);
+
+ /* Always return full write size even if we didn't consume all */
+ return size;
+}
+
+static
+ssize_t test_dev_config_show_u8(struct driver_data_test_device *test_dev,
+ char *buf,
+ u8 config)
+{
+ u8 val;
+
+ mutex_lock(&test_dev->config_mutex);
+ val = config;
+ mutex_unlock(&test_dev->config_mutex);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+
+static ssize_t config_async_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_update_bool(test_dev, buf, count,
+ &config->async);
+}
+
+static ssize_t config_async_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_show_bool(test_dev, buf, config->async);
+}
+static DEVICE_ATTR(config_async, 0644, config_async_show, config_async_store);
+
+static ssize_t config_optional_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_update_bool(test_dev, buf, count,
+ &config->optional);
+}
+
+static ssize_t config_optional_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_show_bool(test_dev, buf, config->optional);
+}
+static DEVICE_ATTR(config_optional, 0644, config_optional_show,
+ config_optional_store);
+
+static ssize_t config_keep_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_update_bool(test_dev, buf, count,
+ &config->keep);
+}
+
+static ssize_t config_keep_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_show_bool(test_dev, buf, config->keep);
+}
+static DEVICE_ATTR(config_keep, 0644, config_keep_show, config_keep_store);
+
+static ssize_t config_enable_opt_cb_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_update_bool(test_dev, buf, count,
+ &config->enable_opt_cb);
+}
+
+static ssize_t config_enable_opt_cb_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_show_bool(test_dev, buf,
+ config->enable_opt_cb);
+}
+static DEVICE_ATTR(config_enable_opt_cb, 0644,
+ config_enable_opt_cb_show,
+ config_enable_opt_cb_store);
+
+static ssize_t config_use_api_versioning_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_update_bool(test_dev, buf, count,
+ &config->use_api_versioning);
+}
+
+static ssize_t config_use_api_versioning_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_show_bool(test_dev, buf,
+ config->use_api_versioning);
+}
+static DEVICE_ATTR(config_use_api_versioning, 0644,
+ config_use_api_versioning_show,
+ config_use_api_versioning_store);
+
+static ssize_t config_api_min_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_update_u8(test_dev, buf, count,
+ &config->api_min);
+}
+
+static ssize_t config_api_min_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_show_u8(test_dev, buf, config->api_min);
+}
+static DEVICE_ATTR(config_api_min, 0644, config_api_min_show, config_api_min_store);
+
+static ssize_t config_api_max_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_update_u8(test_dev, buf, count,
+ &config->api_max);
+}
+
+static ssize_t config_api_max_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_show_u8(test_dev, buf, config->api_max);
+}
+static DEVICE_ATTR(config_api_max, 0644, config_api_max_show, config_api_max_store);
+
+static ssize_t config_api_name_postfix_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+ int ret;
+
+ mutex_lock(&test_dev->config_mutex);
+ kfree_const(config->api_name_postfix);
+ ret = __kstrncpy(&config->api_name_postfix, buf, count, GFP_KERNEL);
+ mutex_unlock(&test_dev->config_mutex);
+
+ return ret;
+}
+
+static ssize_t config_api_name_postfix_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return config_test_show_str(&test_dev->config_mutex, buf,
+ config->api_name_postfix);
+}
+static DEVICE_ATTR(config_api_name_postfix, 0644, config_api_name_postfix_show,
+ config_api_name_postfix_store);
+
+static ssize_t test_result_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_update_int(test_dev, buf, count,
+ &config->test_result);
+}
+
+static ssize_t test_result_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_show_int(test_dev, buf, config->test_result);
+}
+static DEVICE_ATTR(test_result, 0644, test_result_show, test_result_store);
+
+#define TEST_DRIVER_DATA_DEV_ATTR(name) &dev_attr_##name.attr
+
+static struct attribute *test_dev_attrs[] = {
+ TEST_DRIVER_DATA_DEV_ATTR(trigger_config),
+ TEST_DRIVER_DATA_DEV_ATTR(config),
+ TEST_DRIVER_DATA_DEV_ATTR(reset),
+
+ TEST_DRIVER_DATA_DEV_ATTR(config_name),
+ TEST_DRIVER_DATA_DEV_ATTR(config_default_name),
+ TEST_DRIVER_DATA_DEV_ATTR(config_async),
+ TEST_DRIVER_DATA_DEV_ATTR(config_optional),
+ TEST_DRIVER_DATA_DEV_ATTR(config_keep),
+ TEST_DRIVER_DATA_DEV_ATTR(config_use_api_versioning),
+ TEST_DRIVER_DATA_DEV_ATTR(config_enable_opt_cb),
+ TEST_DRIVER_DATA_DEV_ATTR(config_api_min),
+ TEST_DRIVER_DATA_DEV_ATTR(config_api_max),
+ TEST_DRIVER_DATA_DEV_ATTR(config_api_name_postfix),
+ TEST_DRIVER_DATA_DEV_ATTR(test_result),
+
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(test_dev);
+
+void free_test_dev_driver_data(struct driver_data_test_device *test_dev)
+{
+ kfree_const(test_dev->misc_dev.name);
+ test_dev->misc_dev.name = NULL;
+ driver_data_config_free(test_dev);
+ vfree(test_dev);
+}
+
+void unregister_test_dev_driver_data(struct driver_data_test_device *test_dev)
+{
+ wait_for_completion_timeout(&test_dev->request_complete, 5 * HZ);
+ dev_info(test_dev->dev, "removing interface\n");
+ misc_deregister(&test_dev->misc_dev);
+ free_test_dev_driver_data(test_dev);
+}
+
+struct driver_data_test_device *alloc_test_dev_driver_data(int idx)
+{
+ int ret;
+ struct driver_data_test_device *test_dev;
+ struct miscdevice *misc_dev;
+
+ test_dev = vzalloc(sizeof(struct driver_data_test_device));
+ if (!test_dev)
+ goto err_out;
+
+ mutex_init(&test_dev->driver_data_mutex);
+ mutex_init(&test_dev->config_mutex);
+ mutex_init(&test_dev->trigger_mutex);
+ init_completion(&test_dev->request_complete);
+
+ ret = driver_data_config_init(test_dev);
+ if (ret < 0)
+ goto err_out_free;
+
+ test_dev->dev_idx = idx;
+ misc_dev = &test_dev->misc_dev;
+
+ misc_dev->minor = MISC_DYNAMIC_MINOR;
+ misc_dev->name = kasprintf(GFP_KERNEL, "test_driver_data%d", idx);
+ if (!misc_dev->name)
+ goto err_out_free_config;
+
+ misc_dev->fops = &test_fw_fops;
+ misc_dev->groups = test_dev_groups;
+
+ return test_dev;
+
+err_out_free_config:
+ __driver_data_config_free(&test_dev->config);
+err_out_free:
+ kfree(test_dev);
+err_out:
+ return NULL;
+}
+
+static int register_test_dev_driver_data(void)
+{
+ struct driver_data_test_device *test_dev = NULL;
+ int ret = -ENODEV;
+
+ mutex_lock(&reg_dev_mutex);
+
+ /* int should suffice for number of devices, test for wrap */
+ if (unlikely(num_test_devs + 1) < 0) {
+ pr_err("reached limit of number of test devices\n");
+ goto out;
+ }
+
+ test_dev = alloc_test_dev_driver_data(num_test_devs);
+ if (!test_dev) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = misc_register(&test_dev->misc_dev);
+ if (ret) {
+ pr_err("could not register misc device: %d\n", ret);
+ free_test_dev_driver_data(test_dev);
+ goto out;
+ }
+
+ test_dev->dev = test_dev->misc_dev.this_device;
+ list_add_tail(&test_dev->list, &reg_test_devs);
+ dev_info(test_dev->dev, "interface ready\n");
+
+ num_test_devs++;
+
+out:
+ mutex_unlock(&reg_dev_mutex);
+
+ return ret;
+}
+
+static int __init test_driver_data_init(void)
+{
+ int ret;
+
+ ret = register_test_dev_driver_data();
+ if (ret)
+ pr_err("Cannot add first test driver_data device\n");
+
+ return ret;
+}
+late_initcall(test_driver_data_init);
+
+static void __exit test_driver_data_exit(void)
+{
+ struct driver_data_test_device *test_dev, *tmp;
+
+ mutex_lock(&reg_dev_mutex);
+ list_for_each_entry_safe(test_dev, tmp, &reg_test_devs, list) {
+ list_del(&test_dev->list);
+ unregister_test_dev_driver_data(test_dev);
+ }
+ mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_driver_data_exit);
+
+MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/tools/testing/selftests/firmware/Makefile b/tools/testing/selftests/firmware/Makefile
index 1894d625af2d..c9bf6c44435f 100644
--- a/tools/testing/selftests/firmware/Makefile
+++ b/tools/testing/selftests/firmware/Makefile
@@ -3,7 +3,7 @@
# No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
all:

-TEST_PROGS := fw_filesystem.sh fw_fallback.sh
+TEST_PROGS := fw_filesystem.sh fw_fallback.sh driver_data.sh

include ../lib.mk

diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config
index c8137f70e291..0f1a299f9270 100644
--- a/tools/testing/selftests/firmware/config
+++ b/tools/testing/selftests/firmware/config
@@ -1 +1,2 @@
CONFIG_TEST_FIRMWARE=y
+CONFIG_TEST_DRIVER_DATA=y
diff --git a/tools/testing/selftests/firmware/driver_data.sh b/tools/testing/selftests/firmware/driver_data.sh
new file mode 100755
index 000000000000..c830d04390e3
--- /dev/null
+++ b/tools/testing/selftests/firmware/driver_data.sh
@@ -0,0 +1,996 @@
+#!/bin/bash
+# Copyright (C) 2016 Luis R. Rodriguez <mcgrof@kernel.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of copyleft-next (version 0.3.1 or later) as published
+# at http://copyleft-next.org/.
+
+# This performs a series tests against firmware_class to excercise the
+# firmware_class driver with focus only on the extensible driver data API.
+#
+# To make this test self contained, and not pollute your distribution
+# firmware install paths, we reset the custom load directory to a
+# temporary location.
+
+set -e
+
+TEST_NAME="driver_data"
+TEST_DRIVER="test_${TEST_NAME}"
+TEST_DIR=$(dirname $0)
+
+# This represents
+#
+# TEST_ID:TEST_COUNT:ENABLED
+#
+# TEST_ID: is the test id number
+# TEST_COUNT: number of times we should run the test
+# ENABLED: 1 if enabled, 0 otherwise
+#
+# Once these are enabled please leave them as-is. Write your own test,
+# we have tons of space.
+ALL_TESTS="0001:3:1"
+ALL_TESTS="$ALL_TESTS 0002:3:1"
+ALL_TESTS="$ALL_TESTS 0003:3:1"
+ALL_TESTS="$ALL_TESTS 0004:10:1"
+ALL_TESTS="$ALL_TESTS 0005:10:1"
+ALL_TESTS="$ALL_TESTS 0006:10:1"
+ALL_TESTS="$ALL_TESTS 0007:10:1"
+ALL_TESTS="$ALL_TESTS 0008:10:1"
+ALL_TESTS="$ALL_TESTS 0009:10:1"
+ALL_TESTS="$ALL_TESTS 0010:10:1"
+ALL_TESTS="$ALL_TESTS 0011:10:1"
+ALL_TESTS="$ALL_TESTS 0012:1:1"
+ALL_TESTS="$ALL_TESTS 0013:1:1"
+
+# Not yet sure how to automate suspend test well yet. For now we expect a
+# manual run. If using qemu you can resume a guest using something like the
+# following on the monitor pts.
+# system_wakeupakeup | socat - /dev/pts/7,raw,echo=0,crnl
+#ALL_TESTS="$ALL_TESTS 0014:0:1"
+
+test_modprobe()
+{
+ if [ ! -d $DIR ]; then
+ echo "$0: $DIR not present" >&2
+ echo "You must have the following enabled in your kernel:" >&2
+ cat $TEST_DIR/config >&2
+ exit 1
+ fi
+}
+
+function allow_user_defaults()
+{
+ if [ -z $DEFAULT_NUM_TESTS ]; then
+ DEFAULT_NUM_TESTS=50
+ fi
+
+ if [ -z $FW_SYSFSPATH ]; then
+ FW_SYSFSPATH="/sys/module/firmware_class/parameters/path"
+ fi
+
+ if [ -z $OLD_FWPATH ]; then
+ OLD_FWPATH=$(cat $FW_SYSFSPATH)
+ fi
+
+ if [ -z $FWPATH]; then
+ FWPATH=$(mktemp -d)
+ fi
+
+ if [ -z $DEFAULT_DRIVER_DATA ]; then
+ config_reset
+ DEFAULT_DRIVER_DATA=$(config_get_name)
+ fi
+
+ if [ -z $FW ]; then
+ FW="$FWPATH/$DEFAULT_DRIVER_DATA"
+ fi
+
+ if [ -z $SYS_STATE_PATH ]; then
+ SYS_STATE_PATH="/sys/power/state"
+ fi
+
+ # Set the kernel search path.
+ echo -n "$FWPATH" > $FW_SYSFSPATH
+
+ # This is an unlikely real-world firmware content. :)
+ echo "ABCD0123" >"$FW"
+}
+
+test_reqs()
+{
+ if ! which diff 2> /dev/null > /dev/null; then
+ echo "$0: You need diff installed"
+ exit 1
+ fi
+
+ uid=$(id -u)
+ if [ $uid -ne 0 ]; then
+ echo $msg must be run as root >&2
+ exit 0
+ fi
+}
+
+function load_req_mod()
+{
+ trap "test_modprobe" EXIT
+
+ if [ -z $DIR ]; then
+ DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"
+ fi
+
+ if [ ! -d $DIR ]; then
+ modprobe $TEST_DRIVER
+ fi
+}
+
+test_finish()
+{
+ echo -n "$OLD_PATH" >/sys/module/firmware_class/parameters/path
+ rm -f "$FW"
+ rmdir "$FWPATH"
+}
+
+errno_name_to_val()
+{
+ case "$1" in
+ SUCCESS)
+ echo 0;;
+ -EPERM)
+ echo -1;;
+ -ENOENT)
+ echo -2;;
+ -EINVAL)
+ echo -22;;
+ -ERR_ANY)
+ echo -123456;;
+ *)
+ echo invalid;;
+ esac
+}
+
+errno_val_to_name()
+ case "$1" in
+ 0)
+ echo SUCCESS;;
+ -1)
+ echo -EPERM;;
+ -2)
+ echo -ENOENT;;
+ -22)
+ echo -EINVAL;;
+ -123456)
+ echo -ERR_ANY;;
+ *)
+ echo invalid;;
+ esac
+
+config_set_async()
+{
+ if ! echo -n 1 >$DIR/config_async ; then
+ echo "$0: Unable to set to async" >&2
+ exit 1
+ fi
+}
+
+config_disable_async()
+{
+ if ! echo -n 0 >$DIR/config_async ; then
+ echo "$0: Unable to set to sync" >&2
+ exit 1
+ fi
+}
+
+config_set_optional()
+{
+ if ! echo -n 1 >$DIR/config_optional ; then
+ echo "$0: Unable to set to optional" >&2
+ exit 1
+ fi
+}
+
+config_disable_optional()
+{
+ if ! echo -n 0 >$DIR/config_optional ; then
+ echo "$0: Unable to disable optional" >&2
+ exit 1
+ fi
+}
+
+config_set_keep()
+{
+ if ! echo -n 1 >$DIR/config_keep; then
+ echo "$0: Unable to set to keep" >&2
+ exit 1
+ fi
+}
+
+config_disable_keep()
+{
+ if ! echo -n 0 >$DIR/config_keep; then
+ echo "$0: Unable to disable keep option" >&2
+ exit 1
+ fi
+}
+
+config_enable_opt_cb()
+{
+ if ! echo -n 1 >$DIR/config_enable_opt_cb; then
+ echo "$0: Unable to set to optional" >&2
+ exit 1
+ fi
+}
+
+config_enable_api_versioning()
+{
+ if ! echo -n 1 >$DIR/config_use_api_versioning; then
+ echo "$0: Unable to set use_api_versioning option" >&2
+ exit 1
+ fi
+}
+
+config_set_api_name_postfix()
+{
+ if ! echo -n $1 >$DIR/config_api_name_postfix; then
+ echo "$0: Unable to set use_api_versioning option" >&2
+ exit 1
+ fi
+}
+
+config_set_api_min()
+{
+ if ! echo -n $1 >$DIR/config_api_min; then
+ echo "$0: Unable to set config_api_min option" >&2
+ exit 1
+ fi
+}
+
+config_set_api_max()
+{
+ if ! echo -n $1 >$DIR/config_api_max; then
+ echo "$0: Unable to set config_api_max option" >&2
+ exit 1
+ fi
+}
+
+config_add_api_file()
+{
+ TMP_FW="$FWPATH/$1"
+ echo "ABCD0123" >"$TMP_FW"
+}
+
+config_rm_api_file()
+{
+ TMP_FW="$FWPATH/$1"
+ rm -f $TMP_FW
+}
+
+# For special characters use printf directly,
+# refer to driver_data_test_0001
+config_set_name()
+{
+ if ! echo -n $1 >$DIR/config_name; then
+ echo "$0: Unable to set name" >&2
+ exit 1
+ fi
+}
+
+config_get_name()
+{
+ cat $DIR/config_name
+}
+
+# For special characters use printf directly,
+# refer to driver_data_test_0001
+config_set_default_name()
+{
+ if ! echo -n $1 >$DIR/config_default_name; then
+ echo "$0: Unable to set default_name" >&2
+ exit 1
+ fi
+}
+
+config_get_default_name()
+{
+ cat $DIR/config_default_name
+}
+
+config_get_test_result()
+{
+ cat $DIR/test_result
+}
+
+config_reset()
+{
+ if ! echo -n "1" >"$DIR"/reset; then
+ echo "$0: reset shuld have worked" >&2
+ exit 1
+ fi
+}
+
+trigger_release_driver_data()
+{
+ if ! echo -n "1" >"$DIR"/trigger_release_driver_data; then
+ echo "$0: release driver data shuld have worked" >&2
+ exit 1
+ fi
+}
+
+config_show_config()
+{
+ echo "----------------------------------------------------"
+ cat "$DIR"/config
+ echo "----------------------------------------------------"
+}
+
+config_trigger()
+{
+ if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then
+ echo "$1: FAIL - loading should have worked" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - loading driver_data"
+}
+
+config_trigger_want_fail()
+{
+ if echo "1" > $DIR/trigger_config 2>/dev/null; then
+ echo "$1: FAIL - loading was expected to fail" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - loading failed as expected"
+}
+
+config_file_should_match()
+{
+ FILE=$(config_get_name)
+ if [ ! -z $2 ]; then
+ FILE=$2
+ fi
+ # On this one we expect the file to exist so leave stderr in
+ if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > /dev/null; then
+ echo "$1: FAIL - file $FILE did not match contents in /dev/test_driver_data0" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - $FILE == /dev/test_driver_data0"
+}
+
+config_file_should_match_default()
+{
+ FILE=$(config_get_default_name)
+ # On this one we expect the file to exist so leave stderr in
+ if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > /dev/null; then
+ echo "$1: FAIL - file $FILE did not match contents in /dev/test_driver_data0" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - $FILE == /dev/test_driver_data0"
+}
+
+config_file_should_not_match()
+{
+ FILE=$(config_get_name)
+ # File may not exist, so skip those error messages as well
+ if $(diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null) 2> /dev/null ; then
+ echo "$1: FAIL - file $FILE was not expected to match /dev/null" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - $FILE != /dev/test_driver_data0"
+}
+
+config_default_file_should_match()
+{
+ FILE=$(config_get_default_name)
+ diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null
+ if ! $? ; then
+ echo "$1: FAIL - file $FILE expected to match /dev/test_driver_data0" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! [file integrity matches]"
+}
+
+config_default_file_should_not_match()
+{
+ FILE=$(config_get_default_name)
+ diff -q FWPATH/$FILE /dev/test_driver_data0 2> /dev/null
+ if $? 2> /dev/null ; then
+ echo "$1: FAIL - file $FILE was not expected to match test_driver_data0" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK!"
+}
+
+config_expect_result()
+{
+ RC=$(config_get_test_result)
+ RC_NAME=$(errno_val_to_name $RC)
+
+ ERRNO_NAME=$2
+ ERRNO=$(errno_name_to_val $ERRNO_NAME)
+
+ if [[ $ERRNO_NAME = "-ERR_ANY" ]]; then
+ if [[ $RC -ge 0 ]]; then
+ echo "$1: FAIL, test expects $ERRNO_NAME - got $RC_NAME ($RC)" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ elif [[ $RC != $ERRNO ]]; then
+ echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got $RC_NAME ($RC)" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME"
+}
+
+driver_data_set_sync_defaults()
+{
+ config_reset
+}
+
+driver_data_set_async_defaults()
+{
+ config_reset
+ config_set_async
+}
+
+set_system_state()
+{
+ STATE="mem"
+ if [ ! -z $2 ]; then
+ STATE=$2
+ fi
+ echo $STATE > $SYS_STATE_PATH
+}
+
+driver_data_test_0001s()
+{
+ NAME='\000'
+
+ driver_data_set_sync_defaults
+ config_set_name $NAME
+ printf '\000' >"$DIR"/config_name
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+driver_data_test_0001a()
+{
+ NAME='\000'
+
+ driver_data_set_async_defaults
+ printf '\000' >"$DIR"/config_name
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+driver_data_test_0001()
+{
+ driver_data_test_0001s
+ driver_data_test_0001a
+}
+
+driver_data_test_0002s()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_sync_defaults
+ config_set_name $NAME
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+driver_data_test_0002a()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_async_defaults
+ config_set_name $NAME
+ config_trigger_want_fail ${FUNCNAME[0]}
+ # This may seem odd to expect success on a bogus
+ # file but remember this is an async call, the actual
+ # error handling is managed by the async callbacks.
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0002()
+{
+ driver_data_test_0002s
+ driver_data_test_0002a
+}
+
+driver_data_test_0003()
+{
+ config_reset
+ config_file_should_not_match ${FUNCNAME[0]}
+}
+
+driver_data_test_0004s()
+{
+ driver_data_set_sync_defaults
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0004a()
+{
+ driver_data_set_async_defaults
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0004()
+{
+ driver_data_test_0004s
+ driver_data_test_0004a
+}
+
+driver_data_test_0005s()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_sync_defaults
+ config_set_optional
+ config_set_name $NAME
+ config_trigger_want_fail ${FUNCNAME[0]}
+ # We do this to ensure the default backup callback hasn't
+ # been called yet
+ config_file_should_not_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+driver_data_test_0005a()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_async_defaults
+ config_set_optional
+ config_set_name $NAME
+ config_trigger_want_fail ${FUNCNAME[0]}
+ # We do this to ensure the default backup callback hasn't
+ # been called yet
+ config_file_should_not_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0005()
+{
+ driver_data_test_0005s
+ driver_data_test_0005a
+}
+
+driver_data_test_0006s()
+{
+ driver_data_set_sync_defaults
+ config_set_optional
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0006a()
+{
+ driver_data_set_async_defaults
+ config_set_optional
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0006()
+{
+ driver_data_test_0006s
+ driver_data_test_0006a
+}
+
+driver_data_test_0007s()
+{
+ driver_data_set_sync_defaults
+ config_set_keep
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0007a()
+{
+ driver_data_set_async_defaults
+ config_set_keep
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0007()
+{
+ driver_data_test_0007s
+ driver_data_test_0007a
+}
+
+driver_data_test_0008s()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_sync_defaults
+ config_set_name $NAME
+ config_set_optional
+ config_enable_opt_cb
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match_default ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0008a()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_async_defaults
+ config_set_name $NAME
+ config_set_optional
+ config_enable_opt_cb
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match_default ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0008()
+{
+ driver_data_test_0008s
+ driver_data_test_0008a
+}
+
+driver_data_test_0009s()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_sync_defaults
+ config_set_name $NAME
+ config_set_keep
+ config_set_optional
+ config_enable_opt_cb
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match_default ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0009a()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_async_defaults
+ config_set_name $NAME
+ config_set_keep
+ config_set_optional
+ config_enable_opt_cb
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match_default ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0009()
+{
+ driver_data_test_0009s
+ driver_data_test_0009a
+}
+
+driver_data_test_0010s()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_sync_defaults
+ config_set_name $NAME
+ config_set_default_name $NAME
+ config_set_keep
+ config_set_optional
+ config_enable_opt_cb
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_file_should_not_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+driver_data_test_0010a()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_async_defaults
+ config_set_name $NAME
+ config_set_default_name $NAME
+ config_set_keep
+ config_set_optional
+ config_enable_opt_cb
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_file_should_not_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0010()
+{
+ driver_data_test_0010s
+ driver_data_test_0010a
+}
+
+driver_data_test_0011a()
+{
+ driver_data_set_async_defaults
+ config_set_keep
+ config_enable_api_versioning
+
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_file_should_not_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+driver_data_test_0011()
+{
+ driver_data_test_0011a
+}
+
+driver_data_test_0012a()
+{
+ driver_data_set_async_defaults
+ NAME_PREFIX="driver_data_test_0012a_"
+ TARGET_API="4"
+ NAME_POSTFIX=".bin"
+ NAME="${NAME_PREFIX}${TARGET_API}${NAME_POSTFIX}"
+
+ config_set_name $NAME_PREFIX
+ config_set_keep
+ config_enable_api_versioning
+ config_set_api_name_postfix ".bin"
+ config_set_api_min 3
+ config_set_api_max 18
+
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_file_should_not_match ${FUNCNAME[0]} $NAME
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0012()
+{
+ driver_data_test_0012a
+}
+
+driver_data_test_0013a()
+{
+ driver_data_set_async_defaults
+ NAME_PREFIX="driver_data_test_0013a_"
+ TARGET_API="4"
+ NAME_POSTFIX=".bin"
+ NAME="${NAME_PREFIX}${TARGET_API}${NAME_POSTFIX}"
+
+ config_set_name $NAME_PREFIX
+ config_set_keep
+ config_enable_api_versioning
+ config_set_api_name_postfix $NAME_POSTFIX
+ config_set_api_min 3
+ config_set_api_max 18
+ config_add_api_file $NAME
+
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]} $NAME
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+ config_rm_api_file $NAME
+}
+
+driver_data_test_0013()
+{
+ driver_data_test_0013a
+}
+
+driver_data_test_0014a()
+{
+ driver_data_set_async_defaults
+ NAME_PREFIX="driver_data_test_0013a_"
+ TARGET_API="4"
+ NAME_POSTFIX=".bin"
+ NAME="${NAME_PREFIX}${TARGET_API}${NAME_POSTFIX}"
+
+ config_set_name $NAME_PREFIX
+ config_set_keep
+ config_enable_api_versioning
+ config_set_api_name_postfix $NAME_POSTFIX
+ config_set_api_min 3
+ config_set_api_max 18
+ config_add_api_file $NAME
+
+ config_trigger ${FUNCNAME[0]}
+
+ # suspend to memory
+ set_system_state mem
+
+ config_file_should_match ${FUNCNAME[0]} $NAME
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+ config_rm_api_file $NAME
+}
+
+driver_data_test_0014()
+{
+ driver_data_test_0014a
+}
+
+list_tests()
+{
+ echo "Test ID list:"
+ echo
+ echo "TEST_ID x NUM_TEST"
+ echo "TEST_ID: Test ID"
+ echo "NUM_TESTS: Number of recommended times to run the test"
+ echo
+ echo "0001 x $(get_test_count 0001) - Empty string should be ignored"
+ echo "0002 x $(get_test_count 0002) - Files that do not exist should be ignored"
+ echo "0003 x $(get_test_count 0003) - Verify test_driver_data0 has nothing loaded upon reset"
+ echo "0004 x $(get_test_count 0004) - Simple sync and async loader"
+ echo "0005 x $(get_test_count 0005) - Verify optional loading is not fatal"
+ echo "0006 x $(get_test_count 0006) - Verify optional loading enables loading"
+ echo "0007 x $(get_test_count 0007) - Verify keep works"
+ echo "0008 x $(get_test_count 0008) - Verify optional callback works"
+ echo "0009 x $(get_test_count 0009) - Verify optional callback works, keep"
+ echo "0010 x $(get_test_count 0010) - Verify when fallback file is not present"
+ echo "0011 x $(get_test_count 0011) - Verify api setup will fail on invalid values"
+ echo "0012 x $(get_test_count 0012) - Verify api call wills will hunt for files, ignore file"
+ echo "0013 x $(get_test_count 0013) - Verify api call works"
+ echo "0014 x $(get_test_count 0013) - Verify api call works with suspend + resume"
+}
+
+test_reqs
+
+usage()
+{
+ NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .)
+ let NUM_TESTS=$NUM_TESTS+1
+ MAX_TEST=$(printf "%04d\n" $NUM_TESTS)
+ echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |"
+ echo " [ -s <4-number-digit> ] | [ -c <4-number-digit> <test-count>"
+ echo " [ all ] [ -h | --help ] [ -l ]"
+ echo ""
+ echo "Valid tests: 0001-$MAX_TEST"
+ echo ""
+ echo " all Runs all tests (default)"
+ echo " -t Run test ID the number amount of times is recommended"
+ echo " -w Watch test ID run until it runs into an error"
+ echo " -s Run test ID once"
+ echo " -c Run test ID x test-count number of times"
+ echo " -l List all test ID list"
+ echo " -h|--help Help"
+ echo
+ echo "If an error every occurs execution will immediately terminate."
+ echo "If you are adding a new test try using -w <test-ID> first to"
+ echo "make sure the test passes a series of tests."
+ echo
+ echo Example uses:
+ echo
+ echo "$TEST_NAME.sh -- executes all tests"
+ echo "$TEST_NAME.sh -t 0008 -- Executes test ID 0008 number of times is recomended"
+ echo "$TEST_NAME.sh -w 0008 -- Watch test ID 0008 run until an error occurs"
+ echo "$TEST_NAME.sh -s 0008 -- Run test ID 0008 once"
+ echo "$TEST_NAME.sh -c 0008 3 -- Run test ID 0008 three times"
+ echo
+ list_tests
+ exit 1
+}
+
+function test_num()
+{
+ re='^[0-9]+$'
+ if ! [[ $1 =~ $re ]]; then
+ usage
+ fi
+}
+
+function get_test_count()
+{
+ test_num $1
+ TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
+ LAST_TWO=${TEST_DATA#*:*}
+ echo ${LAST_TWO%:*}
+}
+
+function get_test_enabled()
+{
+ test_num $1
+ TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
+ echo ${TEST_DATA#*:*:}
+}
+
+function run_all_tests()
+{
+ for i in $ALL_TESTS ; do
+ TEST_ID=${i%:*:*}
+ ENABLED=$(get_test_enabled $TEST_ID)
+ TEST_COUNT=$(get_test_count $TEST_ID)
+ if [[ $ENABLED -eq "1" ]]; then
+ test_case $TEST_ID $TEST_COUNT
+ fi
+ done
+}
+
+function watch_log()
+{
+ if [ $# -ne 3 ]; then
+ clear
+ fi
+ date
+ echo "Running test: $2 - run #$1"
+}
+
+function watch_case()
+{
+ i=0
+ while [ 1 ]; do
+
+ if [ $# -eq 1 ]; then
+ test_num $1
+ watch_log $i ${TEST_NAME}_test_$1
+ ${TEST_NAME}_test_$1
+ else
+ watch_log $i all
+ run_all_tests
+ fi
+ let i=$i+1
+ done
+}
+
+function test_case()
+{
+ NUM_TESTS=$DEFAULT_NUM_TESTS
+ if [ $# -eq 2 ]; then
+ NUM_TESTS=$2
+ fi
+
+ i=0
+ while [ $i -lt $NUM_TESTS ]; do
+ test_num $1
+ watch_log $i ${TEST_NAME}_test_$1 noclear
+ RUN_TEST=${TEST_NAME}_test_$1
+ $RUN_TEST
+ let i=$i+1
+ done
+}
+
+function parse_args()
+{
+ if [ $# -eq 0 ]; then
+ run_all_tests
+ else
+ if [[ "$1" = "all" ]]; then
+ run_all_tests
+ elif [[ "$1" = "-w" ]]; then
+ shift
+ watch_case $@
+ elif [[ "$1" = "-t" ]]; then
+ shift
+ test_num $1
+ test_case $1 $(get_test_count $1)
+ elif [[ "$1" = "-c" ]]; then
+ shift
+ test_num $1
+ test_num $2
+ test_case $1 $2
+ elif [[ "$1" = "-s" ]]; then
+ shift
+ test_case $1 1
+ elif [[ "$1" = "-l" ]]; then
+ list_tests
+ elif [[ "$1" = "-h" || "$1" = "--help" ]]; then
+ usage
+ else
+ usage
+ fi
+ fi
+}
+
+test_reqs
+load_req_mod
+allow_user_defaults
+
+trap "test_finish" EXIT
+
+parse_args $@
+
+exit 0
--
2.11.0