Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754090AbdDKI3r (ORCPT ); Tue, 11 Apr 2017 04:29:47 -0400 Received: from mail-pf0-f174.google.com ([209.85.192.174]:35052 "EHLO mail-pf0-f174.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754056AbdDKI3l (ORCPT ); Tue, 11 Apr 2017 04:29:41 -0400 Date: Tue, 11 Apr 2017 17:32:52 +0900 From: AKASHI Takahiro To: "Luis R. Rodriguez" Cc: gregkh@linuxfoundation.org, wagi@monom.org, dwmw2@infradead.org, rafal@milecki.pl, arend.vanspriel@broadcom.com, rjw@rjwysocki.net, yi1.li@linux.intel.com, atull@opensource.altera.com, moritz.fischer@ettus.com, pmladek@suse.com, johannes.berg@intel.com, emmanuel.grumbach@intel.com, luciano.coelho@intel.com, kvalo@codeaurora.org, luto@kernel.org, dhowells@redhat.com, pjones@redhat.com, linux-kernel@vger.kernel.org Subject: Re: [PATCH v6 3/5] test: add new driver_data load tester Message-ID: <20170411083248.GE15139@linaro.org> Mail-Followup-To: AKASHI Takahiro , "Luis R. Rodriguez" , gregkh@linuxfoundation.org, wagi@monom.org, dwmw2@infradead.org, rafal@milecki.pl, arend.vanspriel@broadcom.com, rjw@rjwysocki.net, yi1.li@linux.intel.com, atull@opensource.altera.com, moritz.fischer@ettus.com, pmladek@suse.com, johannes.berg@intel.com, emmanuel.grumbach@intel.com, luciano.coelho@intel.com, kvalo@codeaurora.org, luto@kernel.org, dhowells@redhat.com, pjones@redhat.com, linux-kernel@vger.kernel.org References: <20170330032514.17173-1-mcgrof@kernel.org> <20170330032514.17173-4-mcgrof@kernel.org> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20170330032514.17173-4-mcgrof@kernel.org> User-Agent: Mutt/1.5.24 (2015-08-30) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 70781 Lines: 2443 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 > --- > 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 > + * > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* 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(®_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, ®_test_devs); > + dev_info(test_dev->dev, "interface ready\n"); > + > + num_test_devs++; > + > +out: > + mutex_unlock(®_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(®_dev_mutex); > + list_for_each_entry_safe(test_dev, tmp, ®_test_devs, list) { > + list_del(&test_dev->list); > + unregister_test_dev_driver_data(test_dev); > + } > + mutex_unlock(®_dev_mutex); > +} > + > +module_exit(test_driver_data_exit); > + > +MODULE_AUTHOR("Luis R. Rodriguez "); > +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 > +# > +# 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> " > + 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 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 >