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

AKASHI Takahiro
2017-04-11T08:29:47Z

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

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

> + echo -n 1 > /sys/devices/virtual/misc/test_driver_data0/config_async
> + echo -n 1 > /sys/devices/virtual/misc/test_driver_data0/trigger_config
> +
> +A series of tests have been written to test the driver data API thoroughly.
> +A respective test case is expected to bet written as new features get added.
> +For details of existing tests run::
> +
> + tools/testing/selftests/firmware/driver_data.sh -l
> +
> +To see all available options::
> +
> + tools/testing/selftests/firmware/driver_data.sh --help
> +
> +To run a test 0010 case 40 times::
> +
> + tools/testing/selftests/firmware/driver_data.sh -c 0010 40
> +
> Tracking development enhancements and ideas
> ===========================================
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3f025f738600..a0a81c245fb3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5172,6 +5172,7 @@ L: linux-kernel@vger.kernel.org
> S: Maintained
> F: Documentation/firmware_class/
> F: drivers/base/firmware*.c
> +F: lib/test_driver_data.c
> F: include/linux/firmware.h
> F: include/linux/driver_data.h
>
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index 77fadface4f9..53dfd7db557b 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -1964,6 +1964,18 @@ config TEST_FIRMWARE
>
> If unsure, say N.
>
> +config TEST_DRIVER_DATA
> + tristate "Test driver data loading via driver_data APIs"
> + default n
> + depends on FW_LOADER
> + help
> + This builds the "test_driver_data" module that creates a userspace
> + interface for testing driver data loading using the driver_data API.
> + This can be used to control the triggering of driver data loading
> + without needing an actual real device.
> +
> + If unsure, say N.
> +
> config TEST_UDELAY
> tristate "udelay test driver"
> default n
> diff --git a/lib/Makefile b/lib/Makefile
> index 0f64ef3956bf..d5042ad4dad9 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -50,6 +50,7 @@ obj-y += kstrtox.o
> obj-$(CONFIG_TEST_BPF) += test_bpf.o
> obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o
> obj-$(CONFIG_TEST_HASH) += test_hash.o test_siphash.o
> +obj-$(CONFIG_TEST_DRIVER_DATA) += test_driver_data.o
> obj-$(CONFIG_TEST_KASAN) += test_kasan.o
> obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o
> obj-$(CONFIG_TEST_LIST_SORT) += test_list_sort.o
> diff --git a/lib/test_driver_data.c b/lib/test_driver_data.c
> new file mode 100644
> index 000000000000..11175a3b9f0a
> --- /dev/null
> +++ b/lib/test_driver_data.c
> @@ -0,0 +1,1272 @@
> +/*
> + * Driver data test interface
> + *
> + * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of copyleft-next (version 0.3.1 or later) as published
> + * at http://copyleft-next.org/.

Is this compatible with GPLv2 for kernel modules?

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

Do you have good reasons for "the number of times" here?
> +
> +# Not yet sure how to automate suspend test well yet. For now we expect a
> +# manual run. If using qemu you can resume a guest using something like the
> +# following on the monitor pts.
> +# system_wakeupakeup | socat - /dev/pts/7,raw,echo=0,crnl
> +#ALL_TESTS="$ALL_TESTS 0014:0:1"
> +
> +test_modprobe()
> +{
> + if [ ! -d $DIR ]; then
> + echo "$0: $DIR not present" >&2
> + echo "You must have the following enabled in your kernel:" >&2
> + cat $TEST_DIR/config >&2
> + exit 1
> + fi
> +}
> +
> +function allow_user_defaults()
> +{
> + if [ -z $DEFAULT_NUM_TESTS ]; then
> + DEFAULT_NUM_TESTS=50
> + fi
> +
> + if [ -z $FW_SYSFSPATH ]; then
> + FW_SYSFSPATH="/sys/module/firmware_class/parameters/path"
> + fi
> +
> + if [ -z $OLD_FWPATH ]; then
> + OLD_FWPATH=$(cat $FW_SYSFSPATH)
> + fi
> +
> + if [ -z $FWPATH]; then
> + FWPATH=$(mktemp -d)
> + fi
> +
> + if [ -z $DEFAULT_DRIVER_DATA ]; then
> + config_reset
> + DEFAULT_DRIVER_DATA=$(config_get_name)
> + fi
> +
> + if [ -z $FW ]; then
> + FW="$FWPATH/$DEFAULT_DRIVER_DATA"
> + fi
> +
> + if [ -z $SYS_STATE_PATH ]; then
> + SYS_STATE_PATH="/sys/power/state"
> + fi
> +
> + # Set the kernel search path.
> + echo -n "$FWPATH" > $FW_SYSFSPATH
> +
> + # This is an unlikely real-world firmware content. :)
> + echo "ABCD0123" >"$FW"

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

> +}
> +
> +test_reqs()
> +{
> + if ! which diff 2> /dev/null > /dev/null; then
> + echo "$0: You need diff installed"
> + exit 1
> + fi
> +
> + uid=$(id -u)
> + if [ $uid -ne 0 ]; then
> + echo $msg must be run as root >&2
> + exit 0
> + fi
> +}
> +
> +function load_req_mod()
> +{
> + trap "test_modprobe" EXIT
> +
> + if [ -z $DIR ]; then
> + DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"
> + fi
> +
> + if [ ! -d $DIR ]; then
> + modprobe $TEST_DRIVER
> + fi
> +}
> +
> +test_finish()
> +{
> + echo -n "$OLD_PATH" >/sys/module/firmware_class/parameters/path
> + rm -f "$FW"
> + rmdir "$FWPATH"
> +}
> +
> +errno_name_to_val()
> +{
> + case "$1" in
> + SUCCESS)
> + echo 0;;
> + -EPERM)
> + echo -1;;
> + -ENOENT)
> + echo -2;;
> + -EINVAL)
> + echo -22;;
> + -ERR_ANY)
> + echo -123456;;
> + *)
> + echo invalid;;
> + esac
> +}
> +
> +errno_val_to_name()
> + case "$1" in
> + 0)
> + echo SUCCESS;;
> + -1)
> + echo -EPERM;;
> + -2)
> + echo -ENOENT;;
> + -22)
> + echo -EINVAL;;
> + -123456)
> + echo -ERR_ANY;;
> + *)
> + echo invalid;;
> + esac
> +
> +config_set_async()
> +{
> + if ! echo -n 1 >$DIR/config_async ; then
> + echo "$0: Unable to set to async" >&2
> + exit 1
> + fi
> +}
> +
> +config_disable_async()
> +{
> + if ! echo -n 0 >$DIR/config_async ; then
> + echo "$0: Unable to set to sync" >&2
> + exit 1
> + fi
> +}
> +
> +config_set_optional()
> +{
> + if ! echo -n 1 >$DIR/config_optional ; then
> + echo "$0: Unable to set to optional" >&2
> + exit 1
> + fi
> +}
> +
> +config_disable_optional()
> +{
> + if ! echo -n 0 >$DIR/config_optional ; then
> + echo "$0: Unable to disable optional" >&2
> + exit 1
> + fi
> +}
> +
> +config_set_keep()
> +{
> + if ! echo -n 1 >$DIR/config_keep; then
> + echo "$0: Unable to set to keep" >&2
> + exit 1
> + fi
> +}
> +
> +config_disable_keep()
> +{
> + if ! echo -n 0 >$DIR/config_keep; then
> + echo "$0: Unable to disable keep option" >&2
> + exit 1
> + fi
> +}
> +
> +config_enable_opt_cb()
> +{
> + if ! echo -n 1 >$DIR/config_enable_opt_cb; then
> + echo "$0: Unable to set to optional" >&2
> + exit 1
> + fi
> +}
> +
> +config_enable_api_versioning()
> +{
> + if ! echo -n 1 >$DIR/config_use_api_versioning; then
> + echo "$0: Unable to set use_api_versioning option" >&2
> + exit 1
> + fi
> +}
> +
> +config_set_api_name_postfix()
> +{
> + if ! echo -n $1 >$DIR/config_api_name_postfix; then
> + echo "$0: Unable to set use_api_versioning option" >&2
> + exit 1
> + fi
> +}
> +
> +config_set_api_min()
> +{
> + if ! echo -n $1 >$DIR/config_api_min; then
> + echo "$0: Unable to set config_api_min option" >&2
> + exit 1
> + fi
> +}
> +
> +config_set_api_max()
> +{
> + if ! echo -n $1 >$DIR/config_api_max; then
> + echo "$0: Unable to set config_api_max option" >&2
> + exit 1
> + fi
> +}
> +
> +config_add_api_file()
> +{
> + TMP_FW="$FWPATH/$1"
> + echo "ABCD0123" >"$TMP_FW"
> +}
> +
> +config_rm_api_file()
> +{
> + TMP_FW="$FWPATH/$1"
> + rm -f $TMP_FW
> +}
> +
> +# For special characters use printf directly,
> +# refer to driver_data_test_0001
> +config_set_name()
> +{
> + if ! echo -n $1 >$DIR/config_name; then
> + echo "$0: Unable to set name" >&2
> + exit 1
> + fi
> +}
> +
> +config_get_name()
> +{
> + cat $DIR/config_name
> +}
> +
> +# For special characters use printf directly,
> +# refer to driver_data_test_0001
> +config_set_default_name()
> +{
> + if ! echo -n $1 >$DIR/config_default_name; then
> + echo "$0: Unable to set default_name" >&2
> + exit 1
> + fi
> +}
> +
> +config_get_default_name()
> +{
> + cat $DIR/config_default_name
> +}
> +
> +config_get_test_result()
> +{
> + cat $DIR/test_result
> +}
> +
> +config_reset()
> +{
> + if ! echo -n "1" >"$DIR"/reset; then
> + echo "$0: reset shuld have worked" >&2
> + exit 1
> + fi
> +}
> +
> +trigger_release_driver_data()
> +{
> + if ! echo -n "1" >"$DIR"/trigger_release_driver_data; then
> + echo "$0: release driver data shuld have worked" >&2
> + exit 1
> + fi
> +}
> +
> +config_show_config()
> +{
> + echo "----------------------------------------------------"
> + cat "$DIR"/config
> + echo "----------------------------------------------------"
> +}
> +
> +config_trigger()
> +{
> + if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then
> + echo "$1: FAIL - loading should have worked" >&2
> + config_show_config >&2
> + exit 1
> + fi
> + echo "$1: OK! - loading driver_data"
> +}
> +
> +config_trigger_want_fail()
> +{
> + if echo "1" > $DIR/trigger_config 2>/dev/null; then
> + echo "$1: FAIL - loading was expected to fail" >&2
> + config_show_config >&2
> + exit 1
> + fi
> + echo "$1: OK! - loading failed as expected"
> +}
> +
> +config_file_should_match()
> +{
> + FILE=$(config_get_name)
> + if [ ! -z $2 ]; then
> + FILE=$2
> + fi
> + # On this one we expect the file to exist so leave stderr in
> + if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > /dev/null; then
> + echo "$1: FAIL - file $FILE did not match contents in /dev/test_driver_data0" >&2
> + config_show_config >&2
> + exit 1
> + fi
> + echo "$1: OK! - $FILE == /dev/test_driver_data0"
> +}
> +
> +config_file_should_match_default()
> +{
> + FILE=$(config_get_default_name)
> + # On this one we expect the file to exist so leave stderr in
> + if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > /dev/null; then
> + echo "$1: FAIL - file $FILE did not match contents in /dev/test_driver_data0" >&2
> + config_show_config >&2
> + exit 1
> + fi
> + echo "$1: OK! - $FILE == /dev/test_driver_data0"
> +}
> +
> +config_file_should_not_match()
> +{
> + FILE=$(config_get_name)
> + # File may not exist, so skip those error messages as well
> + if $(diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null) 2> /dev/null ; then
> + echo "$1: FAIL - file $FILE was not expected to match /dev/null" >&2
> + config_show_config >&2
> + exit 1
> + fi
> + echo "$1: OK! - $FILE != /dev/test_driver_data0"
> +}
> +
> +config_default_file_should_match()
> +{
> + FILE=$(config_get_default_name)
> + diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null
> + if ! $? ; then
> + echo "$1: FAIL - file $FILE expected to match /dev/test_driver_data0" >&2
> + config_show_config >&2
> + exit 1
> + fi
> + echo "$1: OK! [file integrity matches]"
> +}
> +
> +config_default_file_should_not_match()
> +{
> + FILE=$(config_get_default_name)
> + diff -q FWPATH/$FILE /dev/test_driver_data0 2> /dev/null
> + if $? 2> /dev/null ; then
> + echo "$1: FAIL - file $FILE was not expected to match test_driver_data0" >&2
> + config_show_config >&2
> + exit 1
> + fi
> + echo "$1: OK!"
> +}
> +
> +config_expect_result()
> +{
> + RC=$(config_get_test_result)
> + RC_NAME=$(errno_val_to_name $RC)
> +
> + ERRNO_NAME=$2
> + ERRNO=$(errno_name_to_val $ERRNO_NAME)
> +
> + if [[ $ERRNO_NAME = "-ERR_ANY" ]]; then
> + if [[ $RC -ge 0 ]]; then
> + echo "$1: FAIL, test expects $ERRNO_NAME - got $RC_NAME ($RC)" >&2
> + config_show_config >&2
> + exit 1
> + fi
> + elif [[ $RC != $ERRNO ]]; then
> + echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got $RC_NAME ($RC)" >&2
> + config_show_config >&2
> + exit 1
> + fi
> + echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME"
> +}
> +
> +driver_data_set_sync_defaults()
> +{
> + config_reset
> +}
> +
> +driver_data_set_async_defaults()
> +{
> + config_reset
> + config_set_async
> +}
> +
> +set_system_state()
> +{
> + STATE="mem"
> + if [ ! -z $2 ]; then
> + STATE=$2
> + fi
> + echo $STATE > $SYS_STATE_PATH
> +}
> +
> +driver_data_test_0001s()
> +{
> + NAME='\000'
> +
> + driver_data_set_sync_defaults
> + config_set_name $NAME
> + printf '\000' >"$DIR"/config_name
> + config_trigger_want_fail ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} -EINVAL
> +}
> +
> +driver_data_test_0001a()
> +{
> + NAME='\000'
> +
> + driver_data_set_async_defaults
> + printf '\000' >"$DIR"/config_name
> + config_trigger_want_fail ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} -EINVAL
> +}
> +
> +driver_data_test_0001()
> +{
> + driver_data_test_0001s
> + driver_data_test_0001a
> +}
> +
> +driver_data_test_0002s()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_sync_defaults
> + config_set_name $NAME
> + config_trigger_want_fail ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} -ENOENT
> +}
> +
> +driver_data_test_0002a()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_async_defaults
> + config_set_name $NAME
> + config_trigger_want_fail ${FUNCNAME[0]}
> + # This may seem odd to expect success on a bogus
> + # file but remember this is an async call, the actual
> + # error handling is managed by the async callbacks.
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0002()
> +{
> + driver_data_test_0002s
> + driver_data_test_0002a
> +}
> +
> +driver_data_test_0003()
> +{
> + config_reset
> + config_file_should_not_match ${FUNCNAME[0]}
> +}
> +
> +driver_data_test_0004s()
> +{
> + driver_data_set_sync_defaults
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0004a()
> +{
> + driver_data_set_async_defaults
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0004()
> +{
> + driver_data_test_0004s
> + driver_data_test_0004a
> +}
> +
> +driver_data_test_0005s()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_sync_defaults
> + config_set_optional
> + config_set_name $NAME
> + config_trigger_want_fail ${FUNCNAME[0]}
> + # We do this to ensure the default backup callback hasn't
> + # been called yet
> + config_file_should_not_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0005a()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_async_defaults
> + config_set_optional
> + config_set_name $NAME
> + config_trigger_want_fail ${FUNCNAME[0]}
> + # We do this to ensure the default backup callback hasn't
> + # been called yet
> + config_file_should_not_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0005()
> +{
> + driver_data_test_0005s
> + driver_data_test_0005a
> +}
> +
> +driver_data_test_0006s()
> +{
> + driver_data_set_sync_defaults
> + config_set_optional
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0006a()
> +{
> + driver_data_set_async_defaults
> + config_set_optional
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0006()
> +{
> + driver_data_test_0006s
> + driver_data_test_0006a
> +}
> +
> +driver_data_test_0007s()
> +{
> + driver_data_set_sync_defaults
> + config_set_keep
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0007a()
> +{
> + driver_data_set_async_defaults
> + config_set_keep
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0007()
> +{
> + driver_data_test_0007s
> + driver_data_test_0007a
> +}
> +
> +driver_data_test_0008s()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_sync_defaults
> + config_set_name $NAME
> + config_set_optional
> + config_enable_opt_cb
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match_default ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0008a()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_async_defaults
> + config_set_name $NAME
> + config_set_optional
> + config_enable_opt_cb
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match_default ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0008()
> +{
> + driver_data_test_0008s
> + driver_data_test_0008a
> +}
> +
> +driver_data_test_0009s()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_sync_defaults
> + config_set_name $NAME
> + config_set_keep
> + config_set_optional
> + config_enable_opt_cb
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match_default ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0009a()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_async_defaults
> + config_set_name $NAME
> + config_set_keep
> + config_set_optional
> + config_enable_opt_cb
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match_default ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0009()
> +{
> + driver_data_test_0009s
> + driver_data_test_0009a
> +}
> +
> +driver_data_test_0010s()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_sync_defaults
> + config_set_name $NAME
> + config_set_default_name $NAME
> + config_set_keep
> + config_set_optional
> + config_enable_opt_cb
> + config_trigger_want_fail ${FUNCNAME[0]}
> + config_file_should_not_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} -ENOENT
> +}
> +
> +driver_data_test_0010a()
> +{
> + NAME="nope-$DEFAULT_DRIVER_DATA"
> +
> + driver_data_set_async_defaults
> + config_set_name $NAME
> + config_set_default_name $NAME
> + config_set_keep
> + config_set_optional
> + config_enable_opt_cb
> + config_trigger_want_fail ${FUNCNAME[0]}
> + config_file_should_not_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0010()
> +{
> + driver_data_test_0010s
> + driver_data_test_0010a
> +}
> +
> +driver_data_test_0011a()
> +{
> + driver_data_set_async_defaults
> + config_set_keep
> + config_enable_api_versioning
> +
> + config_trigger_want_fail ${FUNCNAME[0]}
> + config_file_should_not_match ${FUNCNAME[0]}
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0011()
> +{
> + driver_data_test_0011a
> +}
> +
> +driver_data_test_0012a()
> +{
> + driver_data_set_async_defaults
> + NAME_PREFIX="driver_data_test_0012a_"
> + TARGET_API="4"
> + NAME_POSTFIX=".bin"
> + NAME="${NAME_PREFIX}${TARGET_API}${NAME_POSTFIX}"
> +
> + config_set_name $NAME_PREFIX
> + config_set_keep
> + config_enable_api_versioning
> + config_set_api_name_postfix ".bin"
> + config_set_api_min 3
> + config_set_api_max 18
> +
> + config_trigger_want_fail ${FUNCNAME[0]}
> + config_file_should_not_match ${FUNCNAME[0]} $NAME
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> +}
> +
> +driver_data_test_0012()
> +{
> + driver_data_test_0012a
> +}
> +
> +driver_data_test_0013a()
> +{
> + driver_data_set_async_defaults
> + NAME_PREFIX="driver_data_test_0013a_"
> + TARGET_API="4"
> + NAME_POSTFIX=".bin"
> + NAME="${NAME_PREFIX}${TARGET_API}${NAME_POSTFIX}"
> +
> + config_set_name $NAME_PREFIX
> + config_set_keep
> + config_enable_api_versioning
> + config_set_api_name_postfix $NAME_POSTFIX
> + config_set_api_min 3
> + config_set_api_max 18
> + config_add_api_file $NAME
> +
> + config_trigger ${FUNCNAME[0]}
> + config_file_should_match ${FUNCNAME[0]} $NAME
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> + config_rm_api_file $NAME
> +}
> +
> +driver_data_test_0013()
> +{
> + driver_data_test_0013a
> +}
> +
> +driver_data_test_0014a()
> +{
> + driver_data_set_async_defaults
> + NAME_PREFIX="driver_data_test_0013a_"
> + TARGET_API="4"
> + NAME_POSTFIX=".bin"
> + NAME="${NAME_PREFIX}${TARGET_API}${NAME_POSTFIX}"
> +
> + config_set_name $NAME_PREFIX
> + config_set_keep
> + config_enable_api_versioning
> + config_set_api_name_postfix $NAME_POSTFIX
> + config_set_api_min 3
> + config_set_api_max 18
> + config_add_api_file $NAME
> +
> + config_trigger ${FUNCNAME[0]}
> +
> + # suspend to memory
> + set_system_state mem
> +
> + config_file_should_match ${FUNCNAME[0]} $NAME
> + config_expect_result ${FUNCNAME[0]} SUCCESS
> + config_rm_api_file $NAME
> +}
> +
> +driver_data_test_0014()
> +{
> + driver_data_test_0014a
> +}
> +
> +list_tests()
> +{
> + echo "Test ID list:"
> + echo
> + echo "TEST_ID x NUM_TEST"
> + echo "TEST_ID: Test ID"
> + echo "NUM_TESTS: Number of recommended times to run the test"
> + echo
> + echo "0001 x $(get_test_count 0001) - Empty string should be ignored"
> + echo "0002 x $(get_test_count 0002) - Files that do not exist should be ignored"
> + echo "0003 x $(get_test_count 0003) - Verify test_driver_data0 has nothing loaded upon reset"
> + echo "0004 x $(get_test_count 0004) - Simple sync and async loader"
> + echo "0005 x $(get_test_count 0005) - Verify optional loading is not fatal"
> + echo "0006 x $(get_test_count 0006) - Verify optional loading enables loading"
> + echo "0007 x $(get_test_count 0007) - Verify keep works"
> + echo "0008 x $(get_test_count 0008) - Verify optional callback works"
> + echo "0009 x $(get_test_count 0009) - Verify optional callback works, keep"
> + echo "0010 x $(get_test_count 0010) - Verify when fallback file is not present"
> + echo "0011 x $(get_test_count 0011) - Verify api setup will fail on invalid values"
> + echo "0012 x $(get_test_count 0012) - Verify api call wills will hunt for files, ignore file"
> + echo "0013 x $(get_test_count 0013) - Verify api call works"
> + echo "0014 x $(get_test_count 0013) - Verify api call works with suspend + resume"
> +}
> +
> +test_reqs
> +
> +usage()
> +{
> + NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .)
> + let NUM_TESTS=$NUM_TESTS+1
> + MAX_TEST=$(printf "%04d\n" $NUM_TESTS)
> + echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |"
> + echo " [ -s <4-number-digit> ] | [ -c <4-number-digit> <test- count>"
> + echo " [ all ] [ -h | --help ] [ -l ]"
> + echo ""
> + echo "Valid tests: 0001-$MAX_TEST"
> + echo ""
> + echo " all Runs all tests (default)"
> + echo " -t Run test ID the number amount of times is recommended"
> + echo " -w Watch test ID run until it runs into an error"
> + echo " -c Run test ID once"

-> -s

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

-> -c

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

Thanks,
-Takahiro AKASHI

> + echo " -l List all test ID list"
> + echo " -h|--help Help"
> + echo
> + echo "If an error every occurs execution will immediately terminate."
> + echo "If you are adding a new test try using -w <test-ID> first to"
> + echo "make sure the test passes a series of tests."
> + echo
> + echo Example uses:
> + echo
> + echo "$TEST_NAME.sh -- executes all tests"
> + echo "$TEST_NAME.sh -t 0008 -- Executes test ID 0008 number of times is recomended"
> + echo "$TEST_NAME.sh -w 0008 -- Watch test ID 0008 run until an error occurs"
> + echo "$TEST_NAME.sh -s 0008 -- Run test ID 0008 once"
> + echo "$TEST_NAME.sh -c 0008 3 -- Run test ID 0008 three times"
> + echo
> + list_tests
> + exit 1
> +}
> +
> +function test_num()
> +{
> + re='^[0-9]+$'
> + if ! [[ $1 =~ $re ]]; then
> + usage
> + fi
> +}
> +
> +function get_test_count()
> +{
> + test_num $1
> + TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
> + LAST_TWO=${TEST_DATA#*:*}
> + echo ${LAST_TWO%:*}
> +}
> +
> +function get_test_enabled()
> +{
> + test_num $1
> + TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
> + echo ${TEST_DATA#*:*:}
> +}
> +
> +function run_all_tests()
> +{
> + for i in $ALL_TESTS ; do
> + TEST_ID=${i%:*:*}
> + ENABLED=$(get_test_enabled $TEST_ID)
> + TEST_COUNT=$(get_test_count $TEST_ID)
> + if [[ $ENABLED -eq "1" ]]; then
> + test_case $TEST_ID $TEST_COUNT
> + fi
> + done
> +}
> +
> +function watch_log()
> +{
> + if [ $# -ne 3 ]; then
> + clear
> + fi
> + date
> + echo "Running test: $2 - run #$1"
> +}
> +
> +function watch_case()
> +{
> + i=0
> + while [ 1 ]; do
> +
> + if [ $# -eq 1 ]; then
> + test_num $1
> + watch_log $i ${TEST_NAME}_test_$1
> + ${TEST_NAME}_test_$1
> + else
> + watch_log $i all
> + run_all_tests
> + fi
> + let i=$i+1
> + done
> +}
> +
> +function test_case()
> +{
> + NUM_TESTS=$DEFAULT_NUM_TESTS
> + if [ $# -eq 2 ]; then
> + NUM_TESTS=$2
> + fi
> +
> + i=0
> + while [ $i -lt $NUM_TESTS ]; do
> + test_num $1
> + watch_log $i ${TEST_NAME}_test_$1 noclear
> + RUN_TEST=${TEST_NAME}_test_$1
> + $RUN_TEST
> + let i=$i+1
> + done
> +}
> +
> +function parse_args()
> +{
> + if [ $# -eq 0 ]; then
> + run_all_tests
> + else
> + if [[ "$1" = "all" ]]; then
> + run_all_tests
> + elif [[ "$1" = "-w" ]]; then
> + shift
> + watch_case $@
> + elif [[ "$1" = "-t" ]]; then
> + shift
> + test_num $1
> + test_case $1 $(get_test_count $1)
> + elif [[ "$1" = "-c" ]]; then
> + shift
> + test_num $1
> + test_num $2
> + test_case $1 $2
> + elif [[ "$1" = "-s" ]]; then
> + shift
> + test_case $1 1
> + elif [[ "$1" = "-l" ]]; then
> + list_tests
> + elif [[ "$1" = "-h" || "$1" = "--help" ]]; then
> + usage
> + else
> + usage
> + fi
> + fi
> +}
> +
> +test_reqs
> +load_req_mod
> +allow_user_defaults
> +
> +trap "test_finish" EXIT
> +
> +parse_args $@
> +
> +exit 0
> --
> 2.11.0
>