Greg,
Based on popular demand this v6 folds the new API ontop of the old API
and keeps the old schedule_work() async mechanism. It also added more
functionality to demo the real value to the flexible API. This goes
tested on 0-day, and using the existing and new tests provided. These
patches are based on next-20170329.
This series depends on the series which moves the UMH locks onto the
fallback code which I also just sent. If you want this in tree form
you can get it all here:
https://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux-next.git/log/?h=20170329-driver-data-v2-try3
Luis R. Rodriguez (5):
firmware: add extensible driver data params
firmware: add extensible driver data API
test: add new driver_data load tester
iwlwifi: convert to use driver data API
brcmfmac: don't warn user if requested nvram fails
Documentation/driver-api/firmware/driver_data.rst | 109 ++
Documentation/driver-api/firmware/index.rst | 1 +
Documentation/driver-api/firmware/introduction.rst | 16 +
MAINTAINERS | 4 +-
drivers/base/firmware_class.c | 574 ++++++++-
.../broadcom/brcm80211/brcmfmac/firmware.c | 11 +-
drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 67 +-
include/linux/driver_data.h | 288 +++++
include/linux/firmware.h | 2 +
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 +++++++++++++++
15 files changed, 3250 insertions(+), 106 deletions(-)
create mode 100644 Documentation/driver-api/firmware/driver_data.rst
create mode 100644 include/linux/driver_data.h
create mode 100644 lib/test_driver_data.c
create mode 100755 tools/testing/selftests/firmware/driver_data.sh
--
2.11.0
As the firmware API evolves we keep extending functions with more arguments.
Stop this nonsense by proving an extensible data structure which can be used
to represent both user parameters and private internal parameters.
We introduce 3 data structures:
o struct driver_data_req_params - used for user specified parameters
o struct driver_data_priv_params - used for internal use
o struct driver_data_params - stiches both of the the above together,
also only for internal use
This starts off by just making the existing APIs use the new data
structures, it will make subsequent changes easier to review which will
be adding new flexible APIs.
This commit should introduces no functional changes (TM).
Signed-off-by: Luis R. Rodriguez <[email protected]>
---
drivers/base/firmware_class.c | 217 +++++++++++++++++++++++++++++++-----------
include/linux/driver_data.h | 88 +++++++++++++++++
2 files changed, 251 insertions(+), 54 deletions(-)
create mode 100644 include/linux/driver_data.h
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index 54fc4c42f126..f702566554e1 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -19,6 +19,7 @@
#include <linux/workqueue.h>
#include <linux/highmem.h>
#include <linux/firmware.h>
+#include <linux/driver_data.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/file.h>
@@ -40,6 +41,77 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
MODULE_DESCRIPTION("Multi purpose firmware loading support");
MODULE_LICENSE("GPL");
+struct driver_data_priv_params {
+ bool use_fallback;
+ bool use_fallback_uevent;
+ bool no_cache;
+ void *alloc_buf;
+ size_t alloc_buf_size;
+};
+
+struct driver_data_params {
+ const struct firmware *driver_data;
+ const struct driver_data_req_params req_params;
+ struct driver_data_priv_params priv_params;
+};
+
+/*
+ * These are kept to remain backward compatible with old behaviour. Do not
+ * modify them unless you know what you are doing. These are to be used only
+ * by the old API, so:
+ *
+ * Old sync APIs:
+ * o request_firmware(): __DATA_REQ_FIRMWARE()
+ * o request_firmware_direct(): __DATA_REQ_FIRMWARE_DIRECT()
+ * o request_firmware_into_buf(): __DATA_REQ_FIRMWARE_BUF()
+ *
+ * Old async API:
+ * o request_firmware_nowait(): __DATA_REQ_FIRMWARE_NOWAIT()
+ */
+#define __DATA_REQ_FIRMWARE() \
+ .priv_params = { \
+ .use_fallback = !!FW_OPT_FALLBACK, \
+ .use_fallback_uevent = true, \
+ }
+
+#define __DATA_REQ_FIRMWARE_DIRECT() \
+ .req_params = { \
+ .optional = true, \
+ }, \
+ .priv_params = { \
+ .use_fallback = !!FW_OPT_FALLBACK, \
+ }
+
+#define __DATA_REQ_FIRMWARE_BUF(buf, size) \
+ .priv_params = { \
+ .use_fallback = !!FW_OPT_FALLBACK, \
+ .use_fallback_uevent = true, \
+ .alloc_buf = buf, \
+ .alloc_buf_size = size, \
+ .no_cache = true, \
+ }
+
+#define __DATA_REQ_FIRMWARE_NOWAIT(module, uevent, gfp, async_cb, async_ctx) \
+ .req_params = { \
+ .sync_reqs = { \
+ .mode = DRIVER_DATA_ASYNC, \
+ .module = module, \
+ .gfp = gfp, \
+ }, \
+ .cbs.async = { \
+ .found_cb = async_cb, \
+ .found_ctx = async_ctx, \
+ }, \
+ }, \
+ .priv_params = { \
+ .use_fallback = !!FW_OPT_FALLBACK, \
+ .use_fallback_uevent = uevent, \
+ }
+
+#define driver_data_param_use_fallback(params) ((params)->use_fallback)
+#define driver_data_param_uevent(params) ((params)->use_fallback_uevent)
+#define driver_data_param_nocache(params) ((params)->no_cache)
+
/* Builtin firmware support */
#ifdef CONFIG_FW_LOADER
@@ -48,9 +120,19 @@ extern struct builtin_fw __start_builtin_fw[];
extern struct builtin_fw __end_builtin_fw[];
static bool fw_get_builtin_firmware(struct firmware *fw, const char *name,
- void *buf, size_t size)
+ struct driver_data_params *data_params)
{
struct builtin_fw *b_fw;
+ void *buf;
+ size_t size;
+
+ if (data_params) {
+ buf = data_params->priv_params.alloc_buf;
+ size = data_params->priv_params.alloc_buf_size;
+ } else {
+ buf = NULL;
+ size = 0;
+ }
for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) {
if (strcmp(name, b_fw->name) == 0) {
@@ -79,9 +161,9 @@ static bool fw_is_builtin_firmware(const struct firmware *fw)
#else /* Module case - no builtin firmware support */
-static inline bool fw_get_builtin_firmware(struct firmware *fw,
- const char *name, void *buf,
- size_t size)
+static inline
+bool fw_get_builtin_firmware(struct firmware *fw, const char *name,
+ struct driver_data_params *data_params)
{
return false;
}
@@ -296,9 +378,19 @@ static struct firmware_cache fw_cache;
static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
struct firmware_cache *fwc,
- void *dbuf, size_t size)
+ struct driver_data_params *data_params)
{
struct firmware_buf *buf;
+ void *dbuf;
+ size_t size;
+
+ if (data_params) {
+ dbuf = data_params->priv_params.alloc_buf;
+ size = data_params->priv_params.alloc_buf_size;
+ } else {
+ dbuf = NULL;
+ size = 0;
+ }
buf = kzalloc(sizeof(*buf), GFP_ATOMIC);
if (!buf)
@@ -337,8 +429,8 @@ static struct firmware_buf *__fw_lookup_buf(const char *fw_name)
static int fw_lookup_and_allocate_buf(const char *fw_name,
struct firmware_cache *fwc,
- struct firmware_buf **buf, void *dbuf,
- size_t size)
+ struct firmware_buf **buf,
+ struct driver_data_params *data_params)
{
struct firmware_buf *tmp;
@@ -350,7 +442,7 @@ static int fw_lookup_and_allocate_buf(const char *fw_name,
*buf = tmp;
return 1;
}
- tmp = __allocate_fw_buf(fw_name, fwc, dbuf, size);
+ tmp = __allocate_fw_buf(fw_name, fwc, data_params);
if (tmp)
list_add(&tmp->list, &fwc->head);
spin_unlock(&fwc->lock);
@@ -556,7 +648,7 @@ static int fw_add_devm_name(struct device *dev, const char *name)
#endif
static int assign_firmware_buf(struct firmware *fw, struct device *device,
- unsigned int opt_flags)
+ struct driver_data_params *data_params)
{
struct firmware_buf *buf = fw->priv;
@@ -574,15 +666,16 @@ static int assign_firmware_buf(struct firmware *fw, struct device *device,
* should be fixed in devres or driver core.
*/
/* don't cache firmware handled without uevent */
- if (device && (opt_flags & FW_OPT_UEVENT) &&
- !(opt_flags & FW_OPT_NOCACHE))
+ if (device &&
+ driver_data_param_uevent(&data_params->priv_params) &&
+ !driver_data_param_nocache(&data_params->priv_params))
fw_add_devm_name(device, buf->fw_id);
/*
* After caching firmware image is started, let it piggyback
* on request firmware.
*/
- if (!(opt_flags & FW_OPT_NOCACHE) &&
+ if (!driver_data_param_nocache(&data_params->priv_params) &&
buf->fwc->state == FW_LOADER_START_CACHE) {
if (fw_cache_piggyback_on_request(buf->fw_id))
kref_get(&buf->ref);
@@ -1012,7 +1105,8 @@ static const struct attribute_group *fw_dev_attr_groups[] = {
static struct firmware_priv *
fw_create_instance(struct firmware *firmware, const char *fw_name,
- struct device *device, unsigned int opt_flags)
+ struct device *device,
+ struct driver_data_params *data_params)
{
struct firmware_priv *fw_priv;
struct device *f_dev;
@@ -1023,7 +1117,7 @@ fw_create_instance(struct firmware *firmware, const char *fw_name,
goto exit;
}
- fw_priv->nowait = !!(opt_flags & FW_OPT_NOWAIT);
+ fw_priv->nowait = driver_data_req_param_async(&data_params->req_params);
fw_priv->fw = firmware;
f_dev = &fw_priv->dev;
@@ -1038,7 +1132,8 @@ fw_create_instance(struct firmware *firmware, const char *fw_name,
/* load a firmware via user helper */
static int _request_firmware_load(struct firmware_priv *fw_priv,
- unsigned int opt_flags, long timeout)
+ struct driver_data_params *data_params,
+ long timeout)
{
int retval = 0;
struct device *f_dev = &fw_priv->dev;
@@ -1060,7 +1155,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv,
list_add(&buf->pending_list, &pending_fw_head);
mutex_unlock(&fw_lock);
- if (opt_flags & FW_OPT_UEVENT) {
+ if (driver_data_param_uevent(&data_params->priv_params)) {
buf->need_uevent = true;
dev_set_uevent_suppress(f_dev, false);
dev_dbg(f_dev, "firmware: requesting %s\n", buf->fw_id);
@@ -1089,14 +1184,14 @@ static int _request_firmware_load(struct firmware_priv *fw_priv,
static int fw_load_from_user_helper(struct firmware *firmware,
const char *name, struct device *device,
- unsigned int opt_flags)
+ struct driver_data_params *data_params)
{
struct firmware_priv *fw_priv;
long timeout;
int ret;
timeout = firmware_loading_timeout();
- if (opt_flags & FW_OPT_NOWAIT) {
+ if (driver_data_req_param_async(&data_params->req_params)) {
timeout = usermodehelper_read_lock_wait(timeout);
if (!timeout) {
dev_dbg(device, "firmware: %s loading timed out\n",
@@ -1112,17 +1207,17 @@ static int fw_load_from_user_helper(struct firmware *firmware,
}
}
- fw_priv = fw_create_instance(firmware, name, device, opt_flags);
+ fw_priv = fw_create_instance(firmware, name, device, data_params);
if (IS_ERR(fw_priv)) {
ret = PTR_ERR(fw_priv);
goto out_unlock;
}
fw_priv->buf = firmware->priv;
- ret = _request_firmware_load(fw_priv, opt_flags, timeout);
+ ret = _request_firmware_load(fw_priv, data_params, timeout);
if (!ret)
- ret = assign_firmware_buf(firmware, device, opt_flags);
+ ret = assign_firmware_buf(firmware, device, data_params);
out_unlock:
usermodehelper_read_unlock();
@@ -1148,7 +1243,8 @@ static void kill_pending_fw_fallback_reqs(bool only_kill_custom)
#else /* CONFIG_FW_LOADER_USER_HELPER */
static inline int
fw_load_from_user_helper(struct firmware *firmware, const char *name,
- struct device *device, unsigned int opt_flags)
+ struct device *device,
+ struct driver_data_params *data_params)
{
return -ENOENT;
}
@@ -1163,7 +1259,8 @@ static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { }
*/
static int
_request_firmware_prepare(struct firmware **firmware_p, const char *name,
- struct device *device, void *dbuf, size_t size)
+ struct device *device,
+ struct driver_data_params *data_params)
{
struct firmware *firmware;
struct firmware_buf *buf;
@@ -1176,12 +1273,12 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
return -ENOMEM;
}
- if (fw_get_builtin_firmware(firmware, name, dbuf, size)) {
+ if (fw_get_builtin_firmware(firmware, name, data_params)) {
dev_dbg(device, "using built-in %s\n", name);
return 0; /* assigned */
}
- ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf, dbuf, size);
+ ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf, data_params);
/*
* bind with 'buf' now to avoid warning in failure path
@@ -1205,8 +1302,8 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
/* called from request_firmware() and request_firmware_work_func() */
static int
_request_firmware(const struct firmware **firmware_p, const char *name,
- struct device *device, void *buf, size_t size,
- unsigned int opt_flags)
+ struct driver_data_params *data_params,
+ struct device *device)
{
struct firmware *fw = NULL;
int ret;
@@ -1219,7 +1316,7 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
goto out;
}
- ret = _request_firmware_prepare(&fw, name, device, buf, size);
+ ret = _request_firmware_prepare(&fw, name, device, data_params);
if (ret <= 0) /* error or already assigned */
goto out;
@@ -1231,17 +1328,17 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
ret = fw_get_filesystem_firmware(device, fw->priv);
if (ret) {
- if (!(opt_flags & FW_OPT_NO_WARN))
+ if (!driver_data_param_optional(&data_params->req_params))
dev_warn(device,
"Direct firmware load for %s failed with error %d\n",
name, ret);
- if (opt_flags & FW_OPT_USERHELPER) {
+ if (driver_data_param_use_fallback(&data_params->priv_params)) {
dev_warn(device, "Falling back to user helper\n");
ret = fw_load_from_user_helper(fw, name, device,
- opt_flags);
+ data_params);
}
} else
- ret = assign_firmware_buf(fw, device, opt_flags);
+ ret = assign_firmware_buf(fw, device, data_params);
out:
if (ret < 0) {
@@ -1278,11 +1375,13 @@ request_firmware(const struct firmware **firmware_p, const char *name,
struct device *device)
{
int ret;
+ struct driver_data_params data_params = {
+ __DATA_REQ_FIRMWARE(),
+ };
/* Need to pin this module until return */
__module_get(THIS_MODULE);
- ret = _request_firmware(firmware_p, name, device, NULL, 0,
- FW_OPT_UEVENT | FW_OPT_FALLBACK);
+ ret = _request_firmware(firmware_p, name, &data_params, device);
module_put(THIS_MODULE);
return ret;
}
@@ -1303,10 +1402,12 @@ int request_firmware_direct(const struct firmware **firmware_p,
const char *name, struct device *device)
{
int ret;
+ struct driver_data_params data_params = {
+ __DATA_REQ_FIRMWARE_DIRECT(),
+ };
__module_get(THIS_MODULE);
- ret = _request_firmware(firmware_p, name, device, NULL, 0,
- FW_OPT_UEVENT | FW_OPT_NO_WARN);
+ ret = _request_firmware(firmware_p, name, &data_params, device);
module_put(THIS_MODULE);
return ret;
}
@@ -1332,12 +1433,15 @@ request_firmware_into_buf(const struct firmware **firmware_p, const char *name,
struct device *device, void *buf, size_t size)
{
int ret;
+ struct driver_data_params data_params = {
+ __DATA_REQ_FIRMWARE_BUF(buf, size),
+ };
__module_get(THIS_MODULE);
- ret = _request_firmware(firmware_p, name, device, buf, size,
- FW_OPT_UEVENT | FW_OPT_FALLBACK |
- FW_OPT_NOCACHE);
+ ret = _request_firmware(firmware_p, name, &data_params, device);
module_put(THIS_MODULE);
+
+
return ret;
}
EXPORT_SYMBOL(request_firmware_into_buf);
@@ -1359,27 +1463,29 @@ EXPORT_SYMBOL(release_firmware);
/* Async support */
struct firmware_work {
struct work_struct work;
- struct module *module;
+ struct driver_data_params data_params;
const char *name;
struct device *device;
- void *context;
- void (*cont)(const struct firmware *fw, void *context);
- unsigned int opt_flags;
};
static void request_firmware_work_func(struct work_struct *work)
{
struct firmware_work *fw_work;
- const struct firmware *fw;
+ struct driver_data_params *data_params;
+ const struct driver_data_req_params *req_params;
+ const struct driver_data_reqs *sync_reqs;
fw_work = container_of(work, struct firmware_work, work);
+ data_params = &fw_work->data_params;
+ req_params = &data_params->req_params;
+ sync_reqs = &req_params->sync_reqs;
- _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0,
- fw_work->opt_flags);
- fw_work->cont(fw, fw_work->context);
+ _request_firmware(&data_params->driver_data, fw_work->name,
+ data_params, fw_work->device);
+ driver_data_async_call_cb(data_params->driver_data, req_params);
put_device(fw_work->device); /* taken in request_firmware_nowait() */
- module_put(fw_work->module);
+ module_put(sync_reqs->module);
kfree_const(fw_work->name);
kfree(fw_work);
}
@@ -1414,22 +1520,25 @@ request_firmware_nowait(
void (*cont)(const struct firmware *fw, void *context))
{
struct firmware_work *fw_work;
+ struct driver_data_params data_params = {
+ __DATA_REQ_FIRMWARE_NOWAIT(module, uevent, gfp, cont, context),
+ };
+
+ if (!cont)
+ return -EINVAL;
fw_work = kzalloc(sizeof(struct firmware_work), gfp);
if (!fw_work)
return -ENOMEM;
- fw_work->module = module;
fw_work->name = kstrdup_const(name, gfp);
if (!fw_work->name) {
kfree(fw_work);
return -ENOMEM;
}
fw_work->device = device;
- fw_work->context = context;
- fw_work->cont = cont;
- fw_work->opt_flags = FW_OPT_NOWAIT | FW_OPT_FALLBACK |
- (uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER);
+ memcpy(&fw_work->data_params, &data_params,
+ sizeof(struct driver_data_params));
if (!try_module_get(module)) {
kfree_const(fw_work->name);
@@ -1507,7 +1616,7 @@ static int uncache_firmware(const char *fw_name)
pr_debug("%s: %s\n", __func__, fw_name);
- if (fw_get_builtin_firmware(&fw, fw_name, NULL, 0))
+ if (fw_get_builtin_firmware(&fw, fw_name, NULL))
return 0;
buf = fw_lookup_buf(fw_name);
diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
new file mode 100644
index 000000000000..c3d3a4129838
--- /dev/null
+++ b/include/linux/driver_data.h
@@ -0,0 +1,88 @@
+#ifndef _LINUX_DRIVER_DATA_H
+#define _LINUX_DRIVER_DATA_H
+
+#include <linux/types.h>
+#include <linux/compiler.h>
+#include <linux/gfp.h>
+#include <linux/device.h>
+
+/*
+ * Driver Data internals
+ *
+ * Copyright (C) 2017 Luis R. Rodriguez <[email protected]>
+ *
+ * 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/.
+ */
+
+/**
+ * enum driver_data_mode - driver data mode of operation
+ *
+ * DRIVER_DATA_SYNC: your call to request driver data is synchronous. We will
+ * look for the driver data file you have requested immediatley.
+ * DRIVER_DATA_ASYNC: your call to request driver data is asynchronous. We will
+ * schedule the search for your driver data file to be run at a later
+ * time.
+ */
+enum driver_data_mode {
+ DRIVER_DATA_SYNC,
+ DRIVER_DATA_ASYNC,
+};
+
+/* one per driver_data_mode */
+union driver_data_cbs {
+ struct {
+ void (*found_cb)(const struct firmware *driver_data,
+ void *context);
+ void *found_ctx;
+ } async;
+};
+
+struct driver_data_reqs {
+ enum driver_data_mode mode;
+ struct module *module;
+ gfp_t gfp;
+};
+
+/**
+ * struct driver_data_req_params - driver data request parameters
+ * @optional: if true it is not a hard requirement by the caller that this
+ * file be present. An error will not be recorded if the file is not
+ * found.
+ * @sync_reqs: synchronization requirements
+ *
+ * This data structure is intended to carry all requirements and specifications
+ * required to complete the task to get the requested driver date file to the
+ * caller.
+ *
+ */
+struct driver_data_req_params {
+ bool optional;
+ struct driver_data_reqs sync_reqs;
+ const union driver_data_cbs cbs;
+};
+
+#define driver_data_req_param_sync(params) \
+ ((params)->sync_reqs.mode == DRIVER_DATA_SYNC)
+#define driver_data_req_param_async(params) \
+ ((params)->sync_reqs.mode == DRIVER_DATA_ASYNC)
+
+#define driver_data_param_optional(params) ((params)->optional)
+
+#define driver_data_async_cb(params) ((params)->cbs.async.found_cb)
+#define driver_data_async_ctx(params) ((params)->cbs.async.found_ctx)
+
+static inline
+void driver_data_async_call_cb(const struct firmware *driver_data,
+ const struct driver_data_req_params *params)
+{
+ if (params->sync_reqs.mode != DRIVER_DATA_ASYNC)
+ return;
+ if (!driver_data_async_cb(params))
+ return;
+ driver_data_async_cb(params)(driver_data,
+ driver_data_async_ctx(params));
+}
+
+#endif /* _LINUX_DRIVER_DATA_H */
--
2.11.0
The firmware API does not scale well: when new features are added we
either add a new exported symbol or extend the arguments of existing
routines. For the later case this means we need to traverse the kernel
with a slew of collateral evolutions to adjust old driver users. The
firmware API is also now being used for things outside of the scope of
what typically would be considered "firmware". There are other
subsystems which would like to make use of the firmware APIs for similar
things and its clearly not firmware, but have different requirements
and criteria which they'd like to be met for the requested file.
An extensible API is in order:
The driver data API accepts that there are only two types of requests:
a) synchronous requests
b) asynchronous requests
Both requests may have a different requirements which must be met. These
requirements can be described in the struct driver_data_req_params.
This struct is expected to be extended over time to support different
requirements as the kernel evolves.
After a bit of hard work the new interface has been wrapped onto the
functionality. The fallback mechanism has been kept out of the new API
currently because it requires just a bit more grooming and documentation
given new considerations and requirements. Adding support for it will
be rather easy now that the new API sits ontop of the old one. The
request_firmware_into_buf() API also is not enabled on the new API but
it is rather easy to do so -- this call has no current existing users
upstream though. Support will be provided once we add a respective
series of test cases against it and find a proper upstream user for it.
The flexible API also adds a few new bells and whistles:
- By default the kernel will free the driver data file for you after
your callbacks are called, you however are allowed to request that
you wish to keep the driver data file on the requirements params. The
new driver data API is able to free the driver data file for you by
requiring a consumer callback for the driver data file.
- Allows both asynchronous and synchronous request to specify that
driver data files are optional. With the old APIs we had added one
full API call, request_firmware_direct() just for this purpose --
the driver data request APIs allow for you to annotate that a driver
data file is optional for both synchronous or asynchronous requests
through the same two basic set of APIs.
- A firmware API framework is provided to enable daisy chaining a
series of requests for firmware on a range of supported APIs.
Signed-off-by: Luis R. Rodriguez <[email protected]>
---
Documentation/driver-api/firmware/driver_data.rst | 77 +++++
Documentation/driver-api/firmware/index.rst | 1 +
Documentation/driver-api/firmware/introduction.rst | 16 +
MAINTAINERS | 3 +-
drivers/base/firmware_class.c | 357 +++++++++++++++++++++
include/linux/driver_data.h | 202 +++++++++++-
include/linux/firmware.h | 2 +
7 files changed, 656 insertions(+), 2 deletions(-)
create mode 100644 Documentation/driver-api/firmware/driver_data.rst
diff --git a/Documentation/driver-api/firmware/driver_data.rst b/Documentation/driver-api/firmware/driver_data.rst
new file mode 100644
index 000000000000..08407b7568fe
--- /dev/null
+++ b/Documentation/driver-api/firmware/driver_data.rst
@@ -0,0 +1,77 @@
+===============
+driver_data API
+===============
+
+Users of firmware request APIs has grown to include users which are not
+looking for "firmware", but instead general driver data files which have
+been kept oustide of the kernel. The driver data APIs addresses rebranding
+of firmware as generic driver data files, and provides a flexible API which
+mitigates collateral evolutions on the kernel as new functionality is
+introduced.
+
+Driver data modes of operation
+==============================
+
+There are only two types of modes of operation for driver data requests:
+
+ * synchronous - driver_data_request()
+ * asynchronous - driver_data_request_async()
+
+Synchronous requests expect requests to be done immediately, asynchronous
+requests enable requests to be scheduled for a later time.
+
+Driver data request parameters
+==============================
+
+Variations of types of driver data requests are specified by a driver data
+request parameter data structure. This data structure is expected to grow as
+new requirements grow.
+
+Reference counting and releasing the driver data file
+=====================================================
+
+As with the old firmware API both the device and module are bumped with
+reference counts during the driver data requests. This prevents removal
+of the device and module making the driver data request call until the
+driver data request callbacks have completed, either synchronously or
+asynchronously.
+
+The old firmware APIs refcounted the firmware_class module for synchronous
+requests, meanwhile asynchronous requests refcounted the caller's module.
+The driver data request API currently mimics this behaviour, for synchronous
+requests the firmware_class module is refcounted through the use of
+dfl_sync_reqs. In the future we may enable the ability to also refcount the
+caller's module as well. Likewise in the future we may enable asynchronous
+calls to refcount the firmware_class module.
+
+Typical use of the old synchronous firmware APIs consist of the caller
+requesting for "driver data", consuming it after a request and finally
+freeing it. Typical asynchronous use of the old firmware APIs consist of
+the caller requesting for "driver data" and then finally freeing it on
+asynchronous callback.
+
+The driver data request API enables callers to provide a callback for both
+synchronous and asynchronous requests and since consumption can be expected
+in these callbacks it frees it for you by default after callback handlers
+are issued. If you wish to keep the driver data around after your callbacks
+you must specify this through the driver data request parameter data structure.
+
+Synchronizing with async cookies
+================================
+
+The driver data API relies on async cookies to enable users to synchronize
+for any pending async work. The async cookie obtained through an async
+call using driver_data_file_request_async() can be used to synchronize and
+wait for pending work with driver_data_synchronize_request().
+
+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.
+
+Tracking development enhancements and ideas
+===========================================
+
+To help track ongoing development for firmware_class and related items to
+firmware_class refer to the kernel newbies wiki page [0].
+
+[0] http://kernelnewbies.org/KernelProjects/firmware-class-enhancements
diff --git a/Documentation/driver-api/firmware/index.rst b/Documentation/driver-api/firmware/index.rst
index 1abe01793031..c2be92e2628c 100644
--- a/Documentation/driver-api/firmware/index.rst
+++ b/Documentation/driver-api/firmware/index.rst
@@ -7,6 +7,7 @@ Linux Firmware API
introduction
core
request_firmware
+ driver_data
.. only:: subproject and html
diff --git a/Documentation/driver-api/firmware/introduction.rst b/Documentation/driver-api/firmware/introduction.rst
index 211cb44eb972..a0f6a3fa1d5d 100644
--- a/Documentation/driver-api/firmware/introduction.rst
+++ b/Documentation/driver-api/firmware/introduction.rst
@@ -25,3 +25,19 @@ are already using asynchronous initialization mechanisms which will not
stall or delay boot. Even if loading firmware does not take a lot of time
processing firmware might, and this can still delay boot or initialization,
as such mechanisms such as asynchronous probe can help supplement drivers.
+
+Two APIs
+========
+
+Two APIs are provided for firmware:
+
+* request_firmware API - old firmware API
+* driver_data API - flexible API
+
+We have historically extended the firmware API by adding new routines or at
+times extending existing routines with more or less arguments. This doesn't
+scale well, when new arguments are added to existing routines it means we need
+to traverse the kernel with a slew of collateral evolutions to adjust old
+driver users. The driver data API is an extensible API enabling extensions to
+be added by avoiding unnecessary collateral evolutions as features get added.
+New features and development should be added through the driver_data API.
diff --git a/MAINTAINERS b/MAINTAINERS
index 939311d601b8..3f025f738600 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5166,13 +5166,14 @@ F: include/linux/firewire.h
F: include/uapi/linux/firewire*.h
F: tools/firewire/
-FIRMWARE LOADER (request_firmware)
+FIRMWARE LOADER (request_firmware, driver_data)
M: Luis R. Rodriguez <[email protected]>
L: [email protected]
S: Maintained
F: Documentation/firmware_class/
F: drivers/base/firmware*.c
F: include/linux/firmware.h
+F: include/linux/driver_data.h
FLASH ADAPTER DRIVER (IBM Flash Adapter 900GB Full Height PCI Flash Card)
M: Joshua Morris <[email protected]>
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index f702566554e1..cc3c2247980c 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -2,6 +2,7 @@
* firmware_class.c - Multi purpose firmware loading support
*
* Copyright (c) 2003 Manuel Estrada Sainz
+ * Copyright (c) 2017 Luis R. Rodriguez <[email protected]>
*
* Please see Documentation/firmware_class/ for more information.
*
@@ -41,12 +42,20 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
MODULE_DESCRIPTION("Multi purpose firmware loading support");
MODULE_LICENSE("GPL");
+static const struct driver_data_reqs dfl_sync_reqs = {
+ .mode = DRIVER_DATA_SYNC,
+ .module = THIS_MODULE,
+ .gfp = GFP_KERNEL,
+};
+
struct driver_data_priv_params {
bool use_fallback;
bool use_fallback_uevent;
bool no_cache;
void *alloc_buf;
size_t alloc_buf_size;
+ u8 api;
+ bool retry_api;
};
struct driver_data_params {
@@ -1262,6 +1271,7 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
struct device *device,
struct driver_data_params *data_params)
{
+ struct driver_data_priv_params *priv_params = &data_params->priv_params;
struct firmware *firmware;
struct firmware_buf *buf;
int ret;
@@ -1285,6 +1295,7 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
* of requesting firmware.
*/
firmware->priv = buf;
+ firmware->api = priv_params->api;
if (ret > 0) {
ret = fw_state_wait(&buf->fw_st);
@@ -1460,6 +1471,128 @@ void release_firmware(const struct firmware *fw)
}
EXPORT_SYMBOL(release_firmware);
+static int _driver_data_request_api(struct driver_data_params *params,
+ struct device *device,
+ const char *name)
+{
+ struct driver_data_priv_params *priv_params = ¶ms->priv_params;
+ const struct driver_data_req_params *req_params = ¶ms->req_params;
+ int ret;
+ char *try_name;
+ u8 api_max;
+
+ if (priv_params->retry_api) {
+ if (!priv_params->api)
+ return -ENOENT;
+ api_max = priv_params->api - 1;
+ } else
+ api_max = req_params->api_max;
+
+ for (priv_params->api = api_max;
+ priv_params->api >= req_params->api_min;
+ priv_params->api--) {
+ if (req_params->api_name_postfix)
+ try_name = kasprintf(GFP_KERNEL, "%s%d%s",
+ name,
+ priv_params->api,
+ req_params->api_name_postfix);
+ else
+ try_name = kasprintf(GFP_KERNEL, "%s%d",
+ name,
+ priv_params->api);
+ if (!try_name)
+ return -ENOMEM;
+ ret = _request_firmware(¶ms->driver_data, try_name,
+ params, device);
+ kfree(try_name);
+
+ if (!ret)
+ break;
+
+ release_firmware(params->driver_data);
+
+ /*
+ * Only chug on with the API revision hunt if the file we
+ * looked for really was not present. In case of memory issues
+ * or other related system issues we want to bail right away
+ * to not put strain on the system.
+ */
+ if (ret != -ENOENT)
+ break;
+
+ if (!priv_params->api)
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * driver_data_request - synchronous request for a driver data file
+ * @name: name of the driver data file
+ * @params: driver data parameters, it provides all the requirements
+ * parameters which must be met for the file being requested.
+ * @device: device for which firmware is being loaded
+ *
+ * This performs a synchronous driver data lookup with the requirements
+ * specified on @params, if the file was found meeting the criteria requested
+ * 0 is returned. Access to the driver data data can be accessed through
+ * an optional callback set on the @desc. If the driver data is optional
+ * you must specify that on @params and if set you may provide an alternative
+ * callback which if set would be run if the driver data was not found.
+ *
+ * The driver data passed to the callbacks will be NULL unless it was
+ * found matching all the criteria on @params. 0 is always returned if the file
+ * was found unless a callback was provided, in which case the callback's
+ * return value will be passed. Unless the params->keep was set the kernel will
+ * release the driver data for you after your callbacks were processed.
+ *
+ * Reference counting is used during the duration of this call on both the
+ * device and module that made the request. This prevents any callers from
+ * freeing either the device or module prior to completion of this call.
+ */
+int driver_data_request_sync(const char *name,
+ const struct driver_data_req_params *req_params,
+ struct device *device)
+{
+ const struct firmware *driver_data;
+ const struct driver_data_reqs *sync_reqs;
+ struct driver_data_params params = {
+ .req_params = *req_params,
+ };
+ int ret;
+
+ if (!device || !req_params || !name || name[0] == '\0')
+ return -EINVAL;
+
+ if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
+ return -EINVAL;
+
+ if (driver_data_sync_opt_cb(req_params) &&
+ !driver_data_param_optional(req_params))
+ return -EINVAL;
+
+ sync_reqs = &dfl_sync_reqs;
+
+ __module_get(sync_reqs->module);
+ get_device(device);
+
+ ret = _request_firmware(&driver_data, name, ¶ms, device);
+ if (ret && driver_data_param_optional(req_params))
+ ret = driver_data_sync_opt_call_cb(req_params);
+ else
+ ret = driver_data_sync_call_cb(req_params, driver_data);
+
+ if (!driver_data_param_keep(req_params))
+ release_firmware(driver_data);
+
+ put_device(device);
+ module_put(sync_reqs->module);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(driver_data_request_sync);
+
/* Async support */
struct firmware_work {
struct work_struct work;
@@ -1553,6 +1686,230 @@ request_firmware_nowait(
}
EXPORT_SYMBOL(request_firmware_nowait);
+static int validate_api_versioning(struct driver_data_params *params)
+{
+ //struct driver_data_priv_params *priv_params = ¶ms->priv_params;
+ const struct driver_data_req_params *req_params = ¶ms->req_params;
+
+ if (!req_params->api_max)
+ return -EINVAL;
+ if (req_params->api_max < req_params->api_min)
+ return -EINVAL;
+ /*
+ * this is being processed, but the first iteration this check is invalid
+ */
+ /*
+ if (priv_params->api < req_params->api_min)
+ return -EINVAL;
+ */
+ if (driver_data_async_cb(req_params))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int __request_driver_data_api(struct firmware_work *driver_work)
+{
+ struct driver_data_params *params = &driver_work->data_params;
+ const struct driver_data_req_params *req_params = ¶ms->req_params;
+ int ret;
+
+ ret = _driver_data_request_api(params, driver_work->device,
+ driver_work->name);
+ /*
+ * The default async optional callbacks don't work well here, so they
+ * are not supported. Consider what we would return back, we'd need a
+ * non-void optional callback to be able to feed the consumer.
+ */
+ if (ret == -ENOENT)
+ dev_err(driver_work->device,
+ "No API file in range %u - %u could be found\n",
+ req_params->api_min, req_params->api_max);
+
+ return driver_data_async_call_api_cb(params->driver_data, req_params);
+}
+
+static void request_driver_data_single(struct firmware_work *driver_work)
+{
+ struct driver_data_params *params = &driver_work->data_params;
+ const struct driver_data_req_params *req_params = ¶ms->req_params;
+ const struct driver_data_reqs *sync_reqs;
+ int ret;
+
+ sync_reqs = &req_params->sync_reqs;
+
+ ret = _request_firmware(¶ms->driver_data, driver_work->name,
+ params, driver_work->device);
+ if (ret &&
+ driver_data_param_optional(req_params) &&
+ driver_data_async_opt_cb(req_params))
+ driver_data_async_opt_call_cb(req_params);
+ else
+ driver_data_async_call_cb(params->driver_data, req_params);
+
+ if (!driver_data_param_keep(req_params))
+ release_firmware(params->driver_data);
+
+ put_device(driver_work->device);
+ module_put(sync_reqs->module);
+
+ kfree_const(driver_work->name);
+ kfree(driver_work);
+}
+
+/*
+ * Instead of recursion provide a deterministic limit based on the parameters,
+ * and consume less memory.
+ */
+static void request_driver_data_api(struct firmware_work *driver_work)
+{
+ struct driver_data_params *params = &driver_work->data_params;
+ struct driver_data_priv_params *priv_params = ¶ms->priv_params;
+ const struct driver_data_req_params *req_params = ¶ms->req_params;
+ const struct driver_data_reqs *sync_reqs;
+ int ret;
+ u8 i, limit;
+
+ limit = req_params->api_max - req_params->api_min;
+ sync_reqs = &req_params->sync_reqs;
+
+ for (i=0; i <= limit; i++) {
+ ret = validate_api_versioning(params);
+ if (WARN(ret, "Invalid API driver data request")) {
+ ret = driver_data_async_call_api_cb(NULL, req_params);
+ goto out;
+ }
+
+ /*
+ * This does the real work of fetching the driver data through
+ * all the API revisions possible. If found the api and its
+ * return value are passed. If a value of 0 is passed then
+ * *really* does mean everything was peachy. If we catch
+ * -EAGAIN here it means the driver's API callback asked us to
+ * try again.
+ */
+ ret = __request_driver_data_api(driver_work);
+ if (!ret)
+ break;
+
+ priv_params->retry_api = true;
+
+ release_firmware(params->driver_data);
+
+ if (ret != -EAGAIN)
+ break;
+ }
+
+ if (ret && driver_data_param_optional(req_params))
+ driver_data_async_opt_call_cb(req_params);
+
+ if (!driver_data_param_keep(req_params))
+ release_firmware(params->driver_data);
+
+out:
+ put_device(driver_work->device);
+ module_put(sync_reqs->module);
+
+ kfree_const(driver_work->name);
+ kfree(driver_work);
+}
+
+static void request_driver_data_work_func(struct work_struct *work)
+{
+ struct firmware_work *driver_work;
+ struct driver_data_params *data_params;
+ const struct driver_data_req_params *req_params;
+
+ driver_work = container_of(work, struct firmware_work, work);
+ data_params = &driver_work->data_params;
+ req_params = &data_params->req_params;
+
+ if (driver_data_param_uses_api(req_params))
+ request_driver_data_api(driver_work);
+ else
+ request_driver_data_single(driver_work);
+}
+
+/**
+ * driver_data_request_async - asynchronous request for a driver data file
+ * @name: name of the driver data file
+ * @req_params: driver data file request parameters, it provides all the
+ * requirements which must be met for the file being requested.
+ * @device: device for which firmware is being loaded
+ *
+ * This performs an asynchronous driver data file lookup with the requirements
+ * specified on @req_params. The request for the actual driver data file lookup
+ * will be scheduled with schedule_work() to be run at a later time. 0 is
+ * returned if we were able to asynchronously schedlue your work to be run.
+ *
+ * Reference counting is used during the duration of this scheduled call on
+ * both the device and module that made the request. This prevents any callers
+ * from freeing either the device or module prior to completion of the
+ * scheduled work.
+ *
+ * Access to the driver data file data can be accessed through an optional
+ * callback set on the @req_params. If the driver data file is optional you
+ * must specify that on @req_params and if set you may provide an alternative
+ * callback which if set would be run if the driver data file was not found.
+ *
+ * The driver data file passed to the callbacks will always be NULL unless it
+ * was found matching all the criteria on @req_params. Unless the desc->keep
+ * was set the kernel will release the driver data file for you after your
+ * callbacks were processed on the scheduled work.
+ */
+int driver_data_request_async(const char *name,
+ const struct driver_data_req_params *req_params,
+ struct device *device)
+{
+ struct firmware_work *driver_work;
+ const struct driver_data_reqs *sync_reqs;
+ struct firmware_work driver_work_stack = {
+ .data_params.req_params = *req_params,
+ //.device = device,
+ //.name = name,
+ };
+
+ if (!device || !req_params || !name || name[0] == '\0')
+ return -EINVAL;
+
+ if (req_params->sync_reqs.mode != DRIVER_DATA_ASYNC)
+ return -EINVAL;
+
+ if (driver_data_async_opt_cb(req_params) && !req_params->optional)
+ return -EINVAL;
+
+ sync_reqs = &req_params->sync_reqs;
+
+ driver_work = kzalloc(sizeof(struct firmware_work), sync_reqs->gfp);
+ if (!driver_work)
+ return -ENOMEM;
+
+ //memcpy(&driver_work->params, &driver_work_stack.params,
+ // sizeof(struct driver_data_file_work));
+ memcpy(driver_work, &driver_work_stack, sizeof(struct firmware_work));
+
+ driver_work->name = kstrdup_const(name, sync_reqs->gfp);
+ if (!driver_work->name) {
+ kfree(driver_work);
+ return -ENOMEM;
+ }
+ driver_work->device = device;
+
+ if (!try_module_get(sync_reqs->module)) {
+ kfree_const(driver_work->name);
+ kfree(driver_work);
+ return -EFAULT;
+ }
+
+ get_device(driver_work->device);
+
+ INIT_WORK(&driver_work->work, request_driver_data_work_func);
+ schedule_work(&driver_work->work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(driver_data_request_async);
+
#ifdef CONFIG_PM_SLEEP
static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);
diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
index c3d3a4129838..fda1e716017d 100644
--- a/include/linux/driver_data.h
+++ b/include/linux/driver_data.h
@@ -5,6 +5,7 @@
#include <linux/compiler.h>
#include <linux/gfp.h>
#include <linux/device.h>
+#include <linux/firmware.h>
/*
* Driver Data internals
@@ -33,9 +34,25 @@ enum driver_data_mode {
/* one per driver_data_mode */
union driver_data_cbs {
struct {
+ int __must_check
+ (*found_cb)(void *context,
+ const struct firmware *driver_data);
+ void *found_ctx;
+
+ int __must_check (*opt_fail_cb)(void *context);
+ void *opt_fail_ctx;
+ } sync;
+ struct {
void (*found_cb)(const struct firmware *driver_data,
void *context);
void *found_ctx;
+
+ void (*opt_fail_cb)(void *context);
+ void *opt_fail_ctx;
+
+ int __must_check
+ (*found_api_cb)(const struct firmware *driver_data,
+ void *context);
} async;
};
@@ -50,6 +67,30 @@ struct driver_data_reqs {
* @optional: if true it is not a hard requirement by the caller that this
* file be present. An error will not be recorded if the file is not
* found.
+ * @keep: if set the caller wants to claim ownership over the driver data
+ * through one of its callbacks, it must later free it with
+ * release_driver_data(). By default this is set to false and the kernel
+ * will release the driver data file for you after callback processing
+ * has completed.
+ * @uses_api_versioning: if set the caller is indicating that the caller has
+ * an API revision system for the the files being requested using a
+ * simple numeric scheme: there is a max API version supported and the
+ * lowest API version supported. When used the driver data request request
+ * will always look for the filename requested but by first appeneding the
+ * @api_max to it, and looking for that. If that is not available it will
+ * look for any files with API version lower than this until it reaches
+ * @api_min. This enables chaining driver data requests easily on behalf
+ * of device drivers using a simple one digit versioning scheme. When
+ * this is true it will treat all files not found as non-fatal, as
+ * optional, but it requires at least one file to be found. If the
+ * @optional attribute is also true then if no files are found we won't
+ * complain at all.
+ * @api_min: if uses_api_versioning is set, this represents the lowest
+ * version of API supported by the caller.
+ * @api_max: if uses_api_versioning is set, this represents the highest
+ * version of API supported by the caller.
+ * @api_name_postfix: use the name as the driver data name prefix, the API
+ * digit will be placed in the middle, followed by the @api_name_postfix.
* @sync_reqs: synchronization requirements
*
* This data structure is intended to carry all requirements and specifications
@@ -59,20 +100,128 @@ struct driver_data_reqs {
*/
struct driver_data_req_params {
bool optional;
+ bool keep;
+ bool uses_api_versioning;
+ u8 api_min;
+ u8 api_max;
+ const char *api_name_postfix;
struct driver_data_reqs sync_reqs;
const union driver_data_cbs cbs;
};
+/*
+ * We keep these template definitions to a minimum for the most
+ * popular requests.
+ */
+
+/* Typical sync data case */
+#define DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx) \
+ .cbs.sync.found_cb = __found_cb, \
+ .cbs.sync.found_ctx = __ctx
+
+#define DRIVER_DATA_DEFAULT_SYNC(__found_cb, __ctx) \
+ DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx)
+
+#define DRIVER_DATA_KEEP_SYNC(__found_cb, __ctx) \
+ DRIVER_DATA_DEFAULT_SYNC(__found_cb, __ctx), \
+ .keep = true
+
+/* If you have one fallback routine */
+#define DRIVER_DATA_SYNC_OPT_CB(__fail_cb, __ctx) \
+ .optional = true, \
+ .cbs.sync.opt_fail_cb = __fail_cb, \
+ .cbs.sync.opt_fail_ctx = __ctx
+
+/*
+ * Used to define the default asynchronization requirements for
+ * driver_data_request_async(). Drivers can override.
+ */
+#define DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx) \
+ .sync_reqs = { \
+ .mode = DRIVER_DATA_ASYNC, \
+ .module = THIS_MODULE, \
+ .gfp = GFP_KERNEL, \
+ }, \
+ .cbs.async = { \
+ .found_cb = __found_cb, \
+ .found_ctx = __ctx, \
+ }
+
+#define DRIVER_DATA_DEFAULT_ASYNC_OPT(__found_cb, __ctx) \
+ DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx), \
+ .optional = true
+
+#define DRIVER_DATA_KEEP_ASYNC(__found_cb, __ctx) \
+ DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx), \
+ .keep = true
+
+#define DRIVER_DATA_KEEP_ASYNC_OPT(__found_cb, __ctx) \
+ DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx), \
+ .optional = true
+
+#define DRIVER_DATA_ASYNC_OPT_CB(__fail_cb, __ctx) \
+ .optional = true, \
+ .cbs.async.opt_fail_cb = __fail_cb, \
+ .cbs.async.opt_fail_ctx = __ctx
+
+#define DRIVER_DATA_API_CB(__found_api_cb, __ctx) \
+ .sync_reqs = { \
+ .mode = DRIVER_DATA_ASYNC, \
+ .module = THIS_MODULE, \
+ .gfp = GFP_KERNEL, \
+ }, \
+ .cbs.async = { \
+ .found_api_cb = __found_api_cb, \
+ .found_ctx = __ctx, \
+ }
+
+#define DRIVER_DATA_API(__min, __max, __postfix) \
+ .uses_api_versioning = true, \
+ .api_min = __min, \
+ .api_max = __max, \
+ .api_name_postfix = __postfix
+
#define driver_data_req_param_sync(params) \
((params)->sync_reqs.mode == DRIVER_DATA_SYNC)
#define driver_data_req_param_async(params) \
((params)->sync_reqs.mode == DRIVER_DATA_ASYNC)
#define driver_data_param_optional(params) ((params)->optional)
+#define driver_data_param_keep(params) ((params)->keep)
+#define driver_data_param_uses_api(params) ((params)->uses_api_versioning)
+
+#define driver_data_sync_cb(param) ((params)->cbs.sync.found_cb)
+#define driver_data_sync_ctx(params) ((params)->cbs.sync.found_ctx)
+static inline
+int driver_data_sync_call_cb(const struct driver_data_req_params *params,
+ const struct firmware *driver_data)
+{
+ if (!driver_data_req_param_sync(params))
+ return -EINVAL;
+ if (!driver_data_sync_cb(params)) {
+ if (driver_data)
+ return 0;
+ return -ENOENT;
+ }
+ return driver_data_sync_cb(params)(driver_data_sync_ctx(params),
+ driver_data);
+}
+
+#define driver_data_sync_opt_cb(params) ((params)->cbs.sync.opt_fail_cb)
+#define driver_data_sync_opt_ctx(params) ((params)->cbs.sync.opt_fail_ctx)
+static inline
+int driver_data_sync_opt_call_cb(const struct driver_data_req_params *params)
+{
+ if (params->sync_reqs.mode != DRIVER_DATA_SYNC)
+ return -EINVAL;
+ if (!driver_data_sync_opt_cb(params))
+ return 0;
+ return driver_data_sync_opt_cb(params)
+ (driver_data_sync_opt_ctx(params));
+}
#define driver_data_async_cb(params) ((params)->cbs.async.found_cb)
#define driver_data_async_ctx(params) ((params)->cbs.async.found_ctx)
-
static inline
void driver_data_async_call_cb(const struct firmware *driver_data,
const struct driver_data_req_params *params)
@@ -85,4 +234,55 @@ void driver_data_async_call_cb(const struct firmware *driver_data,
driver_data_async_ctx(params));
}
+#define driver_data_async_opt_cb(params) ((params)->cbs.async.opt_fail_cb)
+#define driver_data_async_opt_ctx(params) ((params)->cbs.async.opt_fail_ctx)
+static inline
+void driver_data_async_opt_call_cb(const struct driver_data_req_params *params)
+{
+ if (params->sync_reqs.mode != DRIVER_DATA_ASYNC)
+ return;
+ if (!driver_data_async_opt_cb(params))
+ return;
+ driver_data_async_opt_cb(params)(driver_data_async_opt_ctx(params));
+}
+
+#define driver_data_async_api_cb(params) ((params)->cbs.async.found_api_cb)
+static inline
+int driver_data_async_call_api_cb(const struct firmware *driver_data,
+ const struct driver_data_req_params *params)
+{
+ if (!params->uses_api_versioning)
+ return -EINVAL;
+ if (!driver_data_async_api_cb(params))
+ return -EINVAL;
+ return driver_data_async_api_cb(params)(driver_data,
+ driver_data_async_ctx(params));
+}
+
+#if defined(CONFIG_FW_LOADER) || \
+ (defined(CONFIG_FW_LOADER_MODULE) && defined(MODULE))
+int driver_data_request_sync(const char *name,
+ const struct driver_data_req_params *params,
+ struct device *device);
+int driver_data_request_async(const char *name,
+ const struct driver_data_req_params *params,
+ struct device *device);
+#else
+static
+inline int driver_data_request_sync(const char *name,
+ const struct driver_data_req_params *params,
+ struct device *device)
+{
+ return -EINVAL;
+}
+
+static
+inline int driver_data_request_async(const char *name,
+ const struct driver_data_req_params *params,
+ struct device *device)
+{
+ return -EINVAL;
+}
+#endif
+
#endif /* _LINUX_DRIVER_DATA_H */
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index b1f9f0ccb8ac..3a71924d35d7 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -13,6 +13,8 @@ struct firmware {
const u8 *data;
struct page **pages;
+ u8 api;
+
/* firmware loader private fields */
void *priv;
};
--
2.11.0
The requested nvram is optional, don't nag users about this.
Additionally, the new driver data API enables us to let the API
deal with the freeing of the nvram for us if we happen to free
it immediately on the consumer callback, this driver does that
so take advantage of this feature.
Originally based on patches by Rafał Miłecki.
Signed-off-by: Luis R. Rodriguez <[email protected]>
---
drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
index c7c1e9906500..d77ab264b5c1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
@@ -17,7 +17,7 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/device.h>
-#include <linux/firmware.h>
+#include <linux/driver_data.h>
#include <linux/module.h>
#include <linux/bcm47xx_nvram.h>
@@ -473,7 +473,6 @@ static void brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx)
if (raw_nvram)
bcm47xx_nvram_release_contents(data);
- release_firmware(fw);
if (!nvram && !(fwctx->flags & BRCMF_FW_REQ_NV_OPTIONAL))
goto fail;
@@ -491,6 +490,9 @@ static void brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx)
static void brcmf_fw_request_code_done(const struct firmware *fw, void *ctx)
{
struct brcmf_fw *fwctx = ctx;
+ struct driver_data_req_params params = {
+ DRIVER_DATA_DEFAULT_ASYNC_OPT(brcmf_fw_request_nvram_done, fwctx),
+ };
int ret;
brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev));
@@ -504,10 +506,7 @@ static void brcmf_fw_request_code_done(const struct firmware *fw, void *ctx)
return;
}
fwctx->code = fw;
- ret = request_firmware_nowait(THIS_MODULE, true, fwctx->nvram_name,
- fwctx->dev, GFP_KERNEL, fwctx,
- brcmf_fw_request_nvram_done);
-
+ ret = driver_data_request_async(fwctx->nvram_name, ¶ms, fwctx->dev);
if (!ret)
return;
--
2.11.0
The driver data API provides support for looking for firmware
from a specific set of API ranges, so just use that. Since we
free the firmware on the callback immediately after consuming it,
this also takes avantage of that feature.
Signed-off-by: Luis R. Rodriguez <[email protected]>
---
drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 67 ++++++++++------------------
1 file changed, 23 insertions(+), 44 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
index be466a074c1d..b6643aa5b344 100644
--- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
+++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
@@ -66,7 +66,7 @@
*****************************************************************************/
#include <linux/completion.h>
#include <linux/dma-mapping.h>
-#include <linux/firmware.h>
+#include <linux/driver_data.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
@@ -206,36 +206,20 @@ static int iwl_alloc_fw_desc(struct iwl_drv *drv, struct fw_desc *desc,
return 0;
}
-static void iwl_req_fw_callback(const struct firmware *ucode_raw,
- void *context);
+static int iwl_req_fw_callback(const struct firmware *ucode_raw, void *context);
-static int iwl_request_firmware(struct iwl_drv *drv, bool first)
+static int iwl_request_firmware(struct iwl_drv *drv)
{
const char *name_pre = drv->trans->cfg->fw_name_pre;
- char tag[8];
-
- if (first) {
- drv->fw_index = drv->trans->cfg->ucode_api_max;
- sprintf(tag, "%d", drv->fw_index);
- } else {
- drv->fw_index--;
- sprintf(tag, "%d", drv->fw_index);
- }
-
- if (drv->fw_index < drv->trans->cfg->ucode_api_min) {
- IWL_ERR(drv, "no suitable firmware found!\n");
- return -ENOENT;
- }
-
- snprintf(drv->firmware_name, sizeof(drv->firmware_name), "%s%s.ucode",
- name_pre, tag);
-
- IWL_DEBUG_INFO(drv, "attempting to load firmware '%s'\n",
- drv->firmware_name);
-
- return request_firmware_nowait(THIS_MODULE, 1, drv->firmware_name,
- drv->trans->dev,
- GFP_KERNEL, drv, iwl_req_fw_callback);
+ const struct iwl_cfg *cfg = drv->trans->cfg;
+ const struct driver_data_req_params req_params = {
+ DRIVER_DATA_API_CB(iwl_req_fw_callback, drv),
+ DRIVER_DATA_API(cfg->ucode_api_min, cfg->ucode_api_max, ".ucode"),
+ };
+
+ return driver_data_request_async(name_pre,
+ &req_params,
+ drv->trans->dev);
}
struct fw_img_parsing {
@@ -1237,7 +1221,7 @@ static void _iwl_op_mode_stop(struct iwl_drv *drv)
* If loaded successfully, copies the firmware into buffers
* for the card to fetch (via DMA).
*/
-static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
+static int iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
{
struct iwl_drv *drv = context;
struct iwl_fw *fw = &drv->fw;
@@ -1260,10 +1244,12 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
pieces = kzalloc(sizeof(*pieces), GFP_KERNEL);
if (!pieces)
- return;
+ return -ENOMEM;
- if (!ucode_raw)
- goto try_again;
+ if (!ucode_raw) {
+ err = -ENOENT;
+ goto free;
+ }
IWL_DEBUG_INFO(drv, "Loaded firmware file '%s' (%zd bytes).\n",
drv->firmware_name, ucode_raw->size);
@@ -1426,9 +1412,6 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
fw->ucode_capa.standard_phy_calibration_size =
IWL_MAX_STANDARD_PHY_CALIBRATE_TBL_SIZE;
- /* We have our copies now, allow OS release its copies */
- release_firmware(ucode_raw);
-
mutex_lock(&iwlwifi_opmode_table_mtx);
switch (fw->type) {
case IWL_FW_DVM:
@@ -1483,16 +1466,13 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
goto free;
try_again:
- /* try next, if any */
- release_firmware(ucode_raw);
- if (iwl_request_firmware(drv, false))
- goto out_unbind;
+ err = -EAGAIN;
goto free;
out_free_fw:
+ err = -ENOMEM;
IWL_ERR(drv, "failed to allocate pci memory\n");
iwl_dealloc_ucode(drv);
- release_firmware(ucode_raw);
out_unbind:
complete(&drv->request_firmware_complete);
device_release_driver(drv->trans->dev);
@@ -1501,6 +1481,7 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
kfree(pieces->img[i].sec);
kfree(pieces->dbg_mem_tlv);
kfree(pieces);
+ return err;
}
struct iwl_drv *iwl_drv_start(struct iwl_trans *trans)
@@ -1541,11 +1522,9 @@ struct iwl_drv *iwl_drv_start(struct iwl_trans *trans)
}
#endif
- ret = iwl_request_firmware(drv, true);
- if (ret) {
- IWL_ERR(trans, "Couldn't request the fw\n");
+ ret = iwl_request_firmware(drv);
+ if (ret)
goto err_fw;
- }
return drv;
--
2.11.0
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 <[email protected]>
---
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
+ 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: [email protected]
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 <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of copyleft-next (version 0.3.1 or later) as published
+ * at http://copyleft-next.org/.
+ *
+ * This module provides an interface to trigger and test the driver data API
+ * through a series of configurations and a few triggers. This driver
+ * lacks any extra dependencies, and will not normally be loaded by the
+ * system unless explicitly requested by name. You can also build this
+ * driver into your kernel.
+ *
+ * Although all configurations are already written for and will be supported
+ * for this test driver, ideally we should strive to see what mechanisms we
+ * can put in place to instead automatically generate this sort of test
+ * interface, test cases, and infer results. Its a simple enough interface that
+ * should hopefully enable more exploring in this area.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/completion.h>
+#include <linux/driver_data.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/async.h>
+#include <linux/delay.h>
+#include <linux/vmalloc.h>
+
+/* Used for the fallback default to test against */
+#define TEST_DRIVER_DATA "test-driver_data.bin"
+
+/*
+ * For device allocation / registration
+ */
+static DEFINE_MUTEX(reg_dev_mutex);
+static LIST_HEAD(reg_test_devs);
+
+/*
+ * num_test_devs actually represents the *next* ID of the next
+ * device we will allow to create.
+ */
+int num_test_devs;
+
+/**
+ * test_config - represents configuration for the driver_data API
+ *
+ * @name: the name of the primary driver_data file to look for
+ * @default_name: a fallback example, used to test the optional callback
+ * mechanism.
+ * @async: true if you want to trigger an async request. This will use
+ * driver_data_request_async(). If false the synchronous call will
+ * be used, driver_data_request_sync().
+ * @optional: whether or not the driver_data is optional refer to the
+ * struct driver_data_reg_params @optional field for more information.
+ * @keep: whether or not we wish to free the driver_data on our own, refer to
+ * the struct driver_data_req_params @keep field for more information.
+ * @enable_opt_cb: whether or not the optional callback should be set
+ * on a trigger. There is no equivalent setting on the struct
+ * driver_data_req_params as this is implementation specific, and in
+ * in driver_data API its explicit if you had defined an optional call
+ * back for your descriptor with either DRIVER_DATA_SYNC_OPT_CB() or
+ * DRIVER_DATA_ASYNC_OPT_CB(). Since the params are in a const we have
+ * no option but to use a flag and two const structs to decide which
+ * one we should use.
+ * @use_api_versioning: use the driver data API versioning support. This
+ * currenlty implies you are using an async test.
+ * @api_min: API min version to use for the test.
+ * @api_max: API max version to use for the test.
+ * @api_name_postfix: API name postfix
+ * @test_result: a test may use this to collect the result from the call
+ * of the driver_data_request_async() or driver_data_request_sync() calls
+ * used in their tests. Note that for async calls this typically will be a
+ * successful result (0) unless of course you've used bogus parameters, or
+ * the system is out of memory. Tests against the callbacks can only be
+ * implementation specific, so we don't test for that for now but it may
+ * make sense to build tests cases against a series of semantically
+ * similar family of callbacks that generally represents usage in the
+ * kernel. Synchronous calls return bogus error checks against the
+ * parameters as well, but also return the result of the work from the
+ * callbacks. You can therefore rely on sync calls if you really want to
+ * test for the callback results as well. Errors you can expect:
+ *
+ * API specific:
+ *
+ * 0: success for sync, for async it means request was sent
+ * -EINVAL: invalid parameters or request
+ * -ENOENT: files not found
+ *
+ * System environment:
+ *
+ * -ENOMEM: memory pressure on system
+ * -ENODEV: out of number of devices to test
+ *
+ * The ordering of elements in this struct must match the exact order of the
+ * elements in the ATTRIBUTE_GROUPS(test_dev_config), this is done to know
+ * what corresponding field each device attribute configuration entry maps
+ * to what struct member on test_alloc_dev_attrs().
+ */
+struct test_config {
+ char *name;
+ char *default_name;
+ bool async;
+ bool optional;
+ bool keep;
+ bool enable_opt_cb;
+ bool use_api_versioning;
+ u8 api_min;
+ u8 api_max;
+ char *api_name_postfix;
+
+ int test_result;
+};
+
+/**
+ * test_driver_data_private - private device driver driver_data representation
+ *
+ * @size: size of the data copied, in bytes
+ * @data: the actual data we copied over from driver_data
+ * @written: true if a callback managed to copy data over to the device
+ * successfully. Since different callbacks are used for this purpose
+ * having the data written does not necessarily mean a test case
+ * completed successfully. Each tests case has its own specific
+ * goals.
+ *
+ * Private representation of buffer where we put the device system data.
+ */
+struct test_driver_data_private {
+ size_t size;
+ u8 *data;
+ u8 api;
+ bool written;
+};
+
+/**
+ * driver_data_test_device - test device to help test driver_data
+ *
+ * @dev_idx: unique ID for test device
+ * @config: this keeps the device's own configuration. Instead of creating
+ * different triggers for all possible test cases we can think of in
+ * kernel, we expose a set possible device attributes for tuning the
+ * driver_data API and we to let you tune them in userspace. We then just
+ * provide one trigger.
+ * @test_driver_data: internal private representation of a storage area
+ * a driver might typically use to stuff firmware / driver_data.
+ * @misc_dev: we use a misc device under the hood
+ * @dev: pointer to misc_dev's own struct device
+ * @api_found_calls: number of calls a fetch for a driver was found. We use
+ * for internal use on the api callback.
+ * @driver_data_mutex: for access into the @driver_data, the fake storage
+ * location for the system data we copy.
+ * @config_mutex: used to protect configuration changes
+ * @trigger_mutex: all triggers are mutually exclusive when testing. To help
+ * enable testing you can create a different device, each device has its
+ * own set of protections, mimicking real devices.
+ * @request_complete: used to help the driver inform itself when async
+ * callbacks complete.
+ * list: needed to be part of the reg_test_devs
+ */
+struct driver_data_test_device {
+ int dev_idx;
+ struct test_config config;
+ struct test_driver_data_private test_driver_data;
+ struct miscdevice misc_dev;
+ struct device *dev;
+
+ u8 api_found_calls;
+
+ struct mutex driver_data_mutex;
+ struct mutex config_mutex;
+ struct mutex trigger_mutex;
+ struct completion request_complete;
+ struct list_head list;
+};
+
+static struct miscdevice *dev_to_misc_dev(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static struct driver_data_test_device *
+misc_dev_to_test_dev(struct miscdevice *misc_dev)
+{
+ return container_of(misc_dev, struct driver_data_test_device, misc_dev);
+}
+
+static struct driver_data_test_device *dev_to_test_dev(struct device *dev)
+{
+ struct miscdevice *misc_dev;
+
+ misc_dev = dev_to_misc_dev(dev);
+
+ return misc_dev_to_test_dev(misc_dev);
+}
+
+static ssize_t test_fw_misc_read(struct file *f, char __user *buf,
+ size_t size, loff_t *offset)
+{
+ struct miscdevice *misc_dev = f->private_data;
+ struct driver_data_test_device *test_dev =
+ misc_dev_to_test_dev(misc_dev);
+ struct test_driver_data_private *test_driver_data =
+ &test_dev->test_driver_data;
+ ssize_t ret = 0;
+
+ mutex_lock(&test_dev->driver_data_mutex);
+ if (test_driver_data->written)
+ ret = simple_read_from_buffer(buf, size, offset,
+ test_driver_data->data,
+ test_driver_data->size);
+ mutex_unlock(&test_dev->driver_data_mutex);
+
+ return ret;
+}
+
+static const struct file_operations test_fw_fops = {
+ .owner = THIS_MODULE,
+ .read = test_fw_misc_read,
+};
+
+static
+void free_test_driver_data(struct test_driver_data_private *test_driver_data)
+{
+ kfree(test_driver_data->data);
+ test_driver_data->data = NULL;
+ test_driver_data->size = 0;
+ test_driver_data->api = 0;
+ test_driver_data->written = false;
+}
+
+static int test_load_driver_data(struct driver_data_test_device *test_dev,
+ const struct firmware *driver_data)
+{
+ struct test_driver_data_private *test_driver_data =
+ &test_dev->test_driver_data;
+ int ret = 0;
+
+ if (!driver_data)
+ return -ENOENT;
+
+ mutex_lock(&test_dev->driver_data_mutex);
+
+ free_test_driver_data(test_driver_data);
+
+ test_driver_data->data = kzalloc(driver_data->size, GFP_KERNEL);
+ if (!test_driver_data->data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(test_driver_data->data, driver_data->data, driver_data->size);
+ test_driver_data->size = driver_data->size;
+ test_driver_data->written = true;
+ test_driver_data->api = driver_data->api;
+
+ dev_info(test_dev->dev, "loaded: %zu\n", test_driver_data->size);
+
+out:
+ mutex_unlock(&test_dev->driver_data_mutex);
+
+ return ret;
+}
+
+static int sync_found_cb(void *context, const struct firmware *driver_data)
+{
+ 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 <[email protected]>");
+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 <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of copyleft-next (version 0.3.1 or later) as published
+# at http://copyleft-next.org/.
+
+# This performs a series tests against firmware_class to excercise the
+# firmware_class driver with focus only on the extensible driver data API.
+#
+# To make this test self contained, and not pollute your distribution
+# firmware install paths, we reset the custom load directory to a
+# temporary location.
+
+set -e
+
+TEST_NAME="driver_data"
+TEST_DRIVER="test_${TEST_NAME}"
+TEST_DIR=$(dirname $0)
+
+# This represents
+#
+# TEST_ID:TEST_COUNT:ENABLED
+#
+# TEST_ID: is the test id number
+# TEST_COUNT: number of times we should run the test
+# ENABLED: 1 if enabled, 0 otherwise
+#
+# Once these are enabled please leave them as-is. Write your own test,
+# we have tons of space.
+ALL_TESTS="0001:3:1"
+ALL_TESTS="$ALL_TESTS 0002:3:1"
+ALL_TESTS="$ALL_TESTS 0003:3:1"
+ALL_TESTS="$ALL_TESTS 0004:10:1"
+ALL_TESTS="$ALL_TESTS 0005:10:1"
+ALL_TESTS="$ALL_TESTS 0006:10:1"
+ALL_TESTS="$ALL_TESTS 0007:10:1"
+ALL_TESTS="$ALL_TESTS 0008:10:1"
+ALL_TESTS="$ALL_TESTS 0009:10:1"
+ALL_TESTS="$ALL_TESTS 0010:10:1"
+ALL_TESTS="$ALL_TESTS 0011:10:1"
+ALL_TESTS="$ALL_TESTS 0012:1:1"
+ALL_TESTS="$ALL_TESTS 0013:1:1"
+
+# Not yet sure how to automate suspend test well yet. For now we expect a
+# manual run. If using qemu you can resume a guest using something like the
+# following on the monitor pts.
+# system_wakeupakeup | socat - /dev/pts/7,raw,echo=0,crnl
+#ALL_TESTS="$ALL_TESTS 0014:0:1"
+
+test_modprobe()
+{
+ if [ ! -d $DIR ]; then
+ echo "$0: $DIR not present" >&2
+ echo "You must have the following enabled in your kernel:" >&2
+ cat $TEST_DIR/config >&2
+ exit 1
+ fi
+}
+
+function allow_user_defaults()
+{
+ if [ -z $DEFAULT_NUM_TESTS ]; then
+ DEFAULT_NUM_TESTS=50
+ fi
+
+ if [ -z $FW_SYSFSPATH ]; then
+ FW_SYSFSPATH="/sys/module/firmware_class/parameters/path"
+ fi
+
+ if [ -z $OLD_FWPATH ]; then
+ OLD_FWPATH=$(cat $FW_SYSFSPATH)
+ fi
+
+ if [ -z $FWPATH]; then
+ FWPATH=$(mktemp -d)
+ fi
+
+ if [ -z $DEFAULT_DRIVER_DATA ]; then
+ config_reset
+ DEFAULT_DRIVER_DATA=$(config_get_name)
+ fi
+
+ if [ -z $FW ]; then
+ FW="$FWPATH/$DEFAULT_DRIVER_DATA"
+ fi
+
+ if [ -z $SYS_STATE_PATH ]; then
+ SYS_STATE_PATH="/sys/power/state"
+ fi
+
+ # Set the kernel search path.
+ echo -n "$FWPATH" > $FW_SYSFSPATH
+
+ # This is an unlikely real-world firmware content. :)
+ echo "ABCD0123" >"$FW"
+}
+
+test_reqs()
+{
+ if ! which diff 2> /dev/null > /dev/null; then
+ echo "$0: You need diff installed"
+ exit 1
+ fi
+
+ uid=$(id -u)
+ if [ $uid -ne 0 ]; then
+ echo $msg must be run as root >&2
+ exit 0
+ fi
+}
+
+function load_req_mod()
+{
+ trap "test_modprobe" EXIT
+
+ if [ -z $DIR ]; then
+ DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"
+ fi
+
+ if [ ! -d $DIR ]; then
+ modprobe $TEST_DRIVER
+ fi
+}
+
+test_finish()
+{
+ echo -n "$OLD_PATH" >/sys/module/firmware_class/parameters/path
+ rm -f "$FW"
+ rmdir "$FWPATH"
+}
+
+errno_name_to_val()
+{
+ case "$1" in
+ SUCCESS)
+ echo 0;;
+ -EPERM)
+ echo -1;;
+ -ENOENT)
+ echo -2;;
+ -EINVAL)
+ echo -22;;
+ -ERR_ANY)
+ echo -123456;;
+ *)
+ echo invalid;;
+ esac
+}
+
+errno_val_to_name()
+ case "$1" in
+ 0)
+ echo SUCCESS;;
+ -1)
+ echo -EPERM;;
+ -2)
+ echo -ENOENT;;
+ -22)
+ echo -EINVAL;;
+ -123456)
+ echo -ERR_ANY;;
+ *)
+ echo invalid;;
+ esac
+
+config_set_async()
+{
+ if ! echo -n 1 >$DIR/config_async ; then
+ echo "$0: Unable to set to async" >&2
+ exit 1
+ fi
+}
+
+config_disable_async()
+{
+ if ! echo -n 0 >$DIR/config_async ; then
+ echo "$0: Unable to set to sync" >&2
+ exit 1
+ fi
+}
+
+config_set_optional()
+{
+ if ! echo -n 1 >$DIR/config_optional ; then
+ echo "$0: Unable to set to optional" >&2
+ exit 1
+ fi
+}
+
+config_disable_optional()
+{
+ if ! echo -n 0 >$DIR/config_optional ; then
+ echo "$0: Unable to disable optional" >&2
+ exit 1
+ fi
+}
+
+config_set_keep()
+{
+ if ! echo -n 1 >$DIR/config_keep; then
+ echo "$0: Unable to set to keep" >&2
+ exit 1
+ fi
+}
+
+config_disable_keep()
+{
+ if ! echo -n 0 >$DIR/config_keep; then
+ echo "$0: Unable to disable keep option" >&2
+ exit 1
+ fi
+}
+
+config_enable_opt_cb()
+{
+ if ! echo -n 1 >$DIR/config_enable_opt_cb; then
+ echo "$0: Unable to set to optional" >&2
+ exit 1
+ fi
+}
+
+config_enable_api_versioning()
+{
+ if ! echo -n 1 >$DIR/config_use_api_versioning; then
+ echo "$0: Unable to set use_api_versioning option" >&2
+ exit 1
+ fi
+}
+
+config_set_api_name_postfix()
+{
+ if ! echo -n $1 >$DIR/config_api_name_postfix; then
+ echo "$0: Unable to set use_api_versioning option" >&2
+ exit 1
+ fi
+}
+
+config_set_api_min()
+{
+ if ! echo -n $1 >$DIR/config_api_min; then
+ echo "$0: Unable to set config_api_min option" >&2
+ exit 1
+ fi
+}
+
+config_set_api_max()
+{
+ if ! echo -n $1 >$DIR/config_api_max; then
+ echo "$0: Unable to set config_api_max option" >&2
+ exit 1
+ fi
+}
+
+config_add_api_file()
+{
+ TMP_FW="$FWPATH/$1"
+ echo "ABCD0123" >"$TMP_FW"
+}
+
+config_rm_api_file()
+{
+ TMP_FW="$FWPATH/$1"
+ rm -f $TMP_FW
+}
+
+# For special characters use printf directly,
+# refer to driver_data_test_0001
+config_set_name()
+{
+ if ! echo -n $1 >$DIR/config_name; then
+ echo "$0: Unable to set name" >&2
+ exit 1
+ fi
+}
+
+config_get_name()
+{
+ cat $DIR/config_name
+}
+
+# For special characters use printf directly,
+# refer to driver_data_test_0001
+config_set_default_name()
+{
+ if ! echo -n $1 >$DIR/config_default_name; then
+ echo "$0: Unable to set default_name" >&2
+ exit 1
+ fi
+}
+
+config_get_default_name()
+{
+ cat $DIR/config_default_name
+}
+
+config_get_test_result()
+{
+ cat $DIR/test_result
+}
+
+config_reset()
+{
+ if ! echo -n "1" >"$DIR"/reset; then
+ echo "$0: reset shuld have worked" >&2
+ exit 1
+ fi
+}
+
+trigger_release_driver_data()
+{
+ if ! echo -n "1" >"$DIR"/trigger_release_driver_data; then
+ echo "$0: release driver data shuld have worked" >&2
+ exit 1
+ fi
+}
+
+config_show_config()
+{
+ echo "----------------------------------------------------"
+ cat "$DIR"/config
+ echo "----------------------------------------------------"
+}
+
+config_trigger()
+{
+ if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then
+ echo "$1: FAIL - loading should have worked" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - loading driver_data"
+}
+
+config_trigger_want_fail()
+{
+ if echo "1" > $DIR/trigger_config 2>/dev/null; then
+ echo "$1: FAIL - loading was expected to fail" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - loading failed as expected"
+}
+
+config_file_should_match()
+{
+ FILE=$(config_get_name)
+ if [ ! -z $2 ]; then
+ FILE=$2
+ fi
+ # On this one we expect the file to exist so leave stderr in
+ if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > /dev/null; then
+ echo "$1: FAIL - file $FILE did not match contents in /dev/test_driver_data0" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - $FILE == /dev/test_driver_data0"
+}
+
+config_file_should_match_default()
+{
+ FILE=$(config_get_default_name)
+ # On this one we expect the file to exist so leave stderr in
+ if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_driver_data0 > /dev/null) > /dev/null; then
+ echo "$1: FAIL - file $FILE did not match contents in /dev/test_driver_data0" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - $FILE == /dev/test_driver_data0"
+}
+
+config_file_should_not_match()
+{
+ FILE=$(config_get_name)
+ # File may not exist, so skip those error messages as well
+ if $(diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null) 2> /dev/null ; then
+ echo "$1: FAIL - file $FILE was not expected to match /dev/null" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - $FILE != /dev/test_driver_data0"
+}
+
+config_default_file_should_match()
+{
+ FILE=$(config_get_default_name)
+ diff -q $FWPATH/$FILE /dev/test_driver_data0 2> /dev/null
+ if ! $? ; then
+ echo "$1: FAIL - file $FILE expected to match /dev/test_driver_data0" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! [file integrity matches]"
+}
+
+config_default_file_should_not_match()
+{
+ FILE=$(config_get_default_name)
+ diff -q FWPATH/$FILE /dev/test_driver_data0 2> /dev/null
+ if $? 2> /dev/null ; then
+ echo "$1: FAIL - file $FILE was not expected to match test_driver_data0" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK!"
+}
+
+config_expect_result()
+{
+ RC=$(config_get_test_result)
+ RC_NAME=$(errno_val_to_name $RC)
+
+ ERRNO_NAME=$2
+ ERRNO=$(errno_name_to_val $ERRNO_NAME)
+
+ if [[ $ERRNO_NAME = "-ERR_ANY" ]]; then
+ if [[ $RC -ge 0 ]]; then
+ echo "$1: FAIL, test expects $ERRNO_NAME - got $RC_NAME ($RC)" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ elif [[ $RC != $ERRNO ]]; then
+ echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got $RC_NAME ($RC)" >&2
+ config_show_config >&2
+ exit 1
+ fi
+ echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME"
+}
+
+driver_data_set_sync_defaults()
+{
+ config_reset
+}
+
+driver_data_set_async_defaults()
+{
+ config_reset
+ config_set_async
+}
+
+set_system_state()
+{
+ STATE="mem"
+ if [ ! -z $2 ]; then
+ STATE=$2
+ fi
+ echo $STATE > $SYS_STATE_PATH
+}
+
+driver_data_test_0001s()
+{
+ NAME='\000'
+
+ driver_data_set_sync_defaults
+ config_set_name $NAME
+ printf '\000' >"$DIR"/config_name
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+driver_data_test_0001a()
+{
+ NAME='\000'
+
+ driver_data_set_async_defaults
+ printf '\000' >"$DIR"/config_name
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+driver_data_test_0001()
+{
+ driver_data_test_0001s
+ driver_data_test_0001a
+}
+
+driver_data_test_0002s()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_sync_defaults
+ config_set_name $NAME
+ config_trigger_want_fail ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+driver_data_test_0002a()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_async_defaults
+ config_set_name $NAME
+ config_trigger_want_fail ${FUNCNAME[0]}
+ # This may seem odd to expect success on a bogus
+ # file but remember this is an async call, the actual
+ # error handling is managed by the async callbacks.
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0002()
+{
+ driver_data_test_0002s
+ driver_data_test_0002a
+}
+
+driver_data_test_0003()
+{
+ config_reset
+ config_file_should_not_match ${FUNCNAME[0]}
+}
+
+driver_data_test_0004s()
+{
+ driver_data_set_sync_defaults
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0004a()
+{
+ driver_data_set_async_defaults
+ config_trigger ${FUNCNAME[0]}
+ config_file_should_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+driver_data_test_0004()
+{
+ driver_data_test_0004s
+ driver_data_test_0004a
+}
+
+driver_data_test_0005s()
+{
+ NAME="nope-$DEFAULT_DRIVER_DATA"
+
+ driver_data_set_sync_defaults
+ config_set_optional
+ config_set_name $NAME
+ config_trigger_want_fail ${FUNCNAME[0]}
+ # We do this to ensure the default backup callback hasn't
+ # been called yet
+ config_file_should_not_match ${FUNCNAME[0]}
+ config_expect_result ${FUNCNAME[0]} 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"
+ echo " -s Run test ID x test-count number of times"
+ echo " -l List all test ID list"
+ echo " -h|--help Help"
+ echo
+ echo "If an error every occurs execution will immediately terminate."
+ echo "If you are adding a new test try using -w <test-ID> first to"
+ echo "make sure the test passes a series of tests."
+ echo
+ echo Example uses:
+ echo
+ echo "$TEST_NAME.sh -- executes all tests"
+ echo "$TEST_NAME.sh -t 0008 -- Executes test ID 0008 number of times is recomended"
+ echo "$TEST_NAME.sh -w 0008 -- Watch test ID 0008 run until an error occurs"
+ echo "$TEST_NAME.sh -s 0008 -- Run test ID 0008 once"
+ echo "$TEST_NAME.sh -c 0008 3 -- Run test ID 0008 three times"
+ echo
+ list_tests
+ exit 1
+}
+
+function test_num()
+{
+ re='^[0-9]+$'
+ if ! [[ $1 =~ $re ]]; then
+ usage
+ fi
+}
+
+function get_test_count()
+{
+ test_num $1
+ TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
+ LAST_TWO=${TEST_DATA#*:*}
+ echo ${LAST_TWO%:*}
+}
+
+function get_test_enabled()
+{
+ test_num $1
+ TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$1'}')
+ echo ${TEST_DATA#*:*:}
+}
+
+function run_all_tests()
+{
+ for i in $ALL_TESTS ; do
+ TEST_ID=${i%:*:*}
+ ENABLED=$(get_test_enabled $TEST_ID)
+ TEST_COUNT=$(get_test_count $TEST_ID)
+ if [[ $ENABLED -eq "1" ]]; then
+ test_case $TEST_ID $TEST_COUNT
+ fi
+ done
+}
+
+function watch_log()
+{
+ if [ $# -ne 3 ]; then
+ clear
+ fi
+ date
+ echo "Running test: $2 - run #$1"
+}
+
+function watch_case()
+{
+ i=0
+ while [ 1 ]; do
+
+ if [ $# -eq 1 ]; then
+ test_num $1
+ watch_log $i ${TEST_NAME}_test_$1
+ ${TEST_NAME}_test_$1
+ else
+ watch_log $i all
+ run_all_tests
+ fi
+ let i=$i+1
+ done
+}
+
+function test_case()
+{
+ NUM_TESTS=$DEFAULT_NUM_TESTS
+ if [ $# -eq 2 ]; then
+ NUM_TESTS=$2
+ fi
+
+ i=0
+ while [ $i -lt $NUM_TESTS ]; do
+ test_num $1
+ watch_log $i ${TEST_NAME}_test_$1 noclear
+ RUN_TEST=${TEST_NAME}_test_$1
+ $RUN_TEST
+ let i=$i+1
+ done
+}
+
+function parse_args()
+{
+ if [ $# -eq 0 ]; then
+ run_all_tests
+ else
+ if [[ "$1" = "all" ]]; then
+ run_all_tests
+ elif [[ "$1" = "-w" ]]; then
+ shift
+ watch_case $@
+ elif [[ "$1" = "-t" ]]; then
+ shift
+ test_num $1
+ test_case $1 $(get_test_count $1)
+ elif [[ "$1" = "-c" ]]; then
+ shift
+ test_num $1
+ test_num $2
+ test_case $1 $2
+ elif [[ "$1" = "-s" ]]; then
+ shift
+ test_case $1 1
+ elif [[ "$1" = "-l" ]]; then
+ list_tests
+ elif [[ "$1" = "-h" || "$1" = "--help" ]]; then
+ usage
+ else
+ usage
+ fi
+ fi
+}
+
+test_reqs
+load_req_mod
+allow_user_defaults
+
+trap "test_finish" EXIT
+
+parse_args $@
+
+exit 0
--
2.11.0
On Wed, 2017-03-29 at 20:25 -0700, Luis R. Rodriguez wrote:
> As the firmware API evolves we keep extending functions with more arguments.
> Stop this nonsense by proving an extensible data structure which can be used
> to represent both user parameters and private internal parameters.
>
> We introduce 3 data structures:
>
> o struct driver_data_req_params - used for user specified parameters
> o struct driver_data_priv_params - used for internal use
> o struct driver_data_params - stiches both of the the above together,
> also only for internal use
>
> This starts off by just making the existing APIs use the new data
> structures, it will make subsequent changes easier to review which will
> be adding new flexible APIs.
>
> This commit should introduces no functional changes (TM).
>
> Signed-off-by: Luis R. Rodriguez <[email protected]>
> ---
Looks fine with a few nitpicks.
> diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
> index 54fc4c42f126..f702566554e1 100644
> --- a/drivers/base/firmware_class.c
> +++ b/drivers/base/firmware_class.c
[...]
> @@ -40,6 +41,77 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
> MODULE_DESCRIPTION("Multi purpose firmware loading support");
> MODULE_LICENSE("GPL");
>
> +struct driver_data_priv_params {
> + bool use_fallback;
> + bool use_fallback_uevent;
> + bool no_cache;
> + void *alloc_buf;
> + size_t alloc_buf_size;
> +};
> +
> +struct driver_data_params {
> + const struct firmware *driver_data;
> + const struct driver_data_req_params req_params;
> + struct driver_data_priv_params priv_params;
> +};
> +
> +/*
> + * These are kept to remain backward compatible with old behaviour. Do not
> + * modify them unless you know what you are doing. These are to be used only
> + * by the old API, so:
> + *
> + * Old sync APIs:
> + * o request_firmware(): __DATA_REQ_FIRMWARE()
> + * o request_firmware_direct(): __DATA_REQ_FIRMWARE_DIRECT()
> + * o request_firmware_into_buf(): __DATA_REQ_FIRMWARE_BUF()
> + *
> + * Old async API:
> + * o request_firmware_nowait(): __DATA_REQ_FIRMWARE_NOWAIT()
> + */
> +#define __DATA_REQ_FIRMWARE() \
> + .priv_params = { \
> + .use_fallback = !!FW_OPT_FALLBACK, \
use_fallback is a boolean, so you don't need the double negation here.
[...]
> @@ -1332,12 +1433,15 @@ request_firmware_into_buf(const struct firmware **firmware_p, const char *name,
> struct device *device, void *buf, size_t size)
> {
> int ret;
> + struct driver_data_params data_params = {
> + __DATA_REQ_FIRMWARE_BUF(buf, size),
> + };
>
> __module_get(THIS_MODULE);
> - ret = _request_firmware(firmware_p, name, device, buf, size,
> - FW_OPT_UEVENT | FW_OPT_FALLBACK |
> - FW_OPT_NOCACHE);
> + ret = _request_firmware(firmware_p, name, &data_params, device);
> module_put(THIS_MODULE);
> +
> +
Double empty-lines here.
> return ret;
> }
> EXPORT_SYMBOL(request_firmware_into_buf);
>
[...]
> diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
> new file mode 100644
> index 000000000000..c3d3a4129838
> --- /dev/null
> +++ b/include/linux/driver_data.h
> @@ -0,0 +1,88 @@
> +#ifndef _LINUX_DRIVER_DATA_H
> +#define _LINUX_DRIVER_DATA_H
> +
> +#include <linux/types.h>
> +#include <linux/compiler.h>
> +#include <linux/gfp.h>
> +#include <linux/device.h>
> +
> +/*
> + * Driver Data internals
> + *
> + * Copyright (C) 2017 Luis R. Rodriguez <[email protected]>
> + *
> + * 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/.
> + */
> +
> +/**
> + * enum driver_data_mode - driver data mode of operation
> + *
> + * DRIVER_DATA_SYNC: your call to request driver data is synchronous. We will
> + * look for the driver data file you have requested immediatley.
> + * DRIVER_DATA_ASYNC: your call to request driver data is asynchronous. We will
It should be @DRIVER_DATA_SYNC and @DRIVER_DATA_ASYNC here.
--
Cheers,
Luca.
On Wed, 2017-03-29 at 20:25 -0700, Luis R. Rodriguez wrote:
> The firmware API does not scale well: when new features are added we
> either add a new exported symbol or extend the arguments of existing
> routines. For the later case this means we need to traverse the kernel
> with a slew of collateral evolutions to adjust old driver users. The
> firmware API is also now being used for things outside of the scope of
> what typically would be considered "firmware". There are other
> subsystems which would like to make use of the firmware APIs for similar
> things and its clearly not firmware, but have different requirements
> and criteria which they'd like to be met for the requested file.
>
> An extensible API is in order:
>
> The driver data API accepts that there are only two types of requests:
>
> a) synchronous requests
> b) asynchronous requests
>
> Both requests may have a different requirements which must be met. These
> requirements can be described in the struct driver_data_req_params.
> This struct is expected to be extended over time to support different
> requirements as the kernel evolves.
>
> After a bit of hard work the new interface has been wrapped onto the
> functionality. The fallback mechanism has been kept out of the new API
> currently because it requires just a bit more grooming and documentation
> given new considerations and requirements. Adding support for it will
> be rather easy now that the new API sits ontop of the old one. The
> request_firmware_into_buf() API also is not enabled on the new API but
> it is rather easy to do so -- this call has no current existing users
> upstream though. Support will be provided once we add a respective
> series of test cases against it and find a proper upstream user for it.
>
> The flexible API also adds a few new bells and whistles:
>
> - By default the kernel will free the driver data file for you after
> your callbacks are called, you however are allowed to request that
> you wish to keep the driver data file on the requirements params. The
> new driver data API is able to free the driver data file for you by
> requiring a consumer callback for the driver data file.
> - Allows both asynchronous and synchronous request to specify that
> driver data files are optional. With the old APIs we had added one
> full API call, request_firmware_direct() just for this purpose --
> the driver data request APIs allow for you to annotate that a driver
> data file is optional for both synchronous or asynchronous requests
> through the same two basic set of APIs.
> - A firmware API framework is provided to enable daisy chaining a
> series of requests for firmware on a range of supported APIs.
>
> Signed-off-by: Luis R. Rodriguez <[email protected]>
> ---
> Documentation/driver-api/firmware/driver_data.rst | 77 +++++
> Documentation/driver-api/firmware/index.rst | 1 +
> Documentation/driver-api/firmware/introduction.rst | 16 +
> MAINTAINERS | 3 +-
> drivers/base/firmware_class.c | 357 +++++++++++++++++++++
> include/linux/driver_data.h | 202 +++++++++++-
> include/linux/firmware.h | 2 +
> 7 files changed, 656 insertions(+), 2 deletions(-)
> create mode 100644 Documentation/driver-api/firmware/driver_data.rst
>
> diff --git a/Documentation/driver-api/firmware/driver_data.rst b/Documentation/driver-api/firmware/driver_data.rst
> new file mode 100644
> index 000000000000..08407b7568fe
> --- /dev/null
> +++ b/Documentation/driver-api/firmware/driver_data.rst
> @@ -0,0 +1,77 @@
> +===============
> +driver_data API
> +===============
> +
> +Users of firmware request APIs has grown to include users which are not
Grammar. Maybe "The usage of firmware..."
> +looking for "firmware", but instead general driver data files which have
> +been kept oustide of the kernel. The driver data APIs addresses rebranding
> +of firmware as generic driver data files, and provides a flexible API which
> +mitigates collateral evolutions on the kernel as new functionality is
> +introduced.
This looks more like a commit message than an introduction to the
feature. In the future, we won't care why this was introduced, but we
want to know what it is and how it can be used.
> +
> +Driver data modes of operation
> +==============================
> +
> +There are only two types of modes of operation for driver data requests:
"only" seems irrelevant here.
> +
> + * synchronous - driver_data_request()
> + * asynchronous - driver_data_request_async()
> +
> +Synchronous requests expect requests to be done immediately, asynchronous
> +requests enable requests to be scheduled for a later time.
> +
> +Driver data request parameters
> +==============================
> +
> +Variations of types of driver data requests are specified by a driver data
> +request parameter data structure. This data structure is expected to grow as
> +new requirements grow.
Again, not sure it's relevant to know that it can grow. For
documentation purposes, the important is the *now*.
> +
> +Reference counting and releasing the driver data file
> +=====================================================
> +
> +As with the old firmware API both the device and module are bumped with
> +reference counts during the driver data requests. This prevents removal
> +of the device and module making the driver data request call until the
> +driver data request callbacks have completed, either synchronously or
> +asynchronously.
> +
> +The old firmware APIs refcounted the firmware_class module for synchronous
> +requests, meanwhile asynchronous requests refcounted the caller's module.
> +The driver data request API currently mimics this behaviour, for synchronous
> +requests the firmware_class module is refcounted through the use of
> +dfl_sync_reqs. In the future we may enable the ability to also refcount the
> +caller's module as well. Likewise in the future we may enable asynchronous
> +calls to refcount the firmware_class module.
Ditto. Maybe you could move all the "future" references to the
"Tracking development enhancements and ideas" section?
[...]
> diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
> index f702566554e1..cc3c2247980c 100644
> --- a/drivers/base/firmware_class.c
> +++ b/drivers/base/firmware_class.c
[...]
> @@ -1460,6 +1471,128 @@ void release_firmware(const struct firmware *fw)
> }
> EXPORT_SYMBOL(release_firmware);
>
> +static int _driver_data_request_api(struct driver_data_params *params,
> + struct device *device,
> + const char *name)
> +{
> + struct driver_data_priv_params *priv_params = ¶ms->priv_params;
> + const struct driver_data_req_params *req_params = ¶ms->req_params;
> + int ret;
> + char *try_name;
> + u8 api_max;
> +
> + if (priv_params->retry_api) {
> + if (!priv_params->api)
> + return -ENOENT;
> + api_max = priv_params->api - 1;
> + } else
> + api_max = req_params->api_max;
Braces.
> + for (priv_params->api = api_max;
> + priv_params->api >= req_params->api_min;
> + priv_params->api--) {
> + if (req_params->api_name_postfix)
> + try_name = kasprintf(GFP_KERNEL, "%s%d%s",
> + name,
> + priv_params->api,
> + req_params->api_name_postfix);
> + else
> + try_name = kasprintf(GFP_KERNEL, "%s%d",
> + name,
> + priv_params->api);
> + if (!try_name)
> + return -ENOMEM;
> + ret = _request_firmware(¶ms->driver_data, try_name,
> + params, device);
> + kfree(try_name);
> +
> + if (!ret)
> + break;
> +
> + release_firmware(params->driver_data);
> +
> + /*
> + * Only chug on with the API revision hunt if the file we
> + * looked for really was not present. In case of memory issues
> + * or other related system issues we want to bail right away
> + * to not put strain on the system.
> + */
> + if (ret != -ENOENT)
> + break;
> +
> + if (!priv_params->api)
> + break;
> + }
> +
> + return ret;
> +}
> +
> +/**
> + * driver_data_request - synchronous request for a driver data file
> + * @name: name of the driver data file
> + * @params: driver data parameters, it provides all the requirements
> + * parameters which must be met for the file being requested.
> + * @device: device for which firmware is being loaded
> + *
> + * This performs a synchronous driver data lookup with the requirements
> + * specified on @params, if the file was found meeting the criteria requested
> + * 0 is returned. Access to the driver data data can be accessed through
> + * an optional callback set on the @desc.
Huh? This last sentence seems wrong, I don't even see a @desc anywhere.
> If the driver data is optional
> + * you must specify that on @params and if set you may provide an alternative
> + * callback which if set would be run if the driver data was not found.
> + *
> + * The driver data passed to the callbacks will be NULL unless it was
> + * found matching all the criteria on @params. 0 is always returned if the file
> + * was found unless a callback was provided, in which case the callback's
> + * return value will be passed. Unless the params->keep was set the kernel will
> + * release the driver data for you after your callbacks were processed.
> + *
> + * Reference counting is used during the duration of this call on both the
> + * device and module that made the request. This prevents any callers from
> + * freeing either the device or module prior to completion of this call.
> + */
> +int driver_data_request_sync(const char *name,
> + const struct driver_data_req_params *req_params,
> + struct device *device)
> +{
> + const struct firmware *driver_data;
> + const struct driver_data_reqs *sync_reqs;
> + struct driver_data_params params = {
> + .req_params = *req_params,
> + };
> + int ret;
> +
> + if (!device || !req_params || !name || name[0] == '\0')
> + return -EINVAL;
> +
> + if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
> + return -EINVAL;
Why do you need to check this here? If the caller is calling _sync(),
it's because that's what it needs. This mode value here seems
redundant.
OTOH, if you do have a reason for this value, then you could use
driver_data_request_sync() in this if.
> +/**
> + * driver_data_request_async - asynchronous request for a driver data file
> + * @name: name of the driver data file
> + * @req_params: driver data file request parameters, it provides all the
> + * requirements which must be met for the file being requested.
> + * @device: device for which firmware is being loaded
> + *
> + * This performs an asynchronous driver data file lookup with the requirements
> + * specified on @req_params. The request for the actual driver data file lookup
> + * will be scheduled with schedule_work() to be run at a later time. 0 is
> + * returned if we were able to asynchronously schedlue your work to be run.
> + *
> + * Reference counting is used during the duration of this scheduled call on
> + * both the device and module that made the request. This prevents any callers
> + * from freeing either the device or module prior to completion of the
> + * scheduled work.
> + *
> + * Access to the driver data file data can be accessed through an optional
> + * callback set on the @req_params. If the driver data file is optional you
> + * must specify that on @req_params and if set you may provide an alternative
> + * callback which if set would be run if the driver data file was not found.
> + *
> + * The driver data file passed to the callbacks will always be NULL unless it
> + * was found matching all the criteria on @req_params. Unless the desc->keep
> + * was set the kernel will release the driver data file for you after your
> + * callbacks were processed on the scheduled work.
> + */
> +int driver_data_request_async(const char *name,
> + const struct driver_data_req_params *req_params,
> + struct device *device)
> +{
> + struct firmware_work *driver_work;
> + const struct driver_data_reqs *sync_reqs;
> + struct firmware_work driver_work_stack = {
> + .data_params.req_params = *req_params,
> + //.device = device,
> + //.name = name,
> + };
> +
> + if (!device || !req_params || !name || name[0] == '\0')
> + return -EINVAL;
> +
> + if (req_params->sync_reqs.mode != DRIVER_DATA_ASYNC)
> + return -EINVAL;
Same here.
--
Luca.
On Wed, 2017-03-29 at 20:25 -0700, Luis R. Rodriguez wrote:
> The driver data API provides support for looking for firmware
> from a specific set of API ranges, so just use that. Since we
> free the firmware on the callback immediately after consuming it,
> this also takes avantage of that feature.
>
> Signed-off-by: Luis R. Rodriguez <[email protected]>
> ---
Looks find, with one nitpick.
> drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 67 ++++++++++------------------
> 1 file changed, 23 insertions(+), 44 deletions(-)
>
> diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
> index be466a074c1d..b6643aa5b344 100644
> --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
> +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
[...]
> @@ -1541,11 +1522,9 @@ struct iwl_drv *iwl_drv_start(struct iwl_trans *trans)
> }
> #endif
>
> - ret = iwl_request_firmware(drv, true);
> - if (ret) {
> - IWL_ERR(trans, "Couldn't request the fw\n");
> + ret = iwl_request_firmware(drv);
> + if (ret)
> goto err_fw;
> - }
Why remove the error message here?
--
Cheers,
Luca.
On Mon, Apr 10, 2017 at 12:42:44PM +0000, Coelho, Luciano wrote:
> On Wed, 2017-03-29 at 20:25 -0700, Luis R. Rodriguez wrote:
> > The firmware API does not scale well: when new features are added we
> > either add a new exported symbol or extend the arguments of existing
> > routines. For the later case this means we need to traverse the kernel
> > with a slew of collateral evolutions to adjust old driver users. The
> > firmware API is also now being used for things outside of the scope of
> > what typically would be considered "firmware". There are other
> > subsystems which would like to make use of the firmware APIs for similar
> > things and its clearly not firmware, but have different requirements
> > and criteria which they'd like to be met for the requested file.
> >
> > An extensible API is in order:
> >
> > The driver data API accepts that there are only two types of requests:
> >
> > a) synchronous requests
> > b) asynchronous requests
> >
> > Both requests may have a different requirements which must be met. These
> > requirements can be described in the struct driver_data_req_params.
> > This struct is expected to be extended over time to support different
> > requirements as the kernel evolves.
> >
> > After a bit of hard work the new interface has been wrapped onto the
> > functionality. The fallback mechanism has been kept out of the new API
> > currently because it requires just a bit more grooming and documentation
> > given new considerations and requirements. Adding support for it will
> > be rather easy now that the new API sits ontop of the old one. The
> > request_firmware_into_buf() API also is not enabled on the new API but
> > it is rather easy to do so -- this call has no current existing users
> > upstream though. Support will be provided once we add a respective
> > series of test cases against it and find a proper upstream user for it.
> >
> > The flexible API also adds a few new bells and whistles:
> >
> > - By default the kernel will free the driver data file for you after
> > your callbacks are called, you however are allowed to request that
> > you wish to keep the driver data file on the requirements params. The
> > new driver data API is able to free the driver data file for you by
> > requiring a consumer callback for the driver data file.
> > - Allows both asynchronous and synchronous request to specify that
> > driver data files are optional. With the old APIs we had added one
> > full API call, request_firmware_direct() just for this purpose --
> > the driver data request APIs allow for you to annotate that a driver
> > data file is optional for both synchronous or asynchronous requests
> > through the same two basic set of APIs.
> > - A firmware API framework is provided to enable daisy chaining a
> > series of requests for firmware on a range of supported APIs.
> >
> > Signed-off-by: Luis R. Rodriguez <[email protected]>
> > ---
> > Documentation/driver-api/firmware/driver_data.rst | 77 +++++
> > Documentation/driver-api/firmware/index.rst | 1 +
> > Documentation/driver-api/firmware/introduction.rst | 16 +
> > MAINTAINERS | 3 +-
> > drivers/base/firmware_class.c | 357 +++++++++++++++++++++
> > include/linux/driver_data.h | 202 +++++++++++-
> > include/linux/firmware.h | 2 +
> > 7 files changed, 656 insertions(+), 2 deletions(-)
> > create mode 100644 Documentation/driver-api/firmware/driver_data.rst
> >
> > diff --git a/Documentation/driver-api/firmware/driver_data.rst b/Documentation/driver-api/firmware/driver_data.rst
> > new file mode 100644
> > index 000000000000..08407b7568fe
> > --- /dev/null
> > +++ b/Documentation/driver-api/firmware/driver_data.rst
> > @@ -0,0 +1,77 @@
> > +===============
> > +driver_data API
> > +===============
> > +
> > +Users of firmware request APIs has grown to include users which are not
>
> Grammar. Maybe "The usage of firmware..."
Or, Users of ... have grown in number, including ...
>
> > +looking for "firmware", but instead general driver data files which have
> > +been kept oustide of the kernel. The driver data APIs addresses rebranding
> > +of firmware as generic driver data files, and provides a flexible API which
> > +mitigates collateral evolutions on the kernel as new functionality is
> > +introduced.
>
> This looks more like a commit message than an introduction to the
> feature. In the future, we won't care why this was introduced, but we
> want to know what it is and how it can be used.
>
>
> > +
> > +Driver data modes of operation
> > +==============================
> > +
> > +There are only two types of modes of operation for driver data requests:
>
> "only" seems irrelevant here.
>
>
> > +
> > + * synchronous - driver_data_request()
> > + * asynchronous - driver_data_request_async()
> > +
> > +Synchronous requests expect requests to be done immediately, asynchronous
> > +requests enable requests to be scheduled for a later time.
> > +
> > +Driver data request parameters
> > +==============================
> > +
> > +Variations of types of driver data requests are specified by a driver data
> > +request parameter data structure. This data structure is expected to grow as
> > +new requirements grow.
>
> Again, not sure it's relevant to know that it can grow. For
> documentation purposes, the important is the *now*.
There seem to be a couple of new features/parameters.
Why not list them now:
* optional
* keep
* API versioning
I will add 'data(firmware) signing' here afterward.
>
> > +
> > +Reference counting and releasing the driver data file
> > +=====================================================
> > +
> > +As with the old firmware API both the device and module are bumped with
> > +reference counts during the driver data requests. This prevents removal
> > +of the device and module making the driver data request call until the
> > +driver data request callbacks have completed, either synchronously or
> > +asynchronously.
> > +
> > +The old firmware APIs refcounted the firmware_class module for synchronous
> > +requests, meanwhile asynchronous requests refcounted the caller's module.
> > +The driver data request API currently mimics this behaviour, for synchronous
> > +requests the firmware_class module is refcounted through the use of
> > +dfl_sync_reqs. In the future we may enable the ability to also refcount the
> > +caller's module as well. Likewise in the future we may enable asynchronous
> > +calls to refcount the firmware_class module.
>
> Ditto. Maybe you could move all the "future" references to the
> "Tracking development enhancements and ideas" section?
>
> [...]
>
> > diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
> > index f702566554e1..cc3c2247980c 100644
> > --- a/drivers/base/firmware_class.c
> > +++ b/drivers/base/firmware_class.c
>
> [...]
>
> > @@ -1460,6 +1471,128 @@ void release_firmware(const struct firmware *fw)
> > }
> > EXPORT_SYMBOL(release_firmware);
> >
> > +static int _driver_data_request_api(struct driver_data_params *params,
> > + struct device *device,
> > + const char *name)
> > +{
> > + struct driver_data_priv_params *priv_params = ¶ms->priv_params;
> > + const struct driver_data_req_params *req_params = ¶ms->req_params;
> > + int ret;
> > + char *try_name;
> > + u8 api_max;
> > +
> > + if (priv_params->retry_api) {
> > + if (!priv_params->api)
> > + return -ENOENT;
> > + api_max = priv_params->api - 1;
> > + } else
> > + api_max = req_params->api_max;
>
> Braces.
>
>
> > + for (priv_params->api = api_max;
> > + priv_params->api >= req_params->api_min;
> > + priv_params->api--) {
> > + if (req_params->api_name_postfix)
> > + try_name = kasprintf(GFP_KERNEL, "%s%d%s",
> > + name,
> > + priv_params->api,
> > + req_params->api_name_postfix);
> > + else
> > + try_name = kasprintf(GFP_KERNEL, "%s%d",
> > + name,
> > + priv_params->api);
> > + if (!try_name)
> > + return -ENOMEM;
> > + ret = _request_firmware(¶ms->driver_data, try_name,
> > + params, device);
> > + kfree(try_name);
> > +
> > + if (!ret)
> > + break;
> > +
> > + release_firmware(params->driver_data);
> > +
> > + /*
> > + * Only chug on with the API revision hunt if the file we
> > + * looked for really was not present. In case of memory issues
> > + * or other related system issues we want to bail right away
> > + * to not put strain on the system.
> > + */
> > + if (ret != -ENOENT)
> > + break;
> > +
> > + if (!priv_params->api)
> > + break;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +/**
> > + * driver_data_request - synchronous request for a driver data file
driver_data_request_sync
> > + * @name: name of the driver data file
> > + * @params: driver data parameters, it provides all the requirements
req_params
> > + * parameters which must be met for the file being requested.
> > + * @device: device for which firmware is being loaded
> > + *
> > + * This performs a synchronous driver data lookup with the requirements
> > + * specified on @params, if the file was found meeting the criteria requested
> > + * 0 is returned. Access to the driver data data can be accessed through
> > + * an optional callback set on the @desc.
>
> Huh? This last sentence seems wrong, I don't even see a @desc anywhere.
>
>
> > If the driver data is optional
> > + * you must specify that on @params and if set you may provide an alternative
> > + * callback which if set would be run if the driver data was not found.
> > + *
> > + * The driver data passed to the callbacks will be NULL unless it was
> > + * found matching all the criteria on @params. 0 is always returned if the file
> > + * was found unless a callback was provided, in which case the callback's
> > + * return value will be passed. Unless the params->keep was set the kernel will
> > + * release the driver data for you after your callbacks were processed.
> > + *
> > + * Reference counting is used during the duration of this call on both the
> > + * device and module that made the request. This prevents any callers from
> > + * freeing either the device or module prior to completion of this call.
> > + */
> > +int driver_data_request_sync(const char *name,
> > + const struct driver_data_req_params *req_params,
> > + struct device *device)
> > +{
> > + const struct firmware *driver_data;
> > + const struct driver_data_reqs *sync_reqs;
> > + struct driver_data_params params = {
> > + .req_params = *req_params,
> > + };
> > + int ret;
> > +
> > + if (!device || !req_params || !name || name[0] == '\0')
> > + return -EINVAL;
> > +
> > + if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
> > + return -EINVAL;
>
> Why do you need to check this here? If the caller is calling _sync(),
> it's because that's what it needs. This mode value here seems
> redundant.
>
> OTOH, if you do have a reason for this value, then you could use
> driver_data_request_sync() in this if.
I think two functions, driver_data_request_[a]sync(), can be
unified into one:
int driver_data_request(const char *name,
const struct driver_data_req_params *req_params,
struct device *device)
Thanks,
-Takahiro AKASHI
>
> > +/**
> > + * driver_data_request_async - asynchronous request for a driver data file
> > + * @name: name of the driver data file
> > + * @req_params: driver data file request parameters, it provides all the
> > + * requirements which must be met for the file being requested.
> > + * @device: device for which firmware is being loaded
> > + *
> > + * This performs an asynchronous driver data file lookup with the requirements
> > + * specified on @req_params. The request for the actual driver data file lookup
> > + * will be scheduled with schedule_work() to be run at a later time. 0 is
> > + * returned if we were able to asynchronously schedlue your work to be run.
> > + *
> > + * Reference counting is used during the duration of this scheduled call on
> > + * both the device and module that made the request. This prevents any callers
> > + * from freeing either the device or module prior to completion of the
> > + * scheduled work.
> > + *
> > + * Access to the driver data file data can be accessed through an optional
> > + * callback set on the @req_params. If the driver data file is optional you
> > + * must specify that on @req_params and if set you may provide an alternative
> > + * callback which if set would be run if the driver data file was not found.
> > + *
> > + * The driver data file passed to the callbacks will always be NULL unless it
> > + * was found matching all the criteria on @req_params. Unless the desc->keep
> > + * was set the kernel will release the driver data file for you after your
> > + * callbacks were processed on the scheduled work.
> > + */
> > +int driver_data_request_async(const char *name,
> > + const struct driver_data_req_params *req_params,
> > + struct device *device)
> > +{
> > + struct firmware_work *driver_work;
> > + const struct driver_data_reqs *sync_reqs;
> > + struct firmware_work driver_work_stack = {
> > + .data_params.req_params = *req_params,
> > + //.device = device,
> > + //.name = name,
> > + };
> > +
> > + if (!device || !req_params || !name || name[0] == '\0')
> > + return -EINVAL;
> > +
> > + if (req_params->sync_reqs.mode != DRIVER_DATA_ASYNC)
> > + return -EINVAL;
>
> Same here.
>
> --
> Luca.
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 <[email protected]>
> ---
> 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: [email protected]
> 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 <[email protected]>
> + *
> + * 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(®_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 <[email protected]>");
> +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 <[email protected]>
> +#
> +# 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
>
On Wed, Mar 29, 2017 at 08:25:11PM -0700, Luis R. Rodriguez wrote:
> The firmware API does not scale well: when new features are added we
> either add a new exported symbol or extend the arguments of existing
> routines. For the later case this means we need to traverse the kernel
> with a slew of collateral evolutions to adjust old driver users. The
> firmware API is also now being used for things outside of the scope of
> what typically would be considered "firmware". There are other
> subsystems which would like to make use of the firmware APIs for similar
> things and its clearly not firmware, but have different requirements
> and criteria which they'd like to be met for the requested file.
>
> An extensible API is in order:
>
> The driver data API accepts that there are only two types of requests:
>
> a) synchronous requests
> b) asynchronous requests
>
> Both requests may have a different requirements which must be met. These
> requirements can be described in the struct driver_data_req_params.
> This struct is expected to be extended over time to support different
> requirements as the kernel evolves.
>
> After a bit of hard work the new interface has been wrapped onto the
> functionality. The fallback mechanism has been kept out of the new API
> currently because it requires just a bit more grooming and documentation
> given new considerations and requirements. Adding support for it will
> be rather easy now that the new API sits ontop of the old one. The
> request_firmware_into_buf() API also is not enabled on the new API but
> it is rather easy to do so -- this call has no current existing users
> upstream though. Support will be provided once we add a respective
> series of test cases against it and find a proper upstream user for it.
>
> The flexible API also adds a few new bells and whistles:
>
> - By default the kernel will free the driver data file for you after
> your callbacks are called, you however are allowed to request that
> you wish to keep the driver data file on the requirements params. The
> new driver data API is able to free the driver data file for you by
> requiring a consumer callback for the driver data file.
> - Allows both asynchronous and synchronous request to specify that
> driver data files are optional. With the old APIs we had added one
> full API call, request_firmware_direct() just for this purpose --
> the driver data request APIs allow for you to annotate that a driver
> data file is optional for both synchronous or asynchronous requests
> through the same two basic set of APIs.
> - A firmware API framework is provided to enable daisy chaining a
> series of requests for firmware on a range of supported APIs.
>
> Signed-off-by: Luis R. Rodriguez <[email protected]>
> ---
> Documentation/driver-api/firmware/driver_data.rst | 77 +++++
> Documentation/driver-api/firmware/index.rst | 1 +
> Documentation/driver-api/firmware/introduction.rst | 16 +
I think we'd better to split code and documents into different patches
for easier reviews.
> MAINTAINERS | 3 +-
> drivers/base/firmware_class.c | 357 +++++++++++++++++++++
> include/linux/driver_data.h | 202 +++++++++++-
> include/linux/firmware.h | 2 +
> 7 files changed, 656 insertions(+), 2 deletions(-)
> create mode 100644 Documentation/driver-api/firmware/driver_data.rst
>
> diff --git a/Documentation/driver-api/firmware/driver_data.rst b/Documentation/driver-api/firmware/driver_data.rst
> new file mode 100644
> index 000000000000..08407b7568fe
> --- /dev/null
> +++ b/Documentation/driver-api/firmware/driver_data.rst
> @@ -0,0 +1,77 @@
> +===============
> +driver_data API
> +===============
> +
> +Users of firmware request APIs has grown to include users which are not
> +looking for "firmware", but instead general driver data files which have
> +been kept oustide of the kernel. The driver data APIs addresses rebranding
> +of firmware as generic driver data files, and provides a flexible API which
> +mitigates collateral evolutions on the kernel as new functionality is
> +introduced.
> +
> +Driver data modes of operation
> +==============================
> +
> +There are only two types of modes of operation for driver data requests:
> +
> + * synchronous - driver_data_request()
> + * asynchronous - driver_data_request_async()
> +
> +Synchronous requests expect requests to be done immediately, asynchronous
> +requests enable requests to be scheduled for a later time.
> +
> +Driver data request parameters
> +==============================
> +
> +Variations of types of driver data requests are specified by a driver data
> +request parameter data structure. This data structure is expected to grow as
> +new requirements grow.
> +
> +Reference counting and releasing the driver data file
> +=====================================================
> +
> +As with the old firmware API both the device and module are bumped with
> +reference counts during the driver data requests. This prevents removal
> +of the device and module making the driver data request call until the
> +driver data request callbacks have completed, either synchronously or
> +asynchronously.
> +
> +The old firmware APIs refcounted the firmware_class module for synchronous
> +requests, meanwhile asynchronous requests refcounted the caller's module.
> +The driver data request API currently mimics this behaviour, for synchronous
> +requests the firmware_class module is refcounted through the use of
> +dfl_sync_reqs. In the future we may enable the ability to also refcount the
> +caller's module as well. Likewise in the future we may enable asynchronous
> +calls to refcount the firmware_class module.
> +
> +Typical use of the old synchronous firmware APIs consist of the caller
> +requesting for "driver data", consuming it after a request and finally
> +freeing it. Typical asynchronous use of the old firmware APIs consist of
> +the caller requesting for "driver data" and then finally freeing it on
> +asynchronous callback.
> +
> +The driver data request API enables callers to provide a callback for both
> +synchronous and asynchronous requests and since consumption can be expected
> +in these callbacks it frees it for you by default after callback handlers
> +are issued. If you wish to keep the driver data around after your callbacks
> +you must specify this through the driver data request parameter data structure.
> +
> +Synchronizing with async cookies
> +================================
> +
> +The driver data API relies on async cookies to enable users to synchronize
> +for any pending async work. The async cookie obtained through an async
> +call using driver_data_file_request_async() can be used to synchronize and
> +wait for pending work with driver_data_synchronize_request().
> +
> +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.
> +
> +Tracking development enhancements and ideas
> +===========================================
> +
> +To help track ongoing development for firmware_class and related items to
> +firmware_class refer to the kernel newbies wiki page [0].
> +
> +[0] http://kernelnewbies.org/KernelProjects/firmware-class-enhancements
> diff --git a/Documentation/driver-api/firmware/index.rst b/Documentation/driver-api/firmware/index.rst
> index 1abe01793031..c2be92e2628c 100644
> --- a/Documentation/driver-api/firmware/index.rst
> +++ b/Documentation/driver-api/firmware/index.rst
> @@ -7,6 +7,7 @@ Linux Firmware API
> introduction
> core
> request_firmware
> + driver_data
>
> .. only:: subproject and html
>
> diff --git a/Documentation/driver-api/firmware/introduction.rst b/Documentation/driver-api/firmware/introduction.rst
> index 211cb44eb972..a0f6a3fa1d5d 100644
> --- a/Documentation/driver-api/firmware/introduction.rst
> +++ b/Documentation/driver-api/firmware/introduction.rst
> @@ -25,3 +25,19 @@ are already using asynchronous initialization mechanisms which will not
> stall or delay boot. Even if loading firmware does not take a lot of time
> processing firmware might, and this can still delay boot or initialization,
> as such mechanisms such as asynchronous probe can help supplement drivers.
> +
> +Two APIs
> +========
> +
> +Two APIs are provided for firmware:
> +
> +* request_firmware API - old firmware API
> +* driver_data API - flexible API
You can add links:
* `request_firmware API`_ - old firmware API
* `driver_data API`_ - flexible API
.. _`request_firmware API`: ./request_firmware.rst
.. _`driver_data API`: ./driver_data.rst
> +
> +We have historically extended the firmware API by adding new routines or at
> +times extending existing routines with more or less arguments. This doesn't
> +scale well, when new arguments are added to existing routines it means we need
> +to traverse the kernel with a slew of collateral evolutions to adjust old
> +driver users. The driver data API is an extensible API enabling extensions to
> +be added by avoiding unnecessary collateral evolutions as features get added.
> +New features and development should be added through the driver_data API.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 939311d601b8..3f025f738600 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5166,13 +5166,14 @@ F: include/linux/firewire.h
> F: include/uapi/linux/firewire*.h
> F: tools/firewire/
>
> -FIRMWARE LOADER (request_firmware)
> +FIRMWARE LOADER (request_firmware, driver_data)
> M: Luis R. Rodriguez <[email protected]>
> L: [email protected]
> S: Maintained
> F: Documentation/firmware_class/
> F: drivers/base/firmware*.c
> F: include/linux/firmware.h
> +F: include/linux/driver_data.h
>
> FLASH ADAPTER DRIVER (IBM Flash Adapter 900GB Full Height PCI Flash Card)
> M: Joshua Morris <[email protected]>
> diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
> index f702566554e1..cc3c2247980c 100644
> --- a/drivers/base/firmware_class.c
> +++ b/drivers/base/firmware_class.c
> @@ -2,6 +2,7 @@
> * firmware_class.c - Multi purpose firmware loading support
> *
> * Copyright (c) 2003 Manuel Estrada Sainz
> + * Copyright (c) 2017 Luis R. Rodriguez <[email protected]>
> *
> * Please see Documentation/firmware_class/ for more information.
> *
> @@ -41,12 +42,20 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
> MODULE_DESCRIPTION("Multi purpose firmware loading support");
> MODULE_LICENSE("GPL");
>
> +static const struct driver_data_reqs dfl_sync_reqs = {
> + .mode = DRIVER_DATA_SYNC,
> + .module = THIS_MODULE,
> + .gfp = GFP_KERNEL,
> +};
> +
> struct driver_data_priv_params {
> bool use_fallback;
> bool use_fallback_uevent;
> bool no_cache;
> void *alloc_buf;
> size_t alloc_buf_size;
> + u8 api;
> + bool retry_api;
> };
>
> struct driver_data_params {
> @@ -1262,6 +1271,7 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
> struct device *device,
> struct driver_data_params *data_params)
> {
> + struct driver_data_priv_params *priv_params = &data_params->priv_params;
> struct firmware *firmware;
> struct firmware_buf *buf;
> int ret;
> @@ -1285,6 +1295,7 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
> * of requesting firmware.
> */
> firmware->priv = buf;
> + firmware->api = priv_params->api;
>
> if (ret > 0) {
> ret = fw_state_wait(&buf->fw_st);
> @@ -1460,6 +1471,128 @@ void release_firmware(const struct firmware *fw)
> }
> EXPORT_SYMBOL(release_firmware);
>
> +static int _driver_data_request_api(struct driver_data_params *params,
> + struct device *device,
> + const char *name)
> +{
> + struct driver_data_priv_params *priv_params = ¶ms->priv_params;
> + const struct driver_data_req_params *req_params = ¶ms->req_params;
> + int ret;
> + char *try_name;
> + u8 api_max;
> +
> + if (priv_params->retry_api) {
> + if (!priv_params->api)
> + return -ENOENT;
> + api_max = priv_params->api - 1;
> + } else
> + api_max = req_params->api_max;
> +
> + for (priv_params->api = api_max;
> + priv_params->api >= req_params->api_min;
> + priv_params->api--) {
> + if (req_params->api_name_postfix)
> + try_name = kasprintf(GFP_KERNEL, "%s%d%s",
> + name,
> + priv_params->api,
> + req_params->api_name_postfix);
> + else
> + try_name = kasprintf(GFP_KERNEL, "%s%d",
> + name,
> + priv_params->api);
> + if (!try_name)
> + return -ENOMEM;
> + ret = _request_firmware(¶ms->driver_data, try_name,
> + params, device);
> + kfree(try_name);
> +
> + if (!ret)
> + break;
> +
> + release_firmware(params->driver_data);
> +
> + /*
> + * Only chug on with the API revision hunt if the file we
> + * looked for really was not present. In case of memory issues
> + * or other related system issues we want to bail right away
> + * to not put strain on the system.
> + */
> + if (ret != -ENOENT)
> + break;
> +
> + if (!priv_params->api)
> + break;
> + }
> +
> + return ret;
> +}
> +
> +/**
> + * driver_data_request - synchronous request for a driver data file
> + * @name: name of the driver data file
> + * @params: driver data parameters, it provides all the requirements
> + * parameters which must be met for the file being requested.
> + * @device: device for which firmware is being loaded
> + *
> + * This performs a synchronous driver data lookup with the requirements
> + * specified on @params, if the file was found meeting the criteria requested
> + * 0 is returned. Access to the driver data data can be accessed through
> + * an optional callback set on the @desc. If the driver data is optional
> + * you must specify that on @params and if set you may provide an alternative
> + * callback which if set would be run if the driver data was not found.
> + *
> + * The driver data passed to the callbacks will be NULL unless it was
> + * found matching all the criteria on @params. 0 is always returned if the file
> + * was found unless a callback was provided, in which case the callback's
> + * return value will be passed. Unless the params->keep was set the kernel will
> + * release the driver data for you after your callbacks were processed.
> + *
> + * Reference counting is used during the duration of this call on both the
> + * device and module that made the request. This prevents any callers from
> + * freeing either the device or module prior to completion of this call.
> + */
> +int driver_data_request_sync(const char *name,
> + const struct driver_data_req_params *req_params,
> + struct device *device)
> +{
> + const struct firmware *driver_data;
> + const struct driver_data_reqs *sync_reqs;
> + struct driver_data_params params = {
> + .req_params = *req_params,
> + };
> + int ret;
> +
> + if (!device || !req_params || !name || name[0] == '\0')
> + return -EINVAL;
> +
> + if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
> + return -EINVAL;
> +
> + if (driver_data_sync_opt_cb(req_params) &&
> + !driver_data_param_optional(req_params))
> + return -EINVAL;
> +
> + sync_reqs = &dfl_sync_reqs;
> +
> + __module_get(sync_reqs->module);
> + get_device(device);
> +
> + ret = _request_firmware(&driver_data, name, ¶ms, device);
> + if (ret && driver_data_param_optional(req_params))
> + ret = driver_data_sync_opt_call_cb(req_params);
> + else
> + ret = driver_data_sync_call_cb(req_params, driver_data);
It looks a bit weird to me that a failure callback is called
only if "optional" is set. I think that it makes more sense
that a failure callback is always called if _request_firmware() fails.
In addition, why not always return a return value of _request_firmare()?
Overwriting a return value by any of callback functions doesn't make sense,
particularly, in "sync" case.
One of the problems in this implementation is that we, drivers, have
no chance to know a return value of _request_firmware().
For example, if the signature verification, which I'm now working on, fails,
ENOKEY or EKEYxxx will be returns. We may want to say more detailed
error messages depending on error code.
> + if (!driver_data_param_keep(req_params))
> + release_firmware(driver_data);
> +
> + put_device(device);
> + module_put(sync_reqs->module);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(driver_data_request_sync);
> +
> /* Async support */
> struct firmware_work {
> struct work_struct work;
> @@ -1553,6 +1686,230 @@ request_firmware_nowait(
> }
> EXPORT_SYMBOL(request_firmware_nowait);
>
> +static int validate_api_versioning(struct driver_data_params *params)
> +{
> + //struct driver_data_priv_params *priv_params = ¶ms->priv_params;
> + const struct driver_data_req_params *req_params = ¶ms->req_params;
> +
> + if (!req_params->api_max)
> + return -EINVAL;
> + if (req_params->api_max < req_params->api_min)
> + return -EINVAL;
> + /*
> + * this is being processed, but the first iteration this check is invalid
> + */
> + /*
> + if (priv_params->api < req_params->api_min)
> + return -EINVAL;
> + */
> + if (driver_data_async_cb(req_params))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int __request_driver_data_api(struct firmware_work *driver_work)
> +{
> + struct driver_data_params *params = &driver_work->data_params;
> + const struct driver_data_req_params *req_params = ¶ms->req_params;
> + int ret;
> +
> + ret = _driver_data_request_api(params, driver_work->device,
> + driver_work->name);
> + /*
> + * The default async optional callbacks don't work well here, so they
> + * are not supported. Consider what we would return back, we'd need a
> + * non-void optional callback to be able to feed the consumer.
> + */
> + if (ret == -ENOENT)
> + dev_err(driver_work->device,
> + "No API file in range %u - %u could be found\n",
> + req_params->api_min, req_params->api_max);
> +
> + return driver_data_async_call_api_cb(params->driver_data, req_params);
> +}
> +
> +static void request_driver_data_single(struct firmware_work *driver_work)
> +{
> + struct driver_data_params *params = &driver_work->data_params;
> + const struct driver_data_req_params *req_params = ¶ms->req_params;
> + const struct driver_data_reqs *sync_reqs;
> + int ret;
> +
> + sync_reqs = &req_params->sync_reqs;
> +
> + ret = _request_firmware(¶ms->driver_data, driver_work->name,
> + params, driver_work->device);
> + if (ret &&
> + driver_data_param_optional(req_params) &&
> + driver_data_async_opt_cb(req_params))
> + driver_data_async_opt_call_cb(req_params);
> + else
> + driver_data_async_call_cb(params->driver_data, req_params);
> +
> + if (!driver_data_param_keep(req_params))
> + release_firmware(params->driver_data);
> +
> + put_device(driver_work->device);
> + module_put(sync_reqs->module);
> +
> + kfree_const(driver_work->name);
> + kfree(driver_work);
> +}
> +
> +/*
> + * Instead of recursion provide a deterministic limit based on the parameters,
> + * and consume less memory.
> + */
> +static void request_driver_data_api(struct firmware_work *driver_work)
> +{
> + struct driver_data_params *params = &driver_work->data_params;
> + struct driver_data_priv_params *priv_params = ¶ms->priv_params;
> + const struct driver_data_req_params *req_params = ¶ms->req_params;
> + const struct driver_data_reqs *sync_reqs;
> + int ret;
> + u8 i, limit;
> +
> + limit = req_params->api_max - req_params->api_min;
> + sync_reqs = &req_params->sync_reqs;
> +
> + for (i=0; i <= limit; i++) {
> + ret = validate_api_versioning(params);
> + if (WARN(ret, "Invalid API driver data request")) {
> + ret = driver_data_async_call_api_cb(NULL, req_params);
> + goto out;
> + }
> +
> + /*
> + * This does the real work of fetching the driver data through
> + * all the API revisions possible. If found the api and its
> + * return value are passed. If a value of 0 is passed then
> + * *really* does mean everything was peachy. If we catch
> + * -EAGAIN here it means the driver's API callback asked us to
> + * try again.
> + */
> + ret = __request_driver_data_api(driver_work);
> + if (!ret)
> + break;
> +
> + priv_params->retry_api = true;
> +
> + release_firmware(params->driver_data);
> +
> + if (ret != -EAGAIN)
> + break;
> + }
> +
> + if (ret && driver_data_param_optional(req_params))
> + driver_data_async_opt_call_cb(req_params);
> +
> + if (!driver_data_param_keep(req_params))
> + release_firmware(params->driver_data);
> +
> +out:
> + put_device(driver_work->device);
> + module_put(sync_reqs->module);
> +
> + kfree_const(driver_work->name);
> + kfree(driver_work);
> +}
> +
> +static void request_driver_data_work_func(struct work_struct *work)
> +{
> + struct firmware_work *driver_work;
> + struct driver_data_params *data_params;
> + const struct driver_data_req_params *req_params;
> +
> + driver_work = container_of(work, struct firmware_work, work);
> + data_params = &driver_work->data_params;
> + req_params = &data_params->req_params;
> +
> + if (driver_data_param_uses_api(req_params))
> + request_driver_data_api(driver_work);
> + else
> + request_driver_data_single(driver_work);
> +}
> +
> +/**
> + * driver_data_request_async - asynchronous request for a driver data file
> + * @name: name of the driver data file
> + * @req_params: driver data file request parameters, it provides all the
> + * requirements which must be met for the file being requested.
> + * @device: device for which firmware is being loaded
> + *
> + * This performs an asynchronous driver data file lookup with the requirements
> + * specified on @req_params. The request for the actual driver data file lookup
> + * will be scheduled with schedule_work() to be run at a later time. 0 is
> + * returned if we were able to asynchronously schedlue your work to be run.
> + *
> + * Reference counting is used during the duration of this scheduled call on
> + * both the device and module that made the request. This prevents any callers
> + * from freeing either the device or module prior to completion of the
> + * scheduled work.
> + *
> + * Access to the driver data file data can be accessed through an optional
> + * callback set on the @req_params. If the driver data file is optional you
> + * must specify that on @req_params and if set you may provide an alternative
> + * callback which if set would be run if the driver data file was not found.
> + *
> + * The driver data file passed to the callbacks will always be NULL unless it
> + * was found matching all the criteria on @req_params. Unless the desc->keep
> + * was set the kernel will release the driver data file for you after your
> + * callbacks were processed on the scheduled work.
> + */
> +int driver_data_request_async(const char *name,
> + const struct driver_data_req_params *req_params,
> + struct device *device)
> +{
> + struct firmware_work *driver_work;
> + const struct driver_data_reqs *sync_reqs;
> + struct firmware_work driver_work_stack = {
> + .data_params.req_params = *req_params,
> + //.device = device,
> + //.name = name,
> + };
> +
> + if (!device || !req_params || !name || name[0] == '\0')
> + return -EINVAL;
> +
> + if (req_params->sync_reqs.mode != DRIVER_DATA_ASYNC)
> + return -EINVAL;
> +
> + if (driver_data_async_opt_cb(req_params) && !req_params->optional)
> + return -EINVAL;
> +
> + sync_reqs = &req_params->sync_reqs;
> +
> + driver_work = kzalloc(sizeof(struct firmware_work), sync_reqs->gfp);
> + if (!driver_work)
> + return -ENOMEM;
> +
> + //memcpy(&driver_work->params, &driver_work_stack.params,
> + // sizeof(struct driver_data_file_work));
> + memcpy(driver_work, &driver_work_stack, sizeof(struct firmware_work));
> +
> + driver_work->name = kstrdup_const(name, sync_reqs->gfp);
> + if (!driver_work->name) {
> + kfree(driver_work);
> + return -ENOMEM;
> + }
> + driver_work->device = device;
> +
> + if (!try_module_get(sync_reqs->module)) {
> + kfree_const(driver_work->name);
> + kfree(driver_work);
> + return -EFAULT;
> + }
> +
> + get_device(driver_work->device);
> +
> + INIT_WORK(&driver_work->work, request_driver_data_work_func);
> + schedule_work(&driver_work->work);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(driver_data_request_async);
> +
> #ifdef CONFIG_PM_SLEEP
> static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);
>
> diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
> index c3d3a4129838..fda1e716017d 100644
> --- a/include/linux/driver_data.h
> +++ b/include/linux/driver_data.h
> @@ -5,6 +5,7 @@
> #include <linux/compiler.h>
> #include <linux/gfp.h>
> #include <linux/device.h>
> +#include <linux/firmware.h>
>
> /*
> * Driver Data internals
> @@ -33,9 +34,25 @@ enum driver_data_mode {
> /* one per driver_data_mode */
> union driver_data_cbs {
> struct {
> + int __must_check
> + (*found_cb)(void *context,
> + const struct firmware *driver_data);
> + void *found_ctx;
> +
> + int __must_check (*opt_fail_cb)(void *context);
> + void *opt_fail_ctx;
> + } sync;
> + struct {
> void (*found_cb)(const struct firmware *driver_data,
> void *context);
> void *found_ctx;
> +
> + void (*opt_fail_cb)(void *context);
> + void *opt_fail_ctx;
> +
> + int __must_check
> + (*found_api_cb)(const struct firmware *driver_data,
> + void *context);
> } async;
> };
>
> @@ -50,6 +67,30 @@ struct driver_data_reqs {
> * @optional: if true it is not a hard requirement by the caller that this
> * file be present. An error will not be recorded if the file is not
> * found.
> + * @keep: if set the caller wants to claim ownership over the driver data
> + * through one of its callbacks, it must later free it with
> + * release_driver_data(). By default this is set to false and the kernel
> + * will release the driver data file for you after callback processing
> + * has completed.
> + * @uses_api_versioning: if set the caller is indicating that the caller has
> + * an API revision system for the the files being requested using a
> + * simple numeric scheme: there is a max API version supported and the
> + * lowest API version supported. When used the driver data request request
> + * will always look for the filename requested but by first appeneding the
> + * @api_max to it, and looking for that. If that is not available it will
> + * look for any files with API version lower than this until it reaches
> + * @api_min. This enables chaining driver data requests easily on behalf
> + * of device drivers using a simple one digit versioning scheme. When
> + * this is true it will treat all files not found as non-fatal, as
> + * optional, but it requires at least one file to be found. If the
> + * @optional attribute is also true then if no files are found we won't
> + * complain at all.
> + * @api_min: if uses_api_versioning is set, this represents the lowest
> + * version of API supported by the caller.
> + * @api_max: if uses_api_versioning is set, this represents the highest
> + * version of API supported by the caller.
> + * @api_name_postfix: use the name as the driver data name prefix, the API
> + * digit will be placed in the middle, followed by the @api_name_postfix.
> * @sync_reqs: synchronization requirements
> *
> * This data structure is intended to carry all requirements and specifications
> @@ -59,20 +100,128 @@ struct driver_data_reqs {
> */
> struct driver_data_req_params {
> bool optional;
> + bool keep;
> + bool uses_api_versioning;
Do you have any reason that you don't use bit fields here?
More features are added, more 'boolean' are added.
(I mean it wastes memory.)
Thanks,
-Takahiro AKASHI
> + u8 api_min;
> + u8 api_max;
> + const char *api_name_postfix;
> struct driver_data_reqs sync_reqs;
> const union driver_data_cbs cbs;
> };
>
> +/*
> + * We keep these template definitions to a minimum for the most
> + * popular requests.
> + */
> +
> +/* Typical sync data case */
> +#define DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx) \
> + .cbs.sync.found_cb = __found_cb, \
> + .cbs.sync.found_ctx = __ctx
> +
> +#define DRIVER_DATA_DEFAULT_SYNC(__found_cb, __ctx) \
> + DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx)
> +
> +#define DRIVER_DATA_KEEP_SYNC(__found_cb, __ctx) \
> + DRIVER_DATA_DEFAULT_SYNC(__found_cb, __ctx), \
> + .keep = true
> +
> +/* If you have one fallback routine */
> +#define DRIVER_DATA_SYNC_OPT_CB(__fail_cb, __ctx) \
> + .optional = true, \
> + .cbs.sync.opt_fail_cb = __fail_cb, \
> + .cbs.sync.opt_fail_ctx = __ctx
> +
> +/*
> + * Used to define the default asynchronization requirements for
> + * driver_data_request_async(). Drivers can override.
> + */
> +#define DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx) \
> + .sync_reqs = { \
> + .mode = DRIVER_DATA_ASYNC, \
> + .module = THIS_MODULE, \
> + .gfp = GFP_KERNEL, \
> + }, \
> + .cbs.async = { \
> + .found_cb = __found_cb, \
> + .found_ctx = __ctx, \
> + }
> +
> +#define DRIVER_DATA_DEFAULT_ASYNC_OPT(__found_cb, __ctx) \
> + DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx), \
> + .optional = true
> +
> +#define DRIVER_DATA_KEEP_ASYNC(__found_cb, __ctx) \
> + DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx), \
> + .keep = true
> +
> +#define DRIVER_DATA_KEEP_ASYNC_OPT(__found_cb, __ctx) \
> + DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx), \
> + .optional = true
> +
> +#define DRIVER_DATA_ASYNC_OPT_CB(__fail_cb, __ctx) \
> + .optional = true, \
> + .cbs.async.opt_fail_cb = __fail_cb, \
> + .cbs.async.opt_fail_ctx = __ctx
> +
> +#define DRIVER_DATA_API_CB(__found_api_cb, __ctx) \
> + .sync_reqs = { \
> + .mode = DRIVER_DATA_ASYNC, \
> + .module = THIS_MODULE, \
> + .gfp = GFP_KERNEL, \
> + }, \
> + .cbs.async = { \
> + .found_api_cb = __found_api_cb, \
> + .found_ctx = __ctx, \
> + }
> +
> +#define DRIVER_DATA_API(__min, __max, __postfix) \
> + .uses_api_versioning = true, \
> + .api_min = __min, \
> + .api_max = __max, \
> + .api_name_postfix = __postfix
> +
> #define driver_data_req_param_sync(params) \
> ((params)->sync_reqs.mode == DRIVER_DATA_SYNC)
> #define driver_data_req_param_async(params) \
> ((params)->sync_reqs.mode == DRIVER_DATA_ASYNC)
>
> #define driver_data_param_optional(params) ((params)->optional)
> +#define driver_data_param_keep(params) ((params)->keep)
> +#define driver_data_param_uses_api(params) ((params)->uses_api_versioning)
> +
> +#define driver_data_sync_cb(param) ((params)->cbs.sync.found_cb)
> +#define driver_data_sync_ctx(params) ((params)->cbs.sync.found_ctx)
> +static inline
> +int driver_data_sync_call_cb(const struct driver_data_req_params *params,
> + const struct firmware *driver_data)
> +{
> + if (!driver_data_req_param_sync(params))
> + return -EINVAL;
> + if (!driver_data_sync_cb(params)) {
> + if (driver_data)
> + return 0;
> + return -ENOENT;
> + }
> + return driver_data_sync_cb(params)(driver_data_sync_ctx(params),
> + driver_data);
> +}
> +
> +#define driver_data_sync_opt_cb(params) ((params)->cbs.sync.opt_fail_cb)
> +#define driver_data_sync_opt_ctx(params) ((params)->cbs.sync.opt_fail_ctx)
> +static inline
> +int driver_data_sync_opt_call_cb(const struct driver_data_req_params *params)
> +{
> + if (params->sync_reqs.mode != DRIVER_DATA_SYNC)
> + return -EINVAL;
> + if (!driver_data_sync_opt_cb(params))
> + return 0;
> + return driver_data_sync_opt_cb(params)
> + (driver_data_sync_opt_ctx(params));
> +}
>
> #define driver_data_async_cb(params) ((params)->cbs.async.found_cb)
> #define driver_data_async_ctx(params) ((params)->cbs.async.found_ctx)
> -
> static inline
> void driver_data_async_call_cb(const struct firmware *driver_data,
> const struct driver_data_req_params *params)
> @@ -85,4 +234,55 @@ void driver_data_async_call_cb(const struct firmware *driver_data,
> driver_data_async_ctx(params));
> }
>
> +#define driver_data_async_opt_cb(params) ((params)->cbs.async.opt_fail_cb)
> +#define driver_data_async_opt_ctx(params) ((params)->cbs.async.opt_fail_ctx)
> +static inline
> +void driver_data_async_opt_call_cb(const struct driver_data_req_params *params)
> +{
> + if (params->sync_reqs.mode != DRIVER_DATA_ASYNC)
> + return;
> + if (!driver_data_async_opt_cb(params))
> + return;
> + driver_data_async_opt_cb(params)(driver_data_async_opt_ctx(params));
> +}
> +
> +#define driver_data_async_api_cb(params) ((params)->cbs.async.found_api_cb)
> +static inline
> +int driver_data_async_call_api_cb(const struct firmware *driver_data,
> + const struct driver_data_req_params *params)
> +{
> + if (!params->uses_api_versioning)
> + return -EINVAL;
> + if (!driver_data_async_api_cb(params))
> + return -EINVAL;
> + return driver_data_async_api_cb(params)(driver_data,
> + driver_data_async_ctx(params));
> +}
> +
> +#if defined(CONFIG_FW_LOADER) || \
> + (defined(CONFIG_FW_LOADER_MODULE) && defined(MODULE))
> +int driver_data_request_sync(const char *name,
> + const struct driver_data_req_params *params,
> + struct device *device);
> +int driver_data_request_async(const char *name,
> + const struct driver_data_req_params *params,
> + struct device *device);
> +#else
> +static
> +inline int driver_data_request_sync(const char *name,
> + const struct driver_data_req_params *params,
> + struct device *device)
> +{
> + return -EINVAL;
> +}
> +
> +static
> +inline int driver_data_request_async(const char *name,
> + const struct driver_data_req_params *params,
> + struct device *device)
> +{
> + return -EINVAL;
> +}
> +#endif
> +
> #endif /* _LINUX_DRIVER_DATA_H */
> diff --git a/include/linux/firmware.h b/include/linux/firmware.h
> index b1f9f0ccb8ac..3a71924d35d7 100644
> --- a/include/linux/firmware.h
> +++ b/include/linux/firmware.h
> @@ -13,6 +13,8 @@ struct firmware {
> const u8 *data;
> struct page **pages;
>
> + u8 api;
> +
> /* firmware loader private fields */
> void *priv;
> };
> --
> 2.11.0
>
On Wed, Mar 29, 2017 at 08:25:14PM -0700, Luis R. Rodriguez wrote:
> The requested nvram is optional, don't nag users about this.
> Additionally, the new driver data API enables us to let the API
> deal with the freeing of the nvram for us if we happen to free
> it immediately on the consumer callback, this driver does that
> so take advantage of this feature.
>
> Originally based on patches by Rafał Miłecki.
>
> Signed-off-by: Luis R. Rodriguez <[email protected]>
On another thread [0] Hans has pointed out this firmware is in fact not
optional for certain devices, so will drop this patch from the series.
https://lkml.kernel.org/r/[email protected]
Luis
On Thu, Apr 06, 2017 at 10:26:12AM +0300, Luca Coelho wrote:
> On Wed, 2017-03-29 at 20:25 -0700, Luis R. Rodriguez wrote:
> > Signed-off-by: Luis R. Rodriguez <[email protected]>
> > ---
>
> Looks fine with a few nitpicks.
>
>
> > diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
> > index 54fc4c42f126..f702566554e1 100644
> > --- a/drivers/base/firmware_class.c
> > +++ b/drivers/base/firmware_class.c
>
> [...]
>
> > @@ -40,6 +41,77 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
> > MODULE_DESCRIPTION("Multi purpose firmware loading support");
> > MODULE_LICENSE("GPL");
> >
> > +struct driver_data_priv_params {
> > + bool use_fallback;
> > + bool use_fallback_uevent;
> > + bool no_cache;
> > + void *alloc_buf;
> > + size_t alloc_buf_size;
> > +};
> > +
> > +struct driver_data_params {
> > + const struct firmware *driver_data;
> > + const struct driver_data_req_params req_params;
> > + struct driver_data_priv_params priv_params;
> > +};
> > +
> > +/*
> > + * These are kept to remain backward compatible with old behaviour. Do not
> > + * modify them unless you know what you are doing. These are to be used only
> > + * by the old API, so:
> > + *
> > + * Old sync APIs:
> > + * o request_firmware(): __DATA_REQ_FIRMWARE()
> > + * o request_firmware_direct(): __DATA_REQ_FIRMWARE_DIRECT()
> > + * o request_firmware_into_buf(): __DATA_REQ_FIRMWARE_BUF()
> > + *
> > + * Old async API:
> > + * o request_firmware_nowait(): __DATA_REQ_FIRMWARE_NOWAIT()
> > + */
> > +#define __DATA_REQ_FIRMWARE() \
> > + .priv_params = { \
> > + .use_fallback = !!FW_OPT_FALLBACK, \
>
> use_fallback is a boolean, so you don't need the double negation here.
Actually FW_OPT_FALLBACK can end up being (1U << 2) and I rather
really force just a true or false here, so will prefer to be pedantic
and keep the double negation as FW_OPT_FALLBACK is a flag.
> [...]
>
> > @@ -1332,12 +1433,15 @@ request_firmware_into_buf(const struct firmware **firmware_p, const char *name,
> > struct device *device, void *buf, size_t size)
> > {
> > int ret;
> > + struct driver_data_params data_params = {
> > + __DATA_REQ_FIRMWARE_BUF(buf, size),
> > + };
> >
> > __module_get(THIS_MODULE);
> > - ret = _request_firmware(firmware_p, name, device, buf, size,
> > - FW_OPT_UEVENT | FW_OPT_FALLBACK |
> > - FW_OPT_NOCACHE);
> > + ret = _request_firmware(firmware_p, name, &data_params, device);
> > module_put(THIS_MODULE);
> > +
> > +
>
> Double empty-lines here.
Fixed.
> > return ret;
> > }
> > EXPORT_SYMBOL(request_firmware_into_buf);
> >
>
> [...]
>
> > diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
> > new file mode 100644
> > index 000000000000..c3d3a4129838
> > --- /dev/null
> > +++ b/include/linux/driver_data.h
> > @@ -0,0 +1,88 @@
> > +#ifndef _LINUX_DRIVER_DATA_H
> > +#define _LINUX_DRIVER_DATA_H
> > +
> > +#include <linux/types.h>
> > +#include <linux/compiler.h>
> > +#include <linux/gfp.h>
> > +#include <linux/device.h>
> > +
> > +/*
> > + * Driver Data internals
> > + *
> > + * Copyright (C) 2017 Luis R. Rodriguez <[email protected]>
> > + *
> > + * 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/.
> > + */
> > +
> > +/**
> > + * enum driver_data_mode - driver data mode of operation
> > + *
> > + * DRIVER_DATA_SYNC: your call to request driver data is synchronous. We will
> > + * look for the driver data file you have requested immediatley.
> > + * DRIVER_DATA_ASYNC: your call to request driver data is asynchronous. We will
>
> It should be @DRIVER_DATA_SYNC and @DRIVER_DATA_ASYNC here.
Fixed, thanks for the review!
Luis
On Mon, Apr 10, 2017 at 12:42:44PM +0000, Coelho, Luciano wrote:
> On Wed, 2017-03-29 at 20:25 -0700, Luis R. Rodriguez wrote:
> > The firmware API does not scale well: when new features are added we
> > either add a new exported symbol or extend the arguments of existing
> > routines. For the later case this means we need to traverse the kernel
> > with a slew of collateral evolutions to adjust old driver users. The
> > firmware API is also now being used for things outside of the scope of
> > what typically would be considered "firmware". There are other
> > subsystems which would like to make use of the firmware APIs for similar
> > things and its clearly not firmware, but have different requirements
> > and criteria which they'd like to be met for the requested file.
> >
> > An extensible API is in order:
> >
> > The driver data API accepts that there are only two types of requests:
> >
> > a) synchronous requests
> > b) asynchronous requests
> >
> > Both requests may have a different requirements which must be met. These
> > requirements can be described in the struct driver_data_req_params.
> > This struct is expected to be extended over time to support different
> > requirements as the kernel evolves.
> >
> > After a bit of hard work the new interface has been wrapped onto the
> > functionality. The fallback mechanism has been kept out of the new API
> > currently because it requires just a bit more grooming and documentation
> > given new considerations and requirements. Adding support for it will
> > be rather easy now that the new API sits ontop of the old one. The
> > request_firmware_into_buf() API also is not enabled on the new API but
> > it is rather easy to do so -- this call has no current existing users
> > upstream though. Support will be provided once we add a respective
> > series of test cases against it and find a proper upstream user for it.
> >
> > The flexible API also adds a few new bells and whistles:
> >
> > - By default the kernel will free the driver data file for you after
> > your callbacks are called, you however are allowed to request that
> > you wish to keep the driver data file on the requirements params. The
> > new driver data API is able to free the driver data file for you by
> > requiring a consumer callback for the driver data file.
> > - Allows both asynchronous and synchronous request to specify that
> > driver data files are optional. With the old APIs we had added one
> > full API call, request_firmware_direct() just for this purpose --
> > the driver data request APIs allow for you to annotate that a driver
> > data file is optional for both synchronous or asynchronous requests
> > through the same two basic set of APIs.
> > - A firmware API framework is provided to enable daisy chaining a
> > series of requests for firmware on a range of supported APIs.
> >
> > Signed-off-by: Luis R. Rodriguez <[email protected]>
> > ---
> > Documentation/driver-api/firmware/driver_data.rst | 77 +++++
> > Documentation/driver-api/firmware/index.rst | 1 +
> > Documentation/driver-api/firmware/introduction.rst | 16 +
> > MAINTAINERS | 3 +-
> > drivers/base/firmware_class.c | 357 +++++++++++++++++++++
> > include/linux/driver_data.h | 202 +++++++++++-
> > include/linux/firmware.h | 2 +
> > 7 files changed, 656 insertions(+), 2 deletions(-)
> > create mode 100644 Documentation/driver-api/firmware/driver_data.rst
> >
> > diff --git a/Documentation/driver-api/firmware/driver_data.rst b/Documentation/driver-api/firmware/driver_data.rst
> > new file mode 100644
> > index 000000000000..08407b7568fe
> > --- /dev/null
> > +++ b/Documentation/driver-api/firmware/driver_data.rst
> > @@ -0,0 +1,77 @@
> > +===============
> > +driver_data API
> > +===============
> > +
> > +Users of firmware request APIs has grown to include users which are not
>
> Grammar. Maybe "The usage of firmware..."
Rephrased as :
"The firmware API has grown to include users which are not looking for" ...
Well actually dropped due to the below feedback as well.
> > +looking for "firmware", but instead general driver data files which have
> > +been kept oustide of the kernel. The driver data APIs addresses rebranding
> > +of firmware as generic driver data files, and provides a flexible API which
> > +mitigates collateral evolutions on the kernel as new functionality is
> > +introduced.
>
> This looks more like a commit message than an introduction to the
> feature. In the future, we won't care why this was introduced, but we
> want to know what it is and how it can be used.
Alright, trimmed:
+The driver data APIs provides a flexible API for general driver data file
+lookups. Its flexibility aims at mitigating collateral evolutions on the kernel
+as new functionality is introduced.
> > +
> > +Driver data modes of operation
> > +==============================
> > +
> > +There are only two types of modes of operation for driver data requests:
>
> "only" seems irrelevant here.
Removed.
> > +
> > + * synchronous - driver_data_request()
> > + * asynchronous - driver_data_request_async()
> > +
> > +Synchronous requests expect requests to be done immediately, asynchronous
> > +requests enable requests to be scheduled for a later time.
> > +
> > +Driver data request parameters
> > +==============================
> > +
> > +Variations of types of driver data requests are specified by a driver data
> > +request parameter data structure. This data structure is expected to grow as
> > +new requirements grow.
>
> Again, not sure it's relevant to know that it can grow. For
> documentation purposes, the important is the *now*.
True, however its important to clarify a bit what is mean by a flexible API, so
replaced the last sentence with:
""
The flexibility of the API is provided by expanding the request parameters as
new functionality is needed, without loosely modifying or adding new exported
APIs.
""
> > +
> > +Reference counting and releasing the driver data file
> > +=====================================================
> > +
> > +As with the old firmware API both the device and module are bumped with
> > +reference counts during the driver data requests. This prevents removal
> > +of the device and module making the driver data request call until the
> > +driver data request callbacks have completed, either synchronously or
> > +asynchronously.
> > +
> > +The old firmware APIs refcounted the firmware_class module for synchronous
> > +requests, meanwhile asynchronous requests refcounted the caller's module.
> > +The driver data request API currently mimics this behaviour, for synchronous
> > +requests the firmware_class module is refcounted through the use of
> > +dfl_sync_reqs. In the future we may enable the ability to also refcount the
> > +caller's module as well. Likewise in the future we may enable asynchronous
> > +calls to refcount the firmware_class module.
>
> Ditto. Maybe you could move all the "future" references to the
> [...]
Makes sense, just moved it to the wiki.
> > diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
> > index f702566554e1..cc3c2247980c 100644
> > --- a/drivers/base/firmware_class.c
> > +++ b/drivers/base/firmware_class.c
>
> [...]
>
> > @@ -1460,6 +1471,128 @@ void release_firmware(const struct firmware *fw)
> > }
> > EXPORT_SYMBOL(release_firmware);
> >
> > +static int _driver_data_request_api(struct driver_data_params *params,
> > + struct device *device,
> > + const char *name)
> > +{
> > + struct driver_data_priv_params *priv_params = ¶ms->priv_params;
> > + const struct driver_data_req_params *req_params = ¶ms->req_params;
> > + int ret;
> > + char *try_name;
> > + u8 api_max;
> > +
> > + if (priv_params->retry_api) {
> > + if (!priv_params->api)
> > + return -ENOENT;
> > + api_max = priv_params->api - 1;
> > + } else
> > + api_max = req_params->api_max;
>
> Braces.
Not sure what you mean here, the else is a 1 liner so I skip the brace.
> > + for (priv_params->api = api_max;
> > + priv_params->api >= req_params->api_min;
> > + priv_params->api--) {
> > + if (req_params->api_name_postfix)
> > + try_name = kasprintf(GFP_KERNEL, "%s%d%s",
> > + name,
> > + priv_params->api,
> > + req_params->api_name_postfix);
> > + else
> > + try_name = kasprintf(GFP_KERNEL, "%s%d",
> > + name,
> > + priv_params->api);
> > + if (!try_name)
> > + return -ENOMEM;
> > + ret = _request_firmware(¶ms->driver_data, try_name,
> > + params, device);
> > + kfree(try_name);
> > +
> > + if (!ret)
> > + break;
> > +
> > + release_firmware(params->driver_data);
> > +
> > + /*
> > + * Only chug on with the API revision hunt if the file we
> > + * looked for really was not present. In case of memory issues
> > + * or other related system issues we want to bail right away
> > + * to not put strain on the system.
> > + */
> > + if (ret != -ENOENT)
> > + break;
> > +
> > + if (!priv_params->api)
> > + break;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +/**
> > + * driver_data_request - synchronous request for a driver data file
> > + * @name: name of the driver data file
> > + * @params: driver data parameters, it provides all the requirements
> > + * parameters which must be met for the file being requested.
> > + * @device: device for which firmware is being loaded
> > + *
> > + * This performs a synchronous driver data lookup with the requirements
> > + * specified on @params, if the file was found meeting the criteria requested
> > + * 0 is returned. Access to the driver data data can be accessed through
> > + * an optional callback set on the @desc.
>
> Huh? This last sentence seems wrong, I don't even see a @desc anywhere.
Sorry, the @desc was the old name, due to bike-shedding its now @params, I've
updated this and rephrased it a bit better:
""
Callers get access to any found driver data meeting the specified criteria
through an optional callback set on @params. If the driver data is optional you
must specify that on @params and if set you may provide an alternative
callback which if set would be run if the driver data was not found.
""
> > If the driver data is optional
> > + * you must specify that on @params and if set you may provide an alternative
> > + * callback which if set would be run if the driver data was not found.
> > + *
> > + * The driver data passed to the callbacks will be NULL unless it was
> > + * found matching all the criteria on @params. 0 is always returned if the file
> > + * was found unless a callback was provided, in which case the callback's
> > + * return value will be passed. Unless the params->keep was set the kernel will
> > + * release the driver data for you after your callbacks were processed.
> > + *
> > + * Reference counting is used during the duration of this call on both the
> > + * device and module that made the request. This prevents any callers from
> > + * freeing either the device or module prior to completion of this call.
> > + */
> > +int driver_data_request_sync(const char *name,
> > + const struct driver_data_req_params *req_params,
> > + struct device *device)
> > +{
> > + const struct firmware *driver_data;
> > + const struct driver_data_reqs *sync_reqs;
> > + struct driver_data_params params = {
> > + .req_params = *req_params,
> > + };
> > + int ret;
> > +
> > + if (!device || !req_params || !name || name[0] == '\0')
> > + return -EINVAL;
> > +
> > + if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
> > + return -EINVAL;
>
> Why do you need to check this here? If the caller is calling _sync(),
> it's because that's what it needs. This mode value here seems
> redundant.
Because we allow one data structure to be used for the specified requirements
and technically someone can make a mistake and use the wrong macros to set up
the data structure. This ensures users don't async macros to set up the
parameters and then use the sync call. Eventually the underlying
firmware_class.c code does its own conditional checks on this as well.
> OTOH, if you do have a reason for this value, then you could use
> driver_data_request_sync() in this if.
You mean to allow *one* API call for all. Sure, that's possible, but I think
its nicer to at least expose async/sync mechanisms on the caller side. The
sync/async differences seem like a reasonable enough place to split the API.
Luis
On Tue, Apr 11, 2017 at 05:01:51PM +0900, [email protected] wrote:
> On Mon, Apr 10, 2017 at 12:42:44PM +0000, Coelho, Luciano wrote:
> > On Wed, 2017-03-29 at 20:25 -0700, Luis R. Rodriguez wrote:
> > > +Driver data request parameters
> > > +==============================
> > > +
> > > +Variations of types of driver data requests are specified by a driver data
> > > +request parameter data structure. This data structure is expected to grow as
> > > +new requirements grow.
> >
> > Again, not sure it's relevant to know that it can grow. For
> > documentation purposes, the important is the *now*.
>
> There seem to be a couple of new features/parameters.
> Why not list them now:
> * optional
> * keep
> * API versioning
We can document this on the driver header file and pull in the relevant doc.
> I will add 'data(firmware) signing' here afterward.
Great!
> > > +/**
> > > + * driver_data_request - synchronous request for a driver data file
>
> driver_data_request_sync
Fixed.
> > > + * @name: name of the driver data file
> > > + * @params: driver data parameters, it provides all the requirements
>
> req_params
Fixed.
> > > +int driver_data_request_sync(const char *name,
> > > + const struct driver_data_req_params *req_params,
> > > + struct device *device)
> > > +{
> > > + const struct firmware *driver_data;
> > > + const struct driver_data_reqs *sync_reqs;
> > > + struct driver_data_params params = {
> > > + .req_params = *req_params,
> > > + };
> > > + int ret;
> > > +
> > > + if (!device || !req_params || !name || name[0] == '\0')
> > > + return -EINVAL;
> > > +
> > > + if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
> > > + return -EINVAL;
> >
> > Why do you need to check this here? If the caller is calling _sync(),
> > it's because that's what it needs. This mode value here seems
> > redundant.
> >
> > OTOH, if you do have a reason for this value, then you could use
> > driver_data_request_sync() in this if.
>
> I think two functions, driver_data_request_[a]sync(), can be
> unified into one:
> int driver_data_request(const char *name,
> const struct driver_data_req_params *req_params,
> struct device *device)
>
Indeed but I decided to draw the fine line on differences between sync/async,
following similar core API trends in the kernel, like module request, etc.
The implementation details can vary on setup for async so best also allow us
to easily trace these uses on the actual driver calls.
Luis
On Thu, 2017-04-27 at 05:16 +0200, Luis R. Rodriguez wrote:
> > > @@ -1460,6 +1471,128 @@ void release_firmware(const struct firmware *fw)
> > > }
> > > EXPORT_SYMBOL(release_firmware);
> > >
> > > +static int _driver_data_request_api(struct driver_data_params *params,
> > > + struct device *device,
> > > + const char *name)
> > > +{
> > > + struct driver_data_priv_params *priv_params = ¶ms->priv_params;
> > > + const struct driver_data_req_params *req_params = ¶ms->req_params;
> > > + int ret;
> > > + char *try_name;
> > > + u8 api_max;
> > > +
> > > + if (priv_params->retry_api) {
> > > + if (!priv_params->api)
> > > + return -ENOENT;
> > > + api_max = priv_params->api - 1;
> > > + } else
> > > + api_max = req_params->api_max;
> >
> > Braces.
>
> Not sure what you mean here, the else is a 1 liner so I skip the brace.
It's really a nitpick, but the CodingStyle[1] says:
"Do not unnecessarily use braces where a single statement will do.
[...]
This does not apply if only one branch of a conditional statement is a
single statement; in the latter case use braces in both branches:
if (condition) {
do_this();
do_that();
} else {
otherwise();
}"
[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/coding-style.rst#n175
--
Cheers,
Luca.
On Thu, 2017-04-27 at 05:16 +0200, Luis R. Rodriguez wrote:
> > > +int driver_data_request_sync(const char *name,
> > > + const struct driver_data_req_params *req_params,
> > > + struct device *device)
> > > +{
> > > + const struct firmware *driver_data;
> > > + const struct driver_data_reqs *sync_reqs;
> > > + struct driver_data_params params = {
> > > + .req_params = *req_params,
> > > + };
> > > + int ret;
> > > +
> > > + if (!device || !req_params || !name || name[0] == '\0')
> > > + return -EINVAL;
> > > +
> > > + if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
> > > + return -EINVAL;
> >
> > Why do you need to check this here? If the caller is calling _sync(),
> > it's because that's what it needs. This mode value here seems
> > redundant.
>
> Because we allow one data structure to be used for the specified requirements
> and technically someone can make a mistake and use the wrong macros to set up
> the data structure. This ensures users don't async macros to set up the
> parameters and then use the sync call. Eventually the underlying
> firmware_class.c code does its own conditional checks on this as well.
If this could only happen in a programming error, maybe it's worth a
WARN() then, to make it easier to spot?
> > OTOH, if you do have a reason for this value, then you could use
> > driver_data_request_sync() in this if.
>
> You mean to allow *one* API call for all. Sure, that's possible, but I think
> its nicer to at least expose async/sync mechanisms on the caller side. The
> sync/async differences seem like a reasonable enough place to split the API.
I don't remember the details of this anymore, but doesn't the
driver_data_sync() function does exactly the same check? I meant that
you could do this:
if(WARN_ON_ONCE(!driver_data_request_sync()))
return -EINVAL;
And yes, I think either a single API call for all or not having the mode
in the struct would be cleaner. I think there are better ways to
prevent coding errors (since this should only happen if the user code is
implemented incorrectly).
--
Cheers,
Luca.
On Thu, Apr 27, 2017 at 08:44:27AM +0300, Luca Coelho wrote:
> On Thu, 2017-04-27 at 05:16 +0200, Luis R. Rodriguez wrote:
> > > > @@ -1460,6 +1471,128 @@ void release_firmware(const struct firmware *fw)
> > > > ? }
> > > > ? EXPORT_SYMBOL(release_firmware);
> > > > ?
> > > > +static int _driver_data_request_api(struct driver_data_params *params,
> > > > +?????????????????????????????? struct device *device,
> > > > +?????????????????????????????? const char *name)
> > > > +{
> > > > +???struct driver_data_priv_params *priv_params = ¶ms->priv_params;
> > > > +???const struct driver_data_req_params *req_params = ¶ms->req_params;
> > > > +???int ret;
> > > > +???char *try_name;
> > > > +???u8 api_max;
> > > > +
> > > > +???if (priv_params->retry_api) {
> > > > +???????????if (!priv_params->api)
> > > > +???????????????????return -ENOENT;
> > > > +???????????api_max = priv_params->api - 1;
> > > > +???} else
> > > > +???????????api_max = req_params->api_max;
> > >
> > > Braces.
> >
> > Not sure what you mean here, the else is a 1 liner so I skip the brace.
>
> It's really a nitpick, but the CodingStyle[1] says:
>
> "Do not unnecessarily use braces where a single statement will do.
> [...]
> This does not apply if only one branch of a conditional statement is a
> single statement; in the latter case use braces in both branches:
>
> if (condition) {
> do_this();
> do_that();
> } else {
> otherwise();
> }"
>
> [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/coding-style.rst#n175
Heh, alright. Added the brace.
Luis
On Thu, Apr 27, 2017 at 09:09:47AM +0300, Luca Coelho wrote:
> On Thu, 2017-04-27 at 05:16 +0200, Luis R. Rodriguez wrote:
> > > > +int driver_data_request_sync(const char *name,
> > > > +??????????????????????? const struct driver_data_req_params *req_params,
> > > > +??????????????????????? struct device *device)
> > > > +{
> > > > +???const struct firmware *driver_data;
> > > > +???const struct driver_data_reqs *sync_reqs;
> > > > +???struct driver_data_params params = {
> > > > +???????????.req_params = *req_params,
> > > > +???};
> > > > +???int ret;
> > > > +
> > > > +???if (!device || !req_params || !name || name[0] == '\0')
> > > > +???????????return -EINVAL;
> > > > +
> > > > +???if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
> > > > +???????????return -EINVAL;
> > >
> > > Why do you need to check this here? If the caller is calling _sync(),
> > > it's because that's what it needs.? This mode value here seems
> > > redundant.
> >
> > Because we allow one data structure to be used for the specified requirements
> > and technically someone can make a mistake and use the wrong macros to set up
> > the data structure. This ensures users don't async macros to set up the
> > parameters and then use the sync call. Eventually the underlying
> > firmware_class.c code does its own conditional checks on this as well.
>
> If this could only happen in a programming error, maybe it's worth a
> WARN() then, to make it easier to spot?
>
>
> > > OTOH, if you do have a reason for this value, then you could use
> > > driver_data_request_sync() in this if.
> >
> > You mean to allow *one* API call for all. Sure, that's possible, but I think
> > its nicer to at least expose async/sync mechanisms on the caller side. The
> > sync/async differences seem like a reasonable enough place to split the API.
>
> I don't remember the details of this anymore, but doesn't the
> driver_data_sync() function does exactly the same check? I meant that
> you could do this:
>
> if(WARN_ON_ONCE(!driver_data_request_sync()))
> return -EINVAL;
>
>
> And yes, I think either a single API call for all or not having the mode
> in the struct would be cleaner. I think there are better ways to
> prevent coding errors (since this should only happen if the user code is
> implemented incorrectly).
You're kind of right, this is stupid. The mode is already implied by the
function used, and its an internal thing, so we can just deal with it
ourselves. The reason why we can't just use the sync cb is its completely
optional, we need a piece of information to tell the internal code its
purpose so it can decide what data structures to check for.
I'll try folding the mode into the priv params and remove the checks.
Luis
On Thu, Apr 13, 2017 at 06:36:17PM +0900, AKASHI Takahiro wrote:
> On Wed, Mar 29, 2017 at 08:25:11PM -0700, Luis R. Rodriguez wrote:
> > Signed-off-by: Luis R. Rodriguez <[email protected]>
> > ---
> > Documentation/driver-api/firmware/driver_data.rst | 77 +++++
> > Documentation/driver-api/firmware/index.rst | 1 +
> > Documentation/driver-api/firmware/introduction.rst | 16 +
>
> I think we'd better to split code and documents into different patches
> for easier reviews.
Sure, done.
> > --- a/Documentation/driver-api/firmware/introduction.rst
> > +++ b/Documentation/driver-api/firmware/introduction.rst
> > @@ -25,3 +25,19 @@ are already using asynchronous initialization mechanisms which will not
> > stall or delay boot. Even if loading firmware does not take a lot of time
> > processing firmware might, and this can still delay boot or initialization,
> > as such mechanisms such as asynchronous probe can help supplement drivers.
> > +
> > +Two APIs
> > +========
> > +
> > +Two APIs are provided for firmware:
> > +
> > +* request_firmware API - old firmware API
> > +* driver_data API - flexible API
>
> You can add links:
>
> * `request_firmware API`_ - old firmware API
> * `driver_data API`_ - flexible API
>
> .. _`request_firmware API`: ./request_firmware.rst
> .. _`driver_data API`: ./driver_data.rst
Done!
> > +int driver_data_request_sync(const char *name,
> > + const struct driver_data_req_params *req_params,
> > + struct device *device)
> > +{
> > + const struct firmware *driver_data;
> > + const struct driver_data_reqs *sync_reqs;
> > + struct driver_data_params params = {
> > + .req_params = *req_params,
> > + };
> > + int ret;
> > +
> > + if (!device || !req_params || !name || name[0] == '\0')
> > + return -EINVAL;
> > +
> > + if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
> > + return -EINVAL;
> > +
> > + if (driver_data_sync_opt_cb(req_params) &&
> > + !driver_data_param_optional(req_params))
> > + return -EINVAL;
> > +
> > + sync_reqs = &dfl_sync_reqs;
> > +
> > + __module_get(sync_reqs->module);
> > + get_device(device);
> > +
> > + ret = _request_firmware(&driver_data, name, ¶ms, device);
> > + if (ret && driver_data_param_optional(req_params))
> > + ret = driver_data_sync_opt_call_cb(req_params);
> > + else
> > + ret = driver_data_sync_call_cb(req_params, driver_data);
>
> It looks a bit weird to me that a failure callback is called
> only if "optional" is set. I think that it makes more sense
> that a failure callback is always called if _request_firmware() fails.
Let's think about this: does it make sense for the there to be a callback
if the file was not optional ? Keep in mind the optional flag has its own
semantics, it affects printing on error, on file not found. The semantics
of the "optional callback" is precisely defined for when the first file
is optional, so its by definition.
If we were to not require optional then it would be more of a "failure callback",
as you put it, but then folks could be stuffing these with all their error
paths, and that's not what this is for. The optional callback is to handle
an alternative *viable* approach *iff* the first file we look for is not found.
> In addition, why not always return a return value of _request_firmare()?
> Overwriting a return value by any of callback functions doesn't make sense,
> particularly, in "sync" case.
> One of the problems in this implementation is that we, drivers, have
> no chance to know a return value of _request_firmware().
Ah, good point, well, we can pass it on the optional callback then, this
way no information is lost.
Thoughts?
> For example, if the signature verification, which I'm now working on, fails,
> ENOKEY or EKEYxxx will be returns. We may want to say more detailed
> error messages depending on error code.
Makes sense. If the above suffices let me know.
> > struct driver_data_req_params {
> > bool optional;
> > + bool keep;
> > + bool uses_api_versioning;
>
> Do you have any reason that you don't use bit fields here?
> More features are added, more 'boolean' are added.
> (I mean it wastes memory.)
You're right, will fold into a flags.
Luis
On Mon, Apr 10, 2017 at 04:19:12PM +0300, Luca Coelho wrote:
> On Wed, 2017-03-29 at 20:25 -0700, Luis R. Rodriguez wrote:
> > The driver data API provides support for looking for firmware
> > from a specific set of API ranges, so just use that. Since we
> > free the firmware on the callback immediately after consuming it,
> > this also takes avantage of that feature.
> >
> > Signed-off-by: Luis R. Rodriguez <[email protected]>
> > ---
>
> Looks fine, with one nitpick.
>
>
> > drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 67 ++++++++++------------------
> > 1 file changed, 23 insertions(+), 44 deletions(-)
> >
> > diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
> > index be466a074c1d..b6643aa5b344 100644
> > --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
> > +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
>
> [...]
>
> > @@ -1541,11 +1522,9 @@ struct iwl_drv *iwl_drv_start(struct iwl_trans *trans)
> > }
> > #endif
> >
> > - ret = iwl_request_firmware(drv, true);
> > - if (ret) {
> > - IWL_ERR(trans, "Couldn't request the fw\n");
> > + ret = iwl_request_firmware(drv);
> > + if (ret)
> > goto err_fw;
> > - }
>
> Why remove the error message here?
The driver data API now has enough semantics even for async requests so that
an error is either always issued or supressed (optional is true), driver errors
then are superfluous on error now.
Let me know if this is OK.
Luis
On Tue, Apr 11, 2017 at 05:32:52PM +0900, AKASHI Takahiro wrote:
> 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 <[email protected]>
> > ---
> > 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.
This is true but that is the *test* shell script, and it purposely avoids the
existing firmware path to avoid overriding dummy test files on the production
path. So the above still stands as it is not using the test shell script
driver_data.sh.
I'll add a note:
"""
Note that driver_data.sh uses its own temporary custom path for creating and
looking for driver data files, it does this to not overwrite any production
files you might have which may share the same names used by the test shell
script driver_data.sh. If you are not using the driver_data.sh script your
default path will be used.
"""
> > 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 <[email protected]>
> > + *
> > + * 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?
Yes, I went through all possible channels to vet for this, for details refer
to the thread which explains this [0] where the first attempt was to actually add
the license to the list of compatible licenses. So Linus' preference is to use
MODULE_LICENSE("GPL") rather.
[0] https://lkml.kernel.org/r/CA+55aFyhxcvD+q7tp+-yrSFDKfR0mOHgyEAe=f_94aKLsOu0Og@mail.gmail.com
> > diff --git a/tools/testing/selftests/firmware/driver_data.sh b/tools/testing/selftests/firmware/driver_data.sh
...
> > +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?
Just that 1 was not enough and more than 10 seemed too much. As is the tests
are rather simple compared to what we can do given the flexibility in how we
can perform tests due to the test driver structure, in the future this will
become more important. But best to just get in the basics before we hammer and
expand on this a lot. There is also the question of sharing this sort of logic
with the upper testing layers so that they deal with this and not us
(tools/testing/selftests/), in that sense all this is just sufficient for us to do
our own testing for now, but we may and should consider how to get the upper
layers to deal this for us. But we can address this later.
> > +# 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?
This is a test script so it constructs its own temporary path so it can
have the confidence to overwrite anything it pleases. So in this case yes.
Its just as the old firmware test script.
> > +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
Good thing you highlighted these, I had them flipped, -s was for single run
and -c was for test-count number of times.
> 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
True but I prefer having short-hand notations as well.
PS. In the future I'd highly appreciate if you can trim your responses
so you leave only in context enough information to just review the
criteria you are commenting on, rather than keeping every single line.
Luis
Luis,
On Fri, Apr 28, 2017 at 02:51:44AM +0200, Luis R. Rodriguez wrote:
> On Thu, Apr 13, 2017 at 06:36:17PM +0900, AKASHI Takahiro wrote:
> > On Wed, Mar 29, 2017 at 08:25:11PM -0700, Luis R. Rodriguez wrote:
> > > Signed-off-by: Luis R. Rodriguez <[email protected]>
> > > ---
> > > Documentation/driver-api/firmware/driver_data.rst | 77 +++++
> > > Documentation/driver-api/firmware/index.rst | 1 +
> > > Documentation/driver-api/firmware/introduction.rst | 16 +
> >
> > I think we'd better to split code and documents into different patches
> > for easier reviews.
>
> Sure, done.
>
> > > --- a/Documentation/driver-api/firmware/introduction.rst
> > > +++ b/Documentation/driver-api/firmware/introduction.rst
> > > @@ -25,3 +25,19 @@ are already using asynchronous initialization mechanisms which will not
> > > stall or delay boot. Even if loading firmware does not take a lot of time
> > > processing firmware might, and this can still delay boot or initialization,
> > > as such mechanisms such as asynchronous probe can help supplement drivers.
> > > +
> > > +Two APIs
> > > +========
> > > +
> > > +Two APIs are provided for firmware:
> > > +
> > > +* request_firmware API - old firmware API
> > > +* driver_data API - flexible API
> >
> > You can add links:
> >
> > * `request_firmware API`_ - old firmware API
> > * `driver_data API`_ - flexible API
> >
> > .. _`request_firmware API`: ./request_firmware.rst
> > .. _`driver_data API`: ./driver_data.rst
>
> Done!
>
> > > +int driver_data_request_sync(const char *name,
> > > + const struct driver_data_req_params *req_params,
> > > + struct device *device)
> > > +{
> > > + const struct firmware *driver_data;
> > > + const struct driver_data_reqs *sync_reqs;
> > > + struct driver_data_params params = {
> > > + .req_params = *req_params,
> > > + };
> > > + int ret;
> > > +
> > > + if (!device || !req_params || !name || name[0] == '\0')
> > > + return -EINVAL;
> > > +
> > > + if (req_params->sync_reqs.mode != DRIVER_DATA_SYNC)
> > > + return -EINVAL;
> > > +
> > > + if (driver_data_sync_opt_cb(req_params) &&
> > > + !driver_data_param_optional(req_params))
> > > + return -EINVAL;
> > > +
> > > + sync_reqs = &dfl_sync_reqs;
> > > +
> > > + __module_get(sync_reqs->module);
> > > + get_device(device);
> > > +
> > > + ret = _request_firmware(&driver_data, name, ¶ms, device);
> > > + if (ret && driver_data_param_optional(req_params))
> > > + ret = driver_data_sync_opt_call_cb(req_params);
> > > + else
> > > + ret = driver_data_sync_call_cb(req_params, driver_data);
> >
> > It looks a bit weird to me that a failure callback is called
> > only if "optional" is set. I think that it makes more sense
> > that a failure callback is always called if _request_firmware() fails.
>
> Let's think about this: does it make sense for the there to be a callback
> if the file was not optional ? Keep in mind the optional flag has its own
> semantics, it affects printing on error, on file not found. The semantics
> of the "optional callback" is precisely defined for when the first file
> is optional, so its by definition.
>
> If we were to not require optional then it would be more of a "failure callback",
> as you put it, but then folks could be stuffing these with all their error
> paths, and that's not what this is for. The optional callback is to handle
> an alternative *viable* approach *iff* the first file we look for is not found.
In sync case, I don't think we have a strong reason to have a callback
as we can do anything depending on a return value from _request_firmware().
The only merit would be that we could release buffers automatically?
In async case, I think that we should have a callback whether asynchronous
loader has succeeded or failed in order to know the result.
It will never be "optional" even on failure.
> > In addition, why not always return a return value of _request_firmare()?
> > Overwriting a return value by any of callback functions doesn't make sense,
> > particularly, in "sync" case.
> > One of the problems in this implementation is that we, drivers, have
> > no chance to know a return value of _request_firmware().
>
> Ah, good point, well, we can pass it on the optional callback then, this
> way no information is lost.
>
> Thoughts?
Depends on the discussion above.
Thanks,
-Takahiro AKASHI
> > For example, if the signature verification, which I'm now working on, fails,
> > ENOKEY or EKEYxxx will be returns. We may want to say more detailed
> > error messages depending on error code.
>
> Makes sense. If the above suffices let me know.
>
> > > struct driver_data_req_params {
> > > bool optional;
> > > + bool keep;
> > > + bool uses_api_versioning;
> >
> > Do you have any reason that you don't use bit fields here?
> > More features are added, more 'boolean' are added.
> > (I mean it wastes memory.)
>
> You're right, will fold into a flags.
>
> Luis
On Fri, 2017-04-28 at 02:56 +0200, Luis R. Rodriguez wrote:
> On Mon, Apr 10, 2017 at 04:19:12PM +0300, Luca Coelho wrote:
> > On Wed, 2017-03-29 at 20:25 -0700, Luis R. Rodriguez wrote:
> > > The driver data API provides support for looking for firmware
> > > from a specific set of API ranges, so just use that. Since we
> > > free the firmware on the callback immediately after consuming it,
> > > this also takes avantage of that feature.
> > >
> > > Signed-off-by: Luis R. Rodriguez <[email protected]>
> > > ---
> >
> > Looks fine, with one nitpick.
> >
> >
> > > drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 67 ++++++++++------------------
> > > 1 file changed, 23 insertions(+), 44 deletions(-)
> > >
> > > diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
> > > index be466a074c1d..b6643aa5b344 100644
> > > --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
> > > +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
> >
> > [...]
> >
> > > @@ -1541,11 +1522,9 @@ struct iwl_drv *iwl_drv_start(struct iwl_trans *trans)
> > > }
> > > #endif
> > >
> > > - ret = iwl_request_firmware(drv, true);
> > > - if (ret) {
> > > - IWL_ERR(trans, "Couldn't request the fw\n");
> > > + ret = iwl_request_firmware(drv);
> > > + if (ret)
> > > goto err_fw;
> > > - }
> >
> > Why remove the error message here?
>
> The driver data API now has enough semantics even for async requests so that
> an error is either always issued or supressed (optional is true), driver errors
> then are superfluous on error now.
>
> Let me know if this is OK.
Yeah, that's okay. We can always add something back if we think it's
needed.
--
Cheers,
Luca.
On Fri, Apr 28, 2017 at 12:19:05PM +0900, AKASHI Takahiro wrote:
> > > > + ret = _request_firmware(&driver_data, name, ¶ms, device);
> > > > + if (ret && driver_data_param_optional(req_params))
> > > > + ret = driver_data_sync_opt_call_cb(req_params);
> > > > + else
> > > > + ret = driver_data_sync_call_cb(req_params, driver_data);
> > >
> > > It looks a bit weird to me that a failure callback is called
> > > only if "optional" is set. I think that it makes more sense
> > > that a failure callback is always called if _request_firmware() fails.
> >
> > Let's think about this: does it make sense for the there to be a callback
> > if the file was not optional ? Keep in mind the optional flag has its own
> > semantics, it affects printing on error, on file not found. The semantics
> > of the "optional callback" is precisely defined for when the first file
> > is optional, so its by definition.
> >
> > If we were to not require optional then it would be more of a "failure callback",
> > as you put it, but then folks could be stuffing these with all their error
> > paths, and that's not what this is for. The optional callback is to handle
> > an alternative *viable* approach *iff* the first file we look for is not found.
>
> In sync case, I don't think we have a strong reason to have a callback
> as we can do anything depending on a return value from _request_firmware().
> The only merit would be that we could release buffers automatically?
That's right, if you want that feature you must use a sync callback. Some drivers
have the form that they just copy over the data andr elease immediately. Case
in point see the iwlwifi driver which I converted, managing the releases means
also less errors on part of the driver developer on their error paths, and less
code.
> In async case, I think that we should have a callback whether asynchronous
> loader has succeeded or failed in order to know the result.
There are async requests which are completely optional, but indeed even so if no
callback is set then all that would be done is to check if the file exists, so
I agree with you. I will add a respective check forcing for the async callback.
> It will never be "optional" even on failure.
The driver data may be optional but indeed processing it should not be. Come to think
of it the async case also does not give back the return value so for both async and
sync case we should pass the return value to enable the caller to manage different
failures better.
Will modify both callbacks.
> > > In addition, why not always return a return value of _request_firmare()?
> > > Overwriting a return value by any of callback functions doesn't make sense,
> > > particularly, in "sync" case.
> > > One of the problems in this implementation is that we, drivers, have
> > > no chance to know a return value of _request_firmware().
> >
> > Ah, good point, well, we can pass it on the optional callback then, this
> > way no information is lost.
> >
> > Thoughts?
>
> Depends on the discussion above.
Historically we just passed NULL on the async callback on return, and the sync case
always got the actual return value. I think we want the return value in failure other
than just NULL. Will make these adjustments.
Luis