[PATCH v7 3/5] test: add new driver_data load tester

From: Luis R. Rodriguez
Date: Tue May 02 2017 - 04:49:46 EST


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@xxxxxxxxxx>
---
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@xxxxxxxxxxxxxxx
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..488cc6e9eed4
--- /dev/null
+++ b/lib/test_driver_data.c
@@ -0,0 +1,1270 @@
+/*
+ * Driver data test interface
+ *
+ * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@xxxxxxxxxx>
+ *
+ * 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,
+ DRIVER_DATA_REQ_OPTIONAL |
+ DRIVER_DATA_REQ_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),
+ .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;
+ 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@xxxxxxxxxx>");
+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@xxxxxxxxxx>
+#
+# 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