[PATCH v2 5/8] test: add new sysdata_file_request*() loader tester

From: Luis R. Rodriguez
Date: Thu Jun 16 2016 - 20:00:42 EST


This adds a load tester driver test_sysdata a for the new
extensible sysdata file loader APIs, part of firmware_class.
Since the usermode helper is completely ignored by the sysdata
API the testing is much easier to do.

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

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

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

Signed-off-by: Luis R. Rodriguez <mcgrof@xxxxxxxxxx>
---
lib/Kconfig.debug | 12 +
lib/Makefile | 1 +
lib/test_sysdata.c | 1046 +++++++++++++++++++++++++++
tools/testing/selftests/firmware/sysdata.sh | 633 ++++++++++++++++
4 files changed, 1692 insertions(+)
create mode 100644 lib/test_sysdata.c
create mode 100755 tools/testing/selftests/firmware/sysdata.sh

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 0f9981999a27..ecd60a40e646 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1979,6 +1979,18 @@ config TEST_FIRMWARE

If unsure, say N.

+config TEST_SYSDATA
+ tristate "Test system data loading via sysdata APIs"
+ default n
+ depends on FW_LOADER
+ help
+ This builds the "test_sysdata" module that creates a userspace
+ interface for testing system data file loading using the sysdata API.
+ This can be used to control the triggering of system data file
+ 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 f6455db094e3..c0d0e096a947 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_TEST_HEXDUMP) += test_hexdump.o
obj-y += kstrtox.o
obj-$(CONFIG_TEST_BPF) += test_bpf.o
obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o
+obj-$(CONFIG_TEST_SYSDATA) += test_sysdata.o
obj-$(CONFIG_TEST_HASH) += test_hash.o
obj-$(CONFIG_TEST_KASAN) += test_kasan.o
obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o
diff --git a/lib/test_sysdata.c b/lib/test_sysdata.c
new file mode 100644
index 000000000000..3ad0e0c0f6c3
--- /dev/null
+++ b/lib/test_sysdata.c
@@ -0,0 +1,1046 @@
+/*
+ * System Data test interface
+ *
+ * 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 the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ *
+ * This module provides an interface to trigger and test system 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/sysdata.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_SYSDATA "test-sysdata.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 sysdata API
+ *
+ * @name: the name of the primary sysdata 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
+ * sysdata_file_request_async(). If false the synchronous call will
+ * be used, sysdata_file_request().
+ * @optional: whether or not the sysdata is optional refer to the
+ * struct sysdata_file_desc @optional field for more information.
+ * @keep: whether or not we wish to free the sysdata on our own, refer to
+ * the struct sysdata_file_desc @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
+ * sysdata_file_desc as this is implementation specific, and in
+ * in sysdata API its explicit if you had defined an optional call
+ * back for your descriptor with either SYSDATA_SYNC_OPT_CB() or
+ * SYSDATA_ASYNC_OPT_CB(). Since the descriptor is a const we have
+ * no option but to use a flag and two const structs to decide which
+ * one we should use.
+ * @test_result: a test may use this to collect the result from the call
+ * of the sysdata_file_request_async() or sysdata_file_request() calls
+ * used in their tests. Note that for async calls this typically will
+ * be a successful result (0) unless of course you've have sent in
+ * a bogus descriptor, 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 descriptor 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 descriptor or request
+ * -ENOENT: files not found
+ *
+ * System environment:
+ *
+ * -ENOMEM: memory pressure on system
+ * -ENODEV: out of number of devices to test
+ *
+ * The ordering of elements in this struct must match the exact order of the
+ * elements in the ATTRIBUTE_GROUPS(test_dev_config), this is done to know
+ * what corresponding field each device attribute configuration entry maps
+ * to what struct member on test_alloc_dev_attrs().
+ */
+struct test_config {
+ char *name;
+ char *default_name;
+ bool async;
+ bool optional;
+ bool keep;
+ bool enable_opt_cb;
+
+ int test_result;
+};
+
+/**
+ * test_sysdata_private - private device driver sysdata representation
+ *
+ * @size: size of the data copied, in bytes
+ * @data: the actual data we copied over from sysdata
+ * @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_sysdata_private {
+ size_t size;
+ u8 *data;
+ bool written;
+};
+
+/**
+ * sysdata_test_device - test device to help test sysdata
+ *
+ * @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
+ * sysdata API and we to let you tune them in userspace. We then just
+ * provide one trigger.
+ * @test_sysdata: internal private representation of a storage area
+ * a driver might typically use to stuff firmware / sysdata.
+ * @misc_dev: we use a misc device under the hood
+ * @dev: pointer to misc_dev's own struct device
+ * @sysdata_mutex: for access into the @sysdata, the fake storage location for
+ * the system data we copy.
+ * @config_mutex:
+ * @trigger_mutex: all triggers are mutually exclusive when testing. To help
+ * enable testing you can create a different device, each device has its
+ * own set of protections, mimicking real devices.
+ * list: needed to be part of the reg_test_devs
+ */
+struct sysdata_test_device {
+ int dev_idx;
+ struct test_config config;
+ struct test_sysdata_private test_sysdata;
+ struct miscdevice misc_dev;
+ struct device *dev;
+
+ struct mutex sysdata_mutex;
+ struct mutex config_mutex;
+ struct mutex trigger_mutex;
+ struct list_head list;
+};
+
+static struct miscdevice *dev_to_misc_dev(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static
+struct sysdata_test_device *misc_dev_to_test_dev(struct miscdevice *misc_dev)
+{
+ return container_of(misc_dev, struct sysdata_test_device, misc_dev);
+}
+
+static struct sysdata_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 sysdata_test_device *test_dev = misc_dev_to_test_dev(misc_dev);
+ struct test_sysdata_private *test_sysdata = &test_dev->test_sysdata;
+ ssize_t rc = 0;
+
+ mutex_lock(&test_dev->sysdata_mutex);
+ if (test_sysdata->written)
+ rc = simple_read_from_buffer(buf, size, offset,
+ test_sysdata->data,
+ test_sysdata->size);
+ mutex_unlock(&test_dev->sysdata_mutex);
+
+ return rc;
+}
+
+static const struct file_operations test_fw_fops = {
+ .owner = THIS_MODULE,
+ .read = test_fw_misc_read,
+};
+
+static void free_test_sysdata(struct test_sysdata_private *test_sysdata)
+{
+ kfree(test_sysdata->data);
+ test_sysdata->data = NULL;
+ test_sysdata->size = 0;
+ test_sysdata->written = false;
+}
+
+static int test_load_sysdata(struct sysdata_test_device *test_dev,
+ const struct sysdata_file *sysdata)
+{
+ struct test_sysdata_private *test_sysdata = &test_dev->test_sysdata;
+ int ret = 0;
+
+ if (!sysdata)
+ return -ENOENT;
+
+ mutex_lock(&test_dev->sysdata_mutex);
+
+ free_test_sysdata(test_sysdata);
+
+ test_sysdata->data = kzalloc(sysdata->size, GFP_KERNEL);
+ if (!test_sysdata->data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(test_sysdata->data, sysdata->data, sysdata->size);
+ test_sysdata->size = sysdata->size;
+ test_sysdata->written = true;
+
+ dev_info(test_dev->dev, "loaded: %zu\n", test_sysdata->size);
+
+out:
+ mutex_unlock(&test_dev->sysdata_mutex);
+
+ return ret;
+}
+
+static int sync_found_cb(void *context, const struct sysdata_file *sysdata)
+{
+ struct sysdata_test_device *test_dev = context;
+ int ret;
+
+ ret = test_load_sysdata(test_dev, sysdata);
+ if (ret)
+ dev_info(test_dev->dev, "unable to write sysdata: %d\n", ret);
+ return ret;
+}
+
+static ssize_t config_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sysdata_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 += sprintf(buf, "Custom trigger configuration for: %s\n",
+ dev_name(dev));
+
+ if (config->default_name)
+ len += sprintf(buf+len, "default name:\t%s\n",
+ config->default_name);
+ else
+ len += sprintf(buf+len, "default name:\tEMTPY\n");
+
+ if (config->name)
+ len += sprintf(buf+len, "name:\t\t%s\n", config->name);
+ else
+ len += sprintf(buf+len, "name:\t\tEMPTY\n");
+
+ len += sprintf(buf+len, "type:\t\t%s\n",
+ config->async ? "async" : "sync");
+ len += sprintf(buf+len, "optional:\t%s\n",
+ config->optional ? "true" : "false");
+ len += sprintf(buf+len, "enable_opt_cb:\t%s\n",
+ config->enable_opt_cb ? "true" : "false");
+ len += sprintf(buf+len, "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 sysdata_test_device *test_dev,
+ const struct sysdata_file *sysdata)
+{
+ struct test_config *config = &test_dev->config;
+ int ret;
+
+ ret = test_load_sysdata(test_dev, sysdata);
+ if (ret) {
+ if (!config->optional)
+ dev_info(test_dev->dev, "unable to write sysdata\n");
+ }
+ if (config->keep) {
+ release_sysdata_file(sysdata);
+ sysdata = NULL;
+ }
+ return ret;
+}
+
+static int config_req_default(struct sysdata_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+ int rc;
+ /*
+ * 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 sysdata_file_desc sysdata_desc = {
+ SYSDATA_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);
+
+ rc = sysdata_file_request(config->default_name,
+ &sysdata_desc, test_dev->dev);
+ if (rc)
+ dev_info(test_dev->dev,
+ "load of default '%s' failed: %d\n",
+ config->default_name, rc);
+
+ return rc;
+}
+
+/*
+ * This is the default sync fallback callback, as a fallback this
+ * then uses a sync request.
+ */
+static int config_sync_req_default_cb(void *context)
+{
+ struct sysdata_test_device *test_dev = context;
+ int rc;
+
+ rc = config_req_default(test_dev);
+
+ return rc;
+
+ /* Leave all the error checking for the main caller */
+}
+
+/*
+ * This is the default config->async fallback callback, as a fallback this
+ * then uses a sync request.
+ */
+static void config_async_req_default_cb(void *context)
+{
+ struct sysdata_test_device *test_dev = context;
+
+ config_req_default(test_dev);
+
+ /* Leave all the error checking for the main caller */
+}
+
+static int config_sync_req_cb(void *context,
+ const struct sysdata_file *sysdata)
+{
+ struct sysdata_test_device *test_dev = context;
+
+ return config_load_data(test_dev, sysdata);
+}
+
+static int trigger_config_sync(struct sysdata_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+ int rc;
+ const struct sysdata_file_desc sysdata_desc_default = {
+ SYSDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+ .optional = config->optional,
+ .keep = config->keep,
+ };
+ const struct sysdata_file_desc sysdata_desc_opt_cb = {
+ SYSDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+ SYSDATA_SYNC_OPT_CB(config_sync_req_default_cb, test_dev),
+ .optional = config->optional,
+ .keep = config->keep,
+ };
+ const struct sysdata_file_desc *sysdata_desc;
+
+ if (config->enable_opt_cb)
+ sysdata_desc = &sysdata_desc_opt_cb;
+ else
+ sysdata_desc = &sysdata_desc_default;
+
+ rc = sysdata_file_request(config->name, sysdata_desc, test_dev->dev);
+ if (rc)
+ dev_err(test_dev->dev, "sync load of '%s' failed: %d\n",
+ config->name, rc);
+
+ return rc;
+}
+
+static void config_async_req_cb(const struct sysdata_file *sysdata,
+ void *context)
+{
+ struct sysdata_test_device *test_dev = context;
+ config_load_data(test_dev, sysdata);
+}
+
+static int trigger_config_async(struct sysdata_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+ int rc;
+ const struct sysdata_file_desc sysdata_desc_default = {
+ SYSDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+ .sync_reqs.mode = config->async ?
+ SYNCDATA_ASYNC : SYNCDATA_SYNC,
+ .optional = config->optional,
+ .keep = config->keep,
+ };
+ const struct sysdata_file_desc sysdata_desc_opt_cb = {
+ SYSDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+ SYSDATA_ASYNC_OPT_CB(config_async_req_default_cb, test_dev),
+ .sync_reqs.mode = config->async ?
+ SYNCDATA_ASYNC : SYNCDATA_SYNC,
+ .optional = config->optional,
+ .keep = config->keep,
+ };
+ const struct sysdata_file_desc *sysdata_desc;
+ async_cookie_t async_cookie;
+
+ if (config->enable_opt_cb)
+ sysdata_desc = &sysdata_desc_opt_cb;
+ else
+ sysdata_desc = &sysdata_desc_default;
+
+ rc = sysdata_file_request_async(config->name, sysdata_desc,
+ test_dev->dev,
+ &async_cookie);
+ if (rc) {
+ dev_err(test_dev->dev, "async load of '%s' failed: %d\n",
+ config->name, rc);
+ goto out;
+ }
+
+ sysdata_synchronize_request(async_cookie);
+out:
+ return rc;
+}
+
+static ssize_t
+trigger_config_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sysdata_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_sysdata_private *test_sysdata = &test_dev->test_sysdata;
+ struct test_config *config = &test_dev->config;
+ int rc;
+
+ mutex_lock(&test_dev->trigger_mutex);
+ mutex_lock(&test_dev->config_mutex);
+
+ dev_info(dev, "loading '%s'\n", config->name);
+
+ if (config->async)
+ rc = trigger_config_async(test_dev);
+ else
+ rc = trigger_config_sync(test_dev);
+
+ config->test_result = rc;
+
+ if (rc)
+ goto out;
+
+ if (test_sysdata->written) {
+ dev_info(dev, "loaded: %zu\n", test_sysdata->size);
+ rc = count;
+ } else {
+ dev_err(dev, "failed to load firmware\n");
+ rc = -ENODEV;
+ }
+
+out:
+ mutex_unlock(&test_dev->config_mutex);
+ mutex_unlock(&test_dev->trigger_mutex);
+
+ return rc;
+}
+static DEVICE_ATTR_WO(trigger_config);
+
+/*
+ * XXX: move to kstrncpy() once merged.
+ *
+ * Users should use kfree_const() when freeing these.
+ */
+static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp)
+{
+ *dst = kstrndup(name, count, gfp);
+ if (!*dst)
+ return -ENOSPC;
+ return count;
+}
+
+static int config_copy_name(struct test_config *config,
+ const char *name,
+ size_t count)
+{
+ return __kstrncpy(&config->name, name, count, GFP_KERNEL);
+}
+
+static int config_copy_default_name(struct test_config *config,
+ const char *name,
+ size_t count)
+{
+ return __kstrncpy(&config->default_name, name, count, GFP_KERNEL);
+}
+
+static void __sysdata_config_free(struct test_config *config)
+{
+ kfree_const(config->name);
+ config->name = NULL;
+ kfree_const(config->default_name);
+ config->default_name = NULL;
+}
+
+static void sysdata_config_free(struct sysdata_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+
+ mutex_lock(&test_dev->config_mutex);
+ __sysdata_config_free(config);
+ mutex_unlock(&test_dev->config_mutex);
+}
+
+static int __sysdata_config_init(struct test_config *config)
+{
+ int ret;
+
+ ret = config_copy_name(config, TEST_SYSDATA, strlen(TEST_SYSDATA));
+ if (ret < 0)
+ goto out;
+
+ ret = config_copy_default_name(config, TEST_SYSDATA,
+ strlen(TEST_SYSDATA));
+ if (ret < 0)
+ goto out;
+
+ config->async = false;
+ config->optional = false;
+ config->keep = false;
+ config->enable_opt_cb = false;
+ config->test_result = 0;
+
+out:
+ return ret;
+}
+
+int sysdata_config_init(struct sysdata_test_device *test_dev)
+{
+ struct test_config *config = &test_dev->config;
+ int ret;
+
+ mutex_lock(&test_dev->config_mutex);
+ ret = __sysdata_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 sysdata_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+ int rc;
+
+ mutex_lock(&test_dev->config_mutex);
+ rc = config_copy_name(config, buf, count);
+ mutex_unlock(&test_dev->config_mutex);
+
+ return rc;
+}
+
+static ssize_t config_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sysdata_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ mutex_lock(&test_dev->config_mutex);
+ strcpy(buf, config->name);
+ strcat(buf, "\n");
+ mutex_unlock(&test_dev->config_mutex);
+
+ return strlen(buf) + 1;
+}
+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 sysdata_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+ int rc;
+
+ mutex_lock(&test_dev->config_mutex);
+ rc = config_copy_default_name(config, buf, count);
+ mutex_unlock(&test_dev->config_mutex);
+
+ return rc;
+}
+
+static ssize_t config_default_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sysdata_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ mutex_lock(&test_dev->config_mutex);
+ strcpy(buf, config->default_name);
+ strcat(buf, "\n");
+ mutex_unlock(&test_dev->config_mutex);
+
+ return strlen(buf) + 1;
+}
+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 sysdata_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->sysdata_mutex);
+ free_test_sysdata(&test_dev->test_sysdata);
+ mutex_unlock(&test_dev->sysdata_mutex);
+
+ mutex_lock(&test_dev->config_mutex);
+
+ __sysdata_config_free(config);
+
+ ret = __sysdata_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 sysdata_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 sysdata_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 sysdata_test_device *test_dev,
+ const char *buf, size_t size,
+ int *config)
+{
+ char *end;
+ long new = simple_strtol(buf, &end, 0);
+ if (end == buf || new > INT_MAX || new < INT_MIN)
+ return -EINVAL;
+ mutex_lock(&test_dev->config_mutex);
+ *(int *)config = new;
+ mutex_unlock(&test_dev->config_mutex);
+ /* Always return full write size even if we didn't consume all */
+ return size;
+}
+
+static ssize_t test_dev_config_show_int(struct sysdata_test_device *test_dev,
+ char *buf,
+ int config)
+{
+ int val;
+
+ mutex_lock(&test_dev->config_mutex);
+ val = config;
+ mutex_unlock(&test_dev->config_mutex);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t config_async_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sysdata_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 sysdata_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 sysdata_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 sysdata_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 sysdata_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 sysdata_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 sysdata_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 sysdata_test_device *test_dev = dev_to_test_dev(dev);
+ struct test_config *config = &test_dev->config;
+
+ return test_dev_config_show_bool(test_dev, buf,
+ config->enable_opt_cb);
+}
+static DEVICE_ATTR(config_enable_opt_cb, 0644,
+ config_enable_opt_cb_show,
+ config_enable_opt_cb_store);
+
+static ssize_t test_result_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sysdata_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 sysdata_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 SYSDATA_DEV_ATTR(name) &dev_attr_##name.attr
+
+static struct attribute *test_dev_attrs[] = {
+ SYSDATA_DEV_ATTR(trigger_config),
+ SYSDATA_DEV_ATTR(config),
+ SYSDATA_DEV_ATTR(reset),
+
+ SYSDATA_DEV_ATTR(config_name),
+ SYSDATA_DEV_ATTR(config_default_name),
+ SYSDATA_DEV_ATTR(config_async),
+ SYSDATA_DEV_ATTR(config_optional),
+ SYSDATA_DEV_ATTR(config_keep),
+ SYSDATA_DEV_ATTR(config_enable_opt_cb),
+ SYSDATA_DEV_ATTR(test_result),
+
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(test_dev);
+
+/*
+ * XXX: this could perhaps be made generic already too, but a hunt
+ * for actual users would be needed first. It could be generic
+ * if other test drivers end up using a similar mechanism.
+ */
+const char *test_dev_get_name(const char *base, int idx, gfp_t gfp)
+{
+ const char *name_const;
+ char *name;
+
+ if (!base)
+ return NULL;
+ if (strlen(base) > 30)
+ return NULL;
+ name = kzalloc(1024, gfp);
+ if (!name)
+ return NULL;
+
+ strncat(name, base, strlen(base));
+ sprintf(name+(strlen(base)), "%d", idx);
+ name_const = kstrdup_const(name, gfp);
+
+ kfree(name);
+
+ return name_const;
+}
+
+void free_test_dev_sysdata(struct sysdata_test_device *test_dev)
+{
+ kfree_const(test_dev->misc_dev.name);
+ test_dev->misc_dev.name = NULL;
+ vfree(test_dev);
+ test_dev = NULL;
+ sysdata_config_free(test_dev);
+}
+
+void unregister_test_dev_sysdata(struct sysdata_test_device *test_dev)
+{
+ dev_info(test_dev->dev, "removing interface\n");
+ misc_deregister(&test_dev->misc_dev);
+ free_test_dev_sysdata(test_dev);
+}
+
+struct sysdata_test_device *alloc_test_dev_sysdata(int idx)
+{
+ int rc;
+ struct sysdata_test_device *test_dev;
+ struct miscdevice *misc_dev;
+
+ test_dev = vmalloc(sizeof(struct sysdata_test_device));
+ if (!test_dev) {
+ pr_err("Cannot alloc test_dev\n");
+ goto err_out;
+ }
+
+ mutex_init(&test_dev->sysdata_mutex);
+ mutex_init(&test_dev->config_mutex);
+ mutex_init(&test_dev->trigger_mutex);
+
+ rc = sysdata_config_init(test_dev);
+ if (rc < 0) {
+ pr_err("Cannot alloc sysdata_config_init()\n");
+ goto err_out_free;
+ }
+
+ test_dev->dev_idx = idx;
+ misc_dev = &test_dev->misc_dev;
+
+ misc_dev->minor = MISC_DYNAMIC_MINOR;
+ misc_dev->name = test_dev_get_name("test_sysdata", test_dev->dev_idx,
+ GFP_KERNEL);
+ if (!misc_dev->name) {
+ pr_err("Cannot alloc misc_dev->name\n");
+ goto err_out_free_config;
+ }
+ misc_dev->fops = &test_fw_fops;
+ misc_dev->groups = test_dev_groups;
+
+ return test_dev;
+
+err_out_free_config:
+ __sysdata_config_free(&test_dev->config);
+err_out_free:
+ kfree(test_dev);
+err_out:
+ return NULL;
+}
+
+static int register_test_dev_sysdata(void)
+{
+ struct sysdata_test_device *test_dev = NULL;
+ int rc = -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_sysdata(num_test_devs);
+ if (!test_dev) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ rc = misc_register(&test_dev->misc_dev);
+ if (rc) {
+ pr_err("could not register misc device: %d\n", rc);
+ free_test_dev_sysdata(test_dev);
+ return rc;
+ }
+
+ 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++;
+
+ mutex_unlock(&reg_dev_mutex);
+
+out:
+ return rc;
+}
+
+static int __init test_sysdata_init(void)
+{
+ int rc;
+
+ rc = register_test_dev_sysdata();
+ if (rc)
+ pr_err("Cannot add first test sysdata device\n");
+
+ return rc;
+}
+late_initcall(test_sysdata_init);
+
+static void __exit test_sysdata_exit(void)
+{
+ struct sysdata_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_sysdata(test_dev);
+ }
+ mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_sysdata_exit);
+
+MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@xxxxxxxxxx>");
+MODULE_LICENSE("GPL");
diff --git a/tools/testing/selftests/firmware/sysdata.sh b/tools/testing/selftests/firmware/sysdata.sh
new file mode 100755
index 000000000000..7bc8f9ff8f7d
--- /dev/null
+++ b/tools/testing/selftests/firmware/sysdata.sh
@@ -0,0 +1,633 @@
+#!/bin/bash
+
+# This performs a series tests against firmware_class to excercise the
+# firmware_class driver with focus only on the extensible system data API.
+#
+# To make this test self contained, and note pollute your distribution
+# firmware install paths, we reset the custom load directory to a
+# temporary location.
+
+set -e
+
+DIR=/sys/devices/virtual/misc/test_sysdata0/
+
+if [ ! -d $DIR ]; then
+ modprobe test_sysdata
+ if [ ! -d $DIR ]; then
+ echo "$0: $DIR not present"
+ echo "You must have CONFIG_TEST_FIRMWARE=m or CONFIG_TEST_FIRMWARE=y"
+ exit 1
+ fi
+fi
+
+OLD_FWPATH=$(cat /sys/module/firmware_class/parameters/path)
+
+FWPATH=$(mktemp -d)
+DEFAULT_SYSDATA="test-sysdata.bin"
+FW="$FWPATH/$DEFAULT_SYSDATA"
+
+test_reqs()
+{
+ if ! which diff 2> /dev/null > /dev/null; then
+ echo "$0: You need diff installed"
+ exit 1
+ fi
+}
+
+test_finish()
+{
+ echo -n "$OLD_PATH" >/sys/module/firmware_class/parameters/path
+ rm -f "$FW"
+ rmdir "$FWPATH"
+}
+
+trap "test_finish" EXIT
+
+# Set the kernel search path.
+echo -n "$FWPATH" >/sys/module/firmware_class/parameters/path
+
+# This is an unlikely real-world firmware content. :)
+echo "ABCD0123" >"$FW"
+
+NAME=$(basename "$FW")
+
+errno_name_to_val()
+{
+ case "$1" in
+ SUCCESS)
+ echo 0;;
+ -EPERM)
+ echo -1;;
+ -ENOENT)
+ echo -2;;
+ -EINVAL)
+ echo -22;;
+ -ERR_ANY)
+ echo -123456;;
+ *)
+ echo invalid;;
+ esac
+}
+
+errno_val_to_name()
+ case "$1" in
+ 0)
+ echo SUCCESS;;
+ -1)
+ echo -EPERM;;
+ -2)
+ echo -ENOENT;;
+ -22)
+ echo -EINVAL;;
+ -123456)
+ echo -ERR_ANY;;
+ *)
+ echo invalid;;
+ esac
+
+config_set_async()
+{
+ if ! echo -n 1 >$DIR/config_async ; then
+ echo "$0: Unable to set to async" >&2
+ exit 1
+ fi
+}
+
+config_disable_async()
+{
+ if ! echo -n 0 >$DIR/config_async ; then
+ echo "$0: Unable to set to sync" >&2
+ exit 1
+ fi
+}
+
+config_set_optional()
+{
+ if ! echo -n 1 >$DIR/config_optional ; then
+ echo "$0: Unable to set to optional" >&2
+ exit 1
+ fi
+}
+
+config_disable_optional()
+{
+ if ! echo -n 0 >$DIR/config_optional ; then
+ echo "$0: Unable to disable optional" >&2
+ exit 1
+ fi
+}
+
+config_set_keep()
+{
+ if ! echo -n 1 >$DIR/config_keep; then
+ echo "$0: Unable to set to keep" >&2
+ exit 1
+ fi
+}
+
+config_disable_keep()
+{
+ if ! echo -n 0 >$DIR/config_keep; then
+ echo "$0: Unable to disable keep option" >&2
+ exit 1
+ fi
+}
+
+config_enable_opt_cb()
+{
+ if ! echo -n 1 >$DIR/config_enable_opt_cb; then
+ echo "$0: Unable to set to optional" >&2
+ exit 1
+ fi
+}
+
+config_disable_opt_cb()
+{
+ if ! echo -n 0 >$DIR/config_enable_opt_cb; then
+ echo "$0: Unable to disable keep option" >&2
+ exit 1
+ fi
+}
+
+
+# For special characters use printf directly,
+# refer to sysdata_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 sysdata_test_0001
+config_set_default_name()
+{
+ if ! echo -n $1 >$DIR/config_default_name; then
+ echo "$0: Unable to set default_name" >&2
+ exit 1
+ fi
+}
+
+config_get_default_name()
+{
+ cat $DIR/config_default_name
+}
+
+config_get_test_result()
+{
+ cat $DIR/test_result
+}
+
+config_reset()
+{
+ if ! echo -n "1" >"$DIR"/reset; then
+ echo "$0: reset shuld have worked" >&2
+ exit 1
+ fi
+}
+
+config_show_config()
+{
+ echo "----------------------------------------------------"
+ cat "$DIR"/config
+ echo "----------------------------------------------------"
+}
+
+config_trigger()
+{
+ if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then
+ echo "$1: FAIL - loading should have worked"
+ config_show_config
+ exit 1
+ fi
+ echo "$1: OK! - loading sysdata"
+}
+
+config_trigger_want_fail()
+{
+ if echo "1" > $DIR/trigger_config 2>/dev/null; then
+ echo "$1: FAIL - loading was expected to fail"
+ config_show_config
+ exit 1
+ fi
+ echo "$1: OK! - loading failed as expected"
+}
+
+config_file_should_match()
+{
+ FILE=$(config_get_name)
+ # On this one we expect the file to exist so leave stderr in
+ if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_sysdata0 > /dev/null) > /dev/null; then
+ echo "$1: FAIL - file $FILE did not match contents in /dev/test_sysdata0" >&2
+ config_show_config
+ exit 1
+ fi
+ echo "$1: OK! - $FILE == /dev/test_sysdata0"
+}
+
+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_sysdata0 > /dev/null) > /dev/null; then
+ echo "$1: FAIL - file $FILE did not match contents in /dev/test_sysdata0" >&2
+ config_show_config
+ exit 1
+ fi
+ echo "$1: OK! - $FILE == /dev/test_sysdata0"
+}
+
+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_sysdata0 2> /dev/null) 2> /dev/null ; then
+ echo "$1: FAIL - file $FILE was not expected to match /dev/null" >&2
+ config_show_config
+ exit 1
+ fi
+ echo "$1: OK! - $FILE != /dev/test_sysdata0"
+}
+
+config_default_file_should_match()
+{
+ FILE=$(config_get_default_name)
+ diff -q $FWPATH/$FILE /dev/test_sysdata0 2> /dev/null
+ if ! $? ; then
+ echo "$1: FAIL - file $FILE expected to match /dev/test_sysdata0" >&2
+ config_show_config
+ 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_sysdata0 2> /dev/null
+ if $? 2> /dev/null ; then
+ echo "$1: FAIL - file $FILE was not expected to match test_sysdata0" >&2
+ config_show_config
+ 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
+ exit 1
+ fi
+ elif [[ $RC != $ERRNO ]]; then
+ echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got $RC_NAME ($RC)" >&2
+ config_show_config
+ exit 1
+ fi
+ echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME"
+}
+
+sysdata_set_sync_defaults()
+{
+ config_reset
+}
+
+sysdata_set_async_defaults()
+{
+ config_reset
+ config_set_async
+}
+
+sysdata_test_0001s()
+{
+ NAME='\000'
+
+ sysdata_set_sync_defaults
+ config_set_name $NAME
+ printf '\000' >"$DIR"/config_name
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+sysdata_test_0001a()
+{
+ NAME='\000'
+
+ sysdata_set_async_defaults
+ printf '\000' >"$DIR"/config_name
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+sysdata_test_0001()
+{
+ sysdata_test_0001s
+ sysdata_test_0001a
+}
+
+sysdata_test_0002s()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_set_sync_defaults
+ config_set_name ${FUNCNAME[0]}
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+sysdata_test_0002a()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_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
+}
+
+sysdata_test_0002()
+{
+ #sysdata_test_0002s
+ sysdata_test_0002a
+}
+
+sysdata_test_0003()
+{
+ config_reset
+ config_file_should_not_match ${FUNCNAME[0]}
+}
+
+sysdata_test_0004s()
+{
+ TEST="sysdata_test_0004s"
+
+ sysdata_set_sync_defaults
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+sysdata_test_0004a()
+{
+ sysdata_set_async_defaults
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+sysdata_test_0004()
+{
+ sysdata_test_0004s
+ sysdata_test_0004a
+}
+
+sysdata_test_0005s()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_set_sync_defaults
+ config_set_optional
+ config_set_name $NAME
+ config_trigger_want_fail ${FUNCNAME[0]}
+ # We do this to ensure the default backup callback hasn't
+ # been called yet
+ config_file_should_not_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+sysdata_test_0005a()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_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
+}
+
+sysdata_test_0005()
+{
+ sysdata_test_0005s
+ sysdata_test_0005a
+}
+
+sysdata_test_0006s()
+{
+ sysdata_set_sync_defaults
+ config_set_optional
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+sysdata_test_0006a()
+{
+ sysdata_set_async_defaults
+ config_set_optional
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+sysdata_test_0006()
+{
+ sysdata_test_0006s
+ sysdata_test_0006a
+}
+
+sysdata_test_0007s()
+{
+ sysdata_set_sync_defaults
+ config_set_keep
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+sysdata_test_0007a()
+{
+ sysdata_set_async_defaults
+ config_set_keep
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+sysdata_test_0007()
+{
+ sysdata_test_0007s
+ sysdata_test_0007a
+}
+
+sysdata_test_0008s()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_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
+}
+
+sysdata_test_0008a()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_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
+}
+
+sysdata_test_0008()
+{
+ sysdata_test_0008s
+ sysdata_test_0008a
+}
+
+sysdata_test_0009s()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_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
+}
+
+sysdata_test_0009a()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_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
+}
+
+sysdata_test_0009()
+{
+ sysdata_test_0009s
+ sysdata_test_0009a
+}
+
+sysdata_test_0010s()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_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
+}
+
+sysdata_test_0010a()
+{
+ NAME="nope-$DEFAULT_SYSDATA"
+
+ sysdata_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
+}
+
+sysdata_test_0010()
+{
+ sysdata_test_0010s
+ sysdata_test_0010a
+}
+
+test_reqs
+
+usage()
+{
+ echo "Usage: $0 [ -t <4-number-digit> ]"
+ echo Valid tests: 0001-0005
+ echo
+ echo 0001 - Empty string should be ignored
+ echo 0002 - Files that do not exist should be ignored
+ echo 0003 - Verify test_sysdata0 has nothing loaded upon reset
+ echo 0004 - Simple sync and async loader
+ echo 0005 - Verify optional loading is not fatal
+ echo 0006 - Verify optional loading enables loading
+ echo 0007 - Verify keep works
+ echo 0008 - Verify optional callback works
+ echo 0009 - Verify optional callback works, keep
+ echo 0010 - Verify when fallback file is not present
+ exit 1
+}
+
+# You can ask for a specific test:
+if [[ $# > 0 ]] ; then
+ if [[ $1 != "-t" ]]; then
+ usage
+ fi
+
+ re='^[0-9]+$'
+ if ! [[ $2 =~ $re ]]; then
+ usage
+ fi
+
+ RUN_TEST=sysdata_test_$2
+ $RUN_TEST
+ exit 0
+fi
+
+sysdata_test_0001
+sysdata_test_0002
+sysdata_test_0003
+sysdata_test_0004
+sysdata_test_0005
+sysdata_test_0006
+sysdata_test_0007
+sysdata_test_0008
+sysdata_test_0009
+sysdata_test_0010
+
+exit 0
--
2.8.2