test: add sysdata loader tester
This add a load tester for the new extensible sysdata
file loader, part firmware_class. The usermode helper
can be safely ignored here.
The sysdata API one main dynamic test tmain interfaces.
Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
diff --git a/lib/Makefile b/lib/Makefile
index 7bd6fd4..5d1e97e 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -47,7 +47,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_FIRMWARE) += test_firmware.o test_sysdata.o
obj-$(CONFIG_TEST_KASAN) += test_kasan.o
obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o
obj-$(CONFIG_TEST_LKM) += test_module.o
diff --git a/lib/test_sysdata.c b/lib/test_sysdata.c
new file mode 100644
index 0000000..2e9abad
--- /dev/null
+++ b/lib/test_sysdata.c
@@ -0,0 +1,1055 @@
+/*
+ * System Data test interface
+ *
+ * Copyright (C) 2016 Luis R. Rodriguez <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of 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(®_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, ®_test_devs);
+ dev_info(test_dev->dev, "interface ready\n");
+
+ num_test_devs++;
+
+ mutex_unlock(®_dev_mutex);
+
+out:
+ return rc;
+}
+
+static void do_init_work(struct work_struct *work);
+static DECLARE_WORK(init_work, do_init_work);
+
+static void do_init_work(struct work_struct *work)
+{
+ int rc;
+ ssleep(3);
+ rc = register_test_dev_sysdata();
+ if (rc)
+ pr_err("Cannot add first test sysdata device\n");
+}
+
+/* Let's not clobber init large allocations */
+static int __init test_sysdata_init(void)
+{
+ schedule_work(&init_work);
+ return 0;
+}
+
+module_init(test_sysdata_init);
+
+static void __exit test_sysdata_exit(void)
+{
+ struct sysdata_test_device *test_dev, *tmp;
+
+ mutex_lock(®_dev_mutex);
+ list_for_each_entry_safe(test_dev, tmp, ®_test_devs, list) {
+ list_del(&test_dev->list);
+ unregister_test_dev_sysdata(test_dev);
+ }
+ mutex_unlock(®_dev_mutex);
+}
+
+module_exit(test_sysdata_exit);
+
+MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/tools/testing/selftests/firmware/sysdata.sh b/tools/testing/selftests/firmware/sysdata.sh
new file mode 100755
index 0000000..7bc8f9f
--- /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