linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v6 0/5] firmware: add driver data API
@ 2017-03-30  3:25 Luis R. Rodriguez
  2017-03-30  3:25 ` [PATCH v6 1/5] firmware: add extensible driver data params Luis R. Rodriguez
                   ` (5 more replies)
  0 siblings, 6 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-03-30  3:25 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, takahiro.akashi, dhowells, pjones,
	linux-kernel, Luis R. Rodriguez

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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* [PATCH v6 1/5] firmware: add extensible driver data params
  2017-03-30  3:25 [PATCH v6 0/5] firmware: add driver data API Luis R. Rodriguez
@ 2017-03-30  3:25 ` Luis R. Rodriguez
  2017-04-06  7:26   ` Luca Coelho
  2017-03-30  3:25 ` [PATCH v6 2/5] firmware: add extensible driver data API Luis R. Rodriguez
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-03-30  3:25 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, takahiro.akashi, dhowells, pjones,
	linux-kernel, Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 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 <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of copyleft-next (version 0.3.1 or later) as published
+ * at http://copyleft-next.org/.
+ */
+
+/**
+ * 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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v6 2/5] firmware: add extensible driver data API
  2017-03-30  3:25 [PATCH v6 0/5] firmware: add driver data API Luis R. Rodriguez
  2017-03-30  3:25 ` [PATCH v6 1/5] firmware: add extensible driver data params Luis R. Rodriguez
@ 2017-03-30  3:25 ` Luis R. Rodriguez
  2017-04-10 12:42   ` Coelho, Luciano
  2017-04-13  9:36   ` AKASHI Takahiro
  2017-03-30  3:25 ` [PATCH v6 3/5] test: add new driver_data load tester Luis R. Rodriguez
                   ` (3 subsequent siblings)
  5 siblings, 2 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-03-30  3:25 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, takahiro.akashi, dhowells, pjones,
	linux-kernel, Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 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 <mcgrof@kernel.org>
 L:	linux-kernel@vger.kernel.org
 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 <josh.h.morris@us.ibm.com>
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 <mcgrof@kernel.org>
  *
  * 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 = &params->priv_params;
+	const struct driver_data_req_params *req_params = &params->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(&params->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, &params, 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 = &params->priv_params;
+	const struct driver_data_req_params *req_params = &params->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 = &params->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 = &params->req_params;
+	const struct driver_data_reqs *sync_reqs;
+	int ret;
+
+	sync_reqs = &req_params->sync_reqs;
+
+	ret = _request_firmware(&params->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 = &params->priv_params;
+	const struct driver_data_req_params *req_params = &params->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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v6 3/5] test: add new driver_data load tester
  2017-03-30  3:25 [PATCH v6 0/5] firmware: add driver data API Luis R. Rodriguez
  2017-03-30  3:25 ` [PATCH v6 1/5] firmware: add extensible driver data params Luis R. Rodriguez
  2017-03-30  3:25 ` [PATCH v6 2/5] firmware: add extensible driver data API Luis R. Rodriguez
@ 2017-03-30  3:25 ` Luis R. Rodriguez
  2017-04-11  8:32   ` AKASHI Takahiro
  2017-03-30  3:25 ` [PATCH v6 4/5] iwlwifi: convert to use driver data API Luis R. Rodriguez
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-03-30  3:25 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, takahiro.akashi, dhowells, pjones,
	linux-kernel, Luis R. Rodriguez

This adds a load tester driver test_driver_data a for the new extensible
driver_data loader API, part of firmware_class. This test driver enables
you to build your tests in userspace by exposing knobs of the exported
API to userspace and enables a trigger action to mimic a one time use
of the kernel API. This gives us the flexibility to build test case from
userspace with less kernel changes.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 Documentation/driver-api/firmware/driver_data.rst |   32 +
 MAINTAINERS                                       |    1 +
 lib/Kconfig.debug                                 |   12 +
 lib/Makefile                                      |    1 +
 lib/test_driver_data.c                            | 1272 +++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile         |    2 +-
 tools/testing/selftests/firmware/config           |    1 +
 tools/testing/selftests/firmware/driver_data.sh   |  996 ++++++++++++++++
 8 files changed, 2316 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_driver_data.c
 create mode 100755 tools/testing/selftests/firmware/driver_data.sh

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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v6 4/5] iwlwifi: convert to use driver data API
  2017-03-30  3:25 [PATCH v6 0/5] firmware: add driver data API Luis R. Rodriguez
                   ` (2 preceding siblings ...)
  2017-03-30  3:25 ` [PATCH v6 3/5] test: add new driver_data load tester Luis R. Rodriguez
@ 2017-03-30  3:25 ` Luis R. Rodriguez
  2017-04-10 13:19   ` Luca Coelho
  2017-03-30  3:25 ` [PATCH v6 5/5] brcmfmac: don't warn user if requested nvram fails Luis R. Rodriguez
  2017-05-02  8:49 ` [PATCH v7 0/5] firmware: add driver data API Luis R. Rodriguez
  5 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-03-30  3:25 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, takahiro.akashi, dhowells, pjones,
	linux-kernel, Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v6 5/5] brcmfmac: don't warn user if requested nvram fails
  2017-03-30  3:25 [PATCH v6 0/5] firmware: add driver data API Luis R. Rodriguez
                   ` (3 preceding siblings ...)
  2017-03-30  3:25 ` [PATCH v6 4/5] iwlwifi: convert to use driver data API Luis R. Rodriguez
@ 2017-03-30  3:25 ` Luis R. Rodriguez
  2017-04-27  0:49   ` Luis R. Rodriguez
  2017-05-02  8:49 ` [PATCH v7 0/5] firmware: add driver data API Luis R. Rodriguez
  5 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-03-30  3:25 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, takahiro.akashi, dhowells, pjones,
	linux-kernel, Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 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, &params, fwctx->dev);
 	if (!ret)
 		return;
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 1/5] firmware: add extensible driver data params
  2017-03-30  3:25 ` [PATCH v6 1/5] firmware: add extensible driver data params Luis R. Rodriguez
@ 2017-04-06  7:26   ` Luca Coelho
  2017-04-27  2:05     ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: Luca Coelho @ 2017-04-06  7:26 UTC (permalink / raw)
  To: Luis R. Rodriguez, gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach, kvalo,
	luto, takahiro.akashi, dhowells, pjones, linux-kernel

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 <mcgrof@kernel.org>
> ---

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 <mcgrof@kernel.org>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of copyleft-next (version 0.3.1 or later) as published
> + * at http://copyleft-next.org/.
> + */
> +
> +/**
> + * 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.

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-03-30  3:25 ` [PATCH v6 2/5] firmware: add extensible driver data API Luis R. Rodriguez
@ 2017-04-10 12:42   ` Coelho, Luciano
  2017-04-11  8:01     ` takahiro.akashi
  2017-04-27  3:16     ` Luis R. Rodriguez
  2017-04-13  9:36   ` AKASHI Takahiro
  1 sibling, 2 replies; 98+ messages in thread
From: Coelho, Luciano @ 2017-04-10 12:42 UTC (permalink / raw)
  To: gregkh, mcgrof
  Cc: linux-kernel, pjones, moritz.fischer, takahiro.akashi, dhowells,
	pmladek, Berg, Johannes, rjw, yi1.li, kvalo, luto,
	arend.vanspriel, rafal, dwmw2, wagi, atull, Grumbach, Emmanuel

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 <mcgrof@kernel.org>
> ---
>  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 = &params->priv_params;
> +	const struct driver_data_req_params *req_params = &params->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(&params->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.

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 4/5] iwlwifi: convert to use driver data API
  2017-03-30  3:25 ` [PATCH v6 4/5] iwlwifi: convert to use driver data API Luis R. Rodriguez
@ 2017-04-10 13:19   ` Luca Coelho
  2017-04-28  0:56     ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: Luca Coelho @ 2017-04-10 13:19 UTC (permalink / raw)
  To: Luis R. Rodriguez, gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach, kvalo,
	luto, takahiro.akashi, dhowells, pjones, linux-kernel

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 <mcgrof@kernel.org>
> ---

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.

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-10 12:42   ` Coelho, Luciano
@ 2017-04-11  8:01     ` takahiro.akashi
  2017-04-27  3:23       ` Luis R. Rodriguez
  2017-04-27  3:16     ` Luis R. Rodriguez
  1 sibling, 1 reply; 98+ messages in thread
From: takahiro.akashi @ 2017-04-11  8:01 UTC (permalink / raw)
  To: Coelho, Luciano
  Cc: gregkh, mcgrof, linux-kernel, pjones, moritz.fischer, dhowells,
	pmladek, Berg, Johannes, rjw, yi1.li, kvalo, luto,
	arend.vanspriel, rafal, dwmw2, wagi, atull, Grumbach, Emmanuel

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 <mcgrof@kernel.org>
> > ---
> >  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 = &params->priv_params;
> > +	const struct driver_data_req_params *req_params = &params->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(&params->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.

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-03-30  3:25 ` [PATCH v6 3/5] test: add new driver_data load tester Luis R. Rodriguez
@ 2017-04-11  8:32   ` AKASHI Takahiro
  2017-04-28  1:45     ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: AKASHI Takahiro @ 2017-04-11  8:32 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, dhowells, pjones, linux-kernel

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

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

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

Is this compatible with GPLv2 for kernel modules?

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

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

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

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

               -> -s

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

               -> -c

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

Thanks,
-Takahiro AKASHI

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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-03-30  3:25 ` [PATCH v6 2/5] firmware: add extensible driver data API Luis R. Rodriguez
  2017-04-10 12:42   ` Coelho, Luciano
@ 2017-04-13  9:36   ` AKASHI Takahiro
  2017-04-28  0:51     ` Luis R. Rodriguez
  1 sibling, 1 reply; 98+ messages in thread
From: AKASHI Takahiro @ 2017-04-13  9:36 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, dhowells, pjones, linux-kernel

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 <mcgrof@kernel.org>
> ---
>  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 <mcgrof@kernel.org>
>  L:	linux-kernel@vger.kernel.org
>  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 <josh.h.morris@us.ibm.com>
> 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 <mcgrof@kernel.org>
>   *
>   * 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 = &params->priv_params;
> +	const struct driver_data_req_params *req_params = &params->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(&params->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, &params, 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 = &params->priv_params;
> +	const struct driver_data_req_params *req_params = &params->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 = &params->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 = &params->req_params;
> +	const struct driver_data_reqs *sync_reqs;
> +	int ret;
> +
> +	sync_reqs = &req_params->sync_reqs;
> +
> +	ret = _request_firmware(&params->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 = &params->priv_params;
> +	const struct driver_data_req_params *req_params = &params->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
> 

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 5/5] brcmfmac: don't warn user if requested nvram fails
  2017-03-30  3:25 ` [PATCH v6 5/5] brcmfmac: don't warn user if requested nvram fails Luis R. Rodriguez
@ 2017-04-27  0:49   ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-27  0:49 UTC (permalink / raw)
  To: Luis R. Rodriguez, Hans de Goede, Arend Van Spriel
  Cc: gregkh, wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, takahiro.akashi, dhowells, pjones,
	linux-kernel

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 <mcgrof@kernel.org>

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/09063fc2-af77-ced6-ed90-ab20e2884969@redhat.com

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 1/5] firmware: add extensible driver data params
  2017-04-06  7:26   ` Luca Coelho
@ 2017-04-27  2:05     ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-27  2:05 UTC (permalink / raw)
  To: Luca Coelho
  Cc: Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal, arend.vanspriel,
	rjw, yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, kvalo, luto, takahiro.akashi, dhowells,
	pjones, linux-kernel

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 <mcgrof@kernel.org>
> > ---
> 
> 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 <mcgrof@kernel.org>
> > + *
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms of copyleft-next (version 0.3.1 or later) as published
> > + * at http://copyleft-next.org/.
> > + */
> > +
> > +/**
> > + * 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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-10 12:42   ` Coelho, Luciano
  2017-04-11  8:01     ` takahiro.akashi
@ 2017-04-27  3:16     ` Luis R. Rodriguez
  2017-04-27  5:44       ` Luca Coelho
  2017-04-27  6:09       ` Luca Coelho
  1 sibling, 2 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-27  3:16 UTC (permalink / raw)
  To: Coelho, Luciano
  Cc: gregkh, mcgrof, linux-kernel, pjones, moritz.fischer,
	takahiro.akashi, dhowells, pmladek, Berg, Johannes, rjw, yi1.li,
	kvalo, luto, arend.vanspriel, rafal, dwmw2, wagi, atull,
	Grumbach, Emmanuel

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 <mcgrof@kernel.org>
> > ---
> >  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 = &params->priv_params;
> > +	const struct driver_data_req_params *req_params = &params->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(&params->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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-11  8:01     ` takahiro.akashi
@ 2017-04-27  3:23       ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-27  3:23 UTC (permalink / raw)
  To: takahiro.akashi, Coelho, Luciano, gregkh, mcgrof, linux-kernel,
	pjones, moritz.fischer, dhowells, pmladek, Berg, Johannes, rjw,
	yi1.li, kvalo, luto, arend.vanspriel, rafal, dwmw2, wagi, atull,
	Grumbach, Emmanuel

On Tue, Apr 11, 2017 at 05:01:51PM +0900, takahiro.akashi@linaro.org 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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-27  3:16     ` Luis R. Rodriguez
@ 2017-04-27  5:44       ` Luca Coelho
  2017-04-27  8:04         ` Luis R. Rodriguez
  2017-04-27  6:09       ` Luca Coelho
  1 sibling, 1 reply; 98+ messages in thread
From: Luca Coelho @ 2017-04-27  5:44 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, linux-kernel, pjones, moritz.fischer, takahiro.akashi,
	dhowells, pmladek, Berg, Johannes, rjw, yi1.li, kvalo, luto,
	arend.vanspriel, rafal, dwmw2, wagi, atull, Grumbach, Emmanuel

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 = &params->priv_params;
> > > +   const struct driver_data_req_params *req_params = &params->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.

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-27  3:16     ` Luis R. Rodriguez
  2017-04-27  5:44       ` Luca Coelho
@ 2017-04-27  6:09       ` Luca Coelho
  2017-04-27 10:31         ` Luis R. Rodriguez
  1 sibling, 1 reply; 98+ messages in thread
From: Luca Coelho @ 2017-04-27  6:09 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, linux-kernel, pjones, moritz.fischer, takahiro.akashi,
	dhowells, pmladek, Berg, Johannes, rjw, yi1.li, kvalo, luto,
	arend.vanspriel, rafal, dwmw2, wagi, atull, Grumbach, Emmanuel

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.

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-27  5:44       ` Luca Coelho
@ 2017-04-27  8:04         ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-27  8:04 UTC (permalink / raw)
  To: Luca Coelho
  Cc: Luis R. Rodriguez, gregkh, linux-kernel, pjones, moritz.fischer,
	takahiro.akashi, dhowells, pmladek, Berg, Johannes, rjw, yi1.li,
	kvalo, luto, arend.vanspriel, rafal, dwmw2, wagi, atull,
	Grumbach, Emmanuel

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 = &params->priv_params;
> > > > +   const struct driver_data_req_params *req_params = &params->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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-27  6:09       ` Luca Coelho
@ 2017-04-27 10:31         ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-27 10:31 UTC (permalink / raw)
  To: Luca Coelho
  Cc: Luis R. Rodriguez, gregkh, linux-kernel, pjones, moritz.fischer,
	takahiro.akashi, dhowells, pmladek, Berg, Johannes, rjw, yi1.li,
	kvalo, luto, arend.vanspriel, rafal, dwmw2, wagi, atull,
	Grumbach, Emmanuel

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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-13  9:36   ` AKASHI Takahiro
@ 2017-04-28  0:51     ` Luis R. Rodriguez
  2017-04-28  3:19       ` AKASHI Takahiro
  0 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-28  0:51 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, yi1.li, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	dhowells, pjones, linux-kernel

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 <mcgrof@kernel.org>
> > ---
> >  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, &params, 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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 4/5] iwlwifi: convert to use driver data API
  2017-04-10 13:19   ` Luca Coelho
@ 2017-04-28  0:56     ` Luis R. Rodriguez
  2017-04-28 12:17       ` Luca Coelho
  0 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-28  0:56 UTC (permalink / raw)
  To: Luca Coelho
  Cc: Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal, arend.vanspriel,
	rjw, yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, kvalo, luto, takahiro.akashi, dhowells,
	pjones, linux-kernel

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 <mcgrof@kernel.org>
> > ---
> 
> 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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-04-11  8:32   ` AKASHI Takahiro
@ 2017-04-28  1:45     ` Luis R. Rodriguez
  2017-05-11 10:46       ` AKASHI Takahiro
  0 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-28  1:45 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, yi1.li, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	dhowells, pjones, linux-kernel

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 <mcgrof@kernel.org>
> > ---
> >  Documentation/driver-api/firmware/driver_data.rst |   32 +
> >  MAINTAINERS                                       |    1 +
> >  lib/Kconfig.debug                                 |   12 +
> >  lib/Makefile                                      |    1 +
> >  lib/test_driver_data.c                            | 1272 +++++++++++++++++++++
> >  tools/testing/selftests/firmware/Makefile         |    2 +-
> >  tools/testing/selftests/firmware/config           |    1 +
> >  tools/testing/selftests/firmware/driver_data.sh   |  996 ++++++++++++++++
> >  8 files changed, 2316 insertions(+), 1 deletion(-)
> >  create mode 100644 lib/test_driver_data.c
> >  create mode 100755 tools/testing/selftests/firmware/driver_data.sh
> > 
> > diff --git a/Documentation/driver-api/firmware/driver_data.rst b/Documentation/driver-api/firmware/driver_data.rst
> > index 08407b7568fe..757c2ffa4ba6 100644
> > --- a/Documentation/driver-api/firmware/driver_data.rst
> > +++ b/Documentation/driver-api/firmware/driver_data.rst
> > @@ -68,6 +68,38 @@ When driver_data_file_request_async() completes you can rest assured all the
> >  work for both triggering, and processing the driver data using any of your
> >  callbacks has completed.
> >  
> > +Testing the driver_data API
> > +===========================
> > +
> > +The driver data API has a selftest driver: lib/test_driver_data.c. The
> > +test_driver_data enables you to build your tests in userspace by exposing knobs
> > +of the exported API in userspace and enabling userspace to configure and
> > +trigger a kernel call. This lets us build most possible test cases of
> > +the kernel APIs from userspace.
> > +
> > +The test_driver_data also enables multiple test triggers to be created
> > +enabling testing to be done in parallel, one test interface per test case.
> > +
> > +To test an async call one could do::
> > +
> > +        echo anything > /lib/firmware/test-driver_data.bin
> 
> Your current shell script doesn't search for the firmware in
> /lib/firmware unless you explicitly specify $FWPATH.

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 <mcgrof@kernel.org>
> > + *
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms of copyleft-next (version 0.3.1 or later) as published
> > + * at http://copyleft-next.org/.
> 
> Is this compatible with GPLv2 for kernel modules?

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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-28  0:51     ` Luis R. Rodriguez
@ 2017-04-28  3:19       ` AKASHI Takahiro
  2017-04-29  4:36         ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: AKASHI Takahiro @ 2017-04-28  3:19 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, dhowells, pjones, linux-kernel

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 <mcgrof@kernel.org>
> > > ---
> > >  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, &params, 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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 4/5] iwlwifi: convert to use driver data API
  2017-04-28  0:56     ` Luis R. Rodriguez
@ 2017-04-28 12:17       ` Luca Coelho
  0 siblings, 0 replies; 98+ messages in thread
From: Luca Coelho @ 2017-04-28 12:17 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach, kvalo,
	luto, takahiro.akashi, dhowells, pjones, linux-kernel

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 <mcgrof@kernel.org>
> > > ---
> > 
> > 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.

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 2/5] firmware: add extensible driver data API
  2017-04-28  3:19       ` AKASHI Takahiro
@ 2017-04-29  4:36         ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-04-29  4:36 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, yi1.li, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	dhowells, pjones, linux-kernel

On Fri, Apr 28, 2017 at 12:19:05PM +0900, AKASHI Takahiro wrote:
> > > > +	ret = _request_firmware(&driver_data, name, &params, 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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* [PATCH v7 0/5] firmware: add driver data API
  2017-03-30  3:25 [PATCH v6 0/5] firmware: add driver data API Luis R. Rodriguez
                   ` (4 preceding siblings ...)
  2017-03-30  3:25 ` [PATCH v6 5/5] brcmfmac: don't warn user if requested nvram fails Luis R. Rodriguez
@ 2017-05-02  8:49 ` Luis R. Rodriguez
  2017-05-02  8:49   ` [PATCH v7 1/5] firmware: add extensible driver data params Luis R. Rodriguez
                     ` (5 more replies)
  5 siblings, 6 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-02  8:49 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, linux-kernel, Luis R. Rodriguez

Greg,

On the v6 series [0] I got good amount of feedback from Luca and from
AKASHI Takahiro, who is picking up on the firmware signing work. In this
respin I've addressed their feedback, mostly to do away with redundant
checks while we can just proactively prevent certain situations, added
a int to the callbacks to enable us to propagate the exact reason for
failure, moved features (both exposed to driver and internal) from bool
to flags, split up a lot of data structures to be able to more properly
document them, shoved a lot of helper stuff I had on driver_data.h
into firmware_class.c as there was no point to expose these helpers to
drivers, and split out documentation into its own patch as requested.
The test driver script was adjusted in two test cases given we now get
the proper valid error code from an error :)

I also dropped the brcmfmac patch given Hans de Goede noted that the
nvram file is in fact not optional.

I've also ramped up the documentation a bit more, this time to refer to
the new data structures and the new functionality exported. This series
depends on the other series I just posted which moves the UMH lock stuff
to UMH-only code [1].

This and my previous series are available in git form on my linux-next
20170501-driver-data-try2 branch [2]. In case folks want to play with
a more stable tree I've rebased this and the prior series onto v4.11-rc8,
and is available on my linux v4.11-rc8-driver-data-try3 branch [3]. 

[0] https://lkml.kernel.org/r/20170330032514.17173-1-mcgrof@kernel.org
[1] https://lkml.kernel.org/r/20170502083107.23418-1-mcgrof@kernel.org
[2] https://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux-next.git/log/?h=20170501-driver-data-try2
[3] https://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux-next.git/log/?h=v4.11-rc8-driver-data-try3

Luis R. Rodriguez (5):
  firmware: add extensible driver data params
  firmware: add extensible driver data API
  test: add new driver_data load tester
  firmware: document the extensible driver data API
  iwlwifi: convert to use driver data API

 Documentation/driver-api/firmware/driver_data.rst  |  167 +++
 Documentation/driver-api/firmware/index.rst        |    1 +
 Documentation/driver-api/firmware/introduction.rst |   16 +
 .../driver-api/firmware/request_firmware.rst       |    2 +
 MAINTAINERS                                        |    4 +-
 drivers/base/firmware_class.c                      |  741 ++++++++++--
 drivers/net/wireless/intel/iwlwifi/iwl-drv.c       |   91 +-
 include/linux/driver_data.h                        |  260 ++++
 include/linux/firmware.h                           |    2 +
 lib/Kconfig.debug                                  |   12 +
 lib/Makefile                                       |    1 +
 lib/test_driver_data.c                             | 1270 ++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile          |    2 +-
 tools/testing/selftests/firmware/config            |    1 +
 tools/testing/selftests/firmware/driver_data.sh    |  996 +++++++++++++++
 15 files changed, 3430 insertions(+), 136 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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* [PATCH v7 1/5] firmware: add extensible driver data params
  2017-05-02  8:49 ` [PATCH v7 0/5] firmware: add driver data API Luis R. Rodriguez
@ 2017-05-02  8:49   ` Luis R. Rodriguez
  2017-05-11 18:17     ` Li, Yi
  2017-05-02  8:49   ` [PATCH v7 2/5] firmware: add extensible driver data API Luis R. Rodriguez
                     ` (4 subsequent siblings)
  5 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-02  8:49 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, linux-kernel, Luis R. Rodriguez

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 only
  o struct driver_data_params - stiches both of the the above together,
				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.

A side consequences is get to replace all the old internal "firmware
behavior  options" flags with enums we properly document, remove the
blinding #ifdefs, and compartamentlize the userhelper fallback code
more appropriately unde CONFIG_FW_LOADER_USER_HELPER_FALLBACK.

This commit should introduces no functional changes (TM).

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/base/firmware_class.c | 331 ++++++++++++++++++++++++++++++++----------
 include/linux/driver_data.h   |  82 +++++++++++
 2 files changed, 339 insertions(+), 74 deletions(-)
 create mode 100644 include/linux/driver_data.h

diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index b9f907eedbf7..db7c0bc0ed98 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,149 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
 MODULE_DESCRIPTION("Multi purpose firmware loading support");
 MODULE_LICENSE("GPL");
 
+/**
+ * enum driver_data_mode - driver data mode of operation
+ * @DRIVER_DATA_SYNC: used to determine if we should look for the driver data
+ *	file immediatley.
+ * @DRIVER_DATA_ASYNC: used to determine if we should schedule the search for
+ *	your driver data file to be run at a later time.
+ */
+enum driver_data_mode {
+	DRIVER_DATA_SYNC = 0,
+	DRIVER_DATA_ASYNC,
+};
+
+/**
+ * enum driver_data_priv_reqs - private features only used internally
+ *
+ * @DRIVER_DATA_PRIV_REQ_FALLBACK: specifies that the driver data request
+ * 	will use a fallback mechanism if the kernel's direct filesystem
+ * 	lookup failed to find the requested driver data. If the flag
+ * 	%DRIVER_DATA_PRIV_REQ_FALLBACK is set but the flag
+ * 	%DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT is not set, it means the caller
+ * 	is relying on a custom interface for driver data lookup as a fallback
+ * 	mechanism. The custom interface is expected to find any found driver
+ * 	data using the exposed sysfs interface of the firmware_class. If the
+ * 	custom fallback mechanism is not compatible with the internal caching
+ * 	mechanism for driver data lookups at resume, it will be disabled.
+ * @DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT: indicates that the fallback mechanism
+ * 	this driver data request will rely on will be that of having the kernel
+ * 	issue a uevent to userspace. Userspace in turn is expected to be
+ * 	monitoring for uevents for the firmware_class and will use the
+ * 	exposted sysfs interface to upload the driver data for the caller.
+ * @DRIVER_DATA_PRIV_REQ_NO_CACHE: indicates that the driver data request
+ * 	should not set up and use the internal caching mechanism to assist
+ * 	drivers from fetching driver data at resume time after suspend.
+ */
+enum driver_data_priv_reqs {
+	DRIVER_DATA_PRIV_REQ_FALLBACK			= 1 << 0,
+	DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT		= 1 << 1,
+	DRIVER_DATA_PRIV_REQ_NO_CACHE			= 1 << 2,
+};
+
+/**
+ * struct driver_data_priv_params - private driver data parameters
+ * @mode: mode of operation
+ * @priv_reqs: private set of &enum driver_data_reqs, private requirements for
+ * 	the driver data request
+ * @alloc_buf: buffer area allocated by the caller so we can place the
+ * 	respective driver data
+ * @alloc_buf_size: size of the @alloc_buf
+ * @old_async_cb: used only for request_firmware_nowait() since we won't change
+ * 	all async callbacks to get the return value on failure
+ */
+struct driver_data_priv_params {
+	enum driver_data_mode mode;
+	u64 priv_reqs;
+	void *alloc_buf;
+	size_t alloc_buf_size;
+	void (*old_async_cb)(const struct firmware *driver_data, void *context);
+};
+
+/**
+ * struct driver_data_params
+ * @driver_data: the driver data if found using the requirements specified
+ * 	in @req_params and @priv_params
+ * @req_params: caller's requirements for the driver data to look for
+ * @priv_params: private requirements for the driver data to look for
+ */
+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 = {						\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT,	\
+	}
+
+#define __DATA_REQ_FIRMWARE_DIRECT()					\
+	.req_params = {							\
+		.reqs = DRIVER_DATA_REQ_OPTIONAL,			\
+	},								\
+	.priv_params = {						\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT,	\
+	}
+
+#define __DATA_REQ_FIRMWARE_BUF(buf, size)				\
+	.priv_params = {						\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT |	\
+			     DRIVER_DATA_PRIV_REQ_NO_CACHE,		\
+		.alloc_buf = buf,					\
+		.alloc_buf_size = size,					\
+	}
+
+#define __DATA_REQ_FIRMWARE_NOWAIT(module, uevent, gfp, async_cb, async_ctx) \
+	.req_params = {							\
+		.hold_module = module,					\
+		.gfp = gfp,						\
+		.cbs.async = {						\
+			.found_cb = NULL,				\
+			.found_ctx = async_ctx,				\
+		},							\
+	},								\
+	.priv_params = {						\
+		.mode = DRIVER_DATA_ASYNC,				\
+		.old_async_cb = async_cb,				\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     (uevent ?					\
+			      DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT : 0),\
+	}
+
+#define driver_data_req_param_sync(params)				\
+	((params)->priv_params.mode == DRIVER_DATA_SYNC)
+#define driver_data_req_param_async(params)				\
+	((params)->priv_params.mode == DRIVER_DATA_ASYNC)
+
+#define driver_data_param_use_fallback(params)	\
+	(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_FALLBACK))
+#define driver_data_param_uevent(params)	\
+	(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT))
+#define driver_data_param_nocache(params)	\
+	(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_NO_CACHE))
+
+#define driver_data_param_optional(params)	\
+	(!!((params)->reqs & DRIVER_DATA_REQ_OPTIONAL))
+
+#define driver_data_async_ctx(params)		((params)->cbs.async.found_ctx)
+
 /* Builtin firmware support */
 
 #ifdef CONFIG_FW_LOADER
@@ -48,9 +192,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 +233,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;
 }
@@ -182,22 +336,6 @@ static int __fw_state_check(struct fw_state *fw_st, enum fw_status status)
 
 #endif /* CONFIG_FW_LOADER_USER_HELPER */
 
-/* firmware behavior options */
-#define FW_OPT_UEVENT	(1U << 0)
-#define FW_OPT_NOWAIT	(1U << 1)
-#ifdef CONFIG_FW_LOADER_USER_HELPER
-#define FW_OPT_USERHELPER	(1U << 2)
-#else
-#define FW_OPT_USERHELPER	0
-#endif
-#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK
-#define FW_OPT_FALLBACK		FW_OPT_USERHELPER
-#else
-#define FW_OPT_FALLBACK		0
-#endif
-#define FW_OPT_NO_WARN	(1U << 3)
-#define FW_OPT_NOCACHE	(1U << 4)
-
 struct firmware_cache {
 	/* firmware_buf instance will be added into the below list */
 	spinlock_t lock;
@@ -296,9 +434,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 +485,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 +498,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 +704,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 +722,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);
@@ -1025,7 +1174,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;
@@ -1036,7 +1186,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);
 	fw_priv->fw = firmware;
 	f_dev = &fw_priv->dev;
 
@@ -1051,7 +1201,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;
@@ -1073,7 +1224,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);
@@ -1102,14 +1253,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)) {
 		timeout = usermodehelper_read_lock_wait(timeout);
 		if (!timeout) {
 			dev_dbg(device, "firmware: %s loading timed out\n",
@@ -1125,17 +1276,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();
@@ -1146,7 +1297,8 @@ static int fw_load_from_user_helper(struct firmware *firmware,
 #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;
 }
@@ -1161,7 +1313,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;
@@ -1174,12 +1327,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
@@ -1200,11 +1353,33 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
 	return 1; /* need to load */
 }
 
+#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK
+static int driver_data_fallback(struct firmware *fw, const char *name,
+				struct device *device,
+				struct driver_data_params *data_params,
+				int ret)
+{
+	if (!driver_data_param_use_fallback(&data_params->priv_params))
+		return ret;
+
+	dev_warn(device, "Falling back to user helper\n");
+	return fw_load_from_user_helper(fw, name, device, data_params);
+}
+#else
+static inline int driver_data_fallback(struct firmware *fw, const char *name,
+				       struct device *device,
+				       struct driver_data_params *data_params,
+				       int ret)
+{
+	return ret;
+}
+#endif
+
 /* 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;
@@ -1217,7 +1392,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;
 
@@ -1229,17 +1404,13 @@ _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) {
-			dev_warn(device, "Falling back to user helper\n");
-			ret = fw_load_from_user_helper(fw, name, device,
-						       opt_flags);
-		}
+		ret = driver_data_fallback(fw, name, device, data_params, ret);
 	} else
-		ret = assign_firmware_buf(fw, device, opt_flags);
+		ret = assign_firmware_buf(fw, device, data_params);
 
  out:
 	if (ret < 0) {
@@ -1276,11 +1447,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;
 }
@@ -1301,10 +1474,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;
 }
@@ -1330,12 +1505,14 @@ 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);
@@ -1357,27 +1534,30 @@ 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_priv_params *priv_params;
 
 	fw_work = container_of(work, struct firmware_work, work);
-
-	_request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0,
-			  fw_work->opt_flags);
-	fw_work->cont(fw, fw_work->context);
+	data_params = &fw_work->data_params;
+	req_params = &data_params->req_params;
+	priv_params = &data_params->priv_params;
+
+	_request_firmware(&data_params->driver_data, fw_work->name,
+			  data_params, fw_work->device);
+	priv_params->old_async_cb(data_params->driver_data,
+				  driver_data_async_ctx(req_params));
 	put_device(fw_work->device); /* taken in request_firmware_nowait() */
 
-	module_put(fw_work->module);
+	module_put(req_params->hold_module);
 	kfree_const(fw_work->name);
 	kfree(fw_work);
 }
@@ -1412,22 +1592,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);
@@ -1505,7 +1688,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..7ce3216a9a99
--- /dev/null
+++ b/include/linux/driver_data.h
@@ -0,0 +1,82 @@
+#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 <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of copyleft-next (version 0.3.1 or later) as published
+ * at http://copyleft-next.org/.
+ */
+
+/**
+ * struct driver_data_async_cbs - callbacks for handling driver data requests
+ * @found_cb: callback to be used when the driver data has been found. A
+ *	callback is required. If the requested driver data is found it will
+ *	passed on the callback, using the context set on @found_ctx.
+ * @found_ctx: preferred context to be used as the second argument to
+ * 	@found_cb.
+ *
+ * Used for specifying callbacks and contexts used for when asynchronous driver
+ * data requests have completed. If no driver data is found the error will be
+ * passed on the respective callback.
+ */
+struct driver_data_async_cbs {
+	void (*found_cb)(const struct firmware *driver_data,
+			 void *context,
+			 int error);
+	void *found_ctx;
+};
+
+/**
+ * union driver_data_cbs - callbacks for driver data request
+ * @async: callbacks for handling driver data when asynchronous requests
+ * 	are made.
+ *
+ * Used for placement of callbacks used for handling results from driver
+ * data requests.
+ */
+union driver_data_cbs {
+	struct driver_data_async_cbs async;
+};
+
+/**
+ * enum driver_data_reqs - requirements of the driver data request
+ * @DRIVER_DATA_REQ_OPTIONAL: if set it is not a hard requirement by the
+ *	caller that the file requested be present. An error will not be recorded
+ *	if the file is not found.
+ */
+enum driver_data_reqs {
+	DRIVER_DATA_REQ_OPTIONAL			= 1 << 0,
+};
+
+/**
+ * struct driver_data_req_params - driver data request parameters
+ * @hold_module: module to hold during the driver data request operation. By
+ * 	default if sync requests set this to NULL the firmware_class module
+ * 	will be refcounted during operation.
+ * @gfp: flags to use for allocations when constructing the driver data request,
+ *	prior to scheduling. Unused on driver_data_request_sync().
+ * @reqs: set of &enum driver_data_reqs flags used to configure the driver
+ * 	data request. All of the specified requirements must be met.
+ * @cbs: set of callbacks to use for the driver data request.
+ *
+ * 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 {
+	struct module *hold_module;
+	gfp_t gfp;
+	u64 reqs;
+	const union driver_data_cbs cbs;
+};
+
+#endif /* _LINUX_DRIVER_DATA_H */
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v7 2/5] firmware: add extensible driver data API
  2017-05-02  8:49 ` [PATCH v7 0/5] firmware: add driver data API Luis R. Rodriguez
  2017-05-02  8:49   ` [PATCH v7 1/5] firmware: add extensible driver data params Luis R. Rodriguez
@ 2017-05-02  8:49   ` Luis R. Rodriguez
  2017-05-02  8:49   ` [PATCH v7 3/5] test: add new driver_data load tester Luis R. Rodriguez
                     ` (3 subsequent siblings)
  5 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-02  8:49 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, linux-kernel, Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 MAINTAINERS                   |   3 +-
 drivers/base/firmware_class.c | 410 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/driver_data.h   | 178 ++++++++++++++++++
 include/linux/firmware.h      |   2 +
 4 files changed, 592 insertions(+), 1 deletion(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 2a9aa4be5846..214585cddab0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5220,13 +5220,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 <mcgrof@kernel.org>
 L:	linux-kernel@vger.kernel.org
 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 <josh.h.morris@us.ibm.com>
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index db7c0bc0ed98..7af430a2d656 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 <mcgrof@kernel.org>
  *
  * Please see Documentation/firmware_class/ for more information.
  *
@@ -91,6 +92,12 @@ enum driver_data_priv_reqs {
  * @alloc_buf_size: size of the @alloc_buf
  * @old_async_cb: used only for request_firmware_nowait() since we won't change
  * 	all async callbacks to get the return value on failure
+ * @api: used internally for keeping track of the currently evaluated API
+ * 	versioned file as we iterate between min API and max API.
+ * @retry_api: if the driver replied with -EAGAIN, we must ignore the passed
+ * 	driver data file, and retry again on the hunt from where we left off,
+ * 	this lets us know an attempt to look for more API driver data files
+ * 	is a retry.
  */
 struct driver_data_priv_params {
 	enum driver_data_mode mode;
@@ -98,6 +105,8 @@ struct driver_data_priv_params {
 	void *alloc_buf;
 	size_t alloc_buf_size;
 	void (*old_async_cb)(const struct firmware *driver_data, void *context);
+	u8 api;
+	bool retry_api;
 };
 
 /**
@@ -181,8 +190,68 @@ struct driver_data_params {
 
 #define driver_data_param_optional(params)	\
 	(!!((params)->reqs & DRIVER_DATA_REQ_OPTIONAL))
+#define driver_data_param_keep(params)		\
+	(!!((params)->reqs & DRIVER_DATA_REQ_KEEP))
+#define driver_data_param_uses_api(params)	\
+	(!!((params)->reqs & DRIVER_DATA_REQ_USE_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, int error)
+{
+	if (!driver_data_sync_cb(params))
+		return error;
+	return driver_data_sync_cb(params)(driver_data_sync_ctx(params),
+					   driver_data, error);
+}
+
+#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,
+				 int error)
+{
+	if (!driver_data_sync_opt_cb(params))
+		return error;
+	return driver_data_sync_opt_cb(params)
+		(driver_data_sync_opt_ctx(params), error);
+}
+
+#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,
+			       int error)
+{
+	BUG_ON(!driver_data_async_cb(params));
+	driver_data_async_cb(params)(driver_data,
+				     driver_data_async_ctx(params),
+				     error);
+}
+
+#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,
+				   int error)
+{
+	driver_data_async_opt_cb(params)(driver_data_async_opt_ctx(params),
+					 error);
+}
+
+#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,
+				  int error)
+{
+	return driver_data_async_api_cb(params)(driver_data,
+						driver_data_async_ctx(params),
+						error);
+}
 
 /* Builtin firmware support */
 
@@ -1316,6 +1385,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;
@@ -1339,6 +1409,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);
@@ -1531,6 +1602,131 @@ 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 = &params->priv_params;
+	const struct driver_data_req_params *req_params = &params->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(&params->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_sync - synchronous request for a driver data file
+ * @name: name of the driver data file
+ * @req_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. 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.
+ *
+ * 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;
+	struct module *hold_module;
+	struct driver_data_params params = {
+		.req_params = *req_params,
+		.priv_params = {
+			.mode = DRIVER_DATA_SYNC,
+		},
+	};
+	int ret;
+
+	if (!device || !req_params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (driver_data_sync_opt_cb(req_params) &&
+	    !driver_data_param_optional(req_params))
+		return -EINVAL;
+
+	hold_module = req_params->hold_module ? req_params->hold_module :
+		THIS_MODULE;
+
+	__module_get(hold_module);
+	get_device(device);
+
+	ret = _request_firmware(&driver_data, name, &params, device);
+	if (ret && driver_data_param_optional(req_params))
+		ret = driver_data_sync_opt_call_cb(req_params, ret);
+	else
+		ret = driver_data_sync_call_cb(req_params, driver_data, ret);
+
+	if (!driver_data_param_keep(req_params))
+		release_firmware(driver_data);
+
+	put_device(device);
+	module_put(hold_module);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(driver_data_request_sync);
+
 /* Async support */
 struct firmware_work {
 	struct work_struct work;
@@ -1625,6 +1821,220 @@ request_firmware_nowait(
 }
 EXPORT_SYMBOL(request_firmware_nowait);
 
+static bool
+driver_data_api_versioning_ok(const struct driver_data_req_params *req_params)
+{
+	if (!req_params->api_max ||
+	    (req_params->api_max < req_params->api_min) ||
+	    (driver_data_async_cb(req_params)) ||
+	    (!driver_data_async_api_cb(req_params)))
+		return false;
+
+	return true;
+}
+
+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 = &params->req_params;
+	int ret;
+
+	ret = _driver_data_request_api(params, driver_work->device,
+				       driver_work->name);
+	return driver_data_async_call_api_cb(params->driver_data, req_params,
+					     ret);
+}
+
+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 = &params->req_params;
+	int ret;
+
+	ret = _request_firmware(&params->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, ret);
+	else
+		driver_data_async_call_cb(params->driver_data, req_params, ret);
+
+	if (!driver_data_param_keep(req_params))
+		release_firmware(params->driver_data);
+
+	put_device(driver_work->device);
+	module_put(req_params->hold_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 = &params->priv_params;
+	const struct driver_data_req_params *req_params = &params->req_params;
+	int ret;
+	u8 i, limit;
+
+	limit = req_params->api_max - req_params->api_min;
+
+	for (i=0; i <= limit; i++) {
+		/*
+		 * 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;
+	}
+
+	/*
+	 * Note special case:
+	 *
+	 * If the driver didn't like any of the driver data we gave it, it
+	 * may return -EAGAIN for everything that we fed it. We will treat
+	 * this as non-fatal so optional callbacks can work to address this
+	 * if enabled.
+	 *
+	 * All non -ENOENT and -EAGAIN errors are treated as fatal, so we must
+	 * return immediately. Only -ENONENT and -EAGAIN errors are treated as
+	 * graceful and enables the optional callback.
+	 */
+	if (ret) {
+		if (!driver_data_param_optional(req_params))
+			dev_err(driver_work->device,
+				"No API file in range %u - %u could be found, error: %d\n",
+				req_params->api_min, req_params->api_max, ret);
+		if ((ret == -ENOENT || ret == -EAGAIN) &&
+		    driver_data_async_opt_cb(req_params))
+			driver_data_async_opt_call_cb(req_params, ret);
+	}
+
+	if (!driver_data_param_keep(req_params))
+		release_firmware(params->driver_data);
+
+	put_device(driver_work->device);
+	module_put(req_params->hold_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;
+	struct firmware_work driver_work_stack = {
+		.data_params.req_params = *req_params,
+		.data_params.priv_params = {
+			.mode = DRIVER_DATA_ASYNC,
+		},
+	};
+
+	if (!device || !req_params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (driver_data_async_opt_cb(req_params) &&
+	    !driver_data_param_optional(req_params))
+		return -EINVAL;
+
+	if (!driver_data_async_cb(req_params) &&
+	    !driver_data_async_api_cb(req_params))
+		return -EINVAL;
+
+	if (driver_data_param_uses_api(req_params) &&
+	    !driver_data_api_versioning_ok(req_params))
+		return -EINVAL;
+
+	driver_work = kzalloc(sizeof(struct firmware_work), req_params->gfp);
+	if (!driver_work)
+		return -ENOMEM;
+
+	memcpy(driver_work, &driver_work_stack, sizeof(struct firmware_work));
+
+	driver_work->name = kstrdup_const(name, req_params->gfp);
+	if (!driver_work->name) {
+		kfree(driver_work);
+		return -ENOMEM;
+	}
+	driver_work->device = device;
+
+	if (!try_module_get(req_params->hold_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 7ce3216a9a99..bf51e0b9ad46 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
@@ -17,12 +18,47 @@
  */
 
 /**
+ * struct driver_data_sync_cbs - synchronous driver data callbacks
+ * @found_cb: optional callback to be used when the driver data has been found.
+ * 	A callback is useful if you wish to take advantage of the feature of
+ * 	having your driver_data be released immediately after the callback is
+ * 	called, this feature is enabled by default can can be disabled by
+ * 	setting the flag %DRIVER_DATA_REQ_KEEP.
+ * @found_ctx: preferred context to be used as the second argument to
+ * 	@found_cb.
+ * @opt_fail_cb: if your driver data is optional and you have a viable approach
+ * 	to remedy the lack of finding a driver data with original requirements
+ * 	you can implement your solution on this callback.
+ * @opt_fail_ctx: context to use for @opt_fail_cb
+ *
+ * Used for specifying callbacks and contexts used for when synchronous driver
+ * data requests have completed. If no driver data is found the error will be
+ * passed on the respective callback.
+ */
+struct driver_data_sync_cbs {
+	int __must_check
+		(*found_cb)(void *context,
+			    const struct firmware *driver_data,
+			    int error);
+	void *found_ctx;
+
+	int __must_check (*opt_fail_cb)(void *context, int error);
+	void *opt_fail_ctx;
+};
+
+/**
  * struct driver_data_async_cbs - callbacks for handling driver data requests
  * @found_cb: callback to be used when the driver data has been found. A
  *	callback is required. If the requested driver data is found it will
  *	passed on the callback, using the context set on @found_ctx.
  * @found_ctx: preferred context to be used as the second argument to
  * 	@found_cb.
+ * @opt_fail_cb: if your driver data is optional and you have a viable approach
+ * 	to remedy the lack of finding a driver data with original requirements
+ * 	you can implement your solution on this callback.
+ * @opt_fail_ctx: context to use for @opt_fail_cb
+ * @found_api_cb: callback for the supported version API framework, refer to
+ * 	%DRIVER_DATA_REQ_USE_API_VERSIONING for details.
  *
  * Used for specifying callbacks and contexts used for when asynchronous driver
  * data requests have completed. If no driver data is found the error will be
@@ -33,18 +69,28 @@ struct driver_data_async_cbs {
 			 void *context,
 			 int error);
 	void *found_ctx;
+
+	void (*opt_fail_cb)(void *context, int error);
+	void *opt_fail_ctx;
+
+	int __must_check
+		(*found_api_cb)(const struct firmware *driver_data,
+				void *context, int error);
 };
 
 /**
  * union driver_data_cbs - callbacks for driver data request
  * @async: callbacks for handling driver data when asynchronous requests
  * 	are made.
+ * @sync: callbacks for handling driver data when synchronous requests are
+ * 	made.
  *
  * Used for placement of callbacks used for handling results from driver
  * data requests.
  */
 union driver_data_cbs {
 	struct driver_data_async_cbs async;
+	struct driver_data_sync_cbs sync;
 };
 
 /**
@@ -52,9 +98,30 @@ union driver_data_cbs {
  * @DRIVER_DATA_REQ_OPTIONAL: if set it is not a hard requirement by the
  *	caller that the file requested be present. An error will not be recorded
  *	if the file is not found.
+ * @DRIVER_DATA_REQ_KEEP: by default the kernel will release the driver data
+ *	for you immediately after your respective sync or async callback is
+ *	called.  Use this flag to annotate your requirement is for you to keep
+ *	and free the driver data on your own. You must free the driver data
+ *	using release_driver_data().
+ * @DRIVER_DATA_REQ_USE_API_VERSIONING: indicates 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. The search starts using the filename requested on
+ *	driver_data_request_async(), appending the
+ *	&driver_data_req_params->api_max to it, and ending with a postfix if
+ *	&driver_data_req_params->api_name_postfix is specified.  If that is not
+ *	available it will look for any files with API version lower than this
+ *	until it reaches &driver_data_req_params->api_min. This enables
+ *	chaining driver data requests easily on behalf of device drivers using
+ *	a simple one digit versioning scheme.  This feature requires only one
+ *	file to be present given the API range, it is only required for one
+ *	file in the API range to be present.  If the %DRIVER_DATA_REQ_OPTIONAL
+ *	flag is also enabled then all files are treated as optional.
  */
 enum driver_data_reqs {
 	DRIVER_DATA_REQ_OPTIONAL			= 1 << 0,
+	DRIVER_DATA_REQ_KEEP				= 1 << 1,
+	DRIVER_DATA_REQ_USE_API_VERSIONING		= 1 << 2,
 };
 
 /**
@@ -67,6 +134,12 @@ enum driver_data_reqs {
  * @reqs: set of &enum driver_data_reqs flags used to configure the driver
  * 	data request. All of the specified requirements must be met.
  * @cbs: set of callbacks to use for the driver data request.
+ * @api_min: if %DRIVER_DATA_REQ_USE_API_VERSIONING is set, this represents the
+ * 	lowest version of API supported by the caller.
+ * @api_max: if %DRIVER_DATA_REQ_USE_API_VERSIONING is set, this represents the
+ * 	highest version of API supported by the caller.
+ * @api_name_postfix: optional, indicates to use this as the driver data name
+ * 	postfix when %DRIVER_DATA_REQ_USE_API_VERSIONING is enabled.
  *
  * 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
@@ -76,7 +149,112 @@ struct driver_data_req_params {
 	struct module *hold_module;
 	gfp_t gfp;
 	u64 reqs;
+	u8 api_min;
+	u8 api_max;
+	const char *api_name_postfix;
 	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_DEFAULT_SYNC_REQS(__found_cb, __ctx, __reqs)	\
+	DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx),			\
+	.reqs = (__reqs)
+
+#define DRIVER_DATA_KEEP_SYNC(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_SYNC(__found_cb, __ctx),			\
+	.reqs = DRIVER_DATA_REQ_KEEP
+
+/* If you have one fallback routine */
+#define DRIVER_DATA_SYNC_OPT_CB(__fail_cb, __ctx)			\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL,				\
+	.cbs.sync.opt_fail_cb = __fail_cb,				\
+	.cbs.sync.opt_fail_ctx = __ctx
+
+#define DRIVER_DATA_SYNC_OPT_CB_REQS(__fail_cb, __ctx, __reqs)		\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL | __reqs,			\
+	.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)			\
+	.hold_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),			\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL
+
+#define DRIVER_DATA_KEEP_ASYNC(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx),			\
+	.reqs = DRIVER_DATA_PRIV_REQ_KEEP
+
+#define DRIVER_DATA_KEEP_ASYNC_OPT(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx),			\
+	.reqs = DRIVER_DATA_PRIV_REQ_KEEP |				\
+		DRIVER_DATA_REQ_OPTIONAL
+
+#define DRIVER_DATA_ASYNC_OPT_CB(__fail_cb, __ctx)			\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL,				\
+	.cbs.async.opt_fail_cb = __fail_cb,				\
+	.cbs.async.opt_fail_ctx = __ctx
+
+#define DRIVER_DATA_API_CB(__found_api_cb, __ctx)			\
+	.hold_module = THIS_MODULE,					\
+	.gfp = GFP_KERNEL,						\
+	.cbs.async = {							\
+		.found_api_cb = __found_api_cb,				\
+		.found_ctx = __ctx,					\
+	}
+
+#define DRIVER_DATA_API(__min, __max, __postfix)			\
+	.reqs = DRIVER_DATA_REQ_USE_API_VERSIONING,			\
+	.api_min = __min,						\
+	.api_max = __max,						\
+	.api_name_postfix = __postfix
+
+#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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v7 3/5] test: add new driver_data load tester
  2017-05-02  8:49 ` [PATCH v7 0/5] firmware: add driver data API Luis R. Rodriguez
  2017-05-02  8:49   ` [PATCH v7 1/5] firmware: add extensible driver data params Luis R. Rodriguez
  2017-05-02  8:49   ` [PATCH v7 2/5] firmware: add extensible driver data API Luis R. Rodriguez
@ 2017-05-02  8:49   ` Luis R. Rodriguez
  2017-05-11 10:10     ` AKASHI Takahiro
  2017-05-15 18:23     ` [PATCH v8] " Luis R. Rodriguez
  2017-05-02  8:49   ` [PATCH v7 4/5] firmware: document the extensible driver data API Luis R. Rodriguez
                     ` (2 subsequent siblings)
  5 siblings, 2 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-02  8:49 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, linux-kernel, Luis R. Rodriguez

This adds a load tester driver test_driver_data a for the new extensible
driver_data loader API, part of firmware_class. This test driver enables
you to build your tests in userspace by exposing knobs of the exported
API to userspace and enables a trigger action to mimic a one time use
of the kernel API. This gives us the flexibility to build test case from
userspace with less kernel changes.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 MAINTAINERS                                     |    1 +
 lib/Kconfig.debug                               |   12 +
 lib/Makefile                                    |    1 +
 lib/test_driver_data.c                          | 1270 +++++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile       |    2 +-
 tools/testing/selftests/firmware/config         |    1 +
 tools/testing/selftests/firmware/driver_data.sh |  996 ++++++++++++++++++
 7 files changed, 2282 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_driver_data.c
 create mode 100755 tools/testing/selftests/firmware/driver_data.sh

diff --git a/MAINTAINERS b/MAINTAINERS
index 214585cddab0..1231267ad830 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5226,6 +5226,7 @@ L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	Documentation/firmware_class/
 F:	drivers/base/firmware*.c
+F:	lib/test_driver_data.c
 F:	include/linux/firmware.h
 F:	include/linux/driver_data.h
 
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 122825555822..6704541f4bd0 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 0166fbc0fa81..4be5e119a98c 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..488cc6e9eed4
--- /dev/null
+++ b/lib/test_driver_data.c
@@ -0,0 +1,1270 @@
+/*
+ * Driver data test interface
+ *
+ * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of copyleft-next (version 0.3.1 or later) as published
+ * at http://copyleft-next.org/.
+ *
+ * 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,
+			 int unused_error)
+{
+	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, int unused_error)
+{
+	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, int unused_error)
+{
+	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,
+			      int unused_error)
+{
+	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_REQS(config_sync_req_cb, test_dev,
+					      DRIVER_DATA_REQ_OPTIONAL |
+					      DRIVER_DATA_REQ_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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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, int unused_error)
+{
+	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,
+				   int unused_error)
+{
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	const struct driver_data_req_params req_params_api = {
+		DRIVER_DATA_API_CB(config_async_req_api_cb, test_dev),
+		DRIVER_DATA_API(config->api_min, config->api_max, config->api_name_postfix),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0) |
+			(config->use_api_versioning ? DRIVER_DATA_REQ_USE_API_VERSIONING : 0),
+	};
+	const struct driver_data_req_params *req_params;
+
+	if (config->enable_opt_cb)
+		req_params = &req_params_opt_cb;
+	else if (config->use_api_versioning)
+		req_params = &req_params_api;
+	else
+		req_params = &req_params_default;
+
+	test_dev->api_found_calls = 0;
+	ret = driver_data_request_async(config->name, req_params,
+					test_dev->dev);
+	if (ret) {
+		dev_err(test_dev->dev, "async load of '%s' failed: %d\n",
+			config->name, ret);
+		goto out;
+	}
+
+	/*
+	 * Without waiting for completion we'd return before the async callback
+	 * completes, and any testing analysis done on the results would be
+	 * bogus. We could have used async cookies to avoid having drivers
+	 * avoid adding their own completions and initializing them.
+	 * We have decided its best to keep with the old way of doing things to
+	 * keep things compatible. Deal with it.
+	 */
+	wait_for_completion_timeout(&test_dev->request_complete, 5 * HZ);
+
+out:
+	return ret;
+}
+
+static ssize_t
+trigger_config_store(struct device *dev,
+		     struct device_attribute *attr,
+		     const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_driver_data_private *test_driver_data =
+		&test_dev->test_driver_data;
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->trigger_mutex);
+	mutex_lock(&test_dev->config_mutex);
+
+	dev_info(dev, "loading '%s'\n", config->name);
+
+	if (config->async)
+		ret = trigger_config_async(test_dev);
+	else
+		ret = trigger_config_sync(test_dev);
+
+	config->test_result = ret;
+
+	if (ret)
+		goto out;
+
+	if (test_driver_data->written) {
+		dev_info(dev, "loaded: %zu\n", test_driver_data->size);
+		ret = count;
+	} else {
+		dev_err(dev, "failed to load firmware\n");
+		ret = -ENODEV;
+	}
+
+out:
+	mutex_unlock(&test_dev->config_mutex);
+	mutex_unlock(&test_dev->trigger_mutex);
+
+	return ret;
+}
+static DEVICE_ATTR_WO(trigger_config);
+
+/*
+ * XXX: move to kstrncpy() once merged.
+ *
+ * Users should use kfree_const() when freeing these.
+ */
+static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp)
+{
+	*dst = kstrndup(name, count, gfp);
+	if (!*dst)
+		return -ENOSPC;
+	return count;
+}
+
+static void __driver_data_config_free(struct test_config *config)
+{
+	kfree_const(config->name);
+	config->name = NULL;
+	kfree_const(config->default_name);
+	config->default_name = NULL;
+	kfree_const(config->api_name_postfix);
+	config->api_name_postfix = NULL;
+}
+
+static void driver_data_config_free(struct driver_data_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+
+	mutex_lock(&test_dev->config_mutex);
+	__driver_data_config_free(config);
+	mutex_unlock(&test_dev->config_mutex);
+}
+
+static int __driver_data_config_init(struct test_config *config)
+{
+	int ret;
+
+	ret = __kstrncpy(&config->name, TEST_DRIVER_DATA,
+			 strlen(TEST_DRIVER_DATA), GFP_KERNEL);
+	if (ret < 0)
+		goto out;
+
+	ret = __kstrncpy(&config->default_name, TEST_DRIVER_DATA,
+			 strlen(TEST_DRIVER_DATA), GFP_KERNEL);
+	if (ret < 0)
+		goto out;
+
+	config->async = false;
+	config->optional = false;
+	config->keep = false;
+	config->enable_opt_cb = false;
+	config->use_api_versioning = false;
+	config->api_min = 0;
+	config->api_max = 0;
+	config->test_result = 0;
+
+	return 0;
+
+out:
+	__driver_data_config_free(config);
+	return ret;
+}
+
+int driver_data_config_init(struct driver_data_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	ret = __driver_data_config_init(config);
+	mutex_unlock(&test_dev->config_mutex);
+
+	return ret;
+}
+
+static ssize_t config_name_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	kfree_const(config->name);
+	ret = __kstrncpy(&config->name, buf, count, GFP_KERNEL);
+	mutex_unlock(&test_dev->config_mutex);
+
+	return ret;
+}
+
+/*
+ * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE.
+ */
+static ssize_t config_test_show_str(struct mutex *config_mutex,
+				    char *dst,
+				    char *src)
+{
+	int len;
+
+	mutex_lock(config_mutex);
+	len = snprintf(dst, PAGE_SIZE, "%s\n", src);
+	mutex_unlock(config_mutex);
+
+	return len;
+}
+
+static ssize_t config_name_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return config_test_show_str(&test_dev->config_mutex, buf,
+				    config->name);
+}
+static DEVICE_ATTR(config_name, 0644, config_name_show, config_name_store);
+
+static ssize_t config_default_name_store(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	kfree_const(config->default_name);
+	ret = __kstrncpy(&config->default_name, buf, count, GFP_KERNEL);
+	mutex_unlock(&test_dev->config_mutex);
+
+	return ret;
+}
+
+static ssize_t config_default_name_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return config_test_show_str(&test_dev->config_mutex, buf,
+				    config->default_name);
+}
+static DEVICE_ATTR(config_default_name, 0644, config_default_name_show,
+		   config_default_name_store);
+
+static ssize_t reset_store(struct device *dev,
+			   struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->trigger_mutex);
+
+	mutex_lock(&test_dev->driver_data_mutex);
+	free_test_driver_data(&test_dev->test_driver_data);
+	reinit_completion(&test_dev->request_complete);
+	mutex_unlock(&test_dev->driver_data_mutex);
+
+	mutex_lock(&test_dev->config_mutex);
+
+	__driver_data_config_free(config);
+
+	ret = __driver_data_config_init(config);
+	if (ret < 0) {
+		ret = -ENOMEM;
+		dev_err(dev, "could not alloc settings for config trigger: %d\n",
+		       ret);
+		goto out;
+	}
+
+	dev_info(dev, "reset\n");
+	ret = count;
+
+out:
+	mutex_unlock(&test_dev->config_mutex);
+	mutex_unlock(&test_dev->trigger_mutex);
+
+	return ret;
+}
+static DEVICE_ATTR_WO(reset);
+
+/*
+ * XXX: consider a soluton to generalize drivers to specify their own
+ * mutex, adding it to dev core after this gets merged. This may not
+ * be important for once-in-a-while system tuning parameters, but if
+ * we want to enable fuzz testing, this is really important.
+ *
+ * It may make sense to just have a "struct device configuration mutex"
+ * for these sorts of things, although there is difficulty in that we'd
+ * need dynamically allocated attributes for that. Its the same reason
+ * why we ended up not using the provided standard device attribute
+ * bool, int interfaces.
+ */
+
+static int test_dev_config_update_bool(struct driver_data_test_device *test_dev,
+				       const char *buf, size_t size,
+				       bool *config)
+{
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	if (strtobool(buf, config) < 0)
+		ret = -EINVAL;
+	else
+		ret = size;
+	mutex_unlock(&test_dev->config_mutex);
+
+	return ret;
+}
+
+static ssize_t
+test_dev_config_show_bool(struct driver_data_test_device *test_dev,
+			  char *buf,
+			  bool config)
+{
+	bool val;
+
+	mutex_lock(&test_dev->config_mutex);
+	val = config;
+	mutex_unlock(&test_dev->config_mutex);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static int test_dev_config_update_int(struct driver_data_test_device *test_dev,
+				      const char *buf, size_t size,
+				      int *config)
+{
+	int ret;
+	long new;
+
+	ret = kstrtol(buf, 10, &new);
+	if (ret)
+		return ret;
+
+	if (new > INT_MAX || new < INT_MIN)
+		return -EINVAL;
+
+	mutex_lock(&test_dev->config_mutex);
+	*(int *)config = new;
+	mutex_unlock(&test_dev->config_mutex);
+
+	/* Always return full write size even if we didn't consume all */
+	return size;
+}
+
+static
+ssize_t test_dev_config_show_int(struct driver_data_test_device *test_dev,
+				 char *buf,
+				 int config)
+{
+	int val;
+
+	mutex_lock(&test_dev->config_mutex);
+	val = config;
+	mutex_unlock(&test_dev->config_mutex);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static int test_dev_config_update_u8(struct driver_data_test_device *test_dev,
+				     const char *buf, size_t size,
+				     u8 *config)
+{
+	int ret;
+	long new;
+
+	ret = kstrtol(buf, 10, &new);
+	if (ret)
+		return ret;
+
+	if (new > U8_MAX)
+		return -EINVAL;
+
+	mutex_lock(&test_dev->config_mutex);
+	*(u8 *)config = new;
+	mutex_unlock(&test_dev->config_mutex);
+
+	/* Always return full write size even if we didn't consume all */
+	return size;
+}
+
+static
+ssize_t test_dev_config_show_u8(struct driver_data_test_device *test_dev,
+				char *buf,
+				u8 config)
+{
+	u8 val;
+
+	mutex_lock(&test_dev->config_mutex);
+	val = config;
+	mutex_unlock(&test_dev->config_mutex);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+
+static ssize_t config_async_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_update_bool(test_dev, buf, count,
+					   &config->async);
+}
+
+static ssize_t config_async_show(struct device *dev,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_show_bool(test_dev, buf, config->async);
+}
+static DEVICE_ATTR(config_async, 0644, config_async_show, config_async_store);
+
+static ssize_t config_optional_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_update_bool(test_dev, buf, count,
+					   &config->optional);
+}
+
+static ssize_t config_optional_show(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_show_bool(test_dev, buf, config->optional);
+}
+static DEVICE_ATTR(config_optional, 0644, config_optional_show,
+		   config_optional_store);
+
+static ssize_t config_keep_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_update_bool(test_dev, buf, count,
+					   &config->keep);
+}
+
+static ssize_t config_keep_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_show_bool(test_dev, buf, config->keep);
+}
+static DEVICE_ATTR(config_keep, 0644, config_keep_show, config_keep_store);
+
+static ssize_t config_enable_opt_cb_store(struct device *dev,
+					  struct device_attribute *attr,
+					  const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_update_bool(test_dev, buf, count,
+					   &config->enable_opt_cb);
+}
+
+static ssize_t config_enable_opt_cb_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_show_bool(test_dev, buf,
+					 config->enable_opt_cb);
+}
+static DEVICE_ATTR(config_enable_opt_cb, 0644,
+		   config_enable_opt_cb_show,
+		   config_enable_opt_cb_store);
+
+static ssize_t config_use_api_versioning_store(struct device *dev,
+					       struct device_attribute *attr,
+					       const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_update_bool(test_dev, buf, count,
+					   &config->use_api_versioning);
+}
+
+static ssize_t config_use_api_versioning_show(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_show_bool(test_dev, buf,
+					 config->use_api_versioning);
+}
+static DEVICE_ATTR(config_use_api_versioning, 0644,
+		   config_use_api_versioning_show,
+		   config_use_api_versioning_store);
+
+static ssize_t config_api_min_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_update_u8(test_dev, buf, count,
+					 &config->api_min);
+}
+
+static ssize_t config_api_min_show(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_show_u8(test_dev, buf, config->api_min);
+}
+static DEVICE_ATTR(config_api_min, 0644, config_api_min_show, config_api_min_store);
+
+static ssize_t config_api_max_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_update_u8(test_dev, buf, count,
+					 &config->api_max);
+}
+
+static ssize_t config_api_max_show(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_show_u8(test_dev, buf, config->api_max);
+}
+static DEVICE_ATTR(config_api_max, 0644, config_api_max_show, config_api_max_store);
+
+static ssize_t config_api_name_postfix_store(struct device *dev,
+					     struct device_attribute *attr,
+					     const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	kfree_const(config->api_name_postfix);
+	ret = __kstrncpy(&config->api_name_postfix, buf, count, GFP_KERNEL);
+	mutex_unlock(&test_dev->config_mutex);
+
+	return ret;
+}
+
+static ssize_t config_api_name_postfix_show(struct device *dev,
+					    struct device_attribute *attr,
+					    char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return config_test_show_str(&test_dev->config_mutex, buf,
+				    config->api_name_postfix);
+}
+static DEVICE_ATTR(config_api_name_postfix, 0644, config_api_name_postfix_show,
+		   config_api_name_postfix_store);
+
+static ssize_t test_result_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_update_int(test_dev, buf, count,
+					  &config->test_result);
+}
+
+static ssize_t test_result_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct driver_data_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+
+	return test_dev_config_show_int(test_dev, buf, config->test_result);
+}
+static DEVICE_ATTR(test_result, 0644, test_result_show, test_result_store);
+
+#define TEST_DRIVER_DATA_DEV_ATTR(name)		&dev_attr_##name.attr
+
+static struct attribute *test_dev_attrs[] = {
+	TEST_DRIVER_DATA_DEV_ATTR(trigger_config),
+	TEST_DRIVER_DATA_DEV_ATTR(config),
+	TEST_DRIVER_DATA_DEV_ATTR(reset),
+
+	TEST_DRIVER_DATA_DEV_ATTR(config_name),
+	TEST_DRIVER_DATA_DEV_ATTR(config_default_name),
+	TEST_DRIVER_DATA_DEV_ATTR(config_async),
+	TEST_DRIVER_DATA_DEV_ATTR(config_optional),
+	TEST_DRIVER_DATA_DEV_ATTR(config_keep),
+	TEST_DRIVER_DATA_DEV_ATTR(config_use_api_versioning),
+	TEST_DRIVER_DATA_DEV_ATTR(config_enable_opt_cb),
+	TEST_DRIVER_DATA_DEV_ATTR(config_api_min),
+	TEST_DRIVER_DATA_DEV_ATTR(config_api_max),
+	TEST_DRIVER_DATA_DEV_ATTR(config_api_name_postfix),
+	TEST_DRIVER_DATA_DEV_ATTR(test_result),
+
+	NULL,
+};
+
+ATTRIBUTE_GROUPS(test_dev);
+
+void free_test_dev_driver_data(struct driver_data_test_device *test_dev)
+{
+	kfree_const(test_dev->misc_dev.name);
+	test_dev->misc_dev.name = NULL;
+	vfree(test_dev);
+	test_dev = NULL;
+	driver_data_config_free(test_dev);
+}
+
+void unregister_test_dev_driver_data(struct driver_data_test_device *test_dev)
+{
+	wait_for_completion_timeout(&test_dev->request_complete, 5 * HZ);
+	dev_info(test_dev->dev, "removing interface\n");
+	misc_deregister(&test_dev->misc_dev);
+	kfree(&test_dev->misc_dev.name);
+	free_test_dev_driver_data(test_dev);
+}
+
+struct driver_data_test_device *alloc_test_dev_driver_data(int idx)
+{
+	int ret;
+	struct driver_data_test_device *test_dev;
+	struct miscdevice *misc_dev;
+
+	test_dev = vzalloc(sizeof(struct driver_data_test_device));
+	if (!test_dev)
+		goto err_out;
+
+	mutex_init(&test_dev->driver_data_mutex);
+	mutex_init(&test_dev->config_mutex);
+	mutex_init(&test_dev->trigger_mutex);
+	init_completion(&test_dev->request_complete);
+
+	ret = driver_data_config_init(test_dev);
+	if (ret < 0)
+		goto err_out_free;
+
+	test_dev->dev_idx = idx;
+	misc_dev = &test_dev->misc_dev;
+
+	misc_dev->minor = MISC_DYNAMIC_MINOR;
+	misc_dev->name = kasprintf(GFP_KERNEL, "test_driver_data%d", idx);
+	if (!misc_dev->name)
+		goto err_out_free_config;
+
+	misc_dev->fops = &test_fw_fops;
+	misc_dev->groups = test_dev_groups;
+
+	return test_dev;
+
+err_out_free_config:
+	__driver_data_config_free(&test_dev->config);
+err_out_free:
+	kfree(test_dev);
+err_out:
+	return NULL;
+}
+
+static int register_test_dev_driver_data(void)
+{
+	struct driver_data_test_device *test_dev = NULL;
+	int ret = -ENODEV;
+
+	mutex_lock(&reg_dev_mutex);
+
+	/* int should suffice for number of devices, test for wrap */
+	if (unlikely(num_test_devs + 1) < 0) {
+		pr_err("reached limit of number of test devices\n");
+		goto out;
+	}
+
+	test_dev = alloc_test_dev_driver_data(num_test_devs);
+	if (!test_dev) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = misc_register(&test_dev->misc_dev);
+	if (ret) {
+		pr_err("could not register misc device: %d\n", ret);
+		free_test_dev_driver_data(test_dev);
+		goto out;
+	}
+
+	test_dev->dev = test_dev->misc_dev.this_device;
+	list_add_tail(&test_dev->list, &reg_test_devs);
+	dev_info(test_dev->dev, "interface ready\n");
+
+	num_test_devs++;
+
+out:
+	mutex_unlock(&reg_dev_mutex);
+
+	return ret;
+}
+
+static int __init test_driver_data_init(void)
+{
+	int ret;
+
+	ret = register_test_dev_driver_data();
+	if (ret)
+		pr_err("Cannot add first test driver_data device\n");
+
+	return ret;
+}
+late_initcall(test_driver_data_init);
+
+static void __exit test_driver_data_exit(void)
+{
+	struct driver_data_test_device *test_dev, *tmp;
+
+	mutex_lock(&reg_dev_mutex);
+	list_for_each_entry_safe(test_dev, tmp, &reg_test_devs, list) {
+		list_del(&test_dev->list);
+		unregister_test_dev_driver_data(test_dev);
+	}
+	mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_driver_data_exit);
+
+MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/tools/testing/selftests/firmware/Makefile b/tools/testing/selftests/firmware/Makefile
index 1894d625af2d..c9bf6c44435f 100644
--- a/tools/testing/selftests/firmware/Makefile
+++ b/tools/testing/selftests/firmware/Makefile
@@ -3,7 +3,7 @@
 # No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
 all:
 
-TEST_PROGS := fw_filesystem.sh fw_fallback.sh
+TEST_PROGS := fw_filesystem.sh fw_fallback.sh driver_data.sh
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config
index c8137f70e291..0f1a299f9270 100644
--- a/tools/testing/selftests/firmware/config
+++ b/tools/testing/selftests/firmware/config
@@ -1 +1,2 @@
 CONFIG_TEST_FIRMWARE=y
+CONFIG_TEST_DRIVER_DATA=y
diff --git a/tools/testing/selftests/firmware/driver_data.sh b/tools/testing/selftests/firmware/driver_data.sh
new file mode 100755
index 000000000000..c830d04390e3
--- /dev/null
+++ b/tools/testing/selftests/firmware/driver_data.sh
@@ -0,0 +1,996 @@
+#!/bin/bash
+# Copyright (C) 2016 Luis R. Rodriguez <mcgrof@kernel.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of copyleft-next (version 0.3.1 or later) as published
+# at http://copyleft-next.org/.
+
+# This performs a series tests against firmware_class to excercise the
+# firmware_class driver with focus only on the extensible driver data API.
+#
+# To make this test self contained, and not pollute your distribution
+# firmware install paths, we reset the custom load directory to a
+# temporary location.
+
+set -e
+
+TEST_NAME="driver_data"
+TEST_DRIVER="test_${TEST_NAME}"
+TEST_DIR=$(dirname $0)
+
+# This represents
+#
+# TEST_ID:TEST_COUNT:ENABLED
+#
+# TEST_ID: is the test id number
+# TEST_COUNT: number of times we should run the test
+# ENABLED: 1 if enabled, 0 otherwise
+#
+# Once these are enabled please leave them as-is. Write your own test,
+# we have tons of space.
+ALL_TESTS="0001:3:1"
+ALL_TESTS="$ALL_TESTS 0002:3:1"
+ALL_TESTS="$ALL_TESTS 0003:3:1"
+ALL_TESTS="$ALL_TESTS 0004:10:1"
+ALL_TESTS="$ALL_TESTS 0005:10:1"
+ALL_TESTS="$ALL_TESTS 0006:10:1"
+ALL_TESTS="$ALL_TESTS 0007:10:1"
+ALL_TESTS="$ALL_TESTS 0008:10:1"
+ALL_TESTS="$ALL_TESTS 0009:10:1"
+ALL_TESTS="$ALL_TESTS 0010:10:1"
+ALL_TESTS="$ALL_TESTS 0011:10:1"
+ALL_TESTS="$ALL_TESTS 0012:1:1"
+ALL_TESTS="$ALL_TESTS 0013:1:1"
+
+# 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]} -ENOENT
+}
+
+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]} -EINVAL
+}
+
+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 "    -s      Run test ID once"
+	echo "    -c      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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v7 4/5] firmware: document the extensible driver data API
  2017-05-02  8:49 ` [PATCH v7 0/5] firmware: add driver data API Luis R. Rodriguez
                     ` (2 preceding siblings ...)
  2017-05-02  8:49   ` [PATCH v7 3/5] test: add new driver_data load tester Luis R. Rodriguez
@ 2017-05-02  8:49   ` Luis R. Rodriguez
  2017-05-02  8:49   ` [PATCH v7 5/5] iwlwifi: convert to use " Luis R. Rodriguez
  2017-05-19 19:10   ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
  5 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-02  8:49 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, linux-kernel, Luis R. Rodriguez

This documents the driver data API.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 Documentation/driver-api/firmware/driver_data.rst  | 167 +++++++++++++++++++++
 Documentation/driver-api/firmware/index.rst        |   1 +
 Documentation/driver-api/firmware/introduction.rst |  16 ++
 .../driver-api/firmware/request_firmware.rst       |   2 +
 4 files changed, 186 insertions(+)
 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..be7c7ff99151
--- /dev/null
+++ b/Documentation/driver-api/firmware/driver_data.rst
@@ -0,0 +1,167 @@
+.. _driver_data:
+
+===============
+driver_data API
+===============
+
+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 two types of modes of operation for driver data requests:
+
+  * synchronous  - driver_data_request_sync()
+  * 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. 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.
+
+driver_data_sync_cbs
+--------------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_sync_cbs
+
+driver_data_async_cbs
+---------------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_async_cbs
+
+driver_data_cbs
+---------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_cbs
+
+driver_data_reqs
+----------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_reqs
+
+driver_data_req_params
+----------------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_req_params
+
+Synchronous driver data requests
+================================
+
+Synchronous driver data requests will wait until the driver data is found or
+until an error is returned.
+
+driver_data_request_sync
+------------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_request_sync
+
+Asynchronous driver data requests
+=================================
+
+Asynchronous driver data requests allow driver code to not have to wait
+until the driver data or an error is returned. Function callbacks are
+required so that when the firmware or an error is found the driver is
+informed through the callbacks. Asynchronous driver data requests cannot
+be called from atomic contexts.
+
+driver_data_request_async
+-------------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_request_async
+
+Reference counting and releasing the driver data file
+=====================================================
+
+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. When synchronous requests are made the
+firmware_class is refcounted. When asynchronous requests are made the caller's
+module is refcounted. Asynchronous requests do not refcount the firmware_class
+module.
+
+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.
+
+Driver data private internal functionality
+==========================================
+
+This section documents functionality not exposed to users, but important in
+understanding how the driver data internals work.
+
+driver_data_mode
+----------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_mode
+
+driver_data_priv_reqs
+---------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_priv_reqs
+
+driver_data_priv_params
+-----------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_priv_params
+
+driver_data_params
+------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_params
+
+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
+
+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.
+
+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 29da39ec4b8a..70a3dea0c5de 100644
--- a/Documentation/driver-api/firmware/index.rst
+++ b/Documentation/driver-api/firmware/index.rst
@@ -8,6 +8,7 @@ Linux Firmware API
    core
    request_firmware
    other_interfaces
+   driver_data
 
 .. only::  subproject and html
 
diff --git a/Documentation/driver-api/firmware/introduction.rst b/Documentation/driver-api/firmware/introduction.rst
index 211cb44eb972..c1173bac0dbb 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:
+
+* Old firmware API - :ref:`request_firmware`
+* Flexible driver data API - :ref:`driver_data`
+
+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/Documentation/driver-api/firmware/request_firmware.rst b/Documentation/driver-api/firmware/request_firmware.rst
index 1c2c4967cd43..b31938244b7f 100644
--- a/Documentation/driver-api/firmware/request_firmware.rst
+++ b/Documentation/driver-api/firmware/request_firmware.rst
@@ -1,3 +1,5 @@
+.. _request_firmware:
+
 ====================
 request_firmware API
 ====================
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v7 5/5] iwlwifi: convert to use driver data API
  2017-05-02  8:49 ` [PATCH v7 0/5] firmware: add driver data API Luis R. Rodriguez
                     ` (3 preceding siblings ...)
  2017-05-02  8:49   ` [PATCH v7 4/5] firmware: document the extensible driver data API Luis R. Rodriguez
@ 2017-05-02  8:49   ` Luis R. Rodriguez
  2017-05-19 19:10   ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
  5 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-02  8:49 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, linux-kernel, Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 91 ++++++++++------------------
 1 file changed, 31 insertions(+), 60 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
index 5cfacb0bca84..028854d31f55 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,58 +206,34 @@ 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,
+			       int unused_error);
 
-static int iwl_request_firmware(struct iwl_drv *drv, bool first)
+static const char *iwl_get_fw_name_pre(struct iwl_drv *drv)
 {
-	const struct iwl_cfg *cfg = drv->trans->cfg;
-	char tag[8];
 	const char *fw_pre_name;
 
 	if (drv->trans->cfg->device_family == IWL_DEVICE_FAMILY_8000 &&
 	    CSR_HW_REV_STEP(drv->trans->hw_rev) == SILICON_B_STEP)
-		fw_pre_name = cfg->fw_name_pre_next_step;
+		fw_pre_name = drv->trans->cfg->fw_name_pre_next_step;
 	else
-		fw_pre_name = cfg->fw_name_pre;
-
-	if (first) {
-		drv->fw_index = cfg->ucode_api_max;
-		sprintf(tag, "%d", drv->fw_index);
-	} else {
-		drv->fw_index--;
-		sprintf(tag, "%d", drv->fw_index);
-	}
-
-	if (drv->fw_index < cfg->ucode_api_min) {
-		IWL_ERR(drv, "no suitable firmware found!\n");
-
-		if (cfg->ucode_api_min == cfg->ucode_api_max) {
-			IWL_ERR(drv, "%s%d is required\n", fw_pre_name,
-				cfg->ucode_api_max);
-		} else {
-			IWL_ERR(drv, "minimum version required: %s%d\n",
-				fw_pre_name,
-				cfg->ucode_api_min);
-			IWL_ERR(drv, "maximum version supported: %s%d\n",
-				fw_pre_name,
-				cfg->ucode_api_max);
-		}
-
-		IWL_ERR(drv,
-			"check git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git\n");
-		return -ENOENT;
-	}
+		fw_pre_name = drv->trans->cfg->fw_name_pre;
 
-	snprintf(drv->firmware_name, sizeof(drv->firmware_name), "%s%s.ucode",
-		 fw_pre_name, tag);
-
-	IWL_DEBUG_INFO(drv, "attempting to load firmware '%s'\n",
-		       drv->firmware_name);
+	return fw_pre_name;
+}
 
-	return request_firmware_nowait(THIS_MODULE, 1, drv->firmware_name,
-				       drv->trans->dev,
-				       GFP_KERNEL, drv, iwl_req_fw_callback);
+static int iwl_request_firmware(struct iwl_drv *drv)
+{
+	const char *name_pre = iwl_get_fw_name_pre(drv);
+	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 {
@@ -1259,7 +1235,8 @@ 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,
+			       int unused_error)
 {
 	struct iwl_drv *drv = context;
 	struct iwl_fw *fw = &drv->fw;
@@ -1282,10 +1259,12 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
 
 	pieces = kzalloc(sizeof(*pieces), GFP_KERNEL);
 	if (!pieces)
-		goto out_free_fw;
+		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);
@@ -1448,9 +1427,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:
@@ -1505,15 +1481,11 @@ 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:
 	iwl_dealloc_ucode(drv);
-	release_firmware(ucode_raw);
  out_unbind:
 	complete(&drv->request_firmware_complete);
 	device_release_driver(drv->trans->dev);
@@ -1524,6 +1496,7 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
 		kfree(pieces->dbg_mem_tlv);
 		kfree(pieces);
 	}
+	return err;
 }
 
 struct iwl_drv *iwl_drv_start(struct iwl_trans *trans)
@@ -1564,11 +1537,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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* Re: [PATCH v7 3/5] test: add new driver_data load tester
  2017-05-02  8:49   ` [PATCH v7 3/5] test: add new driver_data load tester Luis R. Rodriguez
@ 2017-05-11 10:10     ` AKASHI Takahiro
  2017-05-11 17:00       ` Luis R. Rodriguez
  2017-05-15 18:23     ` [PATCH v8] " Luis R. Rodriguez
  1 sibling, 1 reply; 98+ messages in thread
From: AKASHI Takahiro @ 2017-05-11 10:10 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, dhowells,
	pjones, hdegoede, linux-kernel

Luis,

On Tue, May 02, 2017 at 01:49:12AM -0700, Luis R. Rodriguez wrote:
> 
> diff --git a/lib/test_driver_data.c b/lib/test_driver_data.c
> new file mode 100644
> index 000000000000..488cc6e9eed4
> --- /dev/null
> +++ b/lib/test_driver_data.c

  ...

> +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_REQS(config_sync_req_cb, test_dev,
> +					      DRIVER_DATA_REQ_OPTIONAL |
> +					      DRIVER_DATA_REQ_KEEP)

Are these flags always on?

> +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);

Removing this test module fails.

The last three lines should be:
	driver_data_config_free(test_dev);
	vfree(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);

Don't need this kfree().

Thanks,
-Takahiro AKASHI

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-04-28  1:45     ` Luis R. Rodriguez
@ 2017-05-11 10:46       ` AKASHI Takahiro
  2017-05-11 17:11         ` Luis R. Rodriguez
                           ` (2 more replies)
  0 siblings, 3 replies; 98+ messages in thread
From: AKASHI Takahiro @ 2017-05-11 10:46 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, dhowells, pjones, linux-kernel

Luis,

On Fri, Apr 28, 2017 at 03:45:35AM +0200, Luis R. Rodriguez wrote:
> > > +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. 
> """

That looks fine, but I think we'd better change the line:

> > > +        echo anything > /lib/firmware/test-driver_data.bin

since it is just incorrect as far as driver_data.sh goes.

> > > diff --git a/lib/test_driver_data.c b/lib/test_driver_data.c
> > > new file mode 100644
> > > index 000000000000..11175a3b9f0a
> > > --- /dev/null
> > > +++ b/lib/test_driver_data.c
> > > @@ -0,0 +1,1272 @@
> > > +/*
> > > + * Driver data test interface
> > > + *
> > > + * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
> > > + *
> > > + * This program is free software; you can redistribute it and/or modify it
> > > + * under the terms of copyleft-next (version 0.3.1 or later) as published
> > > + * at http://copyleft-next.org/.
> > 
> > Is this compatible with GPLv2 for kernel modules?
> 
> 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

Thank you for this heads-up.
According to Linus' comment, he seems to expect an explicit GPL license
term to be in the beginning of the file, and then if you want, an additional
license to be added, quote "if you want to dual-license it, just put something
like "or, at your option, copyleft-next" in the comment at the top."


> > > 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

In my opinion, "1," or "2" given the nature of firmware caching, is good
enough, but it's up to you.

BTW, firmware caching is a bit annoying in my signature tests
because caching will bypass the verification checks when we iterates
tests with different conditions.

> 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.

Right, but looking into the script, even if an user supplies a firmware
blob, the script overwrites it unnecessarily.
This may also be inconvenient if I add signature verification tests.
(Not sure though.)

> > > +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.

Okay, up to you.

> 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.

Got it.

Thanks,
-Takahiro AKASHI

>   Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v7 3/5] test: add new driver_data load tester
  2017-05-11 10:10     ` AKASHI Takahiro
@ 2017-05-11 17:00       ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-11 17:00 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, yi1.li, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	torvalds, keescook, dhowells, pjones, hdegoede, linux-kernel

On Thu, May 11, 2017 at 07:10:18PM +0900, AKASHI Takahiro wrote:
> Luis,
> 
> On Tue, May 02, 2017 at 01:49:12AM -0700, Luis R. Rodriguez wrote:
> > 
> > diff --git a/lib/test_driver_data.c b/lib/test_driver_data.c
> > new file mode 100644
> > index 000000000000..488cc6e9eed4
> > --- /dev/null
> > +++ b/lib/test_driver_data.c
> 
>   ...
> 
> > +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_REQS(config_sync_req_cb, test_dev,
> > +					      DRIVER_DATA_REQ_OPTIONAL |
> > +					      DRIVER_DATA_REQ_KEEP)
> 
> Are these flags always on?

Ah no, indeed they are conditional on the config as with the others.

With this kmemleak on the test driver is back to squeaky clean, I had
failed to test with kmemleak on the test_driver after these changes,
sorry and thanks for picking this up.

> > +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);
> 
> Removing this test module fails.
> 
> The last three lines should be:
> 	driver_data_config_free(test_dev);
> 	vfree(test_dev);

Fixed, thanks!

> > +}
> > +
> > +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);
> 
> Don't need this kfree().

Indeed, thanks!

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-11 10:46       ` AKASHI Takahiro
@ 2017-05-11 17:11         ` Luis R. Rodriguez
  2017-05-17 22:45           ` Li, Yi
  2017-05-11 18:12         ` Luis R. Rodriguez
  2017-05-11 18:26         ` Luis R. Rodriguez
  2 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-11 17:11 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, yi1.li, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	dhowells, pjones, linux-kernel

On Thu, May 11, 2017 at 07:46:27PM +0900, AKASHI Takahiro wrote:
> Luis,
> 
> On Fri, Apr 28, 2017 at 03:45:35AM +0200, Luis R. Rodriguez wrote:
> > > > +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. 
> > """
> 
> That looks fine, but I think we'd better change the line:
> 
> > > > +        echo anything > /lib/firmware/test-driver_data.bin
> 
> since it is just incorrect as far as driver_data.sh goes.

But that is accurate, given the default file we search for on test_driver_data.c
is test-driver_data.bin. It also does not create a conflict to overwrite a file
used on driver_data.sh as driver_data.sh uses a custom path. I think the note
above on custom path is sufficient for the developer or user to be aware of
the fact the driver_data.sh does it own thing, and that the example is just a
manual test case.

What do you mean by that its incorrect ?

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-11 10:46       ` AKASHI Takahiro
  2017-05-11 17:11         ` Luis R. Rodriguez
@ 2017-05-11 18:12         ` Luis R. Rodriguez
  2017-05-11 18:26         ` Luis R. Rodriguez
  2 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-11 18:12 UTC (permalink / raw)
  To: AKASHI Takahiro
  Cc: Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal, arend.vanspriel,
	rjw, yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, dhowells, pjones,
	linux-kernel

On Thu, May 11, 2017 at 07:46:27PM +0900, AKASHI Takahiro wrote:
> On Fri, Apr 28, 2017 at 03:45:35AM +0200, Luis R. Rodriguez wrote:
> > > > diff --git a/lib/test_driver_data.c b/lib/test_driver_data.c
> > > > new file mode 100644
> > > > index 000000000000..11175a3b9f0a
> > > > --- /dev/null
> > > > +++ b/lib/test_driver_data.c
> > > > @@ -0,0 +1,1272 @@
> > > > +/*
> > > > + * Driver data test interface
> > > > + *
> > > > + * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
> > > > + *
> > > > + * This program is free software; you can redistribute it and/or modify it
> > > > + * under the terms of copyleft-next (version 0.3.1 or later) as published
> > > > + * at http://copyleft-next.org/.
> > > 
> > > Is this compatible with GPLv2 for kernel modules?
> > 
> > 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
> 
> Thank you for this heads-up.
> According to Linus' comment, he seems to expect an explicit GPL license
> term to be in the beginning of the file, and then if you want, an additional
> license to be added, quote "if you want to dual-license it, just put something
> like "or, at your option, copyleft-next" in the comment at the top."

I think the or clause thing deserves some clarification so brought this up on the
old thread and Cc'd you.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v7 1/5] firmware: add extensible driver data params
  2017-05-02  8:49   ` [PATCH v7 1/5] firmware: add extensible driver data params Luis R. Rodriguez
@ 2017-05-11 18:17     ` Li, Yi
  2017-05-11 18:28       ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: Li, Yi @ 2017-05-11 18:17 UTC (permalink / raw)
  To: Luis R. Rodriguez, gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, atull, moritz.fischer,
	pmladek, johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo,
	luto, torvalds, keescook, takahiro.akashi, dhowells, pjones,
	hdegoede, linux-kernel



On 5/2/2017 3:49 AM, 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 only
>    o struct driver_data_params - stiches both of the the above together,
> 				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.
>
> A side consequences is get to replace all the old internal "firmware
> behavior  options" flags with enums we properly document, remove the
> blinding #ifdefs, and compartamentlize the userhelper fallback code
> more appropriately unde CONFIG_FW_LOADER_USER_HELPER_FALLBACK.
>
> This commit should introduces no functional changes (TM).
>
> Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
> ---
>   drivers/base/firmware_class.c | 331 ++++++++++++++++++++++++++++++++----------
>   include/linux/driver_data.h   |  82 +++++++++++
>   2 files changed, 339 insertions(+), 74 deletions(-)
>   create mode 100644 include/linux/driver_data.h
> ...
> diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
> new file mode 100644
> index 000000000000..7ce3216a9a99
> --- /dev/null
> +++ b/include/linux/driver_data.h
> @@ -0,0 +1,82 @@
> +#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 <mcgrof@kernel.org>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of copyleft-next (version 0.3.1 or later) as published
> + * at http://copyleft-next.org/.
> + */
> +
> +/**
> + * struct driver_data_async_cbs - callbacks for handling driver data requests
> + * @found_cb: callback to be used when the driver data has been found. A
> + *	callback is required. If the requested driver data is found it will
> + *	passed on the callback, using the context set on @found_ctx.
> + * @found_ctx: preferred context to be used as the second argument to
> + * 	@found_cb.
> + *
> + * Used for specifying callbacks and contexts used for when asynchronous driver
> + * data requests have completed. If no driver data is found the error will be
> + * passed on the respective callback.
> + */
> +struct driver_data_async_cbs {
> +	void (*found_cb)(const struct firmware *driver_data,
> +			 void *context,
> +			 int error);

Any reason why the call back function format are not consistent between 
async and sync mode.  In sync mode, the context is before struct 
firmware. in Async mode, it's reversed.

struct driver_data_sync_cbs {
     int __must_check
         (*found_cb)(void *context,
                 const struct firmware *driver_data,
                 int error);

Yi
> +	void *found_ctx;
> +};
> +
> +/**
> + * union driver_data_cbs - callbacks for driver data request
> + * @async: callbacks for handling driver data when asynchronous requests
> + * 	are made.
> + *
> + * Used for placement of callbacks used for handling results from driver
> + * data requests.
> + */
> +union driver_data_cbs {
> +	struct driver_data_async_cbs async;
> +};
> +
> +/**
> + * enum driver_data_reqs - requirements of the driver data request
> + * @DRIVER_DATA_REQ_OPTIONAL: if set it is not a hard requirement by the
> + *	caller that the file requested be present. An error will not be recorded
> + *	if the file is not found.
> + */
> +enum driver_data_reqs {
> +	DRIVER_DATA_REQ_OPTIONAL			= 1 << 0,
> +};
> +
> +/**
> + * struct driver_data_req_params - driver data request parameters
> + * @hold_module: module to hold during the driver data request operation. By
> + * 	default if sync requests set this to NULL the firmware_class module
> + * 	will be refcounted during operation.
> + * @gfp: flags to use for allocations when constructing the driver data request,
> + *	prior to scheduling. Unused on driver_data_request_sync().
> + * @reqs: set of &enum driver_data_reqs flags used to configure the driver
> + * 	data request. All of the specified requirements must be met.
> + * @cbs: set of callbacks to use for the driver data request.
> + *
> + * 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 {
> +	struct module *hold_module;
> +	gfp_t gfp;
> +	u64 reqs;
> +	const union driver_data_cbs cbs;
> +};
> +
> +#endif /* _LINUX_DRIVER_DATA_H */

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-11 10:46       ` AKASHI Takahiro
  2017-05-11 17:11         ` Luis R. Rodriguez
  2017-05-11 18:12         ` Luis R. Rodriguez
@ 2017-05-11 18:26         ` Luis R. Rodriguez
  2017-05-11 18:32           ` Luis R. Rodriguez
  2017-05-12  0:20           ` AKASHI Takahiro
  2 siblings, 2 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-11 18:26 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, yi1.li, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	dhowells, pjones, linux-kernel

On Thu, May 11, 2017 at 07:46:27PM +0900, AKASHI Takahiro wrote:
> On Fri, Apr 28, 2017 at 03:45:35AM +0200, Luis R. Rodriguez wrote:
> > > > 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
> 
> In my opinion, "1," or "2" given the nature of firmware caching, is good
> enough, but it's up to you.

No, firmware caching deserves its own test unit on its own, but that will be
enabled through a separate test once we move DRIVER_DATA_PRIV_REQ_NO_CACHE 
to DRIVER_DATA_REQ_NO_CACHE (a private feature to a publicly available
enabled feature). This will be enabled for the upcoming work which enables
FGPA firmware upload which will first enable request_firmware_into_buf()
through the driver data API which uses this.

> BTW, firmware caching is a bit annoying in my signature tests
> because caching will bypass the verification checks when we iterates
> tests with different conditions.

It would seems to make sense to me to only need to verify files when read
for the first time, once its cache I don't see why we would re-verify them ?

> > 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.
> 
> Right, but looking into the script, even if an user supplies a firmware
> blob, the script overwrites it unnecessarily.

FWPATH=$(mktemp -d)
...
FW="$FWPATH/$DEFAULT_DRIVER_DATA"
...
echo "ABCD0123" >"$FW"

So this is really just touching the custom path stuff, unless of course the
caller overrides the above variables, in which case its trusted they know what
they are doing, ie custom test cases / setup / system. That was the goal.

> This may also be inconvenient if I add signature verification tests.
> (Not sure though.)

You can feel free to modify this as you see fit to account for firmware
signing. If you run into issues please feel free to make adjustments.

> > > > +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.
> 
> Okay, up to you.

In the end I hope we get rid of all this anyway and replace it with
a centralized way for selftests but IMHO this is a secondary step.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v7 1/5] firmware: add extensible driver data params
  2017-05-11 18:17     ` Li, Yi
@ 2017-05-11 18:28       ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-11 18:28 UTC (permalink / raw)
  To: Li, Yi
  Cc: Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal, arend.vanspriel,
	rjw, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, torvalds,
	keescook, takahiro.akashi, dhowells, pjones, hdegoede,
	linux-kernel

On Thu, May 11, 2017 at 01:17:39PM -0500, Li, Yi wrote:
> On 5/2/2017 3:49 AM, 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 only
> >    o struct driver_data_params - stiches both of the the above together,
> > 				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.
> > 
> > A side consequences is get to replace all the old internal "firmware
> > behavior  options" flags with enums we properly document, remove the
> > blinding #ifdefs, and compartamentlize the userhelper fallback code
> > more appropriately unde CONFIG_FW_LOADER_USER_HELPER_FALLBACK.
> > 
> > This commit should introduces no functional changes (TM).
> > 
> > Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
> > ---
> >   drivers/base/firmware_class.c | 331 ++++++++++++++++++++++++++++++++----------
> >   include/linux/driver_data.h   |  82 +++++++++++
> >   2 files changed, 339 insertions(+), 74 deletions(-)
> >   create mode 100644 include/linux/driver_data.h
> > ...
> > diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
> > new file mode 100644
> > index 000000000000..7ce3216a9a99
> > --- /dev/null
> > +++ b/include/linux/driver_data.h
> > @@ -0,0 +1,82 @@
> > +#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 <mcgrof@kernel.org>
> > + *
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms of copyleft-next (version 0.3.1 or later) as published
> > + * at http://copyleft-next.org/.
> > + */
> > +
> > +/**
> > + * struct driver_data_async_cbs - callbacks for handling driver data requests
> > + * @found_cb: callback to be used when the driver data has been found. A
> > + *	callback is required. If the requested driver data is found it will
> > + *	passed on the callback, using the context set on @found_ctx.
> > + * @found_ctx: preferred context to be used as the second argument to
> > + * 	@found_cb.
> > + *
> > + * Used for specifying callbacks and contexts used for when asynchronous driver
> > + * data requests have completed. If no driver data is found the error will be
> > + * passed on the respective callback.
> > + */
> > +struct driver_data_async_cbs {
> > +	void (*found_cb)(const struct firmware *driver_data,
> > +			 void *context,
> > +			 int error);
> 
> Any reason why the call back function format are not consistent between
> async and sync mode.  In sync mode, the context is before struct firmware.
> in Async mode, it's reversed.
> 
> struct driver_data_sync_cbs {
>     int __must_check
>         (*found_cb)(void *context,
>                 const struct firmware *driver_data,
>                 int error);
> 

Indeed, its just to ensure if the user makes a mistake and uses a sync
callback for an async request we get a compile error, all without using
typdefs or anything nasty.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-11 18:26         ` Luis R. Rodriguez
@ 2017-05-11 18:32           ` Luis R. Rodriguez
  2017-05-12  0:28             ` AKASHI Takahiro
  2017-05-12  0:20           ` AKASHI Takahiro
  1 sibling, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-11 18:32 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, Greg Kroah-Hartman,
	Daniel Wagner, David Woodhouse, rafal, Arend Van Spriel,
	Rafael J. Wysocki, Li, Yi, atull, moritz.fischer, Petr Mladek,
	Johannes Berg, Emmanuel Grumbach, Luciano Coelho, Kalle Valo,
	Andy Lutomirski, David Howells, Peter Jones, linux-kernel

On Thu, May 11, 2017 at 11:26 AM, Luis R. Rodriguez <mcgrof@suse.com> wrote:
>
> It would seems to make sense to me to only need to verify files when read
> for the first time, once its cache I don't see why we would re-verify them ?

To be clear, the fw cache feature reads the files from the fs prior to
suspend, and then uses the in-memory cache on resume. So it would make
sense to me only to rely on fw verification on resume then when the fw
cache is used ?

 Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-11 18:26         ` Luis R. Rodriguez
  2017-05-11 18:32           ` Luis R. Rodriguez
@ 2017-05-12  0:20           ` AKASHI Takahiro
  2017-05-12 15:52             ` Luis R. Rodriguez
  1 sibling, 1 reply; 98+ messages in thread
From: AKASHI Takahiro @ 2017-05-12  0:20 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal, arend.vanspriel,
	rjw, yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, dhowells, pjones,
	linux-kernel

On Thu, May 11, 2017 at 08:26:29PM +0200, Luis R. Rodriguez wrote:
> On Thu, May 11, 2017 at 07:46:27PM +0900, AKASHI Takahiro wrote:
> > On Fri, Apr 28, 2017 at 03:45:35AM +0200, Luis R. Rodriguez wrote:
> > > > > 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
> > 
> > In my opinion, "1," or "2" given the nature of firmware caching, is good
> > enough, but it's up to you.
> 
> No, firmware caching deserves its own test unit on its own, but that will be
> enabled through a separate test once we move DRIVER_DATA_PRIV_REQ_NO_CACHE 
> to DRIVER_DATA_REQ_NO_CACHE (a private feature to a publicly available
> enabled feature). This will be enabled for the upcoming work which enables
> FGPA firmware upload which will first enable request_firmware_into_buf()
> through the driver data API which uses this.

I thought of implementing NO_CACHE option by myself, but came up with
no practical use cases other than my test case :)

> > BTW, firmware caching is a bit annoying in my signature tests
> > because caching will bypass the verification checks when we iterates
> > tests with different conditions.
> 
> It would seems to make sense to me to only need to verify files when read
> for the first time, once its cache I don't see why we would re-verify them ?

Let me explain my test scenario:
case (a): firmware w/o signature for signature-not-required driver
case (b): firmware w/o signature for signature-required driver
   ... and so on

Case (a) and (b) are exercised with the same firmware blob but with
different 'test_config's of test_driver_data driver.

First do case (a) and it succeeds, caching the blob, then do case (b).
It should fail, but actually succeeds due to firmware caching.

Yes, we can change the order, (b) then (a), to make tests run correctly.

But again, when we iterate this set of test cases under "-c" repeatedly,
this will happen again as you can imagine.

Thanks to firmware caching, each of iterations of tests is not executed
under the exactly same condition. That is the problem.

Let's think about more realistic case:
we want to run the system without stopping/rebooting it, but also
want to update some driver. OK, remove the driver module and insert
a new one which, in this case, enables signature verification option.
But, unlike we expect, loading a firmware will succeed anyway even
if we don't provide its signature as it's already in cache.

I hope we can fix it by modifying the logic of caching.

> > > 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.
> > 
> > Right, but looking into the script, even if an user supplies a firmware
> > blob, the script overwrites it unnecessarily.
> 
> FWPATH=$(mktemp -d)
> ...
> FW="$FWPATH/$DEFAULT_DRIVER_DATA"
> ...
> echo "ABCD0123" >"$FW"
> 
> So this is really just touching the custom path stuff, unless of course the
> caller overrides the above variables, in which case its trusted they know what
> they are doing, ie custom test cases / setup / system. That was the goal.

Even if an user gives a full-path name to $FW, do you want to overwrite it?

Thanks,
-Takahiro AKASHI

> > This may also be inconvenient if I add signature verification tests.
> > (Not sure though.)
> 
> You can feel free to modify this as you see fit to account for firmware
> signing. If you run into issues please feel free to make adjustments.
> 
> > > > > +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.
> > 
> > Okay, up to you.
> 
> In the end I hope we get rid of all this anyway and replace it with
> a centralized way for selftests but IMHO this is a secondary step.
> 
>   Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-11 18:32           ` Luis R. Rodriguez
@ 2017-05-12  0:28             ` AKASHI Takahiro
  2017-05-12 15:59               ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: AKASHI Takahiro @ 2017-05-12  0:28 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Greg Kroah-Hartman, Daniel Wagner, David Woodhouse, rafal,
	Arend Van Spriel, Rafael J. Wysocki, Li, Yi, atull,
	moritz.fischer, Petr Mladek, Johannes Berg, Emmanuel Grumbach,
	Luciano Coelho, Kalle Valo, Andy Lutomirski, David Howells,
	Peter Jones, linux-kernel

On Thu, May 11, 2017 at 11:32:30AM -0700, Luis R. Rodriguez wrote:
> On Thu, May 11, 2017 at 11:26 AM, Luis R. Rodriguez <mcgrof@suse.com> wrote:
> >
> > It would seems to make sense to me to only need to verify files when read
> > for the first time, once its cache I don't see why we would re-verify them ?
> 
> To be clear, the fw cache feature reads the files from the fs prior to
> suspend, and then uses the in-memory cache on resume. So it would make
> sense to me only to rely on fw verification on resume then when the fw
> cache is used ?

Good point. I was thinking of need for verification on resume.
As cache is not protected and visible to the kernel, some malware
might want to rewrite it :)

Thanks,
-Takahiro AKASHI

>  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-12  0:20           ` AKASHI Takahiro
@ 2017-05-12 15:52             ` Luis R. Rodriguez
  2017-05-13 18:46               ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-12 15:52 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, yi1.li, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	dhowells, pjones, linux-kernel

On Fri, May 12, 2017 at 09:20:24AM +0900, AKASHI Takahiro wrote:
> On Thu, May 11, 2017 at 08:26:29PM +0200, Luis R. Rodriguez wrote:
> > On Thu, May 11, 2017 at 07:46:27PM +0900, AKASHI Takahiro wrote:
> > > On Fri, Apr 28, 2017 at 03:45:35AM +0200, Luis R. Rodriguez wrote:
> > > > > > diff --git a/tools/testing/selftests/firmware/driver_data.sh b/tools/testing/selftests/firmware/driver_data.sh
> > > > ...
> > > > > > +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
> > > 
> > > In my opinion, "1," or "2" given the nature of firmware caching, is good
> > > enough, but it's up to you.
> > 
> > No, firmware caching deserves its own test unit on its own, but that will be
> > enabled through a separate test once we move DRIVER_DATA_PRIV_REQ_NO_CACHE 
> > to DRIVER_DATA_REQ_NO_CACHE (a private feature to a publicly available
> > enabled feature). This will be enabled for the upcoming work which enables
> > FGPA firmware upload which will first enable request_firmware_into_buf()
> > through the driver data API which uses this.
> 
> I thought of implementing NO_CACHE option by myself, but came up with
> no practical use cases other than my test case :)

FWIW, my understanding is that such work is underway. I think it may be
ready first than your signature work so it may make sense to coordinate
with that developer on their work as you might be able to make use of their
tests, etc.

> > > BTW, firmware caching is a bit annoying in my signature tests
> > > because caching will bypass the verification checks when we iterates
> > > tests with different conditions.
> > 
> > It would seems to make sense to me to only need to verify files when read
> > for the first time, once its cache I don't see why we would re-verify them ?
> 
> Let me explain my test scenario:
> case (a): firmware w/o signature for signature-not-required driver
> case (b): firmware w/o signature for signature-required driver
>    ... and so on
> 
> Case (a) and (b) are exercised with the same firmware blob but with
> different 'test_config's of test_driver_data driver.
> 
> First do case (a) and it succeeds, caching the blob, then do case (b).
> It should fail, but actually succeeds due to firmware caching.
> 
> Yes, we can change the order, (b) then (a), to make tests run correctly.

I see. OK we should consider if firmware requirements can ever vary for
a file. If not then a cached firmware file can have a sig_check which
any request can fill in. Upon request firmware, if the cache is present
*but* the sig check requirements differ (in this simple case where
signature requirements cannot vary) then the cache present will not
suffice so we should allow a full new file check through to allow
to fill the sig_check. If signatures requirements can vary then we
could have a linked list of signature requirements and at cache check
time we'd have to check if they match the requirements.

Just one way to try to address this I think. But hey this is all firmware
signing related. I suppose a more relevant question to this thread is if
there might be current criteria or criteria being introduced in this patch
series which is similar to the signature check which should also be
extended in these sorts of caching checks.

> But again, when we iterate this set of test cases under "-c" repeatedly,
> this will happen again as you can imagine.

Makes sense.

> Thanks to firmware caching, each of iterations of tests is not executed
> under the exactly same condition. That is the problem.

Sure, specially since asynchornous lookups won't be serialized we can't
expect any proper order for which request goes in first. If the extra
caching requirements were however taken into consideration I would expect
this to not matter though.

> Let's think about more realistic case:
> we want to run the system without stopping/rebooting it, but also
> want to update some driver. OK, remove the driver module and insert
> a new one which, in this case, enables signature verification option.
> But, unlike we expect, loading a firmware will succeed anyway even
> if we don't provide its signature as it's already in cache.

Its a good point. If caching had the requirements checks (signature would be
one) then this would fail at the cache lookup and continue to chug on.

For files which are kept the same name, say db.txt the example you provide
also runs into the situation where one file might be signed but through time
can be expected to be updated, as such the signature will change as will the
contents of the file. In such cases the firmware cache would get in the way
of file updates, but let us be clear that the firmware cache is not a cache
of all firmware, its only used prior to suspend and cleared after resume.
As such it seems valid for that time period. Likewise if the cache is known
to not be a good idea for a file they can in the future use the no-cache
requirement criteria, but then they'd have to implement something to deal
with the suspend/resume race.

> I hope we can fix it by modifying the logic of caching.

Sure.

> > > > 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.
> > > 
> > > Right, but looking into the script, even if an user supplies a firmware
> > > blob, the script overwrites it unnecessarily.
> > 
> > FWPATH=$(mktemp -d)
> > ...
> > FW="$FWPATH/$DEFAULT_DRIVER_DATA"
> > ...
> > echo "ABCD0123" >"$FW"
> > 
> > So this is really just touching the custom path stuff, unless of course the
> > caller overrides the above variables, in which case its trusted they know what
> > they are doing, ie custom test cases / setup / system. That was the goal.
> 
> Even if an user gives a full-path name to $FW, do you want to overwrite it?

User cannot give a full path to a file, its one of the promises of the
firmware_class.c today, it uses its own prefix set of directories, it
could be security issue otherwise. In the future this may change and the
scope of what prefixes can be used might change but we're far from there.

If you meant if user gives a full-path to $FW by testing -- well this is
all part of the driver-data.sh and driver-data.sh uses its own custom
temporary directory for stashing tests, so what it does is specific to
it and compartamentalized there. If a users does not use driver-data.sh
and mucks with the knobs on their own, its expected they know what they
are doing, and by default the paths for lookups will be default so its
up to them to make sure to pick a proper file and not overwrite things.

I think its safe for test_driver.c to use the default path and assume
the test user will not go try to overwrite /lib/firwmare/iwlwifi-7260-17.ucode
for example.

If this is your concern we could devise the temporary directory logic in
the script into the test driver but I really would prefer to avoid that.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-12  0:28             ` AKASHI Takahiro
@ 2017-05-12 15:59               ` Luis R. Rodriguez
  2017-05-17  9:08                 ` AKASHI Takahiro
  0 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-12 15:59 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, Greg Kroah-Hartman,
	Daniel Wagner, David Woodhouse, rafal, Arend Van Spriel,
	Rafael J. Wysocki, Li, Yi, atull, moritz.fischer, Petr Mladek,
	Johannes Berg, Emmanuel Grumbach, Luciano Coelho, Kalle Valo,
	Andy Lutomirski, David Howells, Peter Jones, linux-kernel

On Fri, May 12, 2017 at 09:28:47AM +0900, AKASHI Takahiro wrote:
> On Thu, May 11, 2017 at 11:32:30AM -0700, Luis R. Rodriguez wrote:
> > On Thu, May 11, 2017 at 11:26 AM, Luis R. Rodriguez <mcgrof@suse.com> wrote:
> > >
> > > It would seems to make sense to me to only need to verify files when read
> > > for the first time, once its cache I don't see why we would re-verify them ?
> > 
> > To be clear, the fw cache feature reads the files from the fs prior to
> > suspend, and then uses the in-memory cache on resume. So it would make
> > sense to me only to rely on fw verification on resume then when the fw
> > cache is used ?
> 
> Good point. I was thinking of need for verification on resume.

>From what we have discussed so far it would seem to me only necessary
for a sig_check_ok (if we accept a file can have only one signature
requirement) for a cache entry, and if its not set but a lookup needs
a sig check it can do a full fs lookup. If such a lookup succeeded
then it can fill the sig_check_ok in, provided the file contents
match of course, given the file could have changed under the hood
between the last file cache lookup (if the file did change that puts
us at odd with the first lookup, but since its an update and no sig
check is required, I guess it is fine to use its contents).

> As cache is not protected 

Cache should be protected, it should be const and if its not we should fix that.

> and visible to the kernel,

You mean it is visible to the kernel ?

>  some malware might want to rewrite it :)

Right, we want to be pedantic about that sort of stuff and signature
verification can help here but those benefits should carry their own
weight. We should do what we can without file signature verification to
protect the cache.

The cache is short lived though, it exists only during suspend/resume.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-12 15:52             ` Luis R. Rodriguez
@ 2017-05-13 18:46               ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-13 18:46 UTC (permalink / raw)
  To: Luis R. Rodriguez, AKASHI Takahiro, wagi
  Cc: gregkh, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, dhowells, pjones, linux-kernel

On Fri, May 12, 2017 at 05:52:18PM +0200, Luis R. Rodriguez wrote:
> On Fri, May 12, 2017 at 09:20:24AM +0900, AKASHI Takahiro wrote:
> > On Thu, May 11, 2017 at 08:26:29PM +0200, Luis R. Rodriguez wrote:
> > > On Thu, May 11, 2017 at 07:46:27PM +0900, AKASHI Takahiro wrote:
> > > > On Fri, Apr 28, 2017 at 03:45:35AM +0200, Luis R. Rodriguez wrote:
> > > > > > > diff --git a/tools/testing/selftests/firmware/driver_data.sh b/tools/testing/selftests/firmware/driver_data.sh
> > > > > ...
> > > > > > > +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
> > > > 
> > > > In my opinion, "1," or "2" given the nature of firmware caching, is good
> > > > enough, but it's up to you.
> > > 
> > > No, firmware caching deserves its own test unit on its own, but that will be
> > > enabled through a separate test once we move DRIVER_DATA_PRIV_REQ_NO_CACHE 
> > > to DRIVER_DATA_REQ_NO_CACHE (a private feature to a publicly available
> > > enabled feature). This will be enabled for the upcoming work which enables
> > > FGPA firmware upload which will first enable request_firmware_into_buf()
> > > through the driver data API which uses this.
> > 
> > I thought of implementing NO_CACHE option by myself, but came up with
> > no practical use cases other than my test case :)
> 
> FWIW, my understanding is that such work is underway. I think it may be
> ready first than your signature work so it may make sense to coordinate
> with that developer on their work as you might be able to make use of their
> tests, etc.
> 
> > > > BTW, firmware caching is a bit annoying in my signature tests
> > > > because caching will bypass the verification checks when we iterates
> > > > tests with different conditions.
> > > 
> > > It would seems to make sense to me to only need to verify files when read
> > > for the first time, once its cache I don't see why we would re-verify them ?
> > 
> > Let me explain my test scenario:
> > case (a): firmware w/o signature for signature-not-required driver
> > case (b): firmware w/o signature for signature-required driver
> >    ... and so on
> > 
> > Case (a) and (b) are exercised with the same firmware blob but with
> > different 'test_config's of test_driver_data driver.
> > 
> > First do case (a) and it succeeds, caching the blob, then do case (b).
> > It should fail, but actually succeeds due to firmware caching.
> > 
> > Yes, we can change the order, (b) then (a), to make tests run correctly.
> 
> I see. OK we should consider if firmware requirements can ever vary for
> a file. If not then a cached firmware file can have a sig_check which
> any request can fill in. Upon request firmware, if the cache is present
> *but* the sig check requirements differ (in this simple case where
> signature requirements cannot vary) then the cache present will not
> suffice so we should allow a full new file check through to allow
> to fill the sig_check. If signatures requirements can vary then we
> could have a linked list of signature requirements and at cache check
> time we'd have to check if they match the requirements.
> 
> Just one way to try to address this I think. But hey this is all firmware
> signing related. I suppose a more relevant question to this thread is if
> there might be current criteria or criteria being introduced in this patch
> series which is similar to the signature check which should also be
> extended in these sorts of caching checks.
> 
> > But again, when we iterate this set of test cases under "-c" repeatedly,
> > this will happen again as you can imagine.
> 
> Makes sense.
> 
> > Thanks to firmware caching, each of iterations of tests is not executed
> > under the exactly same condition. That is the problem.
> 
> Sure, specially since asynchornous lookups won't be serialized we can't
> expect any proper order for which request goes in first. If the extra
> caching requirements were however taken into consideration I would expect
> this to not matter though.

Note that a bug was just opened on multiple fw requests and only one completes
fine, it sounds like a real bug, and if so sounds related to this topical case
you are testing against as well.

https://bugzilla.kernel.org/show_bug.cgi?id=195477

It has a proposed patch I have not yet had a chance to properly review / test:

https://bugzilla.kernel.org/attachment.cgi?id=256493&action=diff&collapsed=&headers=1&format=raw

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* [PATCH v8] test: add new driver_data load tester
  2017-05-02  8:49   ` [PATCH v7 3/5] test: add new driver_data load tester Luis R. Rodriguez
  2017-05-11 10:10     ` AKASHI Takahiro
@ 2017-05-15 18:23     ` Luis R. Rodriguez
  1 sibling, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-15 18:23 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, linux-kernel, Luis R. Rodriguez

This adds a load tester driver test_driver_data a for the new extensible
driver_data loader API, part of firmware_class. This test driver enables
you to build your tests in userspace by exposing knobs of the exported
API to userspace and enables a trigger action to mimic a one time use
of the kernel API. This gives us the flexibility to build test case from
userspace with less kernel changes.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---

Greg,

This v8 fixes a test driver unload issue, and a configuration typo spotted by
AKASHI.  Its the only issue discovered during review on the v7 series of the
driver data API, and the other series it depends on, other than the
clarification on license, which I've asked Linus about on the old copyleft-next
thread, just to be sure.

FWIW I have rebased my 2 pending firmware series on top of linux-next tag,
next-20170515 (based on v4.12-rc1) and I did not find any conflicts when doing
so, so I don't expect the merge window to have created any issues on my two
pending firmware series. In any case my 2 pending firmware series are available
on my linux-next tree branch 20170515-driver-data [0].

[0] https://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux-next.git/log/?h=20170515-driver-data

 MAINTAINERS                                     |    1 +
 lib/Kconfig.debug                               |   12 +
 lib/Makefile                                    |    1 +
 lib/test_driver_data.c                          | 1270 +++++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile       |    2 +-
 tools/testing/selftests/firmware/config         |    1 +
 tools/testing/selftests/firmware/driver_data.sh |  996 ++++++++++++++++++
 7 files changed, 2282 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_driver_data.c
 create mode 100755 tools/testing/selftests/firmware/driver_data.sh

diff --git a/MAINTAINERS b/MAINTAINERS
index 214585cddab0..1231267ad830 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5226,6 +5226,7 @@ L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	Documentation/firmware_class/
 F:	drivers/base/firmware*.c
+F:	lib/test_driver_data.c
 F:	include/linux/firmware.h
 F:	include/linux/driver_data.h
 
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 122825555822..6704541f4bd0 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 0166fbc0fa81..4be5e119a98c 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..a3dc67b6cf34
--- /dev/null
+++ b/lib/test_driver_data.c
@@ -0,0 +1,1270 @@
+/*
+ * Driver data test interface
+ *
+ * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of copyleft-next (version 0.3.1 or later) as published
+ * at http://copyleft-next.org/.
+ *
+ * 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,
+			 int unused_error)
+{
+	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, int unused_error)
+{
+	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, int unused_error)
+{
+	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,
+			      int unused_error)
+{
+	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_REQS(config_sync_req_cb, test_dev,
+					      (config->optional ?
+					       DRIVER_DATA_REQ_OPTIONAL : 0) |
+					      (config->keep ?
+					       DRIVER_DATA_REQ_KEEP : 0)),
+	};
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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, int unused_error)
+{
+	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,
+				   int unused_error)
+{
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	const struct driver_data_req_params req_params_api = {
+		DRIVER_DATA_API_CB(config_async_req_api_cb, test_dev),
+		DRIVER_DATA_API(config->api_min, config->api_max, config->api_name_postfix),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0) |
+			(config->use_api_versioning ? DRIVER_DATA_REQ_USE_API_VERSIONING : 0),
+	};
+	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;
+	driver_data_config_free(test_dev);
+	vfree(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);
+	free_test_dev_driver_data(test_dev);
+}
+
+struct driver_data_test_device *alloc_test_dev_driver_data(int idx)
+{
+	int ret;
+	struct driver_data_test_device *test_dev;
+	struct miscdevice *misc_dev;
+
+	test_dev = vzalloc(sizeof(struct driver_data_test_device));
+	if (!test_dev)
+		goto err_out;
+
+	mutex_init(&test_dev->driver_data_mutex);
+	mutex_init(&test_dev->config_mutex);
+	mutex_init(&test_dev->trigger_mutex);
+	init_completion(&test_dev->request_complete);
+
+	ret = driver_data_config_init(test_dev);
+	if (ret < 0)
+		goto err_out_free;
+
+	test_dev->dev_idx = idx;
+	misc_dev = &test_dev->misc_dev;
+
+	misc_dev->minor = MISC_DYNAMIC_MINOR;
+	misc_dev->name = kasprintf(GFP_KERNEL, "test_driver_data%d", idx);
+	if (!misc_dev->name)
+		goto err_out_free_config;
+
+	misc_dev->fops = &test_fw_fops;
+	misc_dev->groups = test_dev_groups;
+
+	return test_dev;
+
+err_out_free_config:
+	__driver_data_config_free(&test_dev->config);
+err_out_free:
+	kfree(test_dev);
+err_out:
+	return NULL;
+}
+
+static int register_test_dev_driver_data(void)
+{
+	struct driver_data_test_device *test_dev = NULL;
+	int ret = -ENODEV;
+
+	mutex_lock(&reg_dev_mutex);
+
+	/* int should suffice for number of devices, test for wrap */
+	if (unlikely(num_test_devs + 1) < 0) {
+		pr_err("reached limit of number of test devices\n");
+		goto out;
+	}
+
+	test_dev = alloc_test_dev_driver_data(num_test_devs);
+	if (!test_dev) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = misc_register(&test_dev->misc_dev);
+	if (ret) {
+		pr_err("could not register misc device: %d\n", ret);
+		free_test_dev_driver_data(test_dev);
+		goto out;
+	}
+
+	test_dev->dev = test_dev->misc_dev.this_device;
+	list_add_tail(&test_dev->list, &reg_test_devs);
+	dev_info(test_dev->dev, "interface ready\n");
+
+	num_test_devs++;
+
+out:
+	mutex_unlock(&reg_dev_mutex);
+
+	return ret;
+}
+
+static int __init test_driver_data_init(void)
+{
+	int ret;
+
+	ret = register_test_dev_driver_data();
+	if (ret)
+		pr_err("Cannot add first test driver_data device\n");
+
+	return ret;
+}
+late_initcall(test_driver_data_init);
+
+static void __exit test_driver_data_exit(void)
+{
+	struct driver_data_test_device *test_dev, *tmp;
+
+	mutex_lock(&reg_dev_mutex);
+	list_for_each_entry_safe(test_dev, tmp, &reg_test_devs, list) {
+		list_del(&test_dev->list);
+		unregister_test_dev_driver_data(test_dev);
+	}
+	mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_driver_data_exit);
+
+MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/tools/testing/selftests/firmware/Makefile b/tools/testing/selftests/firmware/Makefile
index 1894d625af2d..c9bf6c44435f 100644
--- a/tools/testing/selftests/firmware/Makefile
+++ b/tools/testing/selftests/firmware/Makefile
@@ -3,7 +3,7 @@
 # No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
 all:
 
-TEST_PROGS := fw_filesystem.sh fw_fallback.sh
+TEST_PROGS := fw_filesystem.sh fw_fallback.sh driver_data.sh
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config
index c8137f70e291..0f1a299f9270 100644
--- a/tools/testing/selftests/firmware/config
+++ b/tools/testing/selftests/firmware/config
@@ -1 +1,2 @@
 CONFIG_TEST_FIRMWARE=y
+CONFIG_TEST_DRIVER_DATA=y
diff --git a/tools/testing/selftests/firmware/driver_data.sh b/tools/testing/selftests/firmware/driver_data.sh
new file mode 100755
index 000000000000..c830d04390e3
--- /dev/null
+++ b/tools/testing/selftests/firmware/driver_data.sh
@@ -0,0 +1,996 @@
+#!/bin/bash
+# Copyright (C) 2016 Luis R. Rodriguez <mcgrof@kernel.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of copyleft-next (version 0.3.1 or later) as published
+# at http://copyleft-next.org/.
+
+# This performs a series tests against firmware_class to excercise the
+# firmware_class driver with focus only on the extensible driver data API.
+#
+# To make this test self contained, and not pollute your distribution
+# firmware install paths, we reset the custom load directory to a
+# temporary location.
+
+set -e
+
+TEST_NAME="driver_data"
+TEST_DRIVER="test_${TEST_NAME}"
+TEST_DIR=$(dirname $0)
+
+# This represents
+#
+# TEST_ID:TEST_COUNT:ENABLED
+#
+# TEST_ID: is the test id number
+# TEST_COUNT: number of times we should run the test
+# ENABLED: 1 if enabled, 0 otherwise
+#
+# Once these are enabled please leave them as-is. Write your own test,
+# we have tons of space.
+ALL_TESTS="0001:3:1"
+ALL_TESTS="$ALL_TESTS 0002:3:1"
+ALL_TESTS="$ALL_TESTS 0003:3:1"
+ALL_TESTS="$ALL_TESTS 0004:10:1"
+ALL_TESTS="$ALL_TESTS 0005:10:1"
+ALL_TESTS="$ALL_TESTS 0006:10:1"
+ALL_TESTS="$ALL_TESTS 0007:10:1"
+ALL_TESTS="$ALL_TESTS 0008:10:1"
+ALL_TESTS="$ALL_TESTS 0009:10:1"
+ALL_TESTS="$ALL_TESTS 0010:10:1"
+ALL_TESTS="$ALL_TESTS 0011:10:1"
+ALL_TESTS="$ALL_TESTS 0012:1:1"
+ALL_TESTS="$ALL_TESTS 0013:1:1"
+
+# 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]} -ENOENT
+}
+
+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]} -EINVAL
+}
+
+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 "    -s      Run test ID once"
+	echo "    -c      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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-12 15:59               ` Luis R. Rodriguez
@ 2017-05-17  9:08                 ` AKASHI Takahiro
  2017-05-17 15:38                   ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: AKASHI Takahiro @ 2017-05-17  9:08 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Greg Kroah-Hartman, Daniel Wagner, David Woodhouse, rafal,
	Arend Van Spriel, Rafael J. Wysocki, Li, Yi, atull,
	moritz.fischer, Petr Mladek, Johannes Berg, Emmanuel Grumbach,
	Luciano Coelho, Kalle Valo, Andy Lutomirski, David Howells,
	Peter Jones, linux-kernel

On Fri, May 12, 2017 at 05:59:23PM +0200, Luis R. Rodriguez wrote:
> On Fri, May 12, 2017 at 09:28:47AM +0900, AKASHI Takahiro wrote:
> > On Thu, May 11, 2017 at 11:32:30AM -0700, Luis R. Rodriguez wrote:
> > > On Thu, May 11, 2017 at 11:26 AM, Luis R. Rodriguez <mcgrof@suse.com> wrote:
> > > >
> > > > It would seems to make sense to me to only need to verify files when read
> > > > for the first time, once its cache I don't see why we would re-verify them ?
> > > 
> > > To be clear, the fw cache feature reads the files from the fs prior to
> > > suspend, and then uses the in-memory cache on resume. So it would make
> > > sense to me only to rely on fw verification on resume then when the fw
> > > cache is used ?
> > 
> > Good point. I was thinking of need for verification on resume.
> 
> From what we have discussed so far it would seem to me only necessary
> for a sig_check_ok (if we accept a file can have only one signature
> requirement) for a cache entry, and if its not set but a lookup needs
> a sig check it can do a full fs lookup. If such a lookup succeeded
> then it can fill the sig_check_ok in, provided the file contents
> match of course, given the file could have changed under the hood
> between the last file cache lookup (if the file did change that puts
> us at odd with the first lookup, but since its an update and no sig
> check is required, I guess it is fine to use its contents).
> 
> > As cache is not protected 
> 
> Cache should be protected, it should be const and if its not we should fix that.

Yeah, but

> > and visible to the kernel,
> 
> You mean it is visible to the kernel ?

your current implementation doesn't provide any write protection.

> >  some malware might want to rewrite it :)
> 
> Right, we want to be pedantic about that sort of stuff and signature
> verification can help here but those benefits should carry their own
> weight. We should do what we can without file signature verification to
> protect the cache.
> 
> The cache is short lived though, it exists only during suspend/resume.

I found out why my test cases fail:
trigger_config_sync() in test_driver_data.c always enables REQ_KEEP flag
and so cached data (firmware_buf->data) has not been cleaned up.
I haven't fixed it in my test environment although I pointed it out before.

But the issue on write protection is still there wehn REQ_KEEP is used.

Thanks,
-Takahiro AKASHI

>   Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-17  9:08                 ` AKASHI Takahiro
@ 2017-05-17 15:38                   ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-17 15:38 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, Greg Kroah-Hartman,
	Daniel Wagner, David Woodhouse, rafal, Arend Van Spriel,
	Rafael J. Wysocki, Li, Yi, atull, moritz.fischer, Petr Mladek,
	Johannes Berg, Emmanuel Grumbach, Luciano Coelho, Kalle Valo,
	Andy Lutomirski, David Howells, Peter Jones, linux-kernel

On Wed, May 17, 2017 at 06:08:36PM +0900, AKASHI Takahiro wrote:
> On Fri, May 12, 2017 at 05:59:23PM +0200, Luis R. Rodriguez wrote:
> > On Fri, May 12, 2017 at 09:28:47AM +0900, AKASHI Takahiro wrote:
> > > On Thu, May 11, 2017 at 11:32:30AM -0700, Luis R. Rodriguez wrote:
> > > > On Thu, May 11, 2017 at 11:26 AM, Luis R. Rodriguez <mcgrof@suse.com> wrote:
> > > > >
> > > > > It would seems to make sense to me to only need to verify files when read
> > > > > for the first time, once its cache I don't see why we would re-verify them ?
> > > > 
> > > > To be clear, the fw cache feature reads the files from the fs prior to
> > > > suspend, and then uses the in-memory cache on resume. So it would make
> > > > sense to me only to rely on fw verification on resume then when the fw
> > > > cache is used ?
> > > 
> > > Good point. I was thinking of need for verification on resume.
> > 
> > From what we have discussed so far it would seem to me only necessary
> > for a sig_check_ok (if we accept a file can have only one signature
> > requirement) for a cache entry, and if its not set but a lookup needs
> > a sig check it can do a full fs lookup. If such a lookup succeeded
> > then it can fill the sig_check_ok in, provided the file contents
> > match of course, given the file could have changed under the hood
> > between the last file cache lookup (if the file did change that puts
> > us at odd with the first lookup, but since its an update and no sig
> > check is required, I guess it is fine to use its contents).
> > 
> > > As cache is not protected 
> > 
> > Cache should be protected, it should be const and if its not we should fix that.
> 
> Yeah, but
> 
> > > and visible to the kernel,
> > 
> > You mean it is visible to the kernel ?
> 
> your current implementation doesn't provide any write protection.

The cache was implemented long ago by someone other than myself. Patches
are welcomed.

> > >  some malware might want to rewrite it :)
> > 
> > Right, we want to be pedantic about that sort of stuff and signature
> > verification can help here but those benefits should carry their own
> > weight. We should do what we can without file signature verification to
> > protect the cache.
> > 
> > The cache is short lived though, it exists only during suspend/resume.
> 
> I found out why my test cases fail:
> trigger_config_sync() in test_driver_data.c always enables REQ_KEEP flag
> and so cached data (firmware_buf->data) has not been cleaned up.
> I haven't fixed it in my test environment although I pointed it out before.

Ah, perhaps the later revision of the test driver I posted captured this fix?

> But the issue on write protection is still there wehn REQ_KEEP is used.

Cache of firmware is a feature internal to the firmware_class, so it can only
be fixed there. Patches welcomed.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-11 17:11         ` Luis R. Rodriguez
@ 2017-05-17 22:45           ` Li, Yi
  2017-05-19 18:31             ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: Li, Yi @ 2017-05-17 22:45 UTC (permalink / raw)
  To: Luis R. Rodriguez, AKASHI Takahiro, Luis R. Rodriguez, gregkh,
	wagi, dwmw2, rafal, arend.vanspriel, rjw, atull, moritz.fischer,
	pmladek, johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo,
	luto, dhowells, pjones, linux-kernel

hi Luis


On 5/11/2017 12:11 PM, Luis R. Rodriguez wrote:
> On Thu, May 11, 2017 at 07:46:27PM +0900, AKASHI Takahiro wrote:
>> Luis,
>>
>> On Fri, Apr 28, 2017 at 03:45:35AM +0200, Luis R. Rodriguez wrote:
>>>>> +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.
>>> """
>> That looks fine, but I think we'd better change the line:
>>
>>>>> +        echo anything > /lib/firmware/test-driver_data.bin
>> since it is just incorrect as far as driver_data.sh goes.
> But that is accurate, given the default file we search for on test_driver_data.c
> is test-driver_data.bin. It also does not create a conflict to overwrite a file
> used on driver_data.sh as driver_data.sh uses a custom path. I think the note
> above on custom path is sufficient for the developer or user to be aware of
> the fact the driver_data.sh does it own thing, and that the example is just a
> manual test case.
> What do you mean by that its incorrect ?

I understand it now, but was on the same boat as Akashi. Renamed my 12MB 
test firmware binary to /lib/firmware/test-driver_data.bin, but 
driver_data.sh only read back 9 byes from the tmp "ABCD0123". :-)
What's the proper way to test a real image in the driver_data.sh script, 
change config_set_name?

Yi

>
>    Luis
>

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v6 3/5] test: add new driver_data load tester
  2017-05-17 22:45           ` Li, Yi
@ 2017-05-19 18:31             ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-19 18:31 UTC (permalink / raw)
  To: Li, Yi
  Cc: AKASHI Takahiro, Luis R. Rodriguez, gregkh, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	dhowells, pjones, linux-kernel

On Wed, May 17, 2017 at 05:45:22PM -0500, Li, Yi wrote:
> hi Luis
> 
> 
> On 5/11/2017 12:11 PM, Luis R. Rodriguez wrote:
> > On Thu, May 11, 2017 at 07:46:27PM +0900, AKASHI Takahiro wrote:
> > > Luis,
> > > 
> > > On Fri, Apr 28, 2017 at 03:45:35AM +0200, Luis R. Rodriguez wrote:
> > > > > > +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.
> > > > """
> > > That looks fine, but I think we'd better change the line:
> > > 
> > > > > > +        echo anything > /lib/firmware/test-driver_data.bin
> > > since it is just incorrect as far as driver_data.sh goes.
> > But that is accurate, given the default file we search for on test_driver_data.c
> > is test-driver_data.bin. It also does not create a conflict to overwrite a file
> > used on driver_data.sh as driver_data.sh uses a custom path. I think the note
> > above on custom path is sufficient for the developer or user to be aware of
> > the fact the driver_data.sh does it own thing, and that the example is just a
> > manual test case.
> > What do you mean by that its incorrect ?
> 
> I understand it now, but was on the same boat as Akashi. Renamed my 12MB
> test firmware binary to /lib/firmware/test-driver_data.bin, but
> driver_data.sh only read back 9 byes from the tmp "ABCD0123". :-)

Right, driver_data.sh is not to stress test a target file, it *builds*
tests and uses a temporary directory to avoid interfering with your own
files.

> What's the proper way to test a real image in the driver_data.sh script,
> change config_set_name?

Yes! You can review how it configures a test case to get an idea but the
name change is one, since you are doing it manually you would just avoid
resetting the firmware test path.

We could extend the script with an argument to leave the directory intact
and trust your own file given in an argument, but that would be a new test
case.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* [PATCH v8 0/5] firmware: add driver data API
  2017-05-02  8:49 ` [PATCH v7 0/5] firmware: add driver data API Luis R. Rodriguez
                     ` (4 preceding siblings ...)
  2017-05-02  8:49   ` [PATCH v7 5/5] iwlwifi: convert to use " Luis R. Rodriguez
@ 2017-05-19 19:10   ` Luis R. Rodriguez
  2017-05-19 19:10     ` [PATCH v8 1/5] firmware: add extensible driver data params Luis R. Rodriguez
                       ` (5 more replies)
  5 siblings, 6 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-19 19:10 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

Greg,

Even though using copyleft-next was fine by Linus [0], AKASHI had brought that
he expected to see an "or" clause on the license declarations when using
copyleft-next. This was the only pending issue from the last v7 series [1]. To
be safe I re-touched the subject and Alan and Ted brought up sufficient reasons
for preferring the "or" language [2], as such this series goes with the "or"
language embraced so that it is even clearer than before that GPLv2 applies
when using copyleft-next on the Linux kernel.

This series depends on the few other UMH fallback lock changes I had submitted
earlier this month [3], and those remain without any noted issues. As usual,
all pending changes for this series are available on my linux-next tree on the
20170519-driver-data branch [4], this series was rebased on next-20170519.

Please let me know if there are any issues or questions.

[0] https://lkml.kernel.org/r/CA+55aFyhxcvD+q7tp+-yrSFDKfR0mOHgyEAe=f_94aKLsOu0Og@mail.gmail.com
[1] https://lkml.kernel.org/r/20170502084914.23588-1-mcgrof@kernel.org
[2] https://lkml.kernel.org/r/CAB=NE6VnftR-OR9iLFT3Lnbp4m35P0NFeGxNpRnAWvBbaemrFg@mail.gmail.com
[3] https://lkml.kernel.org/r/20170502083107.23418-1-mcgrof@kernel.org
[4] https://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux-next.git/log/?h=20170519-driver-data

Luis R. Rodriguez (5):
  firmware: add extensible driver data params
  firmware: add extensible driver data API
  test: add new driver_data load tester
  firmware: document the extensible driver data API
  iwlwifi: convert to use driver data API

 Documentation/driver-api/firmware/driver_data.rst  |  167 +++
 Documentation/driver-api/firmware/index.rst        |    1 +
 Documentation/driver-api/firmware/introduction.rst |   16 +
 .../driver-api/firmware/request_firmware.rst       |    2 +
 MAINTAINERS                                        |    4 +-
 drivers/base/firmware_class.c                      |  751 ++++++++++--
 drivers/net/wireless/intel/iwlwifi/iwl-drv.c       |   91 +-
 include/linux/driver_data.h                        |  266 ++++
 include/linux/firmware.h                           |    2 +
 lib/Kconfig.debug                                  |   12 +
 lib/Makefile                                       |    1 +
 lib/test_driver_data.c                             | 1278 ++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile          |    2 +-
 tools/testing/selftests/firmware/config            |    1 +
 tools/testing/selftests/firmware/driver_data.sh    | 1002 +++++++++++++++
 15 files changed, 3460 insertions(+), 136 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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* [PATCH v8 1/5] firmware: add extensible driver data params
  2017-05-19 19:10   ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
@ 2017-05-19 19:10     ` Luis R. Rodriguez
  2017-05-19 19:10     ` [PATCH v8 2/5] firmware: add extensible driver data API Luis R. Rodriguez
                       ` (4 subsequent siblings)
  5 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-19 19:10 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

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 only
  o struct driver_data_params - stiches both of the the above together,
				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.

A side consequences is get to replace all the old internal "firmware
behavior  options" flags with enums we properly document, remove the
blinding #ifdefs, and compartamentlize the userhelper fallback code
more appropriately unde CONFIG_FW_LOADER_USER_HELPER_FALLBACK.

This commit should introduces no functional changes (TM).

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/base/firmware_class.c | 331 ++++++++++++++++++++++++++++++++----------
 include/linux/driver_data.h   |  88 +++++++++++
 2 files changed, 345 insertions(+), 74 deletions(-)
 create mode 100644 include/linux/driver_data.h

diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index b9f907eedbf7..db7c0bc0ed98 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,149 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
 MODULE_DESCRIPTION("Multi purpose firmware loading support");
 MODULE_LICENSE("GPL");
 
+/**
+ * enum driver_data_mode - driver data mode of operation
+ * @DRIVER_DATA_SYNC: used to determine if we should look for the driver data
+ *	file immediatley.
+ * @DRIVER_DATA_ASYNC: used to determine if we should schedule the search for
+ *	your driver data file to be run at a later time.
+ */
+enum driver_data_mode {
+	DRIVER_DATA_SYNC = 0,
+	DRIVER_DATA_ASYNC,
+};
+
+/**
+ * enum driver_data_priv_reqs - private features only used internally
+ *
+ * @DRIVER_DATA_PRIV_REQ_FALLBACK: specifies that the driver data request
+ * 	will use a fallback mechanism if the kernel's direct filesystem
+ * 	lookup failed to find the requested driver data. If the flag
+ * 	%DRIVER_DATA_PRIV_REQ_FALLBACK is set but the flag
+ * 	%DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT is not set, it means the caller
+ * 	is relying on a custom interface for driver data lookup as a fallback
+ * 	mechanism. The custom interface is expected to find any found driver
+ * 	data using the exposed sysfs interface of the firmware_class. If the
+ * 	custom fallback mechanism is not compatible with the internal caching
+ * 	mechanism for driver data lookups at resume, it will be disabled.
+ * @DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT: indicates that the fallback mechanism
+ * 	this driver data request will rely on will be that of having the kernel
+ * 	issue a uevent to userspace. Userspace in turn is expected to be
+ * 	monitoring for uevents for the firmware_class and will use the
+ * 	exposted sysfs interface to upload the driver data for the caller.
+ * @DRIVER_DATA_PRIV_REQ_NO_CACHE: indicates that the driver data request
+ * 	should not set up and use the internal caching mechanism to assist
+ * 	drivers from fetching driver data at resume time after suspend.
+ */
+enum driver_data_priv_reqs {
+	DRIVER_DATA_PRIV_REQ_FALLBACK			= 1 << 0,
+	DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT		= 1 << 1,
+	DRIVER_DATA_PRIV_REQ_NO_CACHE			= 1 << 2,
+};
+
+/**
+ * struct driver_data_priv_params - private driver data parameters
+ * @mode: mode of operation
+ * @priv_reqs: private set of &enum driver_data_reqs, private requirements for
+ * 	the driver data request
+ * @alloc_buf: buffer area allocated by the caller so we can place the
+ * 	respective driver data
+ * @alloc_buf_size: size of the @alloc_buf
+ * @old_async_cb: used only for request_firmware_nowait() since we won't change
+ * 	all async callbacks to get the return value on failure
+ */
+struct driver_data_priv_params {
+	enum driver_data_mode mode;
+	u64 priv_reqs;
+	void *alloc_buf;
+	size_t alloc_buf_size;
+	void (*old_async_cb)(const struct firmware *driver_data, void *context);
+};
+
+/**
+ * struct driver_data_params
+ * @driver_data: the driver data if found using the requirements specified
+ * 	in @req_params and @priv_params
+ * @req_params: caller's requirements for the driver data to look for
+ * @priv_params: private requirements for the driver data to look for
+ */
+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 = {						\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT,	\
+	}
+
+#define __DATA_REQ_FIRMWARE_DIRECT()					\
+	.req_params = {							\
+		.reqs = DRIVER_DATA_REQ_OPTIONAL,			\
+	},								\
+	.priv_params = {						\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT,	\
+	}
+
+#define __DATA_REQ_FIRMWARE_BUF(buf, size)				\
+	.priv_params = {						\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT |	\
+			     DRIVER_DATA_PRIV_REQ_NO_CACHE,		\
+		.alloc_buf = buf,					\
+		.alloc_buf_size = size,					\
+	}
+
+#define __DATA_REQ_FIRMWARE_NOWAIT(module, uevent, gfp, async_cb, async_ctx) \
+	.req_params = {							\
+		.hold_module = module,					\
+		.gfp = gfp,						\
+		.cbs.async = {						\
+			.found_cb = NULL,				\
+			.found_ctx = async_ctx,				\
+		},							\
+	},								\
+	.priv_params = {						\
+		.mode = DRIVER_DATA_ASYNC,				\
+		.old_async_cb = async_cb,				\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     (uevent ?					\
+			      DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT : 0),\
+	}
+
+#define driver_data_req_param_sync(params)				\
+	((params)->priv_params.mode == DRIVER_DATA_SYNC)
+#define driver_data_req_param_async(params)				\
+	((params)->priv_params.mode == DRIVER_DATA_ASYNC)
+
+#define driver_data_param_use_fallback(params)	\
+	(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_FALLBACK))
+#define driver_data_param_uevent(params)	\
+	(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT))
+#define driver_data_param_nocache(params)	\
+	(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_NO_CACHE))
+
+#define driver_data_param_optional(params)	\
+	(!!((params)->reqs & DRIVER_DATA_REQ_OPTIONAL))
+
+#define driver_data_async_ctx(params)		((params)->cbs.async.found_ctx)
+
 /* Builtin firmware support */
 
 #ifdef CONFIG_FW_LOADER
@@ -48,9 +192,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 +233,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;
 }
@@ -182,22 +336,6 @@ static int __fw_state_check(struct fw_state *fw_st, enum fw_status status)
 
 #endif /* CONFIG_FW_LOADER_USER_HELPER */
 
-/* firmware behavior options */
-#define FW_OPT_UEVENT	(1U << 0)
-#define FW_OPT_NOWAIT	(1U << 1)
-#ifdef CONFIG_FW_LOADER_USER_HELPER
-#define FW_OPT_USERHELPER	(1U << 2)
-#else
-#define FW_OPT_USERHELPER	0
-#endif
-#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK
-#define FW_OPT_FALLBACK		FW_OPT_USERHELPER
-#else
-#define FW_OPT_FALLBACK		0
-#endif
-#define FW_OPT_NO_WARN	(1U << 3)
-#define FW_OPT_NOCACHE	(1U << 4)
-
 struct firmware_cache {
 	/* firmware_buf instance will be added into the below list */
 	spinlock_t lock;
@@ -296,9 +434,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 +485,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 +498,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 +704,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 +722,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);
@@ -1025,7 +1174,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;
@@ -1036,7 +1186,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);
 	fw_priv->fw = firmware;
 	f_dev = &fw_priv->dev;
 
@@ -1051,7 +1201,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;
@@ -1073,7 +1224,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);
@@ -1102,14 +1253,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)) {
 		timeout = usermodehelper_read_lock_wait(timeout);
 		if (!timeout) {
 			dev_dbg(device, "firmware: %s loading timed out\n",
@@ -1125,17 +1276,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();
@@ -1146,7 +1297,8 @@ static int fw_load_from_user_helper(struct firmware *firmware,
 #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;
 }
@@ -1161,7 +1313,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;
@@ -1174,12 +1327,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
@@ -1200,11 +1353,33 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
 	return 1; /* need to load */
 }
 
+#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK
+static int driver_data_fallback(struct firmware *fw, const char *name,
+				struct device *device,
+				struct driver_data_params *data_params,
+				int ret)
+{
+	if (!driver_data_param_use_fallback(&data_params->priv_params))
+		return ret;
+
+	dev_warn(device, "Falling back to user helper\n");
+	return fw_load_from_user_helper(fw, name, device, data_params);
+}
+#else
+static inline int driver_data_fallback(struct firmware *fw, const char *name,
+				       struct device *device,
+				       struct driver_data_params *data_params,
+				       int ret)
+{
+	return ret;
+}
+#endif
+
 /* 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;
@@ -1217,7 +1392,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;
 
@@ -1229,17 +1404,13 @@ _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) {
-			dev_warn(device, "Falling back to user helper\n");
-			ret = fw_load_from_user_helper(fw, name, device,
-						       opt_flags);
-		}
+		ret = driver_data_fallback(fw, name, device, data_params, ret);
 	} else
-		ret = assign_firmware_buf(fw, device, opt_flags);
+		ret = assign_firmware_buf(fw, device, data_params);
 
  out:
 	if (ret < 0) {
@@ -1276,11 +1447,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;
 }
@@ -1301,10 +1474,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;
 }
@@ -1330,12 +1505,14 @@ 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);
@@ -1357,27 +1534,30 @@ 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_priv_params *priv_params;
 
 	fw_work = container_of(work, struct firmware_work, work);
-
-	_request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0,
-			  fw_work->opt_flags);
-	fw_work->cont(fw, fw_work->context);
+	data_params = &fw_work->data_params;
+	req_params = &data_params->req_params;
+	priv_params = &data_params->priv_params;
+
+	_request_firmware(&data_params->driver_data, fw_work->name,
+			  data_params, fw_work->device);
+	priv_params->old_async_cb(data_params->driver_data,
+				  driver_data_async_ctx(req_params));
 	put_device(fw_work->device); /* taken in request_firmware_nowait() */
 
-	module_put(fw_work->module);
+	module_put(req_params->hold_module);
 	kfree_const(fw_work->name);
 	kfree(fw_work);
 }
@@ -1412,22 +1592,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);
@@ -1505,7 +1688,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..272d618a5719
--- /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 <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or at your option) any
+ * later version; or, when distributed separately from the Linux kernel or
+ * incorporated into other software packages, subject to the following license:
+ *
+ * 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/.
+ */
+
+/**
+ * struct driver_data_async_cbs - callbacks for handling driver data requests
+ * @found_cb: callback to be used when the driver data has been found. A
+ *	callback is required. If the requested driver data is found it will
+ *	passed on the callback, using the context set on @found_ctx.
+ * @found_ctx: preferred context to be used as the second argument to
+ * 	@found_cb.
+ *
+ * Used for specifying callbacks and contexts used for when asynchronous driver
+ * data requests have completed. If no driver data is found the error will be
+ * passed on the respective callback.
+ */
+struct driver_data_async_cbs {
+	void (*found_cb)(const struct firmware *driver_data,
+			 void *context,
+			 int error);
+	void *found_ctx;
+};
+
+/**
+ * union driver_data_cbs - callbacks for driver data request
+ * @async: callbacks for handling driver data when asynchronous requests
+ * 	are made.
+ *
+ * Used for placement of callbacks used for handling results from driver
+ * data requests.
+ */
+union driver_data_cbs {
+	struct driver_data_async_cbs async;
+};
+
+/**
+ * enum driver_data_reqs - requirements of the driver data request
+ * @DRIVER_DATA_REQ_OPTIONAL: if set it is not a hard requirement by the
+ *	caller that the file requested be present. An error will not be recorded
+ *	if the file is not found.
+ */
+enum driver_data_reqs {
+	DRIVER_DATA_REQ_OPTIONAL			= 1 << 0,
+};
+
+/**
+ * struct driver_data_req_params - driver data request parameters
+ * @hold_module: module to hold during the driver data request operation. By
+ * 	default if sync requests set this to NULL the firmware_class module
+ * 	will be refcounted during operation.
+ * @gfp: flags to use for allocations when constructing the driver data request,
+ *	prior to scheduling. Unused on driver_data_request_sync().
+ * @reqs: set of &enum driver_data_reqs flags used to configure the driver
+ * 	data request. All of the specified requirements must be met.
+ * @cbs: set of callbacks to use for the driver data request.
+ *
+ * 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 {
+	struct module *hold_module;
+	gfp_t gfp;
+	u64 reqs;
+	const union driver_data_cbs cbs;
+};
+
+#endif /* _LINUX_DRIVER_DATA_H */
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v8 2/5] firmware: add extensible driver data API
  2017-05-19 19:10   ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
  2017-05-19 19:10     ` [PATCH v8 1/5] firmware: add extensible driver data params Luis R. Rodriguez
@ 2017-05-19 19:10     ` Luis R. Rodriguez
  2017-05-19 19:10     ` [PATCH v8 3/5] test: add new driver_data load tester Luis R. Rodriguez
                       ` (3 subsequent siblings)
  5 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-19 19:10 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 MAINTAINERS                   |   3 +-
 drivers/base/firmware_class.c | 420 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/driver_data.h   | 178 ++++++++++++++++++
 include/linux/firmware.h      |   2 +
 4 files changed, 602 insertions(+), 1 deletion(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index f8d77c888cfe..148d032e9401 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5226,13 +5226,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 <mcgrof@kernel.org>
 L:	linux-kernel@vger.kernel.org
 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 <josh.h.morris@us.ibm.com>
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index db7c0bc0ed98..e87e91bcd8f8 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -2,6 +2,17 @@
  * firmware_class.c - Multi purpose firmware loading support
  *
  * Copyright (c) 2003 Manuel Estrada Sainz
+ * Copyright (c) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or at your option) any
+ * later version; or, when distributed separately from the Linux kernel or
+ * incorporated into other software packages, subject to the following license:
+ *
+ * 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/.
  *
  * Please see Documentation/firmware_class/ for more information.
  *
@@ -91,6 +102,12 @@ enum driver_data_priv_reqs {
  * @alloc_buf_size: size of the @alloc_buf
  * @old_async_cb: used only for request_firmware_nowait() since we won't change
  * 	all async callbacks to get the return value on failure
+ * @api: used internally for keeping track of the currently evaluated API
+ * 	versioned file as we iterate between min API and max API.
+ * @retry_api: if the driver replied with -EAGAIN, we must ignore the passed
+ * 	driver data file, and retry again on the hunt from where we left off,
+ * 	this lets us know an attempt to look for more API driver data files
+ * 	is a retry.
  */
 struct driver_data_priv_params {
 	enum driver_data_mode mode;
@@ -98,6 +115,8 @@ struct driver_data_priv_params {
 	void *alloc_buf;
 	size_t alloc_buf_size;
 	void (*old_async_cb)(const struct firmware *driver_data, void *context);
+	u8 api;
+	bool retry_api;
 };
 
 /**
@@ -181,8 +200,68 @@ struct driver_data_params {
 
 #define driver_data_param_optional(params)	\
 	(!!((params)->reqs & DRIVER_DATA_REQ_OPTIONAL))
+#define driver_data_param_keep(params)		\
+	(!!((params)->reqs & DRIVER_DATA_REQ_KEEP))
+#define driver_data_param_uses_api(params)	\
+	(!!((params)->reqs & DRIVER_DATA_REQ_USE_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, int error)
+{
+	if (!driver_data_sync_cb(params))
+		return error;
+	return driver_data_sync_cb(params)(driver_data_sync_ctx(params),
+					   driver_data, error);
+}
+
+#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,
+				 int error)
+{
+	if (!driver_data_sync_opt_cb(params))
+		return error;
+	return driver_data_sync_opt_cb(params)
+		(driver_data_sync_opt_ctx(params), error);
+}
 
+#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,
+			       int error)
+{
+	BUG_ON(!driver_data_async_cb(params));
+	driver_data_async_cb(params)(driver_data,
+				     driver_data_async_ctx(params),
+				     error);
+}
+
+#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,
+				   int error)
+{
+	driver_data_async_opt_cb(params)(driver_data_async_opt_ctx(params),
+					 error);
+}
+
+#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,
+				  int error)
+{
+	return driver_data_async_api_cb(params)(driver_data,
+						driver_data_async_ctx(params),
+						error);
+}
 
 /* Builtin firmware support */
 
@@ -1316,6 +1395,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;
@@ -1339,6 +1419,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);
@@ -1531,6 +1612,131 @@ 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 = &params->priv_params;
+	const struct driver_data_req_params *req_params = &params->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(&params->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_sync - synchronous request for a driver data file
+ * @name: name of the driver data file
+ * @req_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. 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.
+ *
+ * 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;
+	struct module *hold_module;
+	struct driver_data_params params = {
+		.req_params = *req_params,
+		.priv_params = {
+			.mode = DRIVER_DATA_SYNC,
+		},
+	};
+	int ret;
+
+	if (!device || !req_params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (driver_data_sync_opt_cb(req_params) &&
+	    !driver_data_param_optional(req_params))
+		return -EINVAL;
+
+	hold_module = req_params->hold_module ? req_params->hold_module :
+		THIS_MODULE;
+
+	__module_get(hold_module);
+	get_device(device);
+
+	ret = _request_firmware(&driver_data, name, &params, device);
+	if (ret && driver_data_param_optional(req_params))
+		ret = driver_data_sync_opt_call_cb(req_params, ret);
+	else
+		ret = driver_data_sync_call_cb(req_params, driver_data, ret);
+
+	if (!driver_data_param_keep(req_params))
+		release_firmware(driver_data);
+
+	put_device(device);
+	module_put(hold_module);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(driver_data_request_sync);
+
 /* Async support */
 struct firmware_work {
 	struct work_struct work;
@@ -1625,6 +1831,220 @@ request_firmware_nowait(
 }
 EXPORT_SYMBOL(request_firmware_nowait);
 
+static bool
+driver_data_api_versioning_ok(const struct driver_data_req_params *req_params)
+{
+	if (!req_params->api_max ||
+	    (req_params->api_max < req_params->api_min) ||
+	    (driver_data_async_cb(req_params)) ||
+	    (!driver_data_async_api_cb(req_params)))
+		return false;
+
+	return true;
+}
+
+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 = &params->req_params;
+	int ret;
+
+	ret = _driver_data_request_api(params, driver_work->device,
+				       driver_work->name);
+	return driver_data_async_call_api_cb(params->driver_data, req_params,
+					     ret);
+}
+
+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 = &params->req_params;
+	int ret;
+
+	ret = _request_firmware(&params->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, ret);
+	else
+		driver_data_async_call_cb(params->driver_data, req_params, ret);
+
+	if (!driver_data_param_keep(req_params))
+		release_firmware(params->driver_data);
+
+	put_device(driver_work->device);
+	module_put(req_params->hold_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 = &params->priv_params;
+	const struct driver_data_req_params *req_params = &params->req_params;
+	int ret;
+	u8 i, limit;
+
+	limit = req_params->api_max - req_params->api_min;
+
+	for (i=0; i <= limit; i++) {
+		/*
+		 * 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;
+	}
+
+	/*
+	 * Note special case:
+	 *
+	 * If the driver didn't like any of the driver data we gave it, it
+	 * may return -EAGAIN for everything that we fed it. We will treat
+	 * this as non-fatal so optional callbacks can work to address this
+	 * if enabled.
+	 *
+	 * All non -ENOENT and -EAGAIN errors are treated as fatal, so we must
+	 * return immediately. Only -ENONENT and -EAGAIN errors are treated as
+	 * graceful and enables the optional callback.
+	 */
+	if (ret) {
+		if (!driver_data_param_optional(req_params))
+			dev_err(driver_work->device,
+				"No API file in range %u - %u could be found, error: %d\n",
+				req_params->api_min, req_params->api_max, ret);
+		if ((ret == -ENOENT || ret == -EAGAIN) &&
+		    driver_data_async_opt_cb(req_params))
+			driver_data_async_opt_call_cb(req_params, ret);
+	}
+
+	if (!driver_data_param_keep(req_params))
+		release_firmware(params->driver_data);
+
+	put_device(driver_work->device);
+	module_put(req_params->hold_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;
+	struct firmware_work driver_work_stack = {
+		.data_params.req_params = *req_params,
+		.data_params.priv_params = {
+			.mode = DRIVER_DATA_ASYNC,
+		},
+	};
+
+	if (!device || !req_params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (driver_data_async_opt_cb(req_params) &&
+	    !driver_data_param_optional(req_params))
+		return -EINVAL;
+
+	if (!driver_data_async_cb(req_params) &&
+	    !driver_data_async_api_cb(req_params))
+		return -EINVAL;
+
+	if (driver_data_param_uses_api(req_params) &&
+	    !driver_data_api_versioning_ok(req_params))
+		return -EINVAL;
+
+	driver_work = kzalloc(sizeof(struct firmware_work), req_params->gfp);
+	if (!driver_work)
+		return -ENOMEM;
+
+	memcpy(driver_work, &driver_work_stack, sizeof(struct firmware_work));
+
+	driver_work->name = kstrdup_const(name, req_params->gfp);
+	if (!driver_work->name) {
+		kfree(driver_work);
+		return -ENOMEM;
+	}
+	driver_work->device = device;
+
+	if (!try_module_get(req_params->hold_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 272d618a5719..cfeaacfd74d3 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
@@ -23,12 +24,47 @@
  */
 
 /**
+ * struct driver_data_sync_cbs - synchronous driver data callbacks
+ * @found_cb: optional callback to be used when the driver data has been found.
+ * 	A callback is useful if you wish to take advantage of the feature of
+ * 	having your driver_data be released immediately after the callback is
+ * 	called, this feature is enabled by default can can be disabled by
+ * 	setting the flag %DRIVER_DATA_REQ_KEEP.
+ * @found_ctx: preferred context to be used as the second argument to
+ * 	@found_cb.
+ * @opt_fail_cb: if your driver data is optional and you have a viable approach
+ * 	to remedy the lack of finding a driver data with original requirements
+ * 	you can implement your solution on this callback.
+ * @opt_fail_ctx: context to use for @opt_fail_cb
+ *
+ * Used for specifying callbacks and contexts used for when synchronous driver
+ * data requests have completed. If no driver data is found the error will be
+ * passed on the respective callback.
+ */
+struct driver_data_sync_cbs {
+	int __must_check
+		(*found_cb)(void *context,
+			    const struct firmware *driver_data,
+			    int error);
+	void *found_ctx;
+
+	int __must_check (*opt_fail_cb)(void *context, int error);
+	void *opt_fail_ctx;
+};
+
+/**
  * struct driver_data_async_cbs - callbacks for handling driver data requests
  * @found_cb: callback to be used when the driver data has been found. A
  *	callback is required. If the requested driver data is found it will
  *	passed on the callback, using the context set on @found_ctx.
  * @found_ctx: preferred context to be used as the second argument to
  * 	@found_cb.
+ * @opt_fail_cb: if your driver data is optional and you have a viable approach
+ * 	to remedy the lack of finding a driver data with original requirements
+ * 	you can implement your solution on this callback.
+ * @opt_fail_ctx: context to use for @opt_fail_cb
+ * @found_api_cb: callback for the supported version API framework, refer to
+ * 	%DRIVER_DATA_REQ_USE_API_VERSIONING for details.
  *
  * Used for specifying callbacks and contexts used for when asynchronous driver
  * data requests have completed. If no driver data is found the error will be
@@ -39,18 +75,28 @@ struct driver_data_async_cbs {
 			 void *context,
 			 int error);
 	void *found_ctx;
+
+	void (*opt_fail_cb)(void *context, int error);
+	void *opt_fail_ctx;
+
+	int __must_check
+		(*found_api_cb)(const struct firmware *driver_data,
+				void *context, int error);
 };
 
 /**
  * union driver_data_cbs - callbacks for driver data request
  * @async: callbacks for handling driver data when asynchronous requests
  * 	are made.
+ * @sync: callbacks for handling driver data when synchronous requests are
+ * 	made.
  *
  * Used for placement of callbacks used for handling results from driver
  * data requests.
  */
 union driver_data_cbs {
 	struct driver_data_async_cbs async;
+	struct driver_data_sync_cbs sync;
 };
 
 /**
@@ -58,9 +104,30 @@ union driver_data_cbs {
  * @DRIVER_DATA_REQ_OPTIONAL: if set it is not a hard requirement by the
  *	caller that the file requested be present. An error will not be recorded
  *	if the file is not found.
+ * @DRIVER_DATA_REQ_KEEP: by default the kernel will release the driver data
+ *	for you immediately after your respective sync or async callback is
+ *	called.  Use this flag to annotate your requirement is for you to keep
+ *	and free the driver data on your own. You must free the driver data
+ *	using release_driver_data().
+ * @DRIVER_DATA_REQ_USE_API_VERSIONING: indicates 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. The search starts using the filename requested on
+ *	driver_data_request_async(), appending the
+ *	&driver_data_req_params->api_max to it, and ending with a postfix if
+ *	&driver_data_req_params->api_name_postfix is specified.  If that is not
+ *	available it will look for any files with API version lower than this
+ *	until it reaches &driver_data_req_params->api_min. This enables
+ *	chaining driver data requests easily on behalf of device drivers using
+ *	a simple one digit versioning scheme.  This feature requires only one
+ *	file to be present given the API range, it is only required for one
+ *	file in the API range to be present.  If the %DRIVER_DATA_REQ_OPTIONAL
+ *	flag is also enabled then all files are treated as optional.
  */
 enum driver_data_reqs {
 	DRIVER_DATA_REQ_OPTIONAL			= 1 << 0,
+	DRIVER_DATA_REQ_KEEP				= 1 << 1,
+	DRIVER_DATA_REQ_USE_API_VERSIONING		= 1 << 2,
 };
 
 /**
@@ -73,6 +140,12 @@ enum driver_data_reqs {
  * @reqs: set of &enum driver_data_reqs flags used to configure the driver
  * 	data request. All of the specified requirements must be met.
  * @cbs: set of callbacks to use for the driver data request.
+ * @api_min: if %DRIVER_DATA_REQ_USE_API_VERSIONING is set, this represents the
+ * 	lowest version of API supported by the caller.
+ * @api_max: if %DRIVER_DATA_REQ_USE_API_VERSIONING is set, this represents the
+ * 	highest version of API supported by the caller.
+ * @api_name_postfix: optional, indicates to use this as the driver data name
+ * 	postfix when %DRIVER_DATA_REQ_USE_API_VERSIONING is enabled.
  *
  * 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
@@ -82,7 +155,112 @@ struct driver_data_req_params {
 	struct module *hold_module;
 	gfp_t gfp;
 	u64 reqs;
+	u8 api_min;
+	u8 api_max;
+	const char *api_name_postfix;
 	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_DEFAULT_SYNC_REQS(__found_cb, __ctx, __reqs)	\
+	DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx),			\
+	.reqs = (__reqs)
+
+#define DRIVER_DATA_KEEP_SYNC(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_SYNC(__found_cb, __ctx),			\
+	.reqs = DRIVER_DATA_REQ_KEEP
+
+/* If you have one fallback routine */
+#define DRIVER_DATA_SYNC_OPT_CB(__fail_cb, __ctx)			\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL,				\
+	.cbs.sync.opt_fail_cb = __fail_cb,				\
+	.cbs.sync.opt_fail_ctx = __ctx
+
+#define DRIVER_DATA_SYNC_OPT_CB_REQS(__fail_cb, __ctx, __reqs)		\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL | __reqs,			\
+	.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)			\
+	.hold_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),			\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL
+
+#define DRIVER_DATA_KEEP_ASYNC(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx),			\
+	.reqs = DRIVER_DATA_PRIV_REQ_KEEP
+
+#define DRIVER_DATA_KEEP_ASYNC_OPT(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx),			\
+	.reqs = DRIVER_DATA_PRIV_REQ_KEEP |				\
+		DRIVER_DATA_REQ_OPTIONAL
+
+#define DRIVER_DATA_ASYNC_OPT_CB(__fail_cb, __ctx)			\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL,				\
+	.cbs.async.opt_fail_cb = __fail_cb,				\
+	.cbs.async.opt_fail_ctx = __ctx
+
+#define DRIVER_DATA_API_CB(__found_api_cb, __ctx)			\
+	.hold_module = THIS_MODULE,					\
+	.gfp = GFP_KERNEL,						\
+	.cbs.async = {							\
+		.found_api_cb = __found_api_cb,				\
+		.found_ctx = __ctx,					\
+	}
+
+#define DRIVER_DATA_API(__min, __max, __postfix)			\
+	.reqs = DRIVER_DATA_REQ_USE_API_VERSIONING,			\
+	.api_min = __min,						\
+	.api_max = __max,						\
+	.api_name_postfix = __postfix
+
+#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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v8 3/5] test: add new driver_data load tester
  2017-05-19 19:10   ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
  2017-05-19 19:10     ` [PATCH v8 1/5] firmware: add extensible driver data params Luis R. Rodriguez
  2017-05-19 19:10     ` [PATCH v8 2/5] firmware: add extensible driver data API Luis R. Rodriguez
@ 2017-05-19 19:10     ` Luis R. Rodriguez
  2017-05-19 19:10     ` [PATCH v8 4/5] firmware: document the extensible driver data API Luis R. Rodriguez
                       ` (2 subsequent siblings)
  5 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-19 19:10 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

This adds a load tester driver test_driver_data a for the new extensible
driver_data loader API, part of firmware_class. This test driver enables
you to build your tests in userspace by exposing knobs of the exported
API to userspace and enables a trigger action to mimic a one time use
of the kernel API. This gives us the flexibility to build test case from
userspace with less kernel changes.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 MAINTAINERS                                     |    1 +
 lib/Kconfig.debug                               |   12 +
 lib/Makefile                                    |    1 +
 lib/test_driver_data.c                          | 1278 +++++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile       |    2 +-
 tools/testing/selftests/firmware/config         |    1 +
 tools/testing/selftests/firmware/driver_data.sh | 1002 ++++++++++++++++++
 7 files changed, 2296 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_driver_data.c
 create mode 100755 tools/testing/selftests/firmware/driver_data.sh

diff --git a/MAINTAINERS b/MAINTAINERS
index 148d032e9401..a44bdda2ffa9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5232,6 +5232,7 @@ L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	Documentation/firmware_class/
 F:	drivers/base/firmware*.c
+F:	lib/test_driver_data.c
 F:	include/linux/firmware.h
 F:	include/linux/driver_data.h
 
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index f6aece3b8098..4198995ca3f7 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1782,6 +1782,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 07fbe6a75692..5a0dcca7097d 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -47,6 +47,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..422ea6289396
--- /dev/null
+++ b/lib/test_driver_data.c
@@ -0,0 +1,1278 @@
+/*
+ * Driver data test interface
+ *
+ * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or at your option) any
+ * later version; or, when distributed separately from the Linux kernel or
+ * incorporated into other software packages, subject to the following license:
+ *
+ * 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,
+			 int unused_error)
+{
+	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, int unused_error)
+{
+	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, int unused_error)
+{
+	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,
+			      int unused_error)
+{
+	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_REQS(config_sync_req_cb, test_dev,
+					      (config->optional ?
+					       DRIVER_DATA_REQ_OPTIONAL : 0) |
+					      (config->keep ?
+					       DRIVER_DATA_REQ_KEEP : 0)),
+	};
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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, int unused_error)
+{
+	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,
+				   int unused_error)
+{
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	const struct driver_data_req_params req_params_api = {
+		DRIVER_DATA_API_CB(config_async_req_api_cb, test_dev),
+		DRIVER_DATA_API(config->api_min, config->api_max, config->api_name_postfix),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0) |
+			(config->use_api_versioning ? DRIVER_DATA_REQ_USE_API_VERSIONING : 0),
+	};
+	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;
+	driver_data_config_free(test_dev);
+	vfree(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);
+	free_test_dev_driver_data(test_dev);
+}
+
+struct driver_data_test_device *alloc_test_dev_driver_data(int idx)
+{
+	int ret;
+	struct driver_data_test_device *test_dev;
+	struct miscdevice *misc_dev;
+
+	test_dev = vzalloc(sizeof(struct driver_data_test_device));
+	if (!test_dev)
+		goto err_out;
+
+	mutex_init(&test_dev->driver_data_mutex);
+	mutex_init(&test_dev->config_mutex);
+	mutex_init(&test_dev->trigger_mutex);
+	init_completion(&test_dev->request_complete);
+
+	ret = driver_data_config_init(test_dev);
+	if (ret < 0)
+		goto err_out_free;
+
+	test_dev->dev_idx = idx;
+	misc_dev = &test_dev->misc_dev;
+
+	misc_dev->minor = MISC_DYNAMIC_MINOR;
+	misc_dev->name = kasprintf(GFP_KERNEL, "test_driver_data%d", idx);
+	if (!misc_dev->name)
+		goto err_out_free_config;
+
+	misc_dev->fops = &test_fw_fops;
+	misc_dev->groups = test_dev_groups;
+
+	return test_dev;
+
+err_out_free_config:
+	__driver_data_config_free(&test_dev->config);
+err_out_free:
+	kfree(test_dev);
+err_out:
+	return NULL;
+}
+
+static int register_test_dev_driver_data(void)
+{
+	struct driver_data_test_device *test_dev = NULL;
+	int ret = -ENODEV;
+
+	mutex_lock(&reg_dev_mutex);
+
+	/* int should suffice for number of devices, test for wrap */
+	if (unlikely(num_test_devs + 1) < 0) {
+		pr_err("reached limit of number of test devices\n");
+		goto out;
+	}
+
+	test_dev = alloc_test_dev_driver_data(num_test_devs);
+	if (!test_dev) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = misc_register(&test_dev->misc_dev);
+	if (ret) {
+		pr_err("could not register misc device: %d\n", ret);
+		free_test_dev_driver_data(test_dev);
+		goto out;
+	}
+
+	test_dev->dev = test_dev->misc_dev.this_device;
+	list_add_tail(&test_dev->list, &reg_test_devs);
+	dev_info(test_dev->dev, "interface ready\n");
+
+	num_test_devs++;
+
+out:
+	mutex_unlock(&reg_dev_mutex);
+
+	return ret;
+}
+
+static int __init test_driver_data_init(void)
+{
+	int ret;
+
+	ret = register_test_dev_driver_data();
+	if (ret)
+		pr_err("Cannot add first test driver_data device\n");
+
+	return ret;
+}
+late_initcall(test_driver_data_init);
+
+static void __exit test_driver_data_exit(void)
+{
+	struct driver_data_test_device *test_dev, *tmp;
+
+	mutex_lock(&reg_dev_mutex);
+	list_for_each_entry_safe(test_dev, tmp, &reg_test_devs, list) {
+		list_del(&test_dev->list);
+		unregister_test_dev_driver_data(test_dev);
+	}
+	mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_driver_data_exit);
+
+MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/tools/testing/selftests/firmware/Makefile b/tools/testing/selftests/firmware/Makefile
index 1894d625af2d..c9bf6c44435f 100644
--- a/tools/testing/selftests/firmware/Makefile
+++ b/tools/testing/selftests/firmware/Makefile
@@ -3,7 +3,7 @@
 # No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
 all:
 
-TEST_PROGS := fw_filesystem.sh fw_fallback.sh
+TEST_PROGS := fw_filesystem.sh fw_fallback.sh driver_data.sh
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config
index c8137f70e291..0f1a299f9270 100644
--- a/tools/testing/selftests/firmware/config
+++ b/tools/testing/selftests/firmware/config
@@ -1 +1,2 @@
 CONFIG_TEST_FIRMWARE=y
+CONFIG_TEST_DRIVER_DATA=y
diff --git a/tools/testing/selftests/firmware/driver_data.sh b/tools/testing/selftests/firmware/driver_data.sh
new file mode 100755
index 000000000000..bfd0436cce47
--- /dev/null
+++ b/tools/testing/selftests/firmware/driver_data.sh
@@ -0,0 +1,1002 @@
+#!/bin/bash
+# Copyright (C) 2016 Luis R. Rodriguez <mcgrof@kernel.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or at your option) any
+# later version; or, when distributed separately from the Linux kernel or
+# incorporated into other software packages, subject to the following license:
+#
+# 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]} -ENOENT
+}
+
+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]} -EINVAL
+}
+
+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 "    -s      Run test ID once"
+	echo "    -c      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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v8 4/5] firmware: document the extensible driver data API
  2017-05-19 19:10   ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
                       ` (2 preceding siblings ...)
  2017-05-19 19:10     ` [PATCH v8 3/5] test: add new driver_data load tester Luis R. Rodriguez
@ 2017-05-19 19:10     ` Luis R. Rodriguez
  2017-05-19 19:10     ` [PATCH v8 5/5] iwlwifi: convert to use " Luis R. Rodriguez
  2017-06-05 21:33     ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
  5 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-19 19:10 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

This documents the driver data API.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 Documentation/driver-api/firmware/driver_data.rst  | 167 +++++++++++++++++++++
 Documentation/driver-api/firmware/index.rst        |   1 +
 Documentation/driver-api/firmware/introduction.rst |  16 ++
 .../driver-api/firmware/request_firmware.rst       |   2 +
 4 files changed, 186 insertions(+)
 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..be7c7ff99151
--- /dev/null
+++ b/Documentation/driver-api/firmware/driver_data.rst
@@ -0,0 +1,167 @@
+.. _driver_data:
+
+===============
+driver_data API
+===============
+
+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 two types of modes of operation for driver data requests:
+
+  * synchronous  - driver_data_request_sync()
+  * 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. 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.
+
+driver_data_sync_cbs
+--------------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_sync_cbs
+
+driver_data_async_cbs
+---------------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_async_cbs
+
+driver_data_cbs
+---------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_cbs
+
+driver_data_reqs
+----------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_reqs
+
+driver_data_req_params
+----------------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_req_params
+
+Synchronous driver data requests
+================================
+
+Synchronous driver data requests will wait until the driver data is found or
+until an error is returned.
+
+driver_data_request_sync
+------------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_request_sync
+
+Asynchronous driver data requests
+=================================
+
+Asynchronous driver data requests allow driver code to not have to wait
+until the driver data or an error is returned. Function callbacks are
+required so that when the firmware or an error is found the driver is
+informed through the callbacks. Asynchronous driver data requests cannot
+be called from atomic contexts.
+
+driver_data_request_async
+-------------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_request_async
+
+Reference counting and releasing the driver data file
+=====================================================
+
+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. When synchronous requests are made the
+firmware_class is refcounted. When asynchronous requests are made the caller's
+module is refcounted. Asynchronous requests do not refcount the firmware_class
+module.
+
+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.
+
+Driver data private internal functionality
+==========================================
+
+This section documents functionality not exposed to users, but important in
+understanding how the driver data internals work.
+
+driver_data_mode
+----------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_mode
+
+driver_data_priv_reqs
+---------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_priv_reqs
+
+driver_data_priv_params
+-----------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_priv_params
+
+driver_data_params
+------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_params
+
+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
+
+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.
+
+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 29da39ec4b8a..70a3dea0c5de 100644
--- a/Documentation/driver-api/firmware/index.rst
+++ b/Documentation/driver-api/firmware/index.rst
@@ -8,6 +8,7 @@ Linux Firmware API
    core
    request_firmware
    other_interfaces
+   driver_data
 
 .. only::  subproject and html
 
diff --git a/Documentation/driver-api/firmware/introduction.rst b/Documentation/driver-api/firmware/introduction.rst
index 211cb44eb972..c1173bac0dbb 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:
+
+* Old firmware API - :ref:`request_firmware`
+* Flexible driver data API - :ref:`driver_data`
+
+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/Documentation/driver-api/firmware/request_firmware.rst b/Documentation/driver-api/firmware/request_firmware.rst
index 1c2c4967cd43..b31938244b7f 100644
--- a/Documentation/driver-api/firmware/request_firmware.rst
+++ b/Documentation/driver-api/firmware/request_firmware.rst
@@ -1,3 +1,5 @@
+.. _request_firmware:
+
 ====================
 request_firmware API
 ====================
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v8 5/5] iwlwifi: convert to use driver data API
  2017-05-19 19:10   ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
                       ` (3 preceding siblings ...)
  2017-05-19 19:10     ` [PATCH v8 4/5] firmware: document the extensible driver data API Luis R. Rodriguez
@ 2017-05-19 19:10     ` Luis R. Rodriguez
  2017-06-05 21:33     ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
  5 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-05-19 19:10 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 91 ++++++++++------------------
 1 file changed, 31 insertions(+), 60 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
index 5cfacb0bca84..028854d31f55 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,58 +206,34 @@ 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,
+			       int unused_error);
 
-static int iwl_request_firmware(struct iwl_drv *drv, bool first)
+static const char *iwl_get_fw_name_pre(struct iwl_drv *drv)
 {
-	const struct iwl_cfg *cfg = drv->trans->cfg;
-	char tag[8];
 	const char *fw_pre_name;
 
 	if (drv->trans->cfg->device_family == IWL_DEVICE_FAMILY_8000 &&
 	    CSR_HW_REV_STEP(drv->trans->hw_rev) == SILICON_B_STEP)
-		fw_pre_name = cfg->fw_name_pre_next_step;
+		fw_pre_name = drv->trans->cfg->fw_name_pre_next_step;
 	else
-		fw_pre_name = cfg->fw_name_pre;
-
-	if (first) {
-		drv->fw_index = cfg->ucode_api_max;
-		sprintf(tag, "%d", drv->fw_index);
-	} else {
-		drv->fw_index--;
-		sprintf(tag, "%d", drv->fw_index);
-	}
-
-	if (drv->fw_index < cfg->ucode_api_min) {
-		IWL_ERR(drv, "no suitable firmware found!\n");
-
-		if (cfg->ucode_api_min == cfg->ucode_api_max) {
-			IWL_ERR(drv, "%s%d is required\n", fw_pre_name,
-				cfg->ucode_api_max);
-		} else {
-			IWL_ERR(drv, "minimum version required: %s%d\n",
-				fw_pre_name,
-				cfg->ucode_api_min);
-			IWL_ERR(drv, "maximum version supported: %s%d\n",
-				fw_pre_name,
-				cfg->ucode_api_max);
-		}
-
-		IWL_ERR(drv,
-			"check git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git\n");
-		return -ENOENT;
-	}
+		fw_pre_name = drv->trans->cfg->fw_name_pre;
 
-	snprintf(drv->firmware_name, sizeof(drv->firmware_name), "%s%s.ucode",
-		 fw_pre_name, tag);
-
-	IWL_DEBUG_INFO(drv, "attempting to load firmware '%s'\n",
-		       drv->firmware_name);
+	return fw_pre_name;
+}
 
-	return request_firmware_nowait(THIS_MODULE, 1, drv->firmware_name,
-				       drv->trans->dev,
-				       GFP_KERNEL, drv, iwl_req_fw_callback);
+static int iwl_request_firmware(struct iwl_drv *drv)
+{
+	const char *name_pre = iwl_get_fw_name_pre(drv);
+	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 {
@@ -1259,7 +1235,8 @@ 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,
+			       int unused_error)
 {
 	struct iwl_drv *drv = context;
 	struct iwl_fw *fw = &drv->fw;
@@ -1282,10 +1259,12 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
 
 	pieces = kzalloc(sizeof(*pieces), GFP_KERNEL);
 	if (!pieces)
-		goto out_free_fw;
+		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);
@@ -1448,9 +1427,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:
@@ -1505,15 +1481,11 @@ 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:
 	iwl_dealloc_ucode(drv);
-	release_firmware(ucode_raw);
  out_unbind:
 	complete(&drv->request_firmware_complete);
 	device_release_driver(drv->trans->dev);
@@ -1524,6 +1496,7 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
 		kfree(pieces->dbg_mem_tlv);
 		kfree(pieces);
 	}
+	return err;
 }
 
 struct iwl_drv *iwl_drv_start(struct iwl_trans *trans)
@@ -1564,11 +1537,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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* Re: [PATCH v8 0/5] firmware: add driver data API
  2017-05-19 19:10   ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
                       ` (4 preceding siblings ...)
  2017-05-19 19:10     ` [PATCH v8 5/5] iwlwifi: convert to use " Luis R. Rodriguez
@ 2017-06-05 21:33     ` Luis R. Rodriguez
  2017-06-05 21:39       ` [PATCH v9 " Luis R. Rodriguez
  5 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-05 21:33 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel

On Fri, May 19, 2017 at 12:10:35PM -0700, Luis R. Rodriguez wrote:
> Greg,
> 
> Even though using copyleft-next was fine by Linus [0], AKASHI had brought that
> he expected to see an "or" clause on the license declarations when using
> copyleft-next. This was the only pending issue from the last v7 series [1]. To
> be safe I re-touched the subject and Alan and Ted brought up sufficient reasons
> for preferring the "or" language [2], as such this series goes with the "or"
> language embraced so that it is even clearer than before that GPLv2 applies
> when using copyleft-next on the Linux kernel.

There were two small typos / enhancements as an outcome form that thread, one
was a small a typo (extra parenthesis) as pointed out by Pavel Machek [0] and
the other to add an extra "when" to as recommended by Aaron Wolf [1] on a email
that only went to the copyleft-next mailing list. I've made the small typo
corrections and will be shortly posting a v9 based on this. I've also dropped
changing the license on firmware_class.c in favor or just using copyleft-next
on new files.

FWIW I had also posted two other new test drivers using this dual license as
its the default license I'll be using for new material on my part, one is a
dedicated sysctl test driver [2], and the other a kmod stress test driver [3].
Andrew has merged the sysctl test driver on his -mm tree.

[0] https://lkml.kernel.org/r/20170525201439.GA20750@amd
[1] https://lists.fedorahosted.org/archives/list/copyleft-next@lists.fedorahosted.org/thread/46GQD3C4L3RLOEVPBYXA4PVPJU5CCFUT/
[2] https://lkml.kernel.org/r/20170525233729.18530-2-mcgrof@kernel.org
[3] https://lkml.kernel.org/r/20170526211228.27764-4-mcgrof@kernel.org

> This series depends on the few other UMH fallback lock changes I had submitted
> earlier this month [3], and those remain without any noted issues. As usual,
> all pending changes for this series are available on my linux-next tree on the
> 20170519-driver-data branch [4], this series was rebased on next-20170519.

These are merged now and visible on linux-next, thanks!

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* [PATCH v9 0/5] firmware: add driver data API
  2017-06-05 21:33     ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
@ 2017-06-05 21:39       ` Luis R. Rodriguez
  2017-06-05 21:39         ` [PATCH v9 1/5] firmware: add extensible driver data params Luis R. Rodriguez
                           ` (4 more replies)
  0 siblings, 5 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-05 21:39 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

This v9 only makes the small dual license adjustments as noted recently [0].
It is also rebased onto linux-next tag 20170605 which now has all the other
pending changes I had posted.

As usual there is a branch based on linux-next on my kenrel.org tree [1]. If
there are any questions or issues please let me know.

[0] https://lkml.kernel.org/r/20170605213314.GR8951@wotan.suse.de
[1] https://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux-next.git/log/?h=20170605-driver-data

  Luis

Luis R. Rodriguez (5):
  firmware: add extensible driver data params
  firmware: add extensible driver data API
  test: add new driver_data load tester
  firmware: document the extensible driver data API
  iwlwifi: convert to use driver data API

 Documentation/driver-api/firmware/driver_data.rst  |  167 +++
 Documentation/driver-api/firmware/index.rst        |    1 +
 Documentation/driver-api/firmware/introduction.rst |   16 +
 .../driver-api/firmware/request_firmware.rst       |    2 +
 MAINTAINERS                                        |    4 +-
 drivers/base/firmware_class.c                      |  741 ++++++++++--
 drivers/net/wireless/intel/iwlwifi/iwl-drv.c       |   91 +-
 include/linux/driver_data.h                        |  267 ++++
 include/linux/firmware.h                           |    2 +
 lib/Kconfig.debug                                  |   10 +
 lib/Makefile                                       |    1 +
 lib/test_driver_data.c                             | 1279 ++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile          |    2 +-
 tools/testing/selftests/firmware/config            |    1 +
 tools/testing/selftests/firmware/driver_data.sh    | 1003 +++++++++++++++
 15 files changed, 3451 insertions(+), 136 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

^ permalink raw reply	[flat|nested] 98+ messages in thread

* [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-05 21:39       ` [PATCH v9 " Luis R. Rodriguez
@ 2017-06-05 21:39         ` Luis R. Rodriguez
  2017-06-13  9:05           ` Greg KH
  2017-06-05 21:39         ` [PATCH v9 2/5] firmware: add extensible driver data API Luis R. Rodriguez
                           ` (3 subsequent siblings)
  4 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-05 21:39 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

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 only
  o struct driver_data_params - stiches both of the the above together,
				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.

A side consequences is get to replace all the old internal "firmware
behavior  options" flags with enums we properly document, remove the
blinding #ifdefs, and compartamentlize the userhelper fallback code
more appropriately unde CONFIG_FW_LOADER_USER_HELPER_FALLBACK.

This commit should introduces no functional changes (TM).

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/base/firmware_class.c | 331 ++++++++++++++++++++++++++++++++----------
 include/linux/driver_data.h   |  89 ++++++++++++
 2 files changed, 346 insertions(+), 74 deletions(-)
 create mode 100644 include/linux/driver_data.h

diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index b9f907eedbf7..db7c0bc0ed98 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,149 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
 MODULE_DESCRIPTION("Multi purpose firmware loading support");
 MODULE_LICENSE("GPL");
 
+/**
+ * enum driver_data_mode - driver data mode of operation
+ * @DRIVER_DATA_SYNC: used to determine if we should look for the driver data
+ *	file immediatley.
+ * @DRIVER_DATA_ASYNC: used to determine if we should schedule the search for
+ *	your driver data file to be run at a later time.
+ */
+enum driver_data_mode {
+	DRIVER_DATA_SYNC = 0,
+	DRIVER_DATA_ASYNC,
+};
+
+/**
+ * enum driver_data_priv_reqs - private features only used internally
+ *
+ * @DRIVER_DATA_PRIV_REQ_FALLBACK: specifies that the driver data request
+ * 	will use a fallback mechanism if the kernel's direct filesystem
+ * 	lookup failed to find the requested driver data. If the flag
+ * 	%DRIVER_DATA_PRIV_REQ_FALLBACK is set but the flag
+ * 	%DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT is not set, it means the caller
+ * 	is relying on a custom interface for driver data lookup as a fallback
+ * 	mechanism. The custom interface is expected to find any found driver
+ * 	data using the exposed sysfs interface of the firmware_class. If the
+ * 	custom fallback mechanism is not compatible with the internal caching
+ * 	mechanism for driver data lookups at resume, it will be disabled.
+ * @DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT: indicates that the fallback mechanism
+ * 	this driver data request will rely on will be that of having the kernel
+ * 	issue a uevent to userspace. Userspace in turn is expected to be
+ * 	monitoring for uevents for the firmware_class and will use the
+ * 	exposted sysfs interface to upload the driver data for the caller.
+ * @DRIVER_DATA_PRIV_REQ_NO_CACHE: indicates that the driver data request
+ * 	should not set up and use the internal caching mechanism to assist
+ * 	drivers from fetching driver data at resume time after suspend.
+ */
+enum driver_data_priv_reqs {
+	DRIVER_DATA_PRIV_REQ_FALLBACK			= 1 << 0,
+	DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT		= 1 << 1,
+	DRIVER_DATA_PRIV_REQ_NO_CACHE			= 1 << 2,
+};
+
+/**
+ * struct driver_data_priv_params - private driver data parameters
+ * @mode: mode of operation
+ * @priv_reqs: private set of &enum driver_data_reqs, private requirements for
+ * 	the driver data request
+ * @alloc_buf: buffer area allocated by the caller so we can place the
+ * 	respective driver data
+ * @alloc_buf_size: size of the @alloc_buf
+ * @old_async_cb: used only for request_firmware_nowait() since we won't change
+ * 	all async callbacks to get the return value on failure
+ */
+struct driver_data_priv_params {
+	enum driver_data_mode mode;
+	u64 priv_reqs;
+	void *alloc_buf;
+	size_t alloc_buf_size;
+	void (*old_async_cb)(const struct firmware *driver_data, void *context);
+};
+
+/**
+ * struct driver_data_params
+ * @driver_data: the driver data if found using the requirements specified
+ * 	in @req_params and @priv_params
+ * @req_params: caller's requirements for the driver data to look for
+ * @priv_params: private requirements for the driver data to look for
+ */
+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 = {						\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT,	\
+	}
+
+#define __DATA_REQ_FIRMWARE_DIRECT()					\
+	.req_params = {							\
+		.reqs = DRIVER_DATA_REQ_OPTIONAL,			\
+	},								\
+	.priv_params = {						\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT,	\
+	}
+
+#define __DATA_REQ_FIRMWARE_BUF(buf, size)				\
+	.priv_params = {						\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT |	\
+			     DRIVER_DATA_PRIV_REQ_NO_CACHE,		\
+		.alloc_buf = buf,					\
+		.alloc_buf_size = size,					\
+	}
+
+#define __DATA_REQ_FIRMWARE_NOWAIT(module, uevent, gfp, async_cb, async_ctx) \
+	.req_params = {							\
+		.hold_module = module,					\
+		.gfp = gfp,						\
+		.cbs.async = {						\
+			.found_cb = NULL,				\
+			.found_ctx = async_ctx,				\
+		},							\
+	},								\
+	.priv_params = {						\
+		.mode = DRIVER_DATA_ASYNC,				\
+		.old_async_cb = async_cb,				\
+		.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK |		\
+			     (uevent ?					\
+			      DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT : 0),\
+	}
+
+#define driver_data_req_param_sync(params)				\
+	((params)->priv_params.mode == DRIVER_DATA_SYNC)
+#define driver_data_req_param_async(params)				\
+	((params)->priv_params.mode == DRIVER_DATA_ASYNC)
+
+#define driver_data_param_use_fallback(params)	\
+	(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_FALLBACK))
+#define driver_data_param_uevent(params)	\
+	(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT))
+#define driver_data_param_nocache(params)	\
+	(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_NO_CACHE))
+
+#define driver_data_param_optional(params)	\
+	(!!((params)->reqs & DRIVER_DATA_REQ_OPTIONAL))
+
+#define driver_data_async_ctx(params)		((params)->cbs.async.found_ctx)
+
 /* Builtin firmware support */
 
 #ifdef CONFIG_FW_LOADER
@@ -48,9 +192,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 +233,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;
 }
@@ -182,22 +336,6 @@ static int __fw_state_check(struct fw_state *fw_st, enum fw_status status)
 
 #endif /* CONFIG_FW_LOADER_USER_HELPER */
 
-/* firmware behavior options */
-#define FW_OPT_UEVENT	(1U << 0)
-#define FW_OPT_NOWAIT	(1U << 1)
-#ifdef CONFIG_FW_LOADER_USER_HELPER
-#define FW_OPT_USERHELPER	(1U << 2)
-#else
-#define FW_OPT_USERHELPER	0
-#endif
-#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK
-#define FW_OPT_FALLBACK		FW_OPT_USERHELPER
-#else
-#define FW_OPT_FALLBACK		0
-#endif
-#define FW_OPT_NO_WARN	(1U << 3)
-#define FW_OPT_NOCACHE	(1U << 4)
-
 struct firmware_cache {
 	/* firmware_buf instance will be added into the below list */
 	spinlock_t lock;
@@ -296,9 +434,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 +485,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 +498,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 +704,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 +722,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);
@@ -1025,7 +1174,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;
@@ -1036,7 +1186,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);
 	fw_priv->fw = firmware;
 	f_dev = &fw_priv->dev;
 
@@ -1051,7 +1201,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;
@@ -1073,7 +1224,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);
@@ -1102,14 +1253,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)) {
 		timeout = usermodehelper_read_lock_wait(timeout);
 		if (!timeout) {
 			dev_dbg(device, "firmware: %s loading timed out\n",
@@ -1125,17 +1276,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();
@@ -1146,7 +1297,8 @@ static int fw_load_from_user_helper(struct firmware *firmware,
 #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;
 }
@@ -1161,7 +1313,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;
@@ -1174,12 +1327,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
@@ -1200,11 +1353,33 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
 	return 1; /* need to load */
 }
 
+#ifdef CONFIG_FW_LOADER_USER_HELPER_FALLBACK
+static int driver_data_fallback(struct firmware *fw, const char *name,
+				struct device *device,
+				struct driver_data_params *data_params,
+				int ret)
+{
+	if (!driver_data_param_use_fallback(&data_params->priv_params))
+		return ret;
+
+	dev_warn(device, "Falling back to user helper\n");
+	return fw_load_from_user_helper(fw, name, device, data_params);
+}
+#else
+static inline int driver_data_fallback(struct firmware *fw, const char *name,
+				       struct device *device,
+				       struct driver_data_params *data_params,
+				       int ret)
+{
+	return ret;
+}
+#endif
+
 /* 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;
@@ -1217,7 +1392,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;
 
@@ -1229,17 +1404,13 @@ _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) {
-			dev_warn(device, "Falling back to user helper\n");
-			ret = fw_load_from_user_helper(fw, name, device,
-						       opt_flags);
-		}
+		ret = driver_data_fallback(fw, name, device, data_params, ret);
 	} else
-		ret = assign_firmware_buf(fw, device, opt_flags);
+		ret = assign_firmware_buf(fw, device, data_params);
 
  out:
 	if (ret < 0) {
@@ -1276,11 +1447,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;
 }
@@ -1301,10 +1474,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;
 }
@@ -1330,12 +1505,14 @@ 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);
@@ -1357,27 +1534,30 @@ 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_priv_params *priv_params;
 
 	fw_work = container_of(work, struct firmware_work, work);
-
-	_request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0,
-			  fw_work->opt_flags);
-	fw_work->cont(fw, fw_work->context);
+	data_params = &fw_work->data_params;
+	req_params = &data_params->req_params;
+	priv_params = &data_params->priv_params;
+
+	_request_firmware(&data_params->driver_data, fw_work->name,
+			  data_params, fw_work->device);
+	priv_params->old_async_cb(data_params->driver_data,
+				  driver_data_async_ctx(req_params));
 	put_device(fw_work->device); /* taken in request_firmware_nowait() */
 
-	module_put(fw_work->module);
+	module_put(req_params->hold_module);
 	kfree_const(fw_work->name);
 	kfree(fw_work);
 }
@@ -1412,22 +1592,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);
@@ -1505,7 +1688,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..0d7699f2881e
--- /dev/null
+++ b/include/linux/driver_data.h
@@ -0,0 +1,89 @@
+#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 <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or at your option any
+ * later version; or, when distributed separately from the Linux kernel or
+ * when incorporated into other software packages, subject to the following
+ * license:
+ *
+ * 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/.
+ */
+
+/**
+ * struct driver_data_async_cbs - callbacks for handling driver data requests
+ * @found_cb: callback to be used when the driver data has been found. A
+ *	callback is required. If the requested driver data is found it will
+ *	passed on the callback, using the context set on @found_ctx.
+ * @found_ctx: preferred context to be used as the second argument to
+ * 	@found_cb.
+ *
+ * Used for specifying callbacks and contexts used for when asynchronous driver
+ * data requests have completed. If no driver data is found the error will be
+ * passed on the respective callback.
+ */
+struct driver_data_async_cbs {
+	void (*found_cb)(const struct firmware *driver_data,
+			 void *context,
+			 int error);
+	void *found_ctx;
+};
+
+/**
+ * union driver_data_cbs - callbacks for driver data request
+ * @async: callbacks for handling driver data when asynchronous requests
+ * 	are made.
+ *
+ * Used for placement of callbacks used for handling results from driver
+ * data requests.
+ */
+union driver_data_cbs {
+	struct driver_data_async_cbs async;
+};
+
+/**
+ * enum driver_data_reqs - requirements of the driver data request
+ * @DRIVER_DATA_REQ_OPTIONAL: if set it is not a hard requirement by the
+ *	caller that the file requested be present. An error will not be recorded
+ *	if the file is not found.
+ */
+enum driver_data_reqs {
+	DRIVER_DATA_REQ_OPTIONAL			= 1 << 0,
+};
+
+/**
+ * struct driver_data_req_params - driver data request parameters
+ * @hold_module: module to hold during the driver data request operation. By
+ * 	default if sync requests set this to NULL the firmware_class module
+ * 	will be refcounted during operation.
+ * @gfp: flags to use for allocations when constructing the driver data request,
+ *	prior to scheduling. Unused on driver_data_request_sync().
+ * @reqs: set of &enum driver_data_reqs flags used to configure the driver
+ * 	data request. All of the specified requirements must be met.
+ * @cbs: set of callbacks to use for the driver data request.
+ *
+ * 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 {
+	struct module *hold_module;
+	gfp_t gfp;
+	u64 reqs;
+	const union driver_data_cbs cbs;
+};
+
+#endif /* _LINUX_DRIVER_DATA_H */
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v9 2/5] firmware: add extensible driver data API
  2017-06-05 21:39       ` [PATCH v9 " Luis R. Rodriguez
  2017-06-05 21:39         ` [PATCH v9 1/5] firmware: add extensible driver data params Luis R. Rodriguez
@ 2017-06-05 21:39         ` Luis R. Rodriguez
  2017-06-05 21:39         ` [PATCH v9 3/5] test: add new driver_data load tester Luis R. Rodriguez
                           ` (2 subsequent siblings)
  4 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-05 21:39 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 MAINTAINERS                   |   3 +-
 drivers/base/firmware_class.c | 410 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/driver_data.h   | 178 ++++++++++++++++++
 include/linux/firmware.h      |   2 +
 4 files changed, 592 insertions(+), 1 deletion(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index f17ffa17abef..5f7a9aa49281 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5239,13 +5239,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 <mcgrof@kernel.org>
 L:	linux-kernel@vger.kernel.org
 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 <josh.h.morris@us.ibm.com>
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index db7c0bc0ed98..7af430a2d656 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 <mcgrof@kernel.org>
  *
  * Please see Documentation/firmware_class/ for more information.
  *
@@ -91,6 +92,12 @@ enum driver_data_priv_reqs {
  * @alloc_buf_size: size of the @alloc_buf
  * @old_async_cb: used only for request_firmware_nowait() since we won't change
  * 	all async callbacks to get the return value on failure
+ * @api: used internally for keeping track of the currently evaluated API
+ * 	versioned file as we iterate between min API and max API.
+ * @retry_api: if the driver replied with -EAGAIN, we must ignore the passed
+ * 	driver data file, and retry again on the hunt from where we left off,
+ * 	this lets us know an attempt to look for more API driver data files
+ * 	is a retry.
  */
 struct driver_data_priv_params {
 	enum driver_data_mode mode;
@@ -98,6 +105,8 @@ struct driver_data_priv_params {
 	void *alloc_buf;
 	size_t alloc_buf_size;
 	void (*old_async_cb)(const struct firmware *driver_data, void *context);
+	u8 api;
+	bool retry_api;
 };
 
 /**
@@ -181,8 +190,68 @@ struct driver_data_params {
 
 #define driver_data_param_optional(params)	\
 	(!!((params)->reqs & DRIVER_DATA_REQ_OPTIONAL))
+#define driver_data_param_keep(params)		\
+	(!!((params)->reqs & DRIVER_DATA_REQ_KEEP))
+#define driver_data_param_uses_api(params)	\
+	(!!((params)->reqs & DRIVER_DATA_REQ_USE_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, int error)
+{
+	if (!driver_data_sync_cb(params))
+		return error;
+	return driver_data_sync_cb(params)(driver_data_sync_ctx(params),
+					   driver_data, error);
+}
+
+#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,
+				 int error)
+{
+	if (!driver_data_sync_opt_cb(params))
+		return error;
+	return driver_data_sync_opt_cb(params)
+		(driver_data_sync_opt_ctx(params), error);
+}
+
+#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,
+			       int error)
+{
+	BUG_ON(!driver_data_async_cb(params));
+	driver_data_async_cb(params)(driver_data,
+				     driver_data_async_ctx(params),
+				     error);
+}
+
+#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,
+				   int error)
+{
+	driver_data_async_opt_cb(params)(driver_data_async_opt_ctx(params),
+					 error);
+}
+
+#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,
+				  int error)
+{
+	return driver_data_async_api_cb(params)(driver_data,
+						driver_data_async_ctx(params),
+						error);
+}
 
 /* Builtin firmware support */
 
@@ -1316,6 +1385,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;
@@ -1339,6 +1409,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);
@@ -1531,6 +1602,131 @@ 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 = &params->priv_params;
+	const struct driver_data_req_params *req_params = &params->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(&params->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_sync - synchronous request for a driver data file
+ * @name: name of the driver data file
+ * @req_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. 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.
+ *
+ * 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;
+	struct module *hold_module;
+	struct driver_data_params params = {
+		.req_params = *req_params,
+		.priv_params = {
+			.mode = DRIVER_DATA_SYNC,
+		},
+	};
+	int ret;
+
+	if (!device || !req_params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (driver_data_sync_opt_cb(req_params) &&
+	    !driver_data_param_optional(req_params))
+		return -EINVAL;
+
+	hold_module = req_params->hold_module ? req_params->hold_module :
+		THIS_MODULE;
+
+	__module_get(hold_module);
+	get_device(device);
+
+	ret = _request_firmware(&driver_data, name, &params, device);
+	if (ret && driver_data_param_optional(req_params))
+		ret = driver_data_sync_opt_call_cb(req_params, ret);
+	else
+		ret = driver_data_sync_call_cb(req_params, driver_data, ret);
+
+	if (!driver_data_param_keep(req_params))
+		release_firmware(driver_data);
+
+	put_device(device);
+	module_put(hold_module);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(driver_data_request_sync);
+
 /* Async support */
 struct firmware_work {
 	struct work_struct work;
@@ -1625,6 +1821,220 @@ request_firmware_nowait(
 }
 EXPORT_SYMBOL(request_firmware_nowait);
 
+static bool
+driver_data_api_versioning_ok(const struct driver_data_req_params *req_params)
+{
+	if (!req_params->api_max ||
+	    (req_params->api_max < req_params->api_min) ||
+	    (driver_data_async_cb(req_params)) ||
+	    (!driver_data_async_api_cb(req_params)))
+		return false;
+
+	return true;
+}
+
+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 = &params->req_params;
+	int ret;
+
+	ret = _driver_data_request_api(params, driver_work->device,
+				       driver_work->name);
+	return driver_data_async_call_api_cb(params->driver_data, req_params,
+					     ret);
+}
+
+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 = &params->req_params;
+	int ret;
+
+	ret = _request_firmware(&params->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, ret);
+	else
+		driver_data_async_call_cb(params->driver_data, req_params, ret);
+
+	if (!driver_data_param_keep(req_params))
+		release_firmware(params->driver_data);
+
+	put_device(driver_work->device);
+	module_put(req_params->hold_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 = &params->priv_params;
+	const struct driver_data_req_params *req_params = &params->req_params;
+	int ret;
+	u8 i, limit;
+
+	limit = req_params->api_max - req_params->api_min;
+
+	for (i=0; i <= limit; i++) {
+		/*
+		 * 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;
+	}
+
+	/*
+	 * Note special case:
+	 *
+	 * If the driver didn't like any of the driver data we gave it, it
+	 * may return -EAGAIN for everything that we fed it. We will treat
+	 * this as non-fatal so optional callbacks can work to address this
+	 * if enabled.
+	 *
+	 * All non -ENOENT and -EAGAIN errors are treated as fatal, so we must
+	 * return immediately. Only -ENONENT and -EAGAIN errors are treated as
+	 * graceful and enables the optional callback.
+	 */
+	if (ret) {
+		if (!driver_data_param_optional(req_params))
+			dev_err(driver_work->device,
+				"No API file in range %u - %u could be found, error: %d\n",
+				req_params->api_min, req_params->api_max, ret);
+		if ((ret == -ENOENT || ret == -EAGAIN) &&
+		    driver_data_async_opt_cb(req_params))
+			driver_data_async_opt_call_cb(req_params, ret);
+	}
+
+	if (!driver_data_param_keep(req_params))
+		release_firmware(params->driver_data);
+
+	put_device(driver_work->device);
+	module_put(req_params->hold_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;
+	struct firmware_work driver_work_stack = {
+		.data_params.req_params = *req_params,
+		.data_params.priv_params = {
+			.mode = DRIVER_DATA_ASYNC,
+		},
+	};
+
+	if (!device || !req_params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (driver_data_async_opt_cb(req_params) &&
+	    !driver_data_param_optional(req_params))
+		return -EINVAL;
+
+	if (!driver_data_async_cb(req_params) &&
+	    !driver_data_async_api_cb(req_params))
+		return -EINVAL;
+
+	if (driver_data_param_uses_api(req_params) &&
+	    !driver_data_api_versioning_ok(req_params))
+		return -EINVAL;
+
+	driver_work = kzalloc(sizeof(struct firmware_work), req_params->gfp);
+	if (!driver_work)
+		return -ENOMEM;
+
+	memcpy(driver_work, &driver_work_stack, sizeof(struct firmware_work));
+
+	driver_work->name = kstrdup_const(name, req_params->gfp);
+	if (!driver_work->name) {
+		kfree(driver_work);
+		return -ENOMEM;
+	}
+	driver_work->device = device;
+
+	if (!try_module_get(req_params->hold_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 0d7699f2881e..2cb999ed7b4c 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
@@ -24,12 +25,47 @@
  */
 
 /**
+ * struct driver_data_sync_cbs - synchronous driver data callbacks
+ * @found_cb: optional callback to be used when the driver data has been found.
+ * 	A callback is useful if you wish to take advantage of the feature of
+ * 	having your driver_data be released immediately after the callback is
+ * 	called, this feature is enabled by default can can be disabled by
+ * 	setting the flag %DRIVER_DATA_REQ_KEEP.
+ * @found_ctx: preferred context to be used as the second argument to
+ * 	@found_cb.
+ * @opt_fail_cb: if your driver data is optional and you have a viable approach
+ * 	to remedy the lack of finding a driver data with original requirements
+ * 	you can implement your solution on this callback.
+ * @opt_fail_ctx: context to use for @opt_fail_cb
+ *
+ * Used for specifying callbacks and contexts used for when synchronous driver
+ * data requests have completed. If no driver data is found the error will be
+ * passed on the respective callback.
+ */
+struct driver_data_sync_cbs {
+	int __must_check
+		(*found_cb)(void *context,
+			    const struct firmware *driver_data,
+			    int error);
+	void *found_ctx;
+
+	int __must_check (*opt_fail_cb)(void *context, int error);
+	void *opt_fail_ctx;
+};
+
+/**
  * struct driver_data_async_cbs - callbacks for handling driver data requests
  * @found_cb: callback to be used when the driver data has been found. A
  *	callback is required. If the requested driver data is found it will
  *	passed on the callback, using the context set on @found_ctx.
  * @found_ctx: preferred context to be used as the second argument to
  * 	@found_cb.
+ * @opt_fail_cb: if your driver data is optional and you have a viable approach
+ * 	to remedy the lack of finding a driver data with original requirements
+ * 	you can implement your solution on this callback.
+ * @opt_fail_ctx: context to use for @opt_fail_cb
+ * @found_api_cb: callback for the supported version API framework, refer to
+ * 	%DRIVER_DATA_REQ_USE_API_VERSIONING for details.
  *
  * Used for specifying callbacks and contexts used for when asynchronous driver
  * data requests have completed. If no driver data is found the error will be
@@ -40,18 +76,28 @@ struct driver_data_async_cbs {
 			 void *context,
 			 int error);
 	void *found_ctx;
+
+	void (*opt_fail_cb)(void *context, int error);
+	void *opt_fail_ctx;
+
+	int __must_check
+		(*found_api_cb)(const struct firmware *driver_data,
+				void *context, int error);
 };
 
 /**
  * union driver_data_cbs - callbacks for driver data request
  * @async: callbacks for handling driver data when asynchronous requests
  * 	are made.
+ * @sync: callbacks for handling driver data when synchronous requests are
+ * 	made.
  *
  * Used for placement of callbacks used for handling results from driver
  * data requests.
  */
 union driver_data_cbs {
 	struct driver_data_async_cbs async;
+	struct driver_data_sync_cbs sync;
 };
 
 /**
@@ -59,9 +105,30 @@ union driver_data_cbs {
  * @DRIVER_DATA_REQ_OPTIONAL: if set it is not a hard requirement by the
  *	caller that the file requested be present. An error will not be recorded
  *	if the file is not found.
+ * @DRIVER_DATA_REQ_KEEP: by default the kernel will release the driver data
+ *	for you immediately after your respective sync or async callback is
+ *	called.  Use this flag to annotate your requirement is for you to keep
+ *	and free the driver data on your own. You must free the driver data
+ *	using release_driver_data().
+ * @DRIVER_DATA_REQ_USE_API_VERSIONING: indicates 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. The search starts using the filename requested on
+ *	driver_data_request_async(), appending the
+ *	&driver_data_req_params->api_max to it, and ending with a postfix if
+ *	&driver_data_req_params->api_name_postfix is specified.  If that is not
+ *	available it will look for any files with API version lower than this
+ *	until it reaches &driver_data_req_params->api_min. This enables
+ *	chaining driver data requests easily on behalf of device drivers using
+ *	a simple one digit versioning scheme.  This feature requires only one
+ *	file to be present given the API range, it is only required for one
+ *	file in the API range to be present.  If the %DRIVER_DATA_REQ_OPTIONAL
+ *	flag is also enabled then all files are treated as optional.
  */
 enum driver_data_reqs {
 	DRIVER_DATA_REQ_OPTIONAL			= 1 << 0,
+	DRIVER_DATA_REQ_KEEP				= 1 << 1,
+	DRIVER_DATA_REQ_USE_API_VERSIONING		= 1 << 2,
 };
 
 /**
@@ -74,6 +141,12 @@ enum driver_data_reqs {
  * @reqs: set of &enum driver_data_reqs flags used to configure the driver
  * 	data request. All of the specified requirements must be met.
  * @cbs: set of callbacks to use for the driver data request.
+ * @api_min: if %DRIVER_DATA_REQ_USE_API_VERSIONING is set, this represents the
+ * 	lowest version of API supported by the caller.
+ * @api_max: if %DRIVER_DATA_REQ_USE_API_VERSIONING is set, this represents the
+ * 	highest version of API supported by the caller.
+ * @api_name_postfix: optional, indicates to use this as the driver data name
+ * 	postfix when %DRIVER_DATA_REQ_USE_API_VERSIONING is enabled.
  *
  * 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
@@ -83,7 +156,112 @@ struct driver_data_req_params {
 	struct module *hold_module;
 	gfp_t gfp;
 	u64 reqs;
+	u8 api_min;
+	u8 api_max;
+	const char *api_name_postfix;
 	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_DEFAULT_SYNC_REQS(__found_cb, __ctx, __reqs)	\
+	DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx),			\
+	.reqs = (__reqs)
+
+#define DRIVER_DATA_KEEP_SYNC(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_SYNC(__found_cb, __ctx),			\
+	.reqs = DRIVER_DATA_REQ_KEEP
+
+/* If you have one fallback routine */
+#define DRIVER_DATA_SYNC_OPT_CB(__fail_cb, __ctx)			\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL,				\
+	.cbs.sync.opt_fail_cb = __fail_cb,				\
+	.cbs.sync.opt_fail_ctx = __ctx
+
+#define DRIVER_DATA_SYNC_OPT_CB_REQS(__fail_cb, __ctx, __reqs)		\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL | __reqs,			\
+	.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)			\
+	.hold_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),			\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL
+
+#define DRIVER_DATA_KEEP_ASYNC(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx),			\
+	.reqs = DRIVER_DATA_PRIV_REQ_KEEP
+
+#define DRIVER_DATA_KEEP_ASYNC_OPT(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx),			\
+	.reqs = DRIVER_DATA_PRIV_REQ_KEEP |				\
+		DRIVER_DATA_REQ_OPTIONAL
+
+#define DRIVER_DATA_ASYNC_OPT_CB(__fail_cb, __ctx)			\
+	.reqs = DRIVER_DATA_REQ_OPTIONAL,				\
+	.cbs.async.opt_fail_cb = __fail_cb,				\
+	.cbs.async.opt_fail_ctx = __ctx
+
+#define DRIVER_DATA_API_CB(__found_api_cb, __ctx)			\
+	.hold_module = THIS_MODULE,					\
+	.gfp = GFP_KERNEL,						\
+	.cbs.async = {							\
+		.found_api_cb = __found_api_cb,				\
+		.found_ctx = __ctx,					\
+	}
+
+#define DRIVER_DATA_API(__min, __max, __postfix)			\
+	.reqs = DRIVER_DATA_REQ_USE_API_VERSIONING,			\
+	.api_min = __min,						\
+	.api_max = __max,						\
+	.api_name_postfix = __postfix
+
+#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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v9 3/5] test: add new driver_data load tester
  2017-06-05 21:39       ` [PATCH v9 " Luis R. Rodriguez
  2017-06-05 21:39         ` [PATCH v9 1/5] firmware: add extensible driver data params Luis R. Rodriguez
  2017-06-05 21:39         ` [PATCH v9 2/5] firmware: add extensible driver data API Luis R. Rodriguez
@ 2017-06-05 21:39         ` Luis R. Rodriguez
  2017-06-05 21:39         ` [PATCH v9 4/5] firmware: document the extensible driver data API Luis R. Rodriguez
  2017-06-05 21:39         ` [PATCH v9 5/5] iwlwifi: convert to use " Luis R. Rodriguez
  4 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-05 21:39 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

This adds a load tester driver test_driver_data a for the new extensible
driver_data loader API, part of firmware_class. This test driver enables
you to build your tests in userspace by exposing knobs of the exported
API to userspace and enables a trigger action to mimic a one time use
of the kernel API. This gives us the flexibility to build test case from
userspace with less kernel changes.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 MAINTAINERS                                     |    1 +
 lib/Kconfig.debug                               |   10 +
 lib/Makefile                                    |    1 +
 lib/test_driver_data.c                          | 1279 +++++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile       |    2 +-
 tools/testing/selftests/firmware/config         |    1 +
 tools/testing/selftests/firmware/driver_data.sh | 1003 ++++++++++++++++++
 7 files changed, 2296 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_driver_data.c
 create mode 100755 tools/testing/selftests/firmware/driver_data.sh

diff --git a/MAINTAINERS b/MAINTAINERS
index 5f7a9aa49281..1c8235f57487 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5245,6 +5245,7 @@ L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	Documentation/firmware_class/
 F:	drivers/base/firmware*.c
+F:	lib/test_driver_data.c
 F:	include/linux/firmware.h
 F:	include/linux/driver_data.h
 
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 650250aec2d0..14c72e98b6c9 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1791,6 +1791,16 @@ config TEST_SYSCTL
 	  proc sysctl interfaces available to drivers safely without affecting
 	  production knobs which might alter system functionality.
 
+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
diff --git a/lib/Makefile b/lib/Makefile
index 1935a97171db..95da06a20dcd 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_TEST_BPF) += test_bpf.o
 obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o
 obj-$(CONFIG_TEST_SYSCTL) += test_sysctl.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..c176527f23f7
--- /dev/null
+++ b/lib/test_driver_data.c
@@ -0,0 +1,1279 @@
+/*
+ * Driver data test interface
+ *
+ * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or at your option any
+ * later version; or, when distributed separately from the Linux kernel or
+ * when incorporated into other software packages, subject to the following
+ * license:
+ *
+ * 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,
+			 int unused_error)
+{
+	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, int unused_error)
+{
+	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, int unused_error)
+{
+	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,
+			      int unused_error)
+{
+	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_REQS(config_sync_req_cb, test_dev,
+					      (config->optional ?
+					       DRIVER_DATA_REQ_OPTIONAL : 0) |
+					      (config->keep ?
+					       DRIVER_DATA_REQ_KEEP : 0)),
+	};
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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, int unused_error)
+{
+	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,
+				   int unused_error)
+{
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	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),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0),
+	};
+	const struct driver_data_req_params req_params_api = {
+		DRIVER_DATA_API_CB(config_async_req_api_cb, test_dev),
+		DRIVER_DATA_API(config->api_min, config->api_max, config->api_name_postfix),
+		.reqs = (config->optional ? DRIVER_DATA_REQ_OPTIONAL : 0) |
+			(config->keep ? DRIVER_DATA_REQ_KEEP : 0) |
+			(config->use_api_versioning ? DRIVER_DATA_REQ_USE_API_VERSIONING : 0),
+	};
+	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;
+	driver_data_config_free(test_dev);
+	vfree(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);
+	free_test_dev_driver_data(test_dev);
+}
+
+struct driver_data_test_device *alloc_test_dev_driver_data(int idx)
+{
+	int ret;
+	struct driver_data_test_device *test_dev;
+	struct miscdevice *misc_dev;
+
+	test_dev = vzalloc(sizeof(struct driver_data_test_device));
+	if (!test_dev)
+		goto err_out;
+
+	mutex_init(&test_dev->driver_data_mutex);
+	mutex_init(&test_dev->config_mutex);
+	mutex_init(&test_dev->trigger_mutex);
+	init_completion(&test_dev->request_complete);
+
+	ret = driver_data_config_init(test_dev);
+	if (ret < 0)
+		goto err_out_free;
+
+	test_dev->dev_idx = idx;
+	misc_dev = &test_dev->misc_dev;
+
+	misc_dev->minor = MISC_DYNAMIC_MINOR;
+	misc_dev->name = kasprintf(GFP_KERNEL, "test_driver_data%d", idx);
+	if (!misc_dev->name)
+		goto err_out_free_config;
+
+	misc_dev->fops = &test_fw_fops;
+	misc_dev->groups = test_dev_groups;
+
+	return test_dev;
+
+err_out_free_config:
+	__driver_data_config_free(&test_dev->config);
+err_out_free:
+	kfree(test_dev);
+err_out:
+	return NULL;
+}
+
+static int register_test_dev_driver_data(void)
+{
+	struct driver_data_test_device *test_dev = NULL;
+	int ret = -ENODEV;
+
+	mutex_lock(&reg_dev_mutex);
+
+	/* int should suffice for number of devices, test for wrap */
+	if (unlikely(num_test_devs + 1) < 0) {
+		pr_err("reached limit of number of test devices\n");
+		goto out;
+	}
+
+	test_dev = alloc_test_dev_driver_data(num_test_devs);
+	if (!test_dev) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = misc_register(&test_dev->misc_dev);
+	if (ret) {
+		pr_err("could not register misc device: %d\n", ret);
+		free_test_dev_driver_data(test_dev);
+		goto out;
+	}
+
+	test_dev->dev = test_dev->misc_dev.this_device;
+	list_add_tail(&test_dev->list, &reg_test_devs);
+	dev_info(test_dev->dev, "interface ready\n");
+
+	num_test_devs++;
+
+out:
+	mutex_unlock(&reg_dev_mutex);
+
+	return ret;
+}
+
+static int __init test_driver_data_init(void)
+{
+	int ret;
+
+	ret = register_test_dev_driver_data();
+	if (ret)
+		pr_err("Cannot add first test driver_data device\n");
+
+	return ret;
+}
+late_initcall(test_driver_data_init);
+
+static void __exit test_driver_data_exit(void)
+{
+	struct driver_data_test_device *test_dev, *tmp;
+
+	mutex_lock(&reg_dev_mutex);
+	list_for_each_entry_safe(test_dev, tmp, &reg_test_devs, list) {
+		list_del(&test_dev->list);
+		unregister_test_dev_driver_data(test_dev);
+	}
+	mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_driver_data_exit);
+
+MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/tools/testing/selftests/firmware/Makefile b/tools/testing/selftests/firmware/Makefile
index 1894d625af2d..c9bf6c44435f 100644
--- a/tools/testing/selftests/firmware/Makefile
+++ b/tools/testing/selftests/firmware/Makefile
@@ -3,7 +3,7 @@
 # No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
 all:
 
-TEST_PROGS := fw_filesystem.sh fw_fallback.sh
+TEST_PROGS := fw_filesystem.sh fw_fallback.sh driver_data.sh
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config
index c8137f70e291..0f1a299f9270 100644
--- a/tools/testing/selftests/firmware/config
+++ b/tools/testing/selftests/firmware/config
@@ -1 +1,2 @@
 CONFIG_TEST_FIRMWARE=y
+CONFIG_TEST_DRIVER_DATA=y
diff --git a/tools/testing/selftests/firmware/driver_data.sh b/tools/testing/selftests/firmware/driver_data.sh
new file mode 100755
index 000000000000..dc823aef6867
--- /dev/null
+++ b/tools/testing/selftests/firmware/driver_data.sh
@@ -0,0 +1,1003 @@
+#!/bin/bash
+# Copyright (C) 2016 Luis R. Rodriguez <mcgrof@kernel.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or at your option any
+# later version; or, when distributed separately from the Linux kernel or
+# when incorporated into other software packages, subject to the following
+# license:
+#
+# 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]} -ENOENT
+}
+
+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]} -EINVAL
+}
+
+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 "    -s      Run test ID once"
+	echo "    -c      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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v9 4/5] firmware: document the extensible driver data API
  2017-06-05 21:39       ` [PATCH v9 " Luis R. Rodriguez
                           ` (2 preceding siblings ...)
  2017-06-05 21:39         ` [PATCH v9 3/5] test: add new driver_data load tester Luis R. Rodriguez
@ 2017-06-05 21:39         ` Luis R. Rodriguez
  2017-06-05 21:39         ` [PATCH v9 5/5] iwlwifi: convert to use " Luis R. Rodriguez
  4 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-05 21:39 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

This documents the driver data API.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 Documentation/driver-api/firmware/driver_data.rst  | 167 +++++++++++++++++++++
 Documentation/driver-api/firmware/index.rst        |   1 +
 Documentation/driver-api/firmware/introduction.rst |  16 ++
 .../driver-api/firmware/request_firmware.rst       |   2 +
 4 files changed, 186 insertions(+)
 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..be7c7ff99151
--- /dev/null
+++ b/Documentation/driver-api/firmware/driver_data.rst
@@ -0,0 +1,167 @@
+.. _driver_data:
+
+===============
+driver_data API
+===============
+
+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 two types of modes of operation for driver data requests:
+
+  * synchronous  - driver_data_request_sync()
+  * 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. 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.
+
+driver_data_sync_cbs
+--------------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_sync_cbs
+
+driver_data_async_cbs
+---------------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_async_cbs
+
+driver_data_cbs
+---------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_cbs
+
+driver_data_reqs
+----------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_reqs
+
+driver_data_req_params
+----------------------
+.. kernel-doc:: include/linux/driver_data.h
+   :functions: driver_data_req_params
+
+Synchronous driver data requests
+================================
+
+Synchronous driver data requests will wait until the driver data is found or
+until an error is returned.
+
+driver_data_request_sync
+------------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_request_sync
+
+Asynchronous driver data requests
+=================================
+
+Asynchronous driver data requests allow driver code to not have to wait
+until the driver data or an error is returned. Function callbacks are
+required so that when the firmware or an error is found the driver is
+informed through the callbacks. Asynchronous driver data requests cannot
+be called from atomic contexts.
+
+driver_data_request_async
+-------------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_request_async
+
+Reference counting and releasing the driver data file
+=====================================================
+
+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. When synchronous requests are made the
+firmware_class is refcounted. When asynchronous requests are made the caller's
+module is refcounted. Asynchronous requests do not refcount the firmware_class
+module.
+
+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.
+
+Driver data private internal functionality
+==========================================
+
+This section documents functionality not exposed to users, but important in
+understanding how the driver data internals work.
+
+driver_data_mode
+----------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_mode
+
+driver_data_priv_reqs
+---------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_priv_reqs
+
+driver_data_priv_params
+-----------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_priv_params
+
+driver_data_params
+------------------
+.. kernel-doc:: drivers/base/firmware_class.c
+   :functions: driver_data_params
+
+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
+
+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.
+
+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 29da39ec4b8a..70a3dea0c5de 100644
--- a/Documentation/driver-api/firmware/index.rst
+++ b/Documentation/driver-api/firmware/index.rst
@@ -8,6 +8,7 @@ Linux Firmware API
    core
    request_firmware
    other_interfaces
+   driver_data
 
 .. only::  subproject and html
 
diff --git a/Documentation/driver-api/firmware/introduction.rst b/Documentation/driver-api/firmware/introduction.rst
index 211cb44eb972..c1173bac0dbb 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:
+
+* Old firmware API - :ref:`request_firmware`
+* Flexible driver data API - :ref:`driver_data`
+
+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/Documentation/driver-api/firmware/request_firmware.rst b/Documentation/driver-api/firmware/request_firmware.rst
index 1c2c4967cd43..b31938244b7f 100644
--- a/Documentation/driver-api/firmware/request_firmware.rst
+++ b/Documentation/driver-api/firmware/request_firmware.rst
@@ -1,3 +1,5 @@
+.. _request_firmware:
+
 ====================
 request_firmware API
 ====================
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* [PATCH v9 5/5] iwlwifi: convert to use driver data API
  2017-06-05 21:39       ` [PATCH v9 " Luis R. Rodriguez
                           ` (3 preceding siblings ...)
  2017-06-05 21:39         ` [PATCH v9 4/5] firmware: document the extensible driver data API Luis R. Rodriguez
@ 2017-06-05 21:39         ` Luis R. Rodriguez
  4 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-05 21:39 UTC (permalink / raw)
  To: gregkh
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	Luis R. Rodriguez

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 <mcgrof@kernel.org>
---
 drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 91 ++++++++++------------------
 1 file changed, 31 insertions(+), 60 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c
index 5cfacb0bca84..028854d31f55 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,58 +206,34 @@ 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,
+			       int unused_error);
 
-static int iwl_request_firmware(struct iwl_drv *drv, bool first)
+static const char *iwl_get_fw_name_pre(struct iwl_drv *drv)
 {
-	const struct iwl_cfg *cfg = drv->trans->cfg;
-	char tag[8];
 	const char *fw_pre_name;
 
 	if (drv->trans->cfg->device_family == IWL_DEVICE_FAMILY_8000 &&
 	    CSR_HW_REV_STEP(drv->trans->hw_rev) == SILICON_B_STEP)
-		fw_pre_name = cfg->fw_name_pre_next_step;
+		fw_pre_name = drv->trans->cfg->fw_name_pre_next_step;
 	else
-		fw_pre_name = cfg->fw_name_pre;
-
-	if (first) {
-		drv->fw_index = cfg->ucode_api_max;
-		sprintf(tag, "%d", drv->fw_index);
-	} else {
-		drv->fw_index--;
-		sprintf(tag, "%d", drv->fw_index);
-	}
-
-	if (drv->fw_index < cfg->ucode_api_min) {
-		IWL_ERR(drv, "no suitable firmware found!\n");
-
-		if (cfg->ucode_api_min == cfg->ucode_api_max) {
-			IWL_ERR(drv, "%s%d is required\n", fw_pre_name,
-				cfg->ucode_api_max);
-		} else {
-			IWL_ERR(drv, "minimum version required: %s%d\n",
-				fw_pre_name,
-				cfg->ucode_api_min);
-			IWL_ERR(drv, "maximum version supported: %s%d\n",
-				fw_pre_name,
-				cfg->ucode_api_max);
-		}
-
-		IWL_ERR(drv,
-			"check git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git\n");
-		return -ENOENT;
-	}
+		fw_pre_name = drv->trans->cfg->fw_name_pre;
 
-	snprintf(drv->firmware_name, sizeof(drv->firmware_name), "%s%s.ucode",
-		 fw_pre_name, tag);
-
-	IWL_DEBUG_INFO(drv, "attempting to load firmware '%s'\n",
-		       drv->firmware_name);
+	return fw_pre_name;
+}
 
-	return request_firmware_nowait(THIS_MODULE, 1, drv->firmware_name,
-				       drv->trans->dev,
-				       GFP_KERNEL, drv, iwl_req_fw_callback);
+static int iwl_request_firmware(struct iwl_drv *drv)
+{
+	const char *name_pre = iwl_get_fw_name_pre(drv);
+	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 {
@@ -1259,7 +1235,8 @@ 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,
+			       int unused_error)
 {
 	struct iwl_drv *drv = context;
 	struct iwl_fw *fw = &drv->fw;
@@ -1282,10 +1259,12 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
 
 	pieces = kzalloc(sizeof(*pieces), GFP_KERNEL);
 	if (!pieces)
-		goto out_free_fw;
+		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);
@@ -1448,9 +1427,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:
@@ -1505,15 +1481,11 @@ 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:
 	iwl_dealloc_ucode(drv);
-	release_firmware(ucode_raw);
  out_unbind:
 	complete(&drv->request_firmware_complete);
 	device_release_driver(drv->trans->dev);
@@ -1524,6 +1496,7 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
 		kfree(pieces->dbg_mem_tlv);
 		kfree(pieces);
 	}
+	return err;
 }
 
 struct iwl_drv *iwl_drv_start(struct iwl_trans *trans)
@@ -1564,11 +1537,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

^ permalink raw reply related	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-05 21:39         ` [PATCH v9 1/5] firmware: add extensible driver data params Luis R. Rodriguez
@ 2017-06-13  9:05           ` Greg KH
  2017-06-13 10:31             ` Rafał Miłecki
  2017-06-13 19:40             ` Luis R. Rodriguez
  0 siblings, 2 replies; 98+ messages in thread
From: Greg KH @ 2017-06-13  9:05 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel

On Mon, Jun 05, 2017 at 02:39:33PM -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.

Let's take a simple C function interface and make it a more complex
data-driven interface that is impossible to understand and obviously
understand how it is to be used and works!

:(

Seriously, why?  Why are we extending any of this at all?  This series
adds a ton of new "features" and complexity, but for absolutely no gain.

Oh, I take it back, you removed 29 lines from the iwlwifi driver.

That's still not worth it at all, you have yet to sell me on this whole
complex beast.  I can't see why we need it, and if I, one of the few
people who thinks they actually understand this kernel interface, can't
see it, how can you sell it to someone else?

Sorry, but no, I'm still not going to take this series until you show
some _REAL_ benefit for it.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-13  9:05           ` Greg KH
@ 2017-06-13 10:31             ` Rafał Miłecki
  2017-06-13 13:17               ` Greg KH
  2017-06-13 19:40             ` Luis R. Rodriguez
  1 sibling, 1 reply; 98+ messages in thread
From: Rafał Miłecki @ 2017-06-13 10:31 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, wagi, dwmw2, arend.vanspriel, rjw, yi1.li,
	atull, moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel

On 2017-06-13 11:05, Greg KH wrote:
> On Mon, Jun 05, 2017 at 02:39:33PM -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.
> 
> Let's take a simple C function interface and make it a more complex
> data-driven interface that is impossible to understand and obviously
> understand how it is to be used and works!
> 
> :(
> 
> Seriously, why?  Why are we extending any of this at all?  This series
> adds a ton of new "features" and complexity, but for absolutely no 
> gain.
> 
> Oh, I take it back, you removed 29 lines from the iwlwifi driver.
> 
> That's still not worth it at all, you have yet to sell me on this whole
> complex beast.  I can't see why we need it, and if I, one of the few
> people who thinks they actually understand this kernel interface, can't
> see it, how can you sell it to someone else?
> 
> Sorry, but no, I'm still not going to take this series until you show
> some _REAL_ benefit for it.

FWIW I saw (or maybe still see?) a need to extend request_firmware* API 
to
allow silencing a warning if firmware file is missing.

I even sent a trivial patch adding support for this:
[PATCH V4 1/2] firmware: add more flexible request_firmware_async 
function
https://patchwork.kernel.org/patch/9588787/
(I think it still applies) but it got rejected due to Luis's big rework.

To be honest after seeing this big & more complex driver data API I just
gave up and decided I don't care about false problem reports that much 
:(

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-13 10:31             ` Rafał Miłecki
@ 2017-06-13 13:17               ` Greg KH
  2017-06-13 14:12                 ` Rafał Miłecki
  2017-06-13 15:32                 ` Luis R. Rodriguez
  0 siblings, 2 replies; 98+ messages in thread
From: Greg KH @ 2017-06-13 13:17 UTC (permalink / raw)
  To: Rafał Miłecki
  Cc: Luis R. Rodriguez, wagi, dwmw2, arend.vanspriel, rjw, yi1.li,
	atull, moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel

On Tue, Jun 13, 2017 at 12:31:04PM +0200, Rafał Miłecki wrote:
> On 2017-06-13 11:05, Greg KH wrote:
> > On Mon, Jun 05, 2017 at 02:39:33PM -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.
> > 
> > Let's take a simple C function interface and make it a more complex
> > data-driven interface that is impossible to understand and obviously
> > understand how it is to be used and works!
> > 
> > :(
> > 
> > Seriously, why?  Why are we extending any of this at all?  This series
> > adds a ton of new "features" and complexity, but for absolutely no gain.
> > 
> > Oh, I take it back, you removed 29 lines from the iwlwifi driver.
> > 
> > That's still not worth it at all, you have yet to sell me on this whole
> > complex beast.  I can't see why we need it, and if I, one of the few
> > people who thinks they actually understand this kernel interface, can't
> > see it, how can you sell it to someone else?
> > 
> > Sorry, but no, I'm still not going to take this series until you show
> > some _REAL_ benefit for it.
> 
> FWIW I saw (or maybe still see?) a need to extend request_firmware* API to
> allow silencing a warning if firmware file is missing.
> 
> I even sent a trivial patch adding support for this:
> [PATCH V4 1/2] firmware: add more flexible request_firmware_async function
> https://patchwork.kernel.org/patch/9588787/
> (I think it still applies) but it got rejected due to Luis's big rework.

Can you resend this series if it still does apply?

And what exact warning is this silencing?  Normally we want the warning
there, as that implies that something is wrong if the firmware file that
a driver is asking for is not present.  That way the user can know to go
fix it up, right?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-13 13:17               ` Greg KH
@ 2017-06-13 14:12                 ` Rafał Miłecki
  2017-06-13 15:32                 ` Luis R. Rodriguez
  1 sibling, 0 replies; 98+ messages in thread
From: Rafał Miłecki @ 2017-06-13 14:12 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, wagi, dwmw2, arend.vanspriel, rjw, yi1.li,
	atull, moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel

On 06/13/2017 03:17 PM, Greg KH wrote:
> On Tue, Jun 13, 2017 at 12:31:04PM +0200, Rafał Miłecki wrote:
>> On 2017-06-13 11:05, Greg KH wrote:
>>> On Mon, Jun 05, 2017 at 02:39:33PM -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.
>>>
>>> Let's take a simple C function interface and make it a more complex
>>> data-driven interface that is impossible to understand and obviously
>>> understand how it is to be used and works!
>>>
>>> :(
>>>
>>> Seriously, why?  Why are we extending any of this at all?  This series
>>> adds a ton of new "features" and complexity, but for absolutely no gain.
>>>
>>> Oh, I take it back, you removed 29 lines from the iwlwifi driver.
>>>
>>> That's still not worth it at all, you have yet to sell me on this whole
>>> complex beast.  I can't see why we need it, and if I, one of the few
>>> people who thinks they actually understand this kernel interface, can't
>>> see it, how can you sell it to someone else?
>>>
>>> Sorry, but no, I'm still not going to take this series until you show
>>> some _REAL_ benefit for it.
>>
>> FWIW I saw (or maybe still see?) a need to extend request_firmware* API to
>> allow silencing a warning if firmware file is missing.
>>
>> I even sent a trivial patch adding support for this:
>> [PATCH V4 1/2] firmware: add more flexible request_firmware_async function
>> https://patchwork.kernel.org/patch/9588787/
>> (I think it still applies) but it got rejected due to Luis's big rework.
>
> Can you resend this series if it still does apply?

Sure, if you think it's worth trying, I'll do that!


> And what exact warning is this silencing?  Normally we want the warning
> there, as that implies that something is wrong if the firmware file that
> a driver is asking for is not present.  That way the user can know to go
> fix it up, right?

It's because brcmfmac looks for NVRAM in two places: /lib/firmware/ and
platform NVRAM. It's supposed to silence
[   10.801506] brcmfmac 0000:01:00.0: Direct firmware load for brcm/brcmfmac43602-pcie.txt failed with error -2
in case there is platform NVRAM present.

For more details please take a look at:
[PATCH V4 2/2] brcmfmac: don't warn user about NVRAM if fallback to platform one succeeds
https://patchwork.kernel.org/patch/9588791/

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-13 13:17               ` Greg KH
  2017-06-13 14:12                 ` Rafał Miłecki
@ 2017-06-13 15:32                 ` Luis R. Rodriguez
  2017-06-13 15:50                   ` Greg KH
  1 sibling, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-13 15:32 UTC (permalink / raw)
  To: Greg KH
  Cc: Rafał Miłecki, Luis R. Rodriguez, wagi, dwmw2,
	arend.vanspriel, rjw, yi1.li, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	torvalds, keescook, takahiro.akashi, dhowells, pjones, hdegoede,
	alan, tytso, linux-kernel

On Tue, Jun 13, 2017 at 03:17:43PM +0200, Greg KH wrote:
> On Tue, Jun 13, 2017 at 12:31:04PM +0200, Rafał Miłecki wrote:
> > On 2017-06-13 11:05, Greg KH wrote:
> > > On Mon, Jun 05, 2017 at 02:39:33PM -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.
> > > 
> > > Let's take a simple C function interface and make it a more complex
> > > data-driven interface that is impossible to understand and obviously
> > > understand how it is to be used and works!
> > > 
> > > :(
> > > 
> > > Seriously, why?  Why are we extending any of this at all?  This series
> > > adds a ton of new "features" and complexity, but for absolutely no gain.
> > > 
> > > Oh, I take it back, you removed 29 lines from the iwlwifi driver.
> > > 
> > > That's still not worth it at all, you have yet to sell me on this whole
> > > complex beast.  I can't see why we need it, and if I, one of the few
> > > people who thinks they actually understand this kernel interface, can't
> > > see it, how can you sell it to someone else?
> > > 
> > > Sorry, but no, I'm still not going to take this series until you show
> > > some _REAL_ benefit for it.
> > 
> > FWIW I saw (or maybe still see?) a need to extend request_firmware* API to
> > allow silencing a warning if firmware file is missing.
> > 
> > I even sent a trivial patch adding support for this:
> > [PATCH V4 1/2] firmware: add more flexible request_firmware_async function
> > https://patchwork.kernel.org/patch/9588787/
> > (I think it still applies) but it got rejected due to Luis's big rework.
> 
> Can you resend this series if it still does apply?

FWIW just some notes on Rafał's series:

someone else brought up second that his second no longer should be applied as
some devices do need what seems to be today's optional request. Also note that
the approach follows the same I take, just struct a firmware_opts instead of
driver params... and it does not mesh up the old options as I did in my first
patch in this series.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-13 15:32                 ` Luis R. Rodriguez
@ 2017-06-13 15:50                   ` Greg KH
  0 siblings, 0 replies; 98+ messages in thread
From: Greg KH @ 2017-06-13 15:50 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Rafał Miłecki, wagi, dwmw2, arend.vanspriel, rjw,
	yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, torvalds,
	keescook, takahiro.akashi, dhowells, pjones, hdegoede, alan,
	tytso, linux-kernel

On Tue, Jun 13, 2017 at 05:32:49PM +0200, Luis R. Rodriguez wrote:
> On Tue, Jun 13, 2017 at 03:17:43PM +0200, Greg KH wrote:
> > On Tue, Jun 13, 2017 at 12:31:04PM +0200, Rafał Miłecki wrote:
> > > On 2017-06-13 11:05, Greg KH wrote:
> > > > On Mon, Jun 05, 2017 at 02:39:33PM -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.
> > > > 
> > > > Let's take a simple C function interface and make it a more complex
> > > > data-driven interface that is impossible to understand and obviously
> > > > understand how it is to be used and works!
> > > > 
> > > > :(
> > > > 
> > > > Seriously, why?  Why are we extending any of this at all?  This series
> > > > adds a ton of new "features" and complexity, but for absolutely no gain.
> > > > 
> > > > Oh, I take it back, you removed 29 lines from the iwlwifi driver.
> > > > 
> > > > That's still not worth it at all, you have yet to sell me on this whole
> > > > complex beast.  I can't see why we need it, and if I, one of the few
> > > > people who thinks they actually understand this kernel interface, can't
> > > > see it, how can you sell it to someone else?
> > > > 
> > > > Sorry, but no, I'm still not going to take this series until you show
> > > > some _REAL_ benefit for it.
> > > 
> > > FWIW I saw (or maybe still see?) a need to extend request_firmware* API to
> > > allow silencing a warning if firmware file is missing.
> > > 
> > > I even sent a trivial patch adding support for this:
> > > [PATCH V4 1/2] firmware: add more flexible request_firmware_async function
> > > https://patchwork.kernel.org/patch/9588787/
> > > (I think it still applies) but it got rejected due to Luis's big rework.
> > 
> > Can you resend this series if it still does apply?
> 
> FWIW just some notes on Rafał's series:
> 
> someone else brought up second that his second no longer should be applied as
> some devices do need what seems to be today's optional request. Also note that
> the approach follows the same I take, just struct a firmware_opts instead of
> driver params... and it does not mesh up the old options as I did in my first
> patch in this series.

As I have no idea what his series looks like at the moment, why not wait
until they are posted again to review them? :)

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-13  9:05           ` Greg KH
  2017-06-13 10:31             ` Rafał Miłecki
@ 2017-06-13 19:40             ` Luis R. Rodriguez
  2017-06-14 15:57               ` Li, Yi
  2017-06-17 19:38               ` Greg KH
  1 sibling, 2 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-13 19:40 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, wagi, dwmw2, rafal, arend.vanspriel, rjw,
	yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, torvalds,
	keescook, takahiro.akashi, dhowells, pjones, hdegoede, alan,
	tytso, linux-kernel

On Tue, Jun 13, 2017 at 11:05:48AM +0200, Greg KH wrote:
> On Mon, Jun 05, 2017 at 02:39:33PM -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.
> 
> Let's take a simple C function interface and make it a more complex
> data-driven interface that is impossible to understand and obviously
> understand how it is to be used and works!

The firmware codebase was already complex!

What you have to ask yourself really is if this makes it *less complex* and
helps *clean things up* in a much better way than it was before. Also does it
allow us to *pave the way for new functionality easily*, without creating
further mess?

If not, what concrete alternatives do you suggest?

> :(
> 
> Seriously, why?  Why are we extending any of this at all? 

Addressing easy extensibility was a prerequisite of considering firmware signing
support. Its really the *only* reason I started reviewing the firmware API, to the
point I started helping fix quite a bit of bugs and now just became the maintainer.

So my original firmware signing effort, now driven by AKASHI Takahiro, was one
of the original motivations here!

I asked:

	How we should properly extend the API for new functionality in *a real
	clean way* ? This also means cleaning under the rug!

Ease of extensibility is a need we have as otherwise we end up in the same
situation as before, we end up having to add new functionality by extending the
number of arguments to an sync or async call, and as collateral have to changes
*tons* of callers. I think you and I agree on this, correct me if I'm wrong?

Another issue to address when deciding when there is merit for a new API call,
instead of just folding functionality into flags. For this its best consider
not only the future but also what was done in the past given some new desirable
functionality relies on existing mechanisms.

The approach Rafał took does what I do just that it forgets about cleaning
underneath the rug, so I really cannot see how its any different. Case in
point, we have functionality in place today to support no-cache, yet this is a
hidden feature, used implicitly only for one API call, there has been requests
to support the no-cache functionality by Johannes for iwlwifi long ago for the
async call given *they* do their own caching.  How do you suggest do we take
the no-cache functionality used only internally for another API call and apply
it for async?  Do we make a new exported symbol ? Or do we expose this as a
flag on a regular async call?  With the approach I take we expose the internal
functionality clearly and later Li Yi folds it as public API through a
parameter for the async API. Later he extends iwlwifi with just 2 lines of code
to get this functionality. This would later also be used by some other
functionality he's interested in!

Another good example is that the optional call request_firmware_direct() is for
sync, but async also has the same need --  do we add a new API call just for
the same purpose or do we fold some of this functionality as flags?

How do we properly keep evolving functionality for the firmware API? We have
to consider existing features, perhaps some hidden, and new features coming
down the pipe line.

Please take the time to think about all this for a bit, not only the past but
also all the incoming set of features in the pipeline! The FPGA streaming
support for instance reuses the no-cache "internal hidden feature", as well as
the existing request_firmware_into_buf() with some slight variations!  We could
just fold both requirements as parameters.

> This series adds a ton of new "features"

Actually, it *purposely* only folded the optional nature of firmware for async
and added support for enabling drivers to daisy chain requests using a simple
api numbering scheme as a counter to complex internal driver mechanisms --
Intel's solution actually used recursion. I split out this work on its own
series after a long history of working on firmware singing support. I'm now
also cleaning under the rug.

I purposely limited the amount of "features" to get proper review.

> and complexity,

The difficulty to properly understand the firmware code *was there before*, so
I really do believe its unfair for you to say what I have come up with is
complex, question is if its less complex, and *easier* to understand than what
we had before. I'd argue it is easier to understand given 2 developers have
been working off on top of it now and seem to grok it rather well.

Today's code base's complexity is one of the reasons we also we have had quite
a bit of regressions. One of the other issues was lack of proper documentation,
which I have been fixing as of late, but also clearly documenting the purpose
and limitations each new feature. The confusing #ifef'ery over some of the flag
options was also not helpful. This series did away with all that in preference
for annotating we have really 2 modes of operations with a set of features
which can be described in a proper structure, while also driving functionality
on par with functional testing.

> but for absolutely no gain.

I thought I had made the above problem pretty clear before, during some of the
last 9 iteration of the driver data API. Apologies if it was not.

The goal is to enable us to provide an easily extensible API to avoid
unnecessary collateral evolutions. It was only a subset goal of the firmware
signing effort. This in turn also drove me to consider how to clean under the
rug.

Where does one draws the line of a split of an API? After studying the code a
lot I chose that fine line between sync and async. Some even suggested we do
away with this and go with *one* API call, after all an async call really is
nothing more than a sync call with a schedule worker, however knowing easily if
a piece of code blocks or not in a more direct way in the kernel through seemed
important enough for me to draw a clear API distinction.

> Oh, I take it back, you removed 29 lines from the iwlwifi driver.

This I did simply to demo the gain of *one* possible feature.

I could have done something else. For instance, iwlwifi also can use the
no-cache feature. Do we expose a new async call for that alone? There is
also the firmware signing work which tons of people keep asking for.

If I would have bundled all pending features up into one series you would
have told me to break them down. This is why I just demo'd *one* use case.

The rest is in the pipeline, and folks are posting patches based on them.  I
ask you consider all that functionality in light of what we have today and what
that would look like today with our historical approach.

If you look at Yi Li's patchset for the no-cache feature you'd see that when
exposing *old internal functionality* its rather easy and straight forward to
review and understand [0], as another example consider then Yi LI's next
patchset which adds streaming support for FPGAs using the driver data API [1].

So the rest of the "features" you should consider would come with time as with
Yi Li or AKASHI's firmware signing patches [2]. I just added *two* example new
features to demo this. There rest should be considered by getting an idea
of how the API grows and compares to alternatives we have been practicing over
time.

[0] https://lkml.kernel.org/r/1495262819-981-1-git-send-email-yi1.li@linux.intel.com
[1] https://lkml.kernel.org/r/1495262948-1106-4-git-send-email-yi1.li@linux.intel.com
[2] https://lkml.kernel.org/r/20170526030609.1414-1-takahiro.akashi@linaro.org

> That's still not worth it at all, you have yet to sell me on this whole
> complex beast.  I can't see why we need it, and if I, one of the few
> people who thinks they actually understand this kernel interface, can't
> see it, how can you sell it to someone else?

Perhaps you have not tried to clean the code and also not cleanly tried to
extend it with all the functionality I have added. I invite you to do so, and
try accomplish the same I have in this series. Naturally I believe some part of
a different outcome it will be due to subjectivity, and if not I'd like to hear
very specific issues with what I propose.

> Sorry, but no, I'm still not going to take this series until you show
> some _REAL_ benefit for it.

I hope what I describe above helps.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-13 19:40             ` Luis R. Rodriguez
@ 2017-06-14 15:57               ` Li, Yi
  2017-06-17 19:38               ` Greg KH
  1 sibling, 0 replies; 98+ messages in thread
From: Li, Yi @ 2017-06-14 15:57 UTC (permalink / raw)
  To: Luis R. Rodriguez, Greg KH
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, atull, moritz.fischer,
	pmladek, johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo,
	luto, torvalds, keescook, takahiro.akashi, dhowells, pjones,
	hdegoede, alan, tytso, linux-kernel



On 6/13/2017 2:40 PM, Luis R. Rodriguez wrote:
> On Tue, Jun 13, 2017 at 11:05:48AM +0200, Greg KH wrote:
>> On Mon, Jun 05, 2017 at 02:39:33PM -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.
>>
>> Let's take a simple C function interface and make it a more complex
>> data-driven interface that is impossible to understand and obviously
>> understand how it is to be used and works!
> 
> The firmware codebase was already complex!
> 
> What you have to ask yourself really is if this makes it *less complex* and
> helps *clean things up* in a much better way than it was before. Also does it
> allow us to *pave the way for new functionality easily*, without creating
> further mess?
> 
> If not, what concrete alternatives do you suggest?
> 
>> :(
>>
>> Seriously, why?  Why are we extending any of this at all?
> 
> Addressing easy extensibility was a prerequisite of considering firmware signing
> support. Its really the *only* reason I started reviewing the firmware API, to the
> point I started helping fix quite a bit of bugs and now just became the maintainer.
> 
> So my original firmware signing effort, now driven by AKASHI Takahiro, was one
> of the original motivations here!
> 
> I asked:
> 
> 	How we should properly extend the API for new functionality in *a real
> 	clean way* ? This also means cleaning under the rug!
> 
> Ease of extensibility is a need we have as otherwise we end up in the same
> situation as before, we end up having to add new functionality by extending the
> number of arguments to an sync or async call, and as collateral have to changes
> *tons* of callers. I think you and I agree on this, correct me if I'm wrong?
> 
> Another issue to address when deciding when there is merit for a new API call,
> instead of just folding functionality into flags. For this its best consider
> not only the future but also what was done in the past given some new desirable
> functionality relies on existing mechanisms.
> 
> The approach Rafał took does what I do just that it forgets about cleaning
> underneath the rug, so I really cannot see how its any different. Case in
> point, we have functionality in place today to support no-cache, yet this is a
> hidden feature, used implicitly only for one API call, there has been requests
> to support the no-cache functionality by Johannes for iwlwifi long ago for the
> async call given *they* do their own caching.  How do you suggest do we take
> the no-cache functionality used only internally for another API call and apply
> it for async?  Do we make a new exported symbol ? Or do we expose this as a
> flag on a regular async call?  With the approach I take we expose the internal
> functionality clearly and later Li Yi folds it as public API through a
> parameter for the async API. Later he extends iwlwifi with just 2 lines of code
> to get this functionality. This would later also be used by some other
> functionality he's interested in!

Echo, the firmware caching part is indeed complicated. :) Taking example 
of the 10 seconds rule of uncaching, bad things could happen if the end 
drivers could not complete the programming within the time limit or the 
firmware files was removed from filesystem. It might be safer to let end 
drivers to free the cache? Anyway it should only be enabled by those 
drivers need to re-program firmware during suspend/resume instead of as 
an default option, which is expensive for the base firmware class to do 
on each PM cycle.

I do like the concept of expending features as parameters instead of 
adding new API functions.

> 
> Another good example is that the optional call request_firmware_direct() is for
> sync, but async also has the same need --  do we add a new API call just for
> the same purpose or do we fold some of this functionality as flags?
> 
> How do we properly keep evolving functionality for the firmware API? We have
> to consider existing features, perhaps some hidden, and new features coming
> down the pipe line.
> 
> Please take the time to think about all this for a bit, not only the past but
> also all the incoming set of features in the pipeline! The FPGA streaming
> support for instance reuses the no-cache "internal hidden feature", as well as
> the existing request_firmware_into_buf() with some slight variations!  We could
> just fold both requirements as parameters.
> 
>> This series adds a ton of new "features"
> 
> Actually, it *purposely* only folded the optional nature of firmware for async
> and added support for enabling drivers to daisy chain requests using a simple
> api numbering scheme as a counter to complex internal driver mechanisms --
> Intel's solution actually used recursion. I split out this work on its own
> series after a long history of working on firmware singing support. I'm now
> also cleaning under the rug.
> 
> I purposely limited the amount of "features" to get proper review.
> 
>> and complexity,
> 
> The difficulty to properly understand the firmware code *was there before*, so
> I really do believe its unfair for you to say what I have come up with is
> complex, question is if its less complex, and *easier* to understand than what
> we had before. I'd argue it is easier to understand given 2 developers have
> been working off on top of it now and seem to grok it rather well.
> 
> Today's code base's complexity is one of the reasons we also we have had quite
> a bit of regressions. One of the other issues was lack of proper documentation,
> which I have been fixing as of late, but also clearly documenting the purpose
> and limitations each new feature. The confusing #ifef'ery over some of the flag
> options was also not helpful. This series did away with all that in preference
> for annotating we have really 2 modes of operations with a set of features
> which can be described in a proper structure, while also driving functionality
> on par with functional testing.
> 
>> but for absolutely no gain.
> 
> I thought I had made the above problem pretty clear before, during some of the
> last 9 iteration of the driver data API. Apologies if it was not.
> 
> The goal is to enable us to provide an easily extensible API to avoid
> unnecessary collateral evolutions. It was only a subset goal of the firmware
> signing effort. This in turn also drove me to consider how to clean under the
> rug.
> 
> Where does one draws the line of a split of an API? After studying the code a
> lot I chose that fine line between sync and async. Some even suggested we do
> away with this and go with *one* API call, after all an async call really is
> nothing more than a sync call with a schedule worker, however knowing easily if
> a piece of code blocks or not in a more direct way in the kernel through seemed
> important enough for me to draw a clear API distinction.
> 
>> Oh, I take it back, you removed 29 lines from the iwlwifi driver.
> 
> This I did simply to demo the gain of *one* possible feature.
> 
> I could have done something else. For instance, iwlwifi also can use the
> no-cache feature. Do we expose a new async call for that alone? There is
> also the firmware signing work which tons of people keep asking for.
> 
> If I would have bundled all pending features up into one series you would
> have told me to break them down. This is why I just demo'd *one* use case.
> 
> The rest is in the pipeline, and folks are posting patches based on them.  I
> ask you consider all that functionality in light of what we have today and what
> that would look like today with our historical approach.
> 
> If you look at Yi Li's patchset for the no-cache feature you'd see that when
> exposing *old internal functionality* its rather easy and straight forward to
> review and understand [0], as another example consider then Yi LI's next
> patchset which adds streaming support for FPGAs using the driver data API [1].
> 
> So the rest of the "features" you should consider would come with time as with
> Yi Li or AKASHI's firmware signing patches [2]. I just added *two* example new
> features to demo this. There rest should be considered by getting an idea
> of how the API grows and compares to alternatives we have been practicing over
> time.
> 
> [0] https://lkml.kernel.org/r/1495262819-981-1-git-send-email-yi1.li@linux.intel.com
> [1] https://lkml.kernel.org/r/1495262948-1106-4-git-send-email-yi1.li@linux.intel.com
> [2] https://lkml.kernel.org/r/20170526030609.1414-1-takahiro.akashi@linaro.org
> 
>> That's still not worth it at all, you have yet to sell me on this whole
>> complex beast.  I can't see why we need it, and if I, one of the few
>> people who thinks they actually understand this kernel interface, can't
>> see it, how can you sell it to someone else?
> 
> Perhaps you have not tried to clean the code and also not cleanly tried to
> extend it with all the functionality I have added. I invite you to do so, and
> try accomplish the same I have in this series. Naturally I believe some part of
> a different outcome it will be due to subjectivity, and if not I'd like to hear
> very specific issues with what I propose.
> 
>> Sorry, but no, I'm still not going to take this series until you show
>> some _REAL_ benefit for it.
> 
> I hope what I describe above helps.
> 
>    Luis
> 

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-13 19:40             ` Luis R. Rodriguez
  2017-06-14 15:57               ` Li, Yi
@ 2017-06-17 19:38               ` Greg KH
  2017-06-19  7:33                 ` Johannes Berg
                                   ` (3 more replies)
  1 sibling, 4 replies; 98+ messages in thread
From: Greg KH @ 2017-06-17 19:38 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel

On Tue, Jun 13, 2017 at 09:40:11PM +0200, Luis R. Rodriguez wrote:
> On Tue, Jun 13, 2017 at 11:05:48AM +0200, Greg KH wrote:
> > On Mon, Jun 05, 2017 at 02:39:33PM -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.
> > 
> > Let's take a simple C function interface and make it a more complex
> > data-driven interface that is impossible to understand and obviously
> > understand how it is to be used and works!
> 
> The firmware codebase was already complex!

Heh, I'm not arguing with you there :)

> What you have to ask yourself really is if this makes it *less complex* and
> helps *clean things up* in a much better way than it was before. Also does it
> allow us to *pave the way for new functionality easily*, without creating
> further mess?

I agree, that's what I'm saying here.  I just do not see that happening
with your patch set at all.  It's adding more code, a more complex way
to interact with the subsystem, and not making driver writer lives any
easier at all that I can see.

Again, the code is now bigger, does more, with not even any real benefit
for existing users.

> If not, what concrete alternatives do you suggest?

It's working, so leave it alone?  :)

> > :(
> > 
> > Seriously, why?  Why are we extending any of this at all? 
> 
> Addressing easy extensibility was a prerequisite of considering firmware signing
> support. Its really the *only* reason I started reviewing the firmware API, to the
> point I started helping fix quite a bit of bugs and now just became the maintainer.
> 
> So my original firmware signing effort, now driven by AKASHI Takahiro, was one
> of the original motivations here!

But we don't accept kernel patches for some mythical future option that
might be happening some time in the future.  Heck, I'm still not
convinced that firmware signing isn't anything more than just some
snakeoil in the first place!

So while you mention lots of times that all sorts of wonderful things
can now possibly be built on top of the new code, I have yet to see it
(meaning you didn't include it in the patch series.)

To get me to take these changes, you have to show a real need and user
of the code.  Without that it just strongly looks like you are having
fun making a more complex api for no reason that we are then going to be
stuck with maintaining.

So clean away, and fix up, but remember, you have to be able to justify
each change as being needed.  And so far, I'm not sold on this at all,
sorry.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-17 19:38               ` Greg KH
@ 2017-06-19  7:33                 ` Johannes Berg
  2017-06-19 19:41                   ` Luis R. Rodriguez
  2017-06-19 19:35                 ` Luis R. Rodriguez
                                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 98+ messages in thread
From: Johannes Berg @ 2017-06-19  7:33 UTC (permalink / raw)
  To: Greg KH, Luis R. Rodriguez
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, emmanuel.grumbach, luciano.coelho,
	kvalo, luto, torvalds, keescook, takahiro.akashi, dhowells,
	pjones, hdegoede, alan, tytso, linux-kernel

On Sat, 2017-06-17 at 21:38 +0200, Greg KH wrote:

> But we don't accept kernel patches for some mythical future option
> that might be happening some time in the future.  Heck, I'm still not
> convinced that firmware signing isn't anything more than just some
> snakeoil in the first place!

I for one really want the "firmware" signing, because I want to load
the regulatory database through this API, and 

But honestly, I've been waiting for years for that now and started
looking at what it would take to hand-implement that on top of the
existing firmware API. Probably not all that much.

johannes

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-17 19:38               ` Greg KH
  2017-06-19  7:33                 ` Johannes Berg
@ 2017-06-19 19:35                 ` Luis R. Rodriguez
  2017-06-23 15:51                   ` Greg KH
  2017-06-23 15:59                   ` Greg KH
  2017-06-19 22:51                 ` Li, Yi
  2017-06-20 16:27                 ` Vikram Mulukutla
  3 siblings, 2 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-19 19:35 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, wagi, dwmw2, rafal, arend.vanspriel, rjw,
	yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, torvalds,
	keescook, takahiro.akashi, dhowells, pjones, hdegoede, alan,
	tytso, linux-kernel

On Sat, Jun 17, 2017 at 09:38:15PM +0200, Greg KH wrote:
> On Tue, Jun 13, 2017 at 09:40:11PM +0200, Luis R. Rodriguez wrote:
> > On Tue, Jun 13, 2017 at 11:05:48AM +0200, Greg KH wrote:
> > > On Mon, Jun 05, 2017 at 02:39:33PM -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.
> > > 
> > > Let's take a simple C function interface and make it a more complex
> > > data-driven interface that is impossible to understand and obviously
> > > understand how it is to be used and works!
> > 
> > The firmware codebase was already complex!
> 
> Heh, I'm not arguing with you there :)

Great!

> > What you have to ask yourself really is if this makes it *less complex* and
> > helps *clean things up* in a much better way than it was before. Also does it
> > allow us to *pave the way for new functionality easily*, without creating
> > further mess?
> 
> I agree, that's what I'm saying here.  I just do not see that happening
> with your patch set at all.  It's adding more code, a more complex way
> to interact with the subsystem, and not making driver writer lives any
> easier at all that I can see.

There are two things to consider:

a) The current design of the firmware API, and interfaces with exported symbols

The case for the driver data API was that we were being super sloppy with extensions,
to the point was making the internal code base very bug prone and full or redirect
conditionals with #ifdefery nightmware stuff.
   
b) Features of the firmware API

These have to be evaluated on a case by case basis.

> Again, the code is now bigger, does more, with not even any real benefit
> for existing users.

Obviously I disagree strongly, in light of the history of the code. Not only
should the existing code be compared but also how it has evolved and we should
evaluate whether or not its evolution can be dealt with more appropriately.

> > If not, what concrete alternatives do you suggest?
> 
> It's working, so leave it alone?  :)

As the maintainer of the firmware API I **totally** disagree!!

First the firmware API has been evolving as pieces of gum thrown at it. A
proper design of the API in consideration of extensions is in order.  Likewise
goes for testing and documentation. Second, it cannot be said with any serious
tone that the current design has worked well.  It is simply not an accurate
reflection of the codebase. The design of the fallback mechanism and the amount
of issues that have gone through it is a great example.

The issues I have fixed along the way, and the chaotic way in which the API
has evolved have put me to reflect well on a proper design of the firmware API.

> > > :(
> > > 
> > > Seriously, why?  Why are we extending any of this at all? 
> > 
> > Addressing easy extensibility was a prerequisite of considering firmware signing
> > support. Its really the *only* reason I started reviewing the firmware API, to the
> > point I started helping fix quite a bit of bugs and now just became the maintainer.
> > 
> > So my original firmware signing effort, now driven by AKASHI Takahiro, was one
> > of the original motivations here!
> 
> But we don't accept kernel patches for some mythical future option that
> might be happening some time in the future.

Greg, these are non mythical "future options" -- they are real feature requests
and they have all valid reasons and discussions ongoing on the mailing list. The
streaming FPGA support is a concrete valid use case we need to extend *now* and
your reply brilliantly seems to have completely ignored it.

I do agree that firmware signing itself was a largely debatable topic, there
were *really-really* long threads on the topic. However we had a hallway track
at Santa Fe Plumbers where it seemed we reached consensus on a path forward!
The firmware signing topic should be addressed on TAKASHI's patch set [0]. Please
address your NACK and reasons there. Even though it is based on the driver
data API, the main topic points of *logic* for it can *also* be discussed
there.

BUT NOTE:

Before we could move forward with firmware signing we need also a *sane* way to
extend the API to address the needs of firmware signing. So a proper sane API
for future extensions is in order as a prerequisite. This goes along with
testing and documentation.

This goes for *any* new firmware API feature!!!

Firmware signing was just one example feature!!!

Also -- even if they were "mythical" the *point* of this series is to address
extensibility of the API, and so one of the ways to measure the gains of the
design of a new API is to look at what the code would look like when new
features are added. This is why I pointed out to review these proposed changes.

Even more so given I am noting that these are non-mythical features!!

[0] https://lkml.kernel.org/r/20170526030609.1414-1-takahiro.akashi@linaro.org

> Heck, I'm still not convinced that firmware signing isn't anything more than
> just some snakeoil in the first place!

Sure, some may have similar sentiments, this topic should be addressed
separately on TAKASHI's latest patches [0]. Please address this on the patches
posted, Otherwise I really am afraid we would conflating discussions and
confusing the core issue on this thread:

   The future direction of extensibility of the firmware API!

> So while you mention lots of times that all sorts of wonderful things
> can now possibly be built on top of the new code, I have yet to see it
> (meaning you didn't include it in the patch series.)

Clearly I disagree, but perhaps the issue here may be conflating "addressing
extensibility of the firmware API" with actual features.

> To get me to take these changes, you have to show a real need and user
> of the code.

The *core issue* here seems to be that the features for which we have had to
consider new extensions of the firmware API have been debatable, so my arguments
for proper design are being conflated with the features introduced at the time
the redesign is introduced.

The driver data API was originally introduced with firmware signing, and the
topic of firmware signing alone was a hugely debatable topic. Even though I
note those debatable issues were resolved its fair to still question them, but
those must be addressed separately.

For this reason I looked at the next feature on my radar to consider which we
needed which was non-debatable. The next closest feature was that of making
async firmware requests optional, just as request_firmware_direct() exist for
sync requests.  Unfortunately the *need* for this *just disappeared* given that
it was clarified by Hans de Geode that the driver that *needed* this, brcmfmac,
no longer optionally needs the firmware [1]. This is precisely why I dropped
the respective brcmfmac changes in my last iteration of the driver data API.
Even though I dropped the actual driver use, I kept the feature as its
obviously sensible!

But anyway, my point is that this *reason* vanished from upstream all of a
sudden...

Next, as the firmware maintainer, I knew addressing daisy chained requests for
a series of firmware revisions was another *good idea* to address upstream
properly, given this can be complex and the amount of issues that could lurk
there.  You have foo driver which supports a series of ranges of API firmware
X-Y, we should allow for an API which enables this in a easy way.  A good
example complex case was in the iwlwifi driver, it actually uses recursion to
accomplish the above objectives. Despite the recursion design, their layout for
expectations for the file names for a series of API revisions seems very
sensible to me. Its a flexible layout I think we can stick to and support
generically. As such I implemented this on the firmware API and made the
implementation deterministic, avoiding recursion.

This feature alone should be weighed on its own. While I do agree it would seem
to appear we only have *one upstream* user right now, I cannot imagine that
this is generally true, daisy chaining requests on a series of revisions seems
to me a logical approach and I expect much more users may exist, its just a
matter hunting and conversion.

You may argue that *one* upstream users is not sufficient to introduce a new
feature for, but I disagree given we have had new full *API* added for a new
feature on the firmware API even for drivers THAT ARE NOT UPSTREAM! For
instance request_firmware_into_buf() has no upstream users!!!

Now, you might say that even though this is true that there many users of
out-of-tree drivers that need this. While true, if this is the bar we'd go
with, we can't then ignore the iwlwifi userbase, and the possible gains of
having a proper non-recursive use of the daisy chained requests.

Also, its *precisely* this loose API garbage such as what went in with
request_firmware_into_buf() which I'm trying to avoid. Loosely adding API with
hidden features is *not a good idea*, can lead to further misprogramming or odd
bugs. Worst it also paves the way to further sloppy extensions. For instance
the request_firmware_into_buf() API makes use of a no-cache feature which
we *also* can have a use for on *existing* drivers upstream such as iwlwifi
which deal with *caching on its own* ! Its also a requirement for streaming
API for FPGAs.

Also it should be considered that other than addressing the extensibility of
the firmware API, the driver data API intentionally strives to bundle specific
unit tests for *each* feature being introduced. This also goes with a philosophy
re-design of the test driver: instead of making *one* knob per test case in the
C test driver, I am designing the test case configuration completely in
userspace, striving to allow us to build *any* possible test case form
userspace.

[1] https://lkml.kernel.org/r/09063fc2-af77-ced6-ed90-ab20e2884969@redhat.com

> Without that it just strongly looks like you are having
> fun making a more complex api for no reason that we are then going to be
> stuck with maintaining.

Greg, this is rather insulting considering the amount of work I have put
into thought about proper design of the firmware API in consideration of
past issues and its rather loose grotesque evolution which I have been
alerting to and pointing out.

> So clean away, and fix up, but remember, you have to be able to justify
> each change as being needed.  And so far, I'm not sold on this at all,
> sorry.

I have put a lot of work into a proper design here in consideration of the
entire history of the firmware API. We *need* a proper architectural design
for the firmware API to evolve it in ways that does not allow folks to just
throw gum at it. We also want to build test units for each new feature and
ensure *each new feature* is properly documented. That is what I have provided
in this patch series.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-19  7:33                 ` Johannes Berg
@ 2017-06-19 19:41                   ` Luis R. Rodriguez
  2017-06-20  1:26                     ` AKASHI Takahiro
  0 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-19 19:41 UTC (permalink / raw)
  To: Johannes Berg
  Cc: Greg KH, Luis R. Rodriguez, wagi, dwmw2, rafal, arend.vanspriel,
	rjw, yi1.li, atull, moritz.fischer, pmladek, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel

On Mon, Jun 19, 2017 at 09:33:16AM +0200, Johannes Berg wrote:
> On Sat, 2017-06-17 at 21:38 +0200, Greg KH wrote:
> 
> > But we don't accept kernel patches for some mythical future option
> > that might be happening some time in the future.  Heck, I'm still not
> > convinced that firmware signing isn't anything more than just some
> > snakeoil in the first place!
> 
> I for one really want the "firmware" signing, because I want to load
> the regulatory database through this API, and 

This was my original goal as well... and it was also one of the reasons why
the API name change would be much better reflective of future possible uses.

> But honestly, I've been waiting for years for that now and started
> looking at what it would take to hand-implement that on top of the
> existing firmware API. Probably not all that much.

I had proposed changes to do just this long ago, without any new *API*, so we'd
support firmware signing just as we do with module signing. Simple!

It was during these discussions that we realized we actually *wanted* to have
the option to always specify requests with specific signing requirements from
the start, as such a flexible API became a prerequisite and so I prioritized
that work first.

Lets not ignore previous work and prior discussions then, the last effort on this
front was by AKASHI, and it'd be greatly appreciated if the topic of firmware
signing was specifically addressed on that thread there [0].

[0] https://lkml.kernel.org/r/20170526030609.1414-1-takahiro.akashi@linaro.org

 Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-17 19:38               ` Greg KH
  2017-06-19  7:33                 ` Johannes Berg
  2017-06-19 19:35                 ` Luis R. Rodriguez
@ 2017-06-19 22:51                 ` Li, Yi
  2017-06-20  1:48                   ` AKASHI Takahiro
  2017-06-20 16:27                 ` Vikram Mulukutla
  3 siblings, 1 reply; 98+ messages in thread
From: Li, Yi @ 2017-06-19 22:51 UTC (permalink / raw)
  To: Greg KH, Luis R. Rodriguez
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, atull, moritz.fischer,
	pmladek, johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo,
	luto, torvalds, keescook, takahiro.akashi, dhowells, pjones,
	hdegoede, alan, tytso, linux-kernel

Hi Greg,

On 6/17/2017 2:38 PM, Greg KH wrote:
> On Tue, Jun 13, 2017 at 09:40:11PM +0200, Luis R. Rodriguez wrote:
>> On Tue, Jun 13, 2017 at 11:05:48AM +0200, Greg KH wrote:
>>> On Mon, Jun 05, 2017 at 02:39:33PM -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.
>>>
>>> Let's take a simple C function interface and make it a more complex
>>> data-driven interface that is impossible to understand and obviously
>>> understand how it is to be used and works!
>>
>> The firmware codebase was already complex!
> 
> Heh, I'm not arguing with you there :)
> 
>> What you have to ask yourself really is if this makes it *less complex* and
>> helps *clean things up* in a much better way than it was before. Also does it
>> allow us to *pave the way for new functionality easily*, without creating
>> further mess?
> 
> I agree, that's what I'm saying here.  I just do not see that happening
> with your patch set at all.  It's adding more code, a more complex way
> to interact with the subsystem, and not making driver writer lives any
> easier at all that I can see.
> 
> Again, the code is now bigger, does more, with not even any real benefit
> for existing users.

I am still new to the upstreaming world, pardon me if my understanding 
is naive. :) My take with Luis's driver data API is that it adds a 
wrapper on top of the old request_firmware APIs, so the new features can 
be added/disabled by the parameters structures instead of 
adding/changing API functions. Agree that there is not much new for 
existing users. It adds more codes (not necessary more complex) but 
create a consistent way for extension IMO.

Below are 3 examples I tried to add streaming support to load large 
firmware files.
Adding streaming with driver data API: 
https://patchwork.kernel.org/patch/9738503 . This patch series depends on
non-cache patch series https://patchwork.kernel.org/patch/9793825 , 
which is bigger than it should be since it added some codes to test 
firmware caching.
and pre-allocate buffer patch series 
https://patchwork.kernel.org/patch/9738487/

By comparison, here is my old streaming RFC with original firmware 
class: https://lkml.org/lkml/2017/3/9/872
Do you think this is the better way?

Thanks,
Yi

> 
>> If not, what concrete alternatives do you suggest?
> 
> It's working, so leave it alone?  :)
> 
>>> :(
>>>
>>> Seriously, why?  Why are we extending any of this at all?
>>
>> Addressing easy extensibility was a prerequisite of considering firmware signing
>> support. Its really the *only* reason I started reviewing the firmware API, to the
>> point I started helping fix quite a bit of bugs and now just became the maintainer.
>>
>> So my original firmware signing effort, now driven by AKASHI Takahiro, was one
>> of the original motivations here!
> 
> But we don't accept kernel patches for some mythical future option that
> might be happening some time in the future.  Heck, I'm still not
> convinced that firmware signing isn't anything more than just some
> snakeoil in the first place!
> 
> So while you mention lots of times that all sorts of wonderful things
> can now possibly be built on top of the new code, I have yet to see it
> (meaning you didn't include it in the patch series.)
> 
> To get me to take these changes, you have to show a real need and user
> of the code.  Without that it just strongly looks like you are having
> fun making a more complex api for no reason that we are then going to be
> stuck with maintaining.
> 
> So clean away, and fix up, but remember, you have to be able to justify
> each change as being needed.  And so far, I'm not sold on this at all,
> sorry.
> 
> thanks,
> 
> greg k-h
> 

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-19 19:41                   ` Luis R. Rodriguez
@ 2017-06-20  1:26                     ` AKASHI Takahiro
  0 siblings, 0 replies; 98+ messages in thread
From: AKASHI Takahiro @ 2017-06-20  1:26 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Johannes Berg, Greg KH, wagi, dwmw2, rafal, arend.vanspriel, rjw,
	yi1.li, atull, moritz.fischer, pmladek, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, dhowells,
	pjones, hdegoede, alan, tytso, linux-kernel

On Mon, Jun 19, 2017 at 09:41:07PM +0200, Luis R. Rodriguez wrote:
> On Mon, Jun 19, 2017 at 09:33:16AM +0200, Johannes Berg wrote:
> > On Sat, 2017-06-17 at 21:38 +0200, Greg KH wrote:
> > 
> > > But we don't accept kernel patches for some mythical future option
> > > that might be happening some time in the future.  Heck, I'm still not
> > > convinced that firmware signing isn't anything more than just some
> > > snakeoil in the first place!
> > 
> > I for one really want the "firmware" signing, because I want to load
> > the regulatory database through this API, and 
> 
> This was my original goal as well... and it was also one of the reasons why
> the API name change would be much better reflective of future possible uses.
> 
> > But honestly, I've been waiting for years for that now and started
> > looking at what it would take to hand-implement that on top of the
> > existing firmware API. Probably not all that much.
> 
> I had proposed changes to do just this long ago, without any new *API*, so we'd
> support firmware signing just as we do with module signing. Simple!
> 
> It was during these discussions that we realized we actually *wanted* to have
> the option to always specify requests with specific signing requirements from
> the start, as such a flexible API became a prerequisite and so I prioritized
> that work first.
> 
> Lets not ignore previous work and prior discussions then, the last effort on this
> front was by AKASHI, and it'd be greatly appreciated if the topic of firmware
> signing was specifically addressed on that thread there [0].

+1
I always appreciate any comments from those who are for and against
my patch (or firmware signing in general) as well.

-Takahiro AKASHI


> [0] https://lkml.kernel.org/r/20170526030609.1414-1-takahiro.akashi@linaro.org
> 
>  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-19 22:51                 ` Li, Yi
@ 2017-06-20  1:48                   ` AKASHI Takahiro
  2017-06-20 15:20                     ` Li, Yi
  0 siblings, 1 reply; 98+ messages in thread
From: AKASHI Takahiro @ 2017-06-20  1:48 UTC (permalink / raw)
  To: Li, Yi
  Cc: Greg KH, Luis R. Rodriguez, wagi, dwmw2, rafal, arend.vanspriel,
	rjw, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, torvalds,
	keescook, dhowells, pjones, hdegoede, alan, tytso, linux-kernel

On Mon, Jun 19, 2017 at 05:51:08PM -0500, Li, Yi wrote:
> Hi Greg,
> 
> On 6/17/2017 2:38 PM, Greg KH wrote:
> >On Tue, Jun 13, 2017 at 09:40:11PM +0200, Luis R. Rodriguez wrote:
> >>On Tue, Jun 13, 2017 at 11:05:48AM +0200, Greg KH wrote:
> >>>On Mon, Jun 05, 2017 at 02:39:33PM -0700, Luis R. Rodriguez wrote:
> >
> >>What you have to ask yourself really is if this makes it *less complex* and
> >>helps *clean things up* in a much better way than it was before. Also does it
> >>allow us to *pave the way for new functionality easily*, without creating
> >>further mess?
> >
> >I agree, that's what I'm saying here.  I just do not see that happening
> >with your patch set at all.  It's adding more code, a more complex way
> >to interact with the subsystem, and not making driver writer lives any
> >easier at all that I can see.
> >
> >Again, the code is now bigger, does more, with not even any real benefit
> >for existing users.
> 
> I am still new to the upstreaming world, pardon me if my understanding is
> naive. :) My take with Luis's driver data API is that it adds a wrapper on
> top of the old request_firmware APIs, so the new features can be
> added/disabled by the parameters structures instead of adding/changing API
> functions. Agree that there is not much new for existing users. It adds more
> codes (not necessary more complex) but create a consistent way for extension
> IMO.

Most of code of my feature, firmware signing, is implemented in common
place between old and new APIs, while only new API has a parameter,
DRIVER_DATA_REQ_NO_SIG_CHECK, which allow users to enable/disable
this feature per-driver-datum. Simple enough.

So what matters is adding yet another variant of request_firmware_xx()
vs. adding a mere parameter?

Thanks,
-Takahiro AKASHI

> Below are 3 examples I tried to add streaming support to load large firmware
> files.
> Adding streaming with driver data API:
> https://patchwork.kernel.org/patch/9738503 . This patch series depends on
> non-cache patch series https://patchwork.kernel.org/patch/9793825 , which is
> bigger than it should be since it added some codes to test firmware caching.
> and pre-allocate buffer patch series
> https://patchwork.kernel.org/patch/9738487/
> 
> By comparison, here is my old streaming RFC with original firmware class:
> https://lkml.org/lkml/2017/3/9/872
> Do you think this is the better way?
> 
> Thanks,
> Yi

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-20  1:48                   ` AKASHI Takahiro
@ 2017-06-20 15:20                     ` Li, Yi
  0 siblings, 0 replies; 98+ messages in thread
From: Li, Yi @ 2017-06-20 15:20 UTC (permalink / raw)
  To: AKASHI Takahiro, Greg KH, Luis R. Rodriguez, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	torvalds, keescook, dhowells, pjones, hdegoede, alan, tytso,
	linux-kernel



On 6/19/2017 8:48 PM, AKASHI Takahiro wrote:
> On Mon, Jun 19, 2017 at 05:51:08PM -0500, Li, Yi wrote:
>> Hi Greg,
>>
>> On 6/17/2017 2:38 PM, Greg KH wrote:
>>> On Tue, Jun 13, 2017 at 09:40:11PM +0200, Luis R. Rodriguez wrote:
>>>> On Tue, Jun 13, 2017 at 11:05:48AM +0200, Greg KH wrote:
>>>>> On Mon, Jun 05, 2017 at 02:39:33PM -0700, Luis R. Rodriguez wrote:
>>>
>>>> What you have to ask yourself really is if this makes it *less complex* and
>>>> helps *clean things up* in a much better way than it was before. Also does it
>>>> allow us to *pave the way for new functionality easily*, without creating
>>>> further mess?
>>>
>>> I agree, that's what I'm saying here.  I just do not see that happening
>>> with your patch set at all.  It's adding more code, a more complex way
>>> to interact with the subsystem, and not making driver writer lives any
>>> easier at all that I can see.
>>>
>>> Again, the code is now bigger, does more, with not even any real benefit
>>> for existing users.
>>
>> I am still new to the upstreaming world, pardon me if my understanding is
>> naive. :) My take with Luis's driver data API is that it adds a wrapper on
>> top of the old request_firmware APIs, so the new features can be
>> added/disabled by the parameters structures instead of adding/changing API
>> functions. Agree that there is not much new for existing users. It adds more
>> codes (not necessary more complex) but create a consistent way for extension
>> IMO.
> 
> Most of code of my feature, firmware signing, is implemented in common
> place between old and new APIs, while only new API has a parameter,
> DRIVER_DATA_REQ_NO_SIG_CHECK, which allow users to enable/disable
> this feature per-driver-datum. Simple enough.

I meant to say more code does NOT necessary equal to more complex, sorry 
for the confusion.

> 
> So what matters is adding yet another variant of request_firmware_xx()
> vs. adding a mere parameter?

Agree, I also prefer the parameter approach.

> 
> Thanks,
> -Takahiro AKASHI
> 
>> Below are 3 examples I tried to add streaming support to load large firmware
>> files.
>> Adding streaming with driver data API:
>> https://patchwork.kernel.org/patch/9738503 . This patch series depends on
>> non-cache patch series https://patchwork.kernel.org/patch/9793825 , which is
>> bigger than it should be since it added some codes to test firmware caching.
>> and pre-allocate buffer patch series
>> https://patchwork.kernel.org/patch/9738487/
>>
>> By comparison, here is my old streaming RFC with original firmware class:
>> https://lkml.org/lkml/2017/3/9/872
>> Do you think this is the better way?
>>
>> Thanks,
>> Yi

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-17 19:38               ` Greg KH
                                   ` (2 preceding siblings ...)
  2017-06-19 22:51                 ` Li, Yi
@ 2017-06-20 16:27                 ` Vikram Mulukutla
  2017-06-20 17:22                   ` Luis R. Rodriguez
  3 siblings, 1 reply; 98+ messages in thread
From: Vikram Mulukutla @ 2017-06-20 16:27 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, wagi, dwmw2, rafal, arend.vanspriel, rjw,
	yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, torvalds,
	keescook, takahiro.akashi, dhowells, pjones, hdegoede, alan,
	tytso, linux-kernel, linux-kernel-owner


Hi Greg, Luis,

On 2017-06-17 12:38, Greg KH wrote:
> On Tue, Jun 13, 2017 at 09:40:11PM +0200, Luis R. Rodriguez wrote:
>> On Tue, Jun 13, 2017 at 11:05:48AM +0200, Greg KH wrote:
>> > On Mon, Jun 05, 2017 at 02:39:33PM -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.
>> >
>> > Let's take a simple C function interface and make it a more complex
>> > data-driven interface that is impossible to understand and obviously
>> > understand how it is to be used and works!
>> 
>> The firmware codebase was already complex!
> 
> Heh, I'm not arguing with you there :)
> 
>> What you have to ask yourself really is if this makes it *less 
>> complex* and
>> helps *clean things up* in a much better way than it was before. Also 
>> does it
>> allow us to *pave the way for new functionality easily*, without 
>> creating
>> further mess?
> 
> I agree, that's what I'm saying here.  I just do not see that happening
> with your patch set at all.  It's adding more code, a more complex way
> to interact with the subsystem, and not making driver writer lives any
> easier at all that I can see.
> 
> Again, the code is now bigger, does more, with not even any real 
> benefit
> for existing users.
> 
>> If not, what concrete alternatives do you suggest?
> 
> It's working, so leave it alone?  :)
> 

So I wanted to note here that I had a similar internal discussion with 
Stephen
Boyd when he first upstreamed the request_firmware_into_buf API. I 
actually did
change things a bit to pass around a 'desc' structure with all the flags
and parameters in our internal vendor tree [1]. This is a sort of an 
ultra-lite
version of what Luis is trying to do - extensibility - but does not 
change the API
for driver owners. Existing APIs become wrappers around the new API. My 
primary
reason then was the number of things being passed around between layers 
of functions
inside firmware_class seemed a bit untenable.

     Just like Greg, Stephen also brought up the argument about why the
unnecessary complication to the API without any measurable benefit - 
this seemed
a fair argument to me at that time. But here's my point - if Luis agrees 
that _this_
patchset is going slightly Mjolnir, perhaps a light internal rework 
inside
firmware_class might be more suitable towards the extensibility goal 
while keeping
driver API usage as simple as it is today (even if you hate my patch for 
various
reasons)?

Thanks,
Vikram

[1] - 
https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/drivers/base/firmware_class.c?h=msm-3.18&id=7aa7efd3c150840369739893a84bd1d9f9774319

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-20 16:27                 ` Vikram Mulukutla
@ 2017-06-20 17:22                   ` Luis R. Rodriguez
  2017-06-21  0:49                     ` AKASHI Takahiro
  0 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-20 17:22 UTC (permalink / raw)
  To: Vikram Mulukutla
  Cc: Greg KH, Luis R. Rodriguez, wagi, dwmw2, rafal, arend.vanspriel,
	rjw, yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, torvalds,
	keescook, takahiro.akashi, dhowells, pjones, hdegoede, alan,
	tytso, linux-kernel, linux-kernel-owner

On Tue, Jun 20, 2017 at 09:27:45AM -0700, Vikram Mulukutla wrote:
> 
> Hi Greg, Luis,
> 
> On 2017-06-17 12:38, Greg KH wrote:
> > On Tue, Jun 13, 2017 at 09:40:11PM +0200, Luis R. Rodriguez wrote:
> > > On Tue, Jun 13, 2017 at 11:05:48AM +0200, Greg KH wrote:
> > > > On Mon, Jun 05, 2017 at 02:39:33PM -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.
> > > >
> > > > Let's take a simple C function interface and make it a more complex
> > > > data-driven interface that is impossible to understand and obviously
> > > > understand how it is to be used and works!
> > > 
> > > The firmware codebase was already complex!
> > 
> > Heh, I'm not arguing with you there :)
> > 
> > > What you have to ask yourself really is if this makes it *less
> > > complex* and
> > > helps *clean things up* in a much better way than it was before.
> > > Also does it
> > > allow us to *pave the way for new functionality easily*, without
> > > creating
> > > further mess?
> > 
> > I agree, that's what I'm saying here.  I just do not see that happening
> > with your patch set at all.  It's adding more code, a more complex way
> > to interact with the subsystem, and not making driver writer lives any
> > easier at all that I can see.
> > 
> > Again, the code is now bigger, does more, with not even any real benefit
> > for existing users.
> > 
> > > If not, what concrete alternatives do you suggest?
> > 
> > It's working, so leave it alone?  :)
> > 
> 
> So I wanted to note here that I had a similar internal discussion with
> Stephen Boyd when he first upstreamed the request_firmware_into_buf API.

Thanks for sharing!

> I actually did change things a bit to pass around a 'desc' structure with all
> the flags and parameters in our internal vendor tree [1].

I'll note the "desc" approach and name was what I originally used also on my
earlier incarnations of the driver data API (back then the "sysdata API").

> This is a sort of
> an ultra-lite version of what Luis is trying to do - extensibility - but does
> not change the API for driver owners.

What I propose does not change the API for existing drivers either. The changes
I propose allows the flexibility both internally and externally but only
externally for newer APIs and features.

> Existing APIs become wrappers around
> the new API. My primary reason then was the number of things being passed
> around between layers of functions inside firmware_class seemed a bit
> untenable.

Yup!! Same applies to the public API!

> Just like Greg, Stephen also brought up the argument about why the
> unnecessary complication to the API without any measurable benefit - this
> seemed a fair argument to me at that time. But here's my point - if Luis
> agrees that _this_ patchset is going slightly Mjolnir, 

One of the issues you'd face with your changes is you are essentially diverging
now with *similar* cleanup but its *not upstream*, so fixes may require manual
manipulation! The place to address what you need is not a private vendor tree
but *upstream* otherwise your tree will diverge and can make it hard for you to
merge stable fixes or cherry pick enhancements.

The fact that folks are willing to go the such lengths as to *merge* side
internal cleanups to internal APIs should say a lot of confirming the need for
my own work.

I stand by this work as needed, I have been the one doing the heavy lifting of
addressing fixes, documenting it and looking at a real sensible way to extend
it, as such I really do believe its needed. I'm tired of seeing side hacks and
people throwing gum at things.

> perhaps a light
> internal rework inside firmware_class might be more suitable towards the
> extensibility goal while keeping driver API usage as simple as it is today
> (even if you hate my patch for various reasons)?
> 
> [1] - https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/drivers/base/firmware_class.c?h=msm-3.18&id=7aa7efd3c150840369739893a84bd1d9f9774319

What you did is but one step I took, your changes make it easier to shuffle
data around internally. Its not sufficient to clean things up well enough, for
instance look at the "firmware behavior options" which are cleaned up in this
patch 1/5. That's been one pain on our side for a while, people understanding
when a flag applies or a feature, and making sure we document it all.

Then, one of the end goals is to provide extensibility, this is to allow users
to *pass* similar type of struct for either a sync or async call. Without this
the API remains unflexible and I predict we'll end up with the same situation
later anyway.

The approach I took uses an internal struct for passing required data for the
private internal API use. Then it also provides a public struct which can be
used to grow requirements to make only *new* API easily extensible.

So we need all three things to move forward.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-20 17:22                   ` Luis R. Rodriguez
@ 2017-06-21  0:49                     ` AKASHI Takahiro
  2017-06-23 16:33                       ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: AKASHI Takahiro @ 2017-06-21  0:49 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Vikram Mulukutla, Greg KH, wagi, dwmw2, rafal, arend.vanspriel,
	rjw, yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, torvalds,
	keescook, dhowells, pjones, hdegoede, alan, tytso, linux-kernel,
	linux-kernel-owner

On Tue, Jun 20, 2017 at 07:22:19PM +0200, Luis R. Rodriguez wrote:
> On Tue, Jun 20, 2017 at 09:27:45AM -0700, Vikram Mulukutla wrote:
> > 
> > perhaps a light
> > internal rework inside firmware_class might be more suitable towards the
> > extensibility goal while keeping driver API usage as simple as it is today
> > (even if you hate my patch for various reasons)?
> > 
> > [1] - https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/drivers/base/firmware_class.c?h=msm-3.18&id=7aa7efd3c150840369739893a84bd1d9f9774319
> 
> What you did is but one step I took, your changes make it easier to shuffle
> data around internally. Its not sufficient to clean things up well enough, for
> instance look at the "firmware behavior options" which are cleaned up in this
> patch 1/5. That's been one pain on our side for a while, people understanding
> when a flag applies or a feature, and making sure we document it all.
> 
> Then, one of the end goals is to provide extensibility, this is to allow users
> to *pass* similar type of struct for either a sync or async call. Without this
> the API remains unflexible and I predict we'll end up with the same situation
> later anyway.
> 
> The approach I took uses an internal struct for passing required data for the
> private internal API use. Then it also provides a public struct which can be
> used to grow requirements to make only *new* API easily extensible.
> 
> So we need all three things to move forward.

Given the discussions here, it would be better to split your [1/5] and
[2/5] into more smaller pieces, say,
  * re-factor the old APIs with introducing private fw_desc
  * add new APIs with extra public arguments
  * extend new APIs per-feature
      - sync/async callbacks
      - API version, and so on

This way, you can illustrate how your approach evolves and it may
mitigate people's concern about how complicated it is on the surface,
allowing for discussing each of aspects of new APIs separately.

Thanks,
-Takahiro AKASHI

>   Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-19 19:35                 ` Luis R. Rodriguez
@ 2017-06-23 15:51                   ` Greg KH
  2017-06-23 22:43                     ` Luis R. Rodriguez
  2017-06-23 15:59                   ` Greg KH
  1 sibling, 1 reply; 98+ messages in thread
From: Greg KH @ 2017-06-23 15:51 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel

On Mon, Jun 19, 2017 at 09:35:22PM +0200, Luis R. Rodriguez wrote:
> > I agree, that's what I'm saying here.  I just do not see that happening
> > with your patch set at all.  It's adding more code, a more complex way
> > to interact with the subsystem, and not making driver writer lives any
> > easier at all that I can see.
> 
> There are two things to consider:
> 
> a) The current design of the firmware API, and interfaces with exported symbols
> 
> The case for the driver data API was that we were being super sloppy with extensions,
> to the point was making the internal code base very bug prone and full or redirect
> conditionals with #ifdefery nightmware stuff.
>    
> b) Features of the firmware API
> 
> These have to be evaluated on a case by case basis.

Wait, no, you didn't address my main complaint at all here.  You are
adding complexity for no perceived gain at all with this patch set.

Now you might feel that this series gets you moving forward toward an
end goal of reduced complexity and wonderfulness, but you know how
kernel development works, you have to justify _all_ of your changes, not
just some future end result that is not even presented here.

<wall of text snipped>

I, and others I know, have told you to work on simplifying your
responses, and descriptions, of patches.  Take the extra time to make a
shorter answer.  You will get better results, as I dread having to read
and respond to them currently.

I know you have spent a lot of time and effort on this work, but as it
stands, this crazy new interface (data-driven api vs. the traditional
procedural apis we know and love in Linux), is not acceptable at all.

It's also blocking real bug fixes and features that people want
addressed, which isn't acceptable.

Please take the time to step back, and see if you really want to spend
the effort into creating something that you can easily justify and break
down into acceptable patches.  If so, great, do it, but as it stands
today, that is not what you have done here, at all.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-19 19:35                 ` Luis R. Rodriguez
  2017-06-23 15:51                   ` Greg KH
@ 2017-06-23 15:59                   ` Greg KH
  2017-06-23 22:47                     ` Luis R. Rodriguez
  1 sibling, 1 reply; 98+ messages in thread
From: Greg KH @ 2017-06-23 15:59 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, takahiro.akashi,
	dhowells, pjones, hdegoede, alan, tytso, linux-kernel

To respond to one issue in your wall-of-text response:

On Mon, Jun 19, 2017 at 09:35:22PM +0200, Luis R. Rodriguez wrote:
> You may argue that *one* upstream users is not sufficient to introduce a new
> feature for, but I disagree given we have had new full *API* added for a new
> feature on the firmware API even for drivers THAT ARE NOT UPSTREAM! For
> instance request_firmware_into_buf() has no upstream users!!!

That's not acceptable at all, I'll send a patch after this to remove
that.  We don't keep apis around with no in-kernel users, you know this.

> Now, you might say that even though this is true that there many users of
> out-of-tree drivers that need this. While true, if this is the bar we'd go
> with, we can't then ignore the iwlwifi userbase, and the possible gains of
> having a proper non-recursive use of the daisy chained requests.

Nope, I don't care about out-of-tree drivers as we have no idea what is
going on there at all.  I've always had this position.

Patch forthcoming.

greg k-h

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-21  0:49                     ` AKASHI Takahiro
@ 2017-06-23 16:33                       ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-23 16:33 UTC (permalink / raw)
  To: AKASHI Takahiro, Luis R. Rodriguez, Vikram Mulukutla, Greg KH,
	wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li, atull,
	moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, torvalds, keescook, dhowells,
	pjones, hdegoede, alan, tytso, linux-kernel, linux-kernel-owner

On Wed, Jun 21, 2017 at 09:49:35AM +0900, AKASHI Takahiro wrote:
> On Tue, Jun 20, 2017 at 07:22:19PM +0200, Luis R. Rodriguez wrote:
> > On Tue, Jun 20, 2017 at 09:27:45AM -0700, Vikram Mulukutla wrote:
> > > 
> > > perhaps a light
> > > internal rework inside firmware_class might be more suitable towards the
> > > extensibility goal while keeping driver API usage as simple as it is today
> > > (even if you hate my patch for various reasons)?
> > > 
> > > [1] - https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/drivers/base/firmware_class.c?h=msm-3.18&id=7aa7efd3c150840369739893a84bd1d9f9774319
> > 
> > What you did is but one step I took, your changes make it easier to shuffle
> > data around internally. Its not sufficient to clean things up well enough, for
> > instance look at the "firmware behavior options" which are cleaned up in this
> > patch 1/5. That's been one pain on our side for a while, people understanding
> > when a flag applies or a feature, and making sure we document it all.
> > 
> > Then, one of the end goals is to provide extensibility, this is to allow users
> > to *pass* similar type of struct for either a sync or async call. Without this
> > the API remains unflexible and I predict we'll end up with the same situation
> > later anyway.
> > 
> > The approach I took uses an internal struct for passing required data for the
> > private internal API use. Then it also provides a public struct which can be
> > used to grow requirements to make only *new* API easily extensible.
> > 
> > So we need all three things to move forward.
> 
> Given the discussions here, it would be better to split your [1/5] and
> [2/5] into more smaller pieces, say,
>   * re-factor the old APIs with introducing private fw_desc

So patch 1/5 introduces 3 structs:

o struct driver_data_req_params  - used for user specified parameters
o struct driver_data_priv_params - used for internal use only
o struct driver_data_params - stiches both of the above together,
  only for internal use

I could certainly split the patch to introduce each separately.

>   * add new APIs with extra public arguments

This was split out already, patch 2 is the first patch introducing new API.

>   * extend new APIs per-feature
>       - sync/async callbacks

I suppose the base would be what we have today, only in new form. And sure,
I can add the features one by one...

>       - API version, and so on

Right.

> This way, you can illustrate how your approach evolves and it may
> mitigate people's concern about how complicated it is on the surface,
> allowing for discussing each of aspects of new APIs separately.

This makes sense. Greg, does this make sense to you? Or are you stone cold
against all this?

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-23 15:51                   ` Greg KH
@ 2017-06-23 22:43                     ` Luis R. Rodriguez
  2017-06-23 23:09                       ` Linus Torvalds
  2017-06-24 12:40                       ` Greg KH
  0 siblings, 2 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-23 22:43 UTC (permalink / raw)
  To: Greg KH, Julia Lawall
  Cc: Luis R. Rodriguez, wagi, dwmw2, rafal, arend.vanspriel, rjw,
	yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, jkosina,
	torvalds, keescook, takahiro.akashi, dhowells, pjones, hdegoede,
	alan, tytso, NeilBrown, Christoph Hellwig, linux-kernel

On Fri, Jun 23, 2017 at 11:51:23PM +0800, Greg KH wrote:
> On Mon, Jun 19, 2017 at 09:35:22PM +0200, Luis R. Rodriguez wrote:
> > > I agree, that's what I'm saying here.  I just do not see that happening
> > > with your patch set at all.  It's adding more code, a more complex way
> > > to interact with the subsystem, and not making driver writer lives any
> > > easier at all that I can see.
> > 
> > There are two things to consider:
> > 
> > a) The current design of the firmware API, and interfaces with exported symbols
> > 
> > The case for the driver data API was that we were being super sloppy with extensions,
> > to the point was making the internal code base very bug prone and full or redirect
> > conditionals with #ifdefery nightmware stuff.
> >    
> > b) Features of the firmware API
> > 
> > These have to be evaluated on a case by case basis.
> 
> Wait, no, you didn't address my main complaint at all here.  You are
> adding complexity for no perceived gain at all with this patch set.
> 
> Now you might feel that this series gets you moving forward toward an
> end goal of reduced complexity and wonderfulness, but you know how
> kernel development works, you have to justify _all_ of your changes, not
> just some future end result that is not even presented here.

My point was that a) was already complex, so to say what I'm adding
to address a) is complex would be unfair, so rather the question should
be if its less complex, or if there are valid technical issues I'd like
to hear them.

> <wall of text snipped>
> 
> I, and others I know, have told you to work on simplifying your
> responses, and descriptions, of patches.  Take the extra time to make a
> shorter answer.  You will get better results, as I dread having to read
> and respond to them currently.

Sure, point taken!

> I know you have spent a lot of time and effort on this work, but as it
> stands, this crazy new interface (data-driven api vs. the traditional
> procedural apis we know and love in Linux), is not acceptable at all.

Ah, yes! Here is what I believe seems to be the *crux* issue of these patch
series and I'm happy we have finally landed on it. Yes, indeed the new API
proposed here provides more flexibility, and it does so by embracing a
"data driven" API Vs the traditional procedural APIs we have seen for
*the firmware API*. If by data-driven you mean using structs to drive the
requirements, instead of adding more API per new feature.

I would strongly disagree that we always prefer adding new functional APIs
loosely.  I think this should be reviewed on a case by case basis and it should
be up to the maintainer who has better visibility into the history of the code,
and what is coming. A quick git log grep found a recent commit example where we
take on a flexible API on other *random* places in Linux. Refer to for example
the change form bioset_create_nobvec() to bioset_create() and use flags in the
patch titled "blk: replace bioset_create_nobvec() with a flags arg to
bioset_create()", present on linux-next [0] followed by "blk: make the bioset
rescue_workqueue optional." [1] which also extends the flags and uses them.

To argue that we *still* need to keep doing a functional approach for the
firmware API and keep adding new routines for new features, seems insane to me
at this point -- if that is what you were suggesting...

Also, the reason *why* I think this discussion is important is that it also
implicates the amount of collateral evolutions needed per API. If you embrace
data-driven API (or flags, or structs for APIs) you should see less patches
due to collateral evolutions. In a way its my own resolution to mitigate
*unnecessary* collateral evolutions by proper architecture. Where that line is
drawn should be up to the designers of the API.

For system calls, for instance, I think its well accepted we *never* want to
make inflexible APIs. There is strong history for why that is the case. We
cannot compare system calls to exported symbols, at all, but we can learn a bit
from the flexibility efforts on them for exported symbols as well.  Its *real
news* to me that we *always* prefer a procedural preferences for APIs /
exported symbols.

[0] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=011067b05668b05aae88e5a24cff0ca0a67ca0b0
[1] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=47e0fb461fca1a68a566c82fcc006cc787312d8c

> It's also blocking real bug fixes and features that people want
> addressed, which isn't acceptable.

What? What bugs ? I have addressed *every single* stable issue in queue and
have sent patches for them and they do not depend in any way on any of this
series! In fact I've taken liberty to go to great lengths to ensure *stable*
proposals *get proper review* so we *do the right thing*. Case in point was the
recent -ERESTART crap which in the end we ended up moving towards preferring
adding new swait API for stable for it.

I have ZERO stable fixes pending on my queue! Please name the bug pending.

In so far as features.. yes ! This delays adding new features because the crux
of the discussion is *how* to add them, a flag or new parameter in a struct
*or* do we add yet-another-API-call? I'm making a case for the first.

> Please take the time to step back, and see if you really want to spend
> the effort into creating something that you can easily justify and break
> down into acceptable patches.  If so, great, do it, but as it stands
> today, that is not what you have done here, at all.

AKASHI addressed how to break down the patches further. I will take that on!
Your point above on data-driven Vs functional however still deserves some
discussion as otherwise the effort on the changes I've made are pointless.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-23 15:59                   ` Greg KH
@ 2017-06-23 22:47                     ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-23 22:47 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, wagi, dwmw2, rafal, arend.vanspriel, rjw,
	yi1.li, atull, moritz.fischer, pmladek, johannes.berg,
	emmanuel.grumbach, luciano.coelho, kvalo, luto, torvalds,
	keescook, takahiro.akashi, dhowells, pjones, hdegoede, alan,
	tytso, linux-kernel

On Fri, Jun 23, 2017 at 11:59:36PM +0800, Greg KH wrote:
> On Mon, Jun 19, 2017 at 09:35:22PM +0200, Luis R. Rodriguez wrote:
> > You may argue that *one* upstream users is not sufficient to introduce a new
> > feature for, but I disagree given we have had new full *API* added for a new
> > feature on the firmware API even for drivers THAT ARE NOT UPSTREAM! For
> > instance request_firmware_into_buf() has no upstream users!!!
> 
> That's not acceptable at all, I'll send a patch after this to remove
> that.  We don't keep apis around with no in-kernel users, you know this.

I'm delighted to hear we can do away with the request_firmware_into_buf() crap.

> > Now, you might say that even though this is true that there many users of
> > out-of-tree drivers that need this. While true, if this is the bar we'd go
> > with, we can't then ignore the iwlwifi userbase, and the possible gains of
> > having a proper non-recursive use of the daisy chained requests.
> 
> Nope, I don't care about out-of-tree drivers as we have no idea what is
> going on there at all.  I've always had this position.

Beautiful. Music to my ears.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-23 22:43                     ` Luis R. Rodriguez
@ 2017-06-23 23:09                       ` Linus Torvalds
  2017-06-24  0:48                         ` Luis R. Rodriguez
  2017-06-24 12:40                       ` Greg KH
  1 sibling, 1 reply; 98+ messages in thread
From: Linus Torvalds @ 2017-06-23 23:09 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Greg KH, Julia Lawall, Daniel Wagner, David Woodhouse, rafal,
	Arend Van Spriel, Rafael J. Wysocki, Li, Yi, atull,
	Moritz Fischer, Petr Mladek, Johannes Berg, Emmanuel Grumbach,
	Coelho, Luciano, Kalle Valo, Andrew Lutomirski, Jiri Kosina,
	Kees Cook, AKASHI, Takahiro, David Howells, Peter Jones,
	Hans de Goede, Alan Cox, Theodore Ts'o, NeilBrown,
	Christoph Hellwig, Linux Kernel Mailing List

On Fri, Jun 23, 2017 at 3:43 PM, Luis R. Rodriguez <mcgrof@kernel.org> wrote:
>
> Ah, yes! Here is what I believe seems to be the *crux* issue of these patch
> series and I'm happy we have finally landed on it. Yes, indeed the new API
> proposed here provides more flexibility, and it does so by embracing a
> "data driven" API Vs the traditional procedural APIs we have seen for
> *the firmware API*.

This has been going on forever. Everybody hates your data-driven one.
It's too indirect, it adds all those nasty "descriptors" of what to
do, and it doesn't match what the current model does at all.

Things like that may be ok as an internal implementation, but even
there it's questionable if it then means a big disconnect between what
people actually use (the normal functional model) and the
implementation.

The thing is, it's much better to just have functions that load the
firmware data. Have them named that way ("load_firmware()"), and act
that way ("just load the damn firmware file") instead of having odd
descriptors that describe what is goign to be done and some state for
it, and then get passed around.

Don't add this kind of crazy abstraction complexity.

If somebody wants to veryify a signature on a firmware file, they
should *NOT* fill in a descriptor that says "check signature when
loading". Thats' complete BS.

They should just do "load_firmware()" and then "check_signature()" or whatever.

Would such a "load firmware file, then check signature" take a few
lines (with error handling)? Yes.

But it is a *simple* interface. It doesn't have some stupid "struct
driver_data_params" that needs to be filled in with random details (or
has magic macros in a header file that fill in default values). It's
_straightforward_.

                Linus

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-23 23:09                       ` Linus Torvalds
@ 2017-06-24  0:48                         ` Luis R. Rodriguez
  2017-06-24 12:39                           ` Greg KH
  0 siblings, 1 reply; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-24  0:48 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Luis R. Rodriguez, Greg KH, Julia Lawall, Daniel Wagner,
	David Woodhouse, rafal, Arend Van Spriel, Rafael J. Wysocki, Li,
	Yi, atull, Moritz Fischer, Petr Mladek, Johannes Berg,
	Emmanuel Grumbach, Coelho, Luciano, Kalle Valo,
	Andrew Lutomirski, Jiri Kosina, Kees Cook, AKASHI, Takahiro,
	David Howells, Peter Jones, Hans de Goede, Alan Cox,
	Theodore Ts'o, NeilBrown, Christoph Hellwig,
	Linux Kernel Mailing List

On Fri, Jun 23, 2017 at 04:09:29PM -0700, Linus Torvalds wrote:
> On Fri, Jun 23, 2017 at 3:43 PM, Luis R. Rodriguez <mcgrof@kernel.org> wrote:
> >
> > Ah, yes! Here is what I believe seems to be the *crux* issue of these patch
> > series and I'm happy we have finally landed on it. Yes, indeed the new API
> > proposed here provides more flexibility, and it does so by embracing a
> > "data driven" API Vs the traditional procedural APIs we have seen for
> > *the firmware API*.
> 
> This has been going on forever. Everybody hates your data-driven one.

Before you, the only person who had expressed disdain here was Greg.

> It's too indirect, it adds all those nasty "descriptors" of what to
> do, and it doesn't match what the current model does at all.

This is good feedback, I do accept deciding where to draw the line is hard.
I decided to go with blocking/non-blocking as the fine line.

> Things like that may be ok as an internal implementation, but even
> there it's questionable if it then means a big disconnect between what
> people actually use (the normal functional model) and the
> implementation.

A vendor tree implemented their *own* solution and were willing to maintain
it despite this likely making it hard to port stable fixes. That I think says
a lot for a need...

> The thing is, it's much better to just have functions that load the
> firmware data. Have them named that way ("load_firmware()"), and act
> that way ("just load the damn firmware file") instead of having odd
> descriptors that describe what is goign to be done and some state for
> it, and then get passed around.
> 
> Don't add this kind of crazy abstraction complexity.
> 
> If somebody wants to veryify a signature on a firmware file, they
> should *NOT* fill in a descriptor that says "check signature when
> loading". Thats' complete BS.
> 
> They should just do "load_firmware()" and then "check_signature()" or whatever.

That's a fair suggestion for firmware signing! And I'll let AKASHI comment on
whether or not that would suffice for his requirements given he's now
addressing firmware signing.

There are still other requirements and features in the pipeline for which we
can consider parameters to parse for, rather than adding new API. Case in
point, do we want *one* API just to disable the firmware cache? Specially
knowing that another feature in the pipeline later would make use of this as a
requirement?

Or let us just consider the very simple *optional* async firmware. Do we add
*one* full new API call just for that?

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-24  0:48                         ` Luis R. Rodriguez
@ 2017-06-24 12:39                           ` Greg KH
  2017-06-26 17:33                             ` Luis R. Rodriguez
  0 siblings, 1 reply; 98+ messages in thread
From: Greg KH @ 2017-06-24 12:39 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Linus Torvalds, Julia Lawall, Daniel Wagner, David Woodhouse,
	rafal, Arend Van Spriel, Rafael J. Wysocki, Li, Yi, atull,
	Moritz Fischer, Petr Mladek, Johannes Berg, Emmanuel Grumbach,
	Coelho, Luciano, Kalle Valo, Andrew Lutomirski, Jiri Kosina,
	Kees Cook, AKASHI, Takahiro, David Howells, Peter Jones,
	Hans de Goede, Alan Cox, Theodore Ts'o, NeilBrown,
	Christoph Hellwig, Linux Kernel Mailing List

On Sat, Jun 24, 2017 at 02:48:28AM +0200, Luis R. Rodriguez wrote:
> On Fri, Jun 23, 2017 at 04:09:29PM -0700, Linus Torvalds wrote:
> > On Fri, Jun 23, 2017 at 3:43 PM, Luis R. Rodriguez <mcgrof@kernel.org> wrote:
> > >
> > > Ah, yes! Here is what I believe seems to be the *crux* issue of these patch
> > > series and I'm happy we have finally landed on it. Yes, indeed the new API
> > > proposed here provides more flexibility, and it does so by embracing a
> > > "data driven" API Vs the traditional procedural APIs we have seen for
> > > *the firmware API*.
> > 
> > This has been going on forever. Everybody hates your data-driven one.
> 
> Before you, the only person who had expressed disdain here was Greg.

Very few people actually review code, you know that.

> > Things like that may be ok as an internal implementation, but even
> > there it's questionable if it then means a big disconnect between what
> > people actually use (the normal functional model) and the
> > implementation.
> 
> A vendor tree implemented their *own* solution and were willing to maintain
> it despite this likely making it hard to port stable fixes. That I think says
> a lot for a need...

What vendor tree?  Where was it shipped?  Why was it external and how is
it different from your patches?  Was it used because your version has
taken so long to be submitted/reviwed?

> There are still other requirements and features in the pipeline for which we
> can consider parameters to parse for, rather than adding new API. Case in
> point, do we want *one* API just to disable the firmware cache? Specially
> knowing that another feature in the pipeline later would make use of this as a
> requirement?

Again, I do not care!  You can not justify patches today with some
mythical thing in the future that might never even happen.

Again, as it stands, this patch series is unacceptable, and the added
complexity of a crazy api that goes against almost all normal in-kernel
apis, is only one part of the reason.  The other being the loads of
added code for no apparent benifit at all.

So please both fix the api to be "normal", and show as to why these
patches are actually needed _today_, otherwise we can just live with
what we have now just fine and muddle along like always.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-23 22:43                     ` Luis R. Rodriguez
  2017-06-23 23:09                       ` Linus Torvalds
@ 2017-06-24 12:40                       ` Greg KH
  2017-06-26 15:50                         ` Luis R. Rodriguez
  1 sibling, 1 reply; 98+ messages in thread
From: Greg KH @ 2017-06-24 12:40 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Julia Lawall, wagi, dwmw2, rafal, arend.vanspriel, rjw, yi1.li,
	atull, moritz.fischer, pmladek, johannes.berg, emmanuel.grumbach,
	luciano.coelho, kvalo, luto, jkosina, torvalds, keescook,
	takahiro.akashi, dhowells, pjones, hdegoede, alan, tytso,
	NeilBrown, Christoph Hellwig, linux-kernel

On Sat, Jun 24, 2017 at 12:43:38AM +0200, Luis R. Rodriguez wrote:
> > It's also blocking real bug fixes and features that people want
> > addressed, which isn't acceptable.
> 
> What? What bugs ? I have addressed *every single* stable issue in queue and
> have sent patches for them and they do not depend in any way on any of this
> series!

Ok, so we are all caught up now with bug fixes?  That's good, I was not
aware of that, so nevermind that objection, my fault.

greg k-h

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-24 12:40                       ` Greg KH
@ 2017-06-26 15:50                         ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-26 15:50 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, Julia Lawall, wagi, dwmw2, rafal,
	arend.vanspriel, rjw, yi1.li, atull, moritz.fischer, pmladek,
	johannes.berg, emmanuel.grumbach, luciano.coelho, kvalo, luto,
	jkosina, torvalds, keescook, takahiro.akashi, dhowells, pjones,
	hdegoede, alan, tytso, NeilBrown, Christoph Hellwig,
	linux-kernel

On Sat, Jun 24, 2017 at 02:40:57PM +0200, Greg KH wrote:
> On Sat, Jun 24, 2017 at 12:43:38AM +0200, Luis R. Rodriguez wrote:
> > > It's also blocking real bug fixes and features that people want
> > > addressed, which isn't acceptable.
> > 
> > What? What bugs ? I have addressed *every single* stable issue in queue and
> > have sent patches for them and they do not depend in any way on any of this
> > series!
> 
> Ok, so we are all caught up now with bug fixes?  That's good, I was not
> aware of that, so nevermind that objection, my fault.

Yes. I've been following up on all actively reported bugs and have even
implemented better alternatives based on discussions.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-24 12:39                           ` Greg KH
@ 2017-06-26 17:33                             ` Luis R. Rodriguez
  2017-06-26 18:19                               ` Rafał Miłecki
  2017-06-27  2:28                               ` Vikram Mulukutla
  0 siblings, 2 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-26 17:33 UTC (permalink / raw)
  To: Greg KH, Vikram Mulukutla, Stephen Boyd
  Cc: Luis R. Rodriguez, Linus Torvalds, Julia Lawall, Daniel Wagner,
	David Woodhouse, rafal, Arend Van Spriel, Rafael J. Wysocki, Li,
	Yi, atull, Moritz Fischer, Petr Mladek, Johannes Berg,
	Emmanuel Grumbach, Coelho, Luciano, Kalle Valo,
	Andrew Lutomirski, Jiri Kosina, Kees Cook, AKASHI, Takahiro,
	David Howells, Peter Jones, Hans de Goede, Alan Cox,
	Theodore Ts'o, NeilBrown, Christoph Hellwig,
	Linux Kernel Mailing List

On Sat, Jun 24, 2017 at 02:39:51PM +0200, Greg KH wrote:
> On Sat, Jun 24, 2017 at 02:48:28AM +0200, Luis R. Rodriguez wrote:
> > On Fri, Jun 23, 2017 at 04:09:29PM -0700, Linus Torvalds wrote:
> > > On Fri, Jun 23, 2017 at 3:43 PM, Luis R. Rodriguez <mcgrof@kernel.org> wrote:
> > > >
> > > > Ah, yes! Here is what I believe seems to be the *crux* issue of these patch
> > > > series and I'm happy we have finally landed on it. Yes, indeed the new API
> > > > proposed here provides more flexibility, and it does so by embracing a
> > > > "data driven" API Vs the traditional procedural APIs we have seen for
> > > > *the firmware API*.
> > > 
> > > This has been going on forever. Everybody hates your data-driven one.
> > 
> > Before you, the only person who had expressed disdain here was Greg.
> 
> Very few people actually review code, you know that.

Using that logic, then of course "everybody" was *very* fitting ;)

Then again others who actually are working on extending the firmware API (Yi
Li), or maintaining vendor trees (Vikram), did express their opinions on the
current codebase and their appreciate for the changes I made, however this went
selectively unnoticed.

> > > Things like that may be ok as an internal implementation, but even
> > > there it's questionable if it then means a big disconnect between what
> > > people actually use (the normal functional model) and the
> > > implementation.
> > 
> > A vendor tree implemented their *own* solution and were willing to maintain
> > it despite this likely making it hard to port stable fixes. That I think says
> > a lot for a need...
> 
> What vendor tree?  Where was it shipped? 

The msm-3.18 kernel [0], so assuming this goes to mobile devices, this could
mean millions of devices.

https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/drivers/base/firmware_class.c?h=msm-3.18&id=7aa7efd3c150840369739893a84bd1d9f9774319 

> Why was it external and how is it different from your patches?

As is typical with external trees -- it would seem Vikram actually wrote the
original request_firmware_into_buf() API for the msm tree.  It contained the
fw_desc changes. Stephen Boyd seems to have worked on the actual upstreaming
effort and he dropped that fw_desc effort from the upstreaming effort.

Vikarm noted he had had a similar internal discussion with Stephen Stephen Boyd
as I am with you on this thread back when request_firmware_into_buf() was being
upstreamed [0]. He noted that back then reason for this proposed change was
that "the number of things being passed around between layers of functions
inside firmware_class seemed a bit untenable". I will note around that time I
had proposed a similar change using the fw_desc name, it was only later that
this renamed to a params postfix as Linus did not like the descriptor name.

[0] https://lkml.kernel.org/r/20ac6fa65c8ff4ef83386aa1e8d5ca91@codeaurora.org

The only difference is that his patch does only modifying the private members
of the internal API and routines from my patch 1/5, and he kept the
"descriptor" name Linus disliked a while ago. This is precisely why AKASHI
noted I could split up my patch 1 in more ways in this series to help *patch
review*.

> Was it used because your version has taken so long to be submitted/reviwed?

Vikram would have a better idea as he is the one who authored it, but it would
seem this effort was in parallel to my own at that time.

> > There are still other requirements and features in the pipeline for which we
> > can consider parameters to parse for, rather than adding new API. Case in
> > point, do we want *one* API just to disable the firmware cache? Specially
> > knowing that another feature in the pipeline later would make use of this as a
> > requirement?
> 
> Again, I do not care!  You can not justify patches today with some
> mythical thing in the future that might never even happen.

Some of these features are things actually being discussed for a while, so to
say they are mythical is not accurate. I can trace back firmware signing
discussions back to 2015, along with Plumbers in person discussions where we
seem to have agreed upon a path forward among a few folks who disagreed on a
technical basis. Linaro has a clear interest so AKASHI picked up that work now
as I have been busy with general maintainer duties. The fact that Linus just
suggested an alternative approach to a params approach is new, and yet to be
reviewe by AKASHI for firmware signing.

Granting the option to make async firmware optional was discussed since
December 2016 by RafaÅ [1]. It was only later during my driver data API changes
that Hans noted the nvram part was actually *not* optional [2] so this
requirement dropped. *However* as the maintainer I believ ethis requirement *is
sensible* and would not be surprised if alternative firmware already exists
where this is what is intended. 

The streaming support for FPGAs has been going through a round of reviews since
March [3]. The fact that you only become aware or jump into review now does not
make them mythical.

[1] https://lkml.kernel.org/r/CACna6rxOGo0e9U7eXpUgnnBuxL+x1B0JBf9ZBq2WPbaBE=YZ-g@mail.gmail.com
[2] https://lkml.kernel.org/r/09063fc2-af77-ced6-ed90-ab20e2884969@redhat.com
[3] https://lkml.kernel.org/r/1489105090-4996-1-git-send-email-yi1.li@linux.intel.com

> Again, as it stands, this patch series is unacceptable, and the added
> complexity of a crazy api that goes against almost all normal in-kernel
> apis, is only one part of the reason.

Back to the *real* highlight and *crux* of this thread:

This a paradigm position, and I'm fine to go with it! I sure hope my original
logic here is not forgotten, the goal was to demo and show an API which
mitigates unnecessary collateral evolutions. I'll also note an *alternative* has
not yet been clearly suggested, so I'd be *delighted* to hear alternatives.

> The other being the loads of added code for no apparent benifit at all.

I can split up patches as AKASHI suggested.

> So please both fix the api to be "normal",

Will do, but an alternative to the approach would be appreciated. Take the
optional firmware for async requests as a good example to start with.

> and show as to why these
> patches are actually needed _today_, otherwise we can just live with
> what we have now just fine and muddle along like always.

I think a cleanup for the internal API, flags, and things shuffling back and
forward makes sense already so will start off with *just that*.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-26 17:33                             ` Luis R. Rodriguez
@ 2017-06-26 18:19                               ` Rafał Miłecki
  2017-06-26 21:29                                 ` Luis R. Rodriguez
  2017-06-27  2:28                               ` Vikram Mulukutla
  1 sibling, 1 reply; 98+ messages in thread
From: Rafał Miłecki @ 2017-06-26 18:19 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Greg KH, Vikram Mulukutla, Stephen Boyd, Linus Torvalds,
	Julia Lawall, Daniel Wagner, David Woodhouse, Arend Van Spriel,
	Rafael J. Wysocki, Li, Yi, atull, Moritz Fischer, Petr Mladek,
	Johannes Berg, Emmanuel Grumbach, Coelho, Luciano, Kalle Valo,
	Andrew Lutomirski, Jiri Kosina, Kees Cook, AKASHI, Takahiro,
	David Howells, Peter Jones, Hans de Goede, Alan Cox,
	Theodore Ts'o, NeilBrown, Christoph Hellwig,
	Linux Kernel Mailing List

On 2017-06-26 19:33, Luis R. Rodriguez wrote:
> On Sat, Jun 24, 2017 at 02:39:51PM +0200, Greg KH wrote:
>> On Sat, Jun 24, 2017 at 02:48:28AM +0200, Luis R. Rodriguez wrote:
>> > On Fri, Jun 23, 2017 at 04:09:29PM -0700, Linus Torvalds wrote:
>> > > On Fri, Jun 23, 2017 at 3:43 PM, Luis R. Rodriguez <mcgrof@kernel.org> wrote:
>> > > >
>> > > > Ah, yes! Here is what I believe seems to be the *crux* issue of these patch
>> > > > series and I'm happy we have finally landed on it. Yes, indeed the new API
>> > > > proposed here provides more flexibility, and it does so by embracing a
>> > > > "data driven" API Vs the traditional procedural APIs we have seen for
>> > > > *the firmware API*.
>> > >
>> > > This has been going on forever. Everybody hates your data-driven one.
>> >
>> > Before you, the only person who had expressed disdain here was Greg.
>> 
>> Very few people actually review code, you know that.
> 
> Using that logic, then of course "everybody" was *very* fitting ;)
> 
> Then again others who actually are working on extending the firmware 
> API (Yi
> Li), or maintaining vendor trees (Vikram), did express their opinions 
> on the
> current codebase and their appreciate for the changes I made, however 
> this went
> selectively unnoticed.
> 
>> > > Things like that may be ok as an internal implementation, but even
>> > > there it's questionable if it then means a big disconnect between what
>> > > people actually use (the normal functional model) and the
>> > > implementation.
>> >
>> > A vendor tree implemented their *own* solution and were willing to maintain
>> > it despite this likely making it hard to port stable fixes. That I think says
>> > a lot for a need...
>> 
>> What vendor tree?  Where was it shipped?
> 
> The msm-3.18 kernel [0], so assuming this goes to mobile devices, this 
> could
> mean millions of devices.
> 
> https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/drivers/base/firmware_class.c?h=msm-3.18&id=7aa7efd3c150840369739893a84bd1d9f9774319
> 
>> Why was it external and how is it different from your patches?
> 
> As is typical with external trees -- it would seem Vikram actually 
> wrote the
> original request_firmware_into_buf() API for the msm tree.  It 
> contained the
> fw_desc changes. Stephen Boyd seems to have worked on the actual 
> upstreaming
> effort and he dropped that fw_desc effort from the upstreaming effort.
> 
> Vikarm noted he had had a similar internal discussion with Stephen 
> Stephen Boyd
> as I am with you on this thread back when request_firmware_into_buf() 
> was being
> upstreamed [0]. He noted that back then reason for this proposed change 
> was
> that "the number of things being passed around between layers of 
> functions
> inside firmware_class seemed a bit untenable". I will note around that 
> time I
> had proposed a similar change using the fw_desc name, it was only later 
> that
> this renamed to a params postfix as Linus did not like the descriptor 
> name.
> 
> [0] 
> https://lkml.kernel.org/r/20ac6fa65c8ff4ef83386aa1e8d5ca91@codeaurora.org
> 
> The only difference is that his patch does only modifying the private 
> members
> of the internal API and routines from my patch 1/5, and he kept the
> "descriptor" name Linus disliked a while ago. This is precisely why 
> AKASHI
> noted I could split up my patch 1 in more ways in this series to help 
> *patch
> review*.
> 
>> Was it used because your version has taken so long to be 
>> submitted/reviwed?
> 
> Vikram would have a better idea as he is the one who authored it, but 
> it would
> seem this effort was in parallel to my own at that time.
> 
>> > There are still other requirements and features in the pipeline for which we
>> > can consider parameters to parse for, rather than adding new API. Case in
>> > point, do we want *one* API just to disable the firmware cache? Specially
>> > knowing that another feature in the pipeline later would make use of this as a
>> > requirement?
>> 
>> Again, I do not care!  You can not justify patches today with some
>> mythical thing in the future that might never even happen.
> 
> Some of these features are things actually being discussed for a while, 
> so to
> say they are mythical is not accurate. I can trace back firmware 
> signing
> discussions back to 2015, along with Plumbers in person discussions 
> where we
> seem to have agreed upon a path forward among a few folks who disagreed 
> on a
> technical basis. Linaro has a clear interest so AKASHI picked up that 
> work now
> as I have been busy with general maintainer duties. The fact that Linus 
> just
> suggested an alternative approach to a params approach is new, and yet 
> to be
> reviewe by AKASHI for firmware signing.
> 
> Granting the option to make async firmware optional was discussed since
> December 2016 by RafaÅ [1]. It was only later during my driver data API 
> changes
> that Hans noted the nvram part was actually *not* optional [2] so this
> requirement dropped. *However* as the maintainer I believ ethis 
> requirement *is
> sensible* and would not be surprised if alternative firmware already 
> exists
> where this is what is intended.

I believe there was a misunderstanding of my patch by Hans. The point of 
my
patch was to don't display warning *IF* we can use alternative soruce 
and
get the NVRAM (firmware) from platform data (special partition used by 
the
bootloader and accessible by the operating system).

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-26 18:19                               ` Rafał Miłecki
@ 2017-06-26 21:29                                 ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-26 21:29 UTC (permalink / raw)
  To: Rafał Miłecki
  Cc: Luis R. Rodriguez, Greg KH, Vikram Mulukutla, Stephen Boyd,
	Linus Torvalds, Julia Lawall, Daniel Wagner, David Woodhouse,
	Arend Van Spriel, Rafael J. Wysocki, Li, Yi, atull,
	Moritz Fischer, Petr Mladek, Johannes Berg, Emmanuel Grumbach,
	Coelho, Luciano, Kalle Valo, Andrew Lutomirski, Jiri Kosina,
	Kees Cook, AKASHI, Takahiro, David Howells, Peter Jones,
	Hans de Goede, Alan Cox, Theodore Ts'o, NeilBrown,
	Christoph Hellwig, Linux Kernel Mailing List

On Mon, Jun 26, 2017 at 08:19:07PM +0200, Rafał Miłecki wrote:
> On 2017-06-26 19:33, Luis R. Rodriguez wrote:
> > On Sat, Jun 24, 2017 at 02:39:51PM +0200, Greg KH wrote:
> > > > There are still other requirements and features in the pipeline for which we
> > > > can consider parameters to parse for, rather than adding new API. Case in
> > > > point, do we want *one* API just to disable the firmware cache? Specially
> > > > knowing that another feature in the pipeline later would make use of this as a
> > > > requirement?
> > > 
> > > Again, I do not care!  You can not justify patches today with some
> > > mythical thing in the future that might never even happen.
> > 
> > Granting the option to make async firmware optional was discussed since
> > December 2016 by RafaÅ [1]. It was only later during my driver data API
> > changes that Hans noted the nvram part was actually *not* optional [2] so
> > this requirement dropped. *However* as the maintainer I believ ethis
> > requirement *is sensible* and would not be surprised if alternative
> > firmware already exists where this is what is intended.
> 
> I believe there was a misunderstanding of my patch by Hans. The point of my
> patch was to don't display warning *IF* we can use alternative soruce and
> get the NVRAM (firmware) from platform data (special partition used by the
> bootloader and accessible by the operating system).

Oh, are you saying the optional async firmware loading is still a requirement
for this driver? Are you, Hans, and Arend Van Spriel in agreement on this?

If so then that definitely makes 3 effective changes in my radar for extensions
to the firmware API.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-26 17:33                             ` Luis R. Rodriguez
  2017-06-26 18:19                               ` Rafał Miłecki
@ 2017-06-27  2:28                               ` Vikram Mulukutla
  2017-06-27 17:25                                 ` Luis R. Rodriguez
  1 sibling, 1 reply; 98+ messages in thread
From: Vikram Mulukutla @ 2017-06-27  2:28 UTC (permalink / raw)
  To: Luis R. Rodriguez, Greg KH, Stephen Boyd
  Cc: Linus Torvalds, Julia Lawall, Daniel Wagner, David Woodhouse,
	rafal, Arend Van Spriel, Rafael J. Wysocki, Li, Yi, atull,
	Moritz Fischer, Petr Mladek, Johannes Berg, Emmanuel Grumbach,
	Coelho, Luciano, Kalle Valo, Andrew Lutomirski, Jiri Kosina,
	Kees Cook, AKASHI, Takahiro, David Howells, Peter Jones,
	Hans de Goede, Alan Cox, Theodore Ts'o, NeilBrown,
	Christoph Hellwig, Linux Kernel Mailing List

On 6/26/2017 10:33 AM, Luis R. Rodriguez wrote:
> On Sat, Jun 24, 2017 at 02:39:51PM +0200, Greg KH wrote:
>> On Sat, Jun 24, 2017 at 02:48:28AM +0200, Luis R. Rodriguez wrote:
>>> On Fri, Jun 23, 2017 at 04:09:29PM -0700, Linus Torvalds wrote:
>>>> On Fri, Jun 23, 2017 at 3:43 PM, Luis R. Rodriguez 
>>>> <mcgrof@kernel.org> wrote:
>>>>> 
>>>>> Ah, yes! Here is what I believe seems to be the *crux* issue of 
>>>>> these patch
>>>>> series and I'm happy we have finally landed on it. Yes, indeed the 
>>>>> new API
>>>>> proposed here provides more flexibility, and it does so by 
>>>>> embracing a
>>>>> "data driven" API Vs the traditional procedural APIs we have seen 
>>>>> for
>>>>> *the firmware API*.
>>>> 
>>>> This has been going on forever. Everybody hates your data-driven 
>>>> one.
>>> 
>>> Before you, the only person who had expressed disdain here was Greg.
>> 
>> Very few people actually review code, you know that.
> 
> Using that logic, then of course "everybody" was *very* fitting ;)
> 
> Then again others who actually are working on extending the firmware 
> API (Yi
> Li), or maintaining vendor trees (Vikram), did express their opinions 
> on the
> current codebase and their appreciate for the changes I made, however 
> this went
> selectively unnoticed.
> 
>>>> Things like that may be ok as an internal implementation, but even
>>>> there it's questionable if it then means a big disconnect between 
>>>> what
>>>> people actually use (the normal functional model) and the
>>>> implementation.
>>> 
>>> A vendor tree implemented their *own* solution and were willing to 
>>> maintain
>>> it despite this likely making it hard to port stable fixes. That I 
>>> think says
>>> a lot for a need...
>> 
>> What vendor tree?  Where was it shipped?
> 
> The msm-3.18 kernel [0], so assuming this goes to mobile devices, this 
> could
> mean millions of devices.
> 
> https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/drivers/base/firmware_class.c?h=msm-3.18&id=7aa7efd3c150840369739893a84bd1d9f9774319
> 
>> Why was it external and how is it different from your patches?
> 
> As is typical with external trees -- it would seem Vikram actually 
> wrote the
> original request_firmware_into_buf() API for the msm tree.  It 
> contained the
> fw_desc changes. Stephen Boyd seems to have worked on the actual 
> upstreaming
> effort and he dropped that fw_desc effort from the upstreaming effort.
> 
> Vikarm noted he had had a similar internal discussion with Stephen 
> Stephen Boyd
> as I am with you on this thread back when request_firmware_into_buf() 
> was being
> upstreamed [0]. He noted that back then reason for this proposed change 
> was
> that "the number of things being passed around between layers of 
> functions
> inside firmware_class seemed a bit untenable". I will note around that 
> time I
> had proposed a similar change using the fw_desc name, it was only later 
> that
> this renamed to a params postfix as Linus did not like the descriptor 
> name.
> 
> [0] 
> https://lkml.kernel.org/r/20ac6fa65c8ff4ef83386aa1e8d5ca91@codeaurora.org
> 
> The only difference is that his patch does only modifying the private 
> members
> of the internal API and routines from my patch 1/5, and he kept the
> "descriptor" name Linus disliked a while ago. This is precisely why 
> AKASHI
> noted I could split up my patch 1 in more ways in this series to help 
> *patch
> review*.
> 
>> Was it used because your version has taken so long to be 
>> submitted/reviwed?
> 
> Vikram would have a better idea as he is the one who authored it, but 
> it would
> seem this effort was in parallel to my own at that time.
> 

I must shamefully admit that the story is a bit older - the patch I
originally worked on was on a v3.4 based tree. We had been forward
porting it until Stephen Boyd was kind enough (or tired of it) to take
time out of his clock maintainer-ship and upstream the
request_firmware_into_buf API. At that point of time it seemed that the
'desc' approach was unnecessary, and I agreed. So Luis's series came
in much later and wasn't a factor in forward-porting the patches.
     While it does seem that the _internal_ implementation of
firmware_class can be a bit friendlier to adding the features that
are on their way, I can't say the same about the API being exposed to
drivers in mainline; maintainers and folks with more experience in
kernel API evolution are better equipped to answer that question.

Thanks,
Vikram

^ permalink raw reply	[flat|nested] 98+ messages in thread

* Re: [PATCH v9 1/5] firmware: add extensible driver data params
  2017-06-27  2:28                               ` Vikram Mulukutla
@ 2017-06-27 17:25                                 ` Luis R. Rodriguez
  0 siblings, 0 replies; 98+ messages in thread
From: Luis R. Rodriguez @ 2017-06-27 17:25 UTC (permalink / raw)
  To: Vikram Mulukutla
  Cc: Luis R. Rodriguez, Greg KH, Stephen Boyd, Linus Torvalds,
	Julia Lawall, Daniel Wagner, David Woodhouse, rafal,
	Arend Van Spriel, Rafael J. Wysocki, Li, Yi, atull,
	Moritz Fischer, Petr Mladek, Johannes Berg, Emmanuel Grumbach,
	Coelho, Luciano, Kalle Valo, Andrew Lutomirski, Jiri Kosina,
	Kees Cook, AKASHI, Takahiro, David Howells, Peter Jones,
	Hans de Goede, Alan Cox, Theodore Ts'o, NeilBrown,
	Christoph Hellwig, Linux Kernel Mailing List

On Mon, Jun 26, 2017 at 07:28:12PM -0700, Vikram Mulukutla wrote:
> On 6/26/2017 10:33 AM, Luis R. Rodriguez wrote:
> > On Sat, Jun 24, 2017 at 02:39:51PM +0200, Greg KH wrote:
> > > Was it used because your version has taken so long to be
> > > submitted/reviwed?
> > 
> > Vikram would have a better idea as he is the one who authored it, but it
> > would seem this effort was in parallel to my own at that time.
> > 
> 
> I must shamefully admit that the story is a bit older - the patch I
> originally worked on was on a v3.4 based tree. 
 
Oh wow so we had *two* separate parallel efforts to simplify this code
somehow...

My earliest sysdata API was based on v4.2-rc5 [0], this was after we decided we
*wanted* to enable to pass more arguments for fw signing from the start, to
enable custom fw criteria, as my original fw signing effort was completely
transparent to the API and matched what we did with module signing [1], and
based on v4.1-rc3.

Only difference is you just worked on the internal data tossed around. I
provided a way to also use this for growing the API.

[0] https://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux-next.git/log/?h=20150805-sysdata
[1] https://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux-next.git/log/?h=fw-signing-v2-20150513

> We had been forward
> porting it until Stephen Boyd was kind enough (or tired of it) to take
> time out of his clock maintainer-ship and upstream the
> request_firmware_into_buf API. At that point of time it seemed that the
> 'desc' approach was unnecessary, and I agreed.

It was very much needed and it could have helped. Next time please just send
patches right away!

> So Luis's series came
> in much later and wasn't a factor in forward-porting the patches.
>     While it does seem that the _internal_ implementation of
> firmware_class can be a bit friendlier to adding the features that
> are on their way, I can't say the same about the API being exposed to
> drivers in mainline; maintainers and folks with more experience in
> kernel API evolution are better equipped to answer that question.

I actually am not aware how seriously the postulation to the problem I decided
to take on is being considered here...

Some of it may seem straight forward to some based on experience, but due to
the size of the kernel inspired by my prior effort to study collateral
evolutions for both forward and backporting purposes, I've decided to take on
the problem in a bit different light.

Just as your primary reason for your changes was that "the number of things
being passed around between layers of functions inside firmware_class seemed a
bit untenable", I also believed that the way in which we were loosely growing
the firmware API through unnecessary collateral evolutions was untenable.

I will confess that growing the API was just one consideration, another long
term lofty goal also aims towards automatic test driver generation, and enough
is sprinkled on test_driver_data.c that I hope some could infer perhaps how
I started thinking that might be possible one day.

I'm happy to park such effort, so long as we just decide with a path forward so
we can move on and chug on. Perhaps in the future some folks may want to
re-evaluate and consider the approach a bit further.

  Luis

^ permalink raw reply	[flat|nested] 98+ messages in thread

end of thread, other threads:[~2017-06-27 17:26 UTC | newest]

Thread overview: 98+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-03-30  3:25 [PATCH v6 0/5] firmware: add driver data API Luis R. Rodriguez
2017-03-30  3:25 ` [PATCH v6 1/5] firmware: add extensible driver data params Luis R. Rodriguez
2017-04-06  7:26   ` Luca Coelho
2017-04-27  2:05     ` Luis R. Rodriguez
2017-03-30  3:25 ` [PATCH v6 2/5] firmware: add extensible driver data API Luis R. Rodriguez
2017-04-10 12:42   ` Coelho, Luciano
2017-04-11  8:01     ` takahiro.akashi
2017-04-27  3:23       ` Luis R. Rodriguez
2017-04-27  3:16     ` Luis R. Rodriguez
2017-04-27  5:44       ` Luca Coelho
2017-04-27  8:04         ` Luis R. Rodriguez
2017-04-27  6:09       ` Luca Coelho
2017-04-27 10:31         ` Luis R. Rodriguez
2017-04-13  9:36   ` AKASHI Takahiro
2017-04-28  0:51     ` Luis R. Rodriguez
2017-04-28  3:19       ` AKASHI Takahiro
2017-04-29  4:36         ` Luis R. Rodriguez
2017-03-30  3:25 ` [PATCH v6 3/5] test: add new driver_data load tester Luis R. Rodriguez
2017-04-11  8:32   ` AKASHI Takahiro
2017-04-28  1:45     ` Luis R. Rodriguez
2017-05-11 10:46       ` AKASHI Takahiro
2017-05-11 17:11         ` Luis R. Rodriguez
2017-05-17 22:45           ` Li, Yi
2017-05-19 18:31             ` Luis R. Rodriguez
2017-05-11 18:12         ` Luis R. Rodriguez
2017-05-11 18:26         ` Luis R. Rodriguez
2017-05-11 18:32           ` Luis R. Rodriguez
2017-05-12  0:28             ` AKASHI Takahiro
2017-05-12 15:59               ` Luis R. Rodriguez
2017-05-17  9:08                 ` AKASHI Takahiro
2017-05-17 15:38                   ` Luis R. Rodriguez
2017-05-12  0:20           ` AKASHI Takahiro
2017-05-12 15:52             ` Luis R. Rodriguez
2017-05-13 18:46               ` Luis R. Rodriguez
2017-03-30  3:25 ` [PATCH v6 4/5] iwlwifi: convert to use driver data API Luis R. Rodriguez
2017-04-10 13:19   ` Luca Coelho
2017-04-28  0:56     ` Luis R. Rodriguez
2017-04-28 12:17       ` Luca Coelho
2017-03-30  3:25 ` [PATCH v6 5/5] brcmfmac: don't warn user if requested nvram fails Luis R. Rodriguez
2017-04-27  0:49   ` Luis R. Rodriguez
2017-05-02  8:49 ` [PATCH v7 0/5] firmware: add driver data API Luis R. Rodriguez
2017-05-02  8:49   ` [PATCH v7 1/5] firmware: add extensible driver data params Luis R. Rodriguez
2017-05-11 18:17     ` Li, Yi
2017-05-11 18:28       ` Luis R. Rodriguez
2017-05-02  8:49   ` [PATCH v7 2/5] firmware: add extensible driver data API Luis R. Rodriguez
2017-05-02  8:49   ` [PATCH v7 3/5] test: add new driver_data load tester Luis R. Rodriguez
2017-05-11 10:10     ` AKASHI Takahiro
2017-05-11 17:00       ` Luis R. Rodriguez
2017-05-15 18:23     ` [PATCH v8] " Luis R. Rodriguez
2017-05-02  8:49   ` [PATCH v7 4/5] firmware: document the extensible driver data API Luis R. Rodriguez
2017-05-02  8:49   ` [PATCH v7 5/5] iwlwifi: convert to use " Luis R. Rodriguez
2017-05-19 19:10   ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
2017-05-19 19:10     ` [PATCH v8 1/5] firmware: add extensible driver data params Luis R. Rodriguez
2017-05-19 19:10     ` [PATCH v8 2/5] firmware: add extensible driver data API Luis R. Rodriguez
2017-05-19 19:10     ` [PATCH v8 3/5] test: add new driver_data load tester Luis R. Rodriguez
2017-05-19 19:10     ` [PATCH v8 4/5] firmware: document the extensible driver data API Luis R. Rodriguez
2017-05-19 19:10     ` [PATCH v8 5/5] iwlwifi: convert to use " Luis R. Rodriguez
2017-06-05 21:33     ` [PATCH v8 0/5] firmware: add " Luis R. Rodriguez
2017-06-05 21:39       ` [PATCH v9 " Luis R. Rodriguez
2017-06-05 21:39         ` [PATCH v9 1/5] firmware: add extensible driver data params Luis R. Rodriguez
2017-06-13  9:05           ` Greg KH
2017-06-13 10:31             ` Rafał Miłecki
2017-06-13 13:17               ` Greg KH
2017-06-13 14:12                 ` Rafał Miłecki
2017-06-13 15:32                 ` Luis R. Rodriguez
2017-06-13 15:50                   ` Greg KH
2017-06-13 19:40             ` Luis R. Rodriguez
2017-06-14 15:57               ` Li, Yi
2017-06-17 19:38               ` Greg KH
2017-06-19  7:33                 ` Johannes Berg
2017-06-19 19:41                   ` Luis R. Rodriguez
2017-06-20  1:26                     ` AKASHI Takahiro
2017-06-19 19:35                 ` Luis R. Rodriguez
2017-06-23 15:51                   ` Greg KH
2017-06-23 22:43                     ` Luis R. Rodriguez
2017-06-23 23:09                       ` Linus Torvalds
2017-06-24  0:48                         ` Luis R. Rodriguez
2017-06-24 12:39                           ` Greg KH
2017-06-26 17:33                             ` Luis R. Rodriguez
2017-06-26 18:19                               ` Rafał Miłecki
2017-06-26 21:29                                 ` Luis R. Rodriguez
2017-06-27  2:28                               ` Vikram Mulukutla
2017-06-27 17:25                                 ` Luis R. Rodriguez
2017-06-24 12:40                       ` Greg KH
2017-06-26 15:50                         ` Luis R. Rodriguez
2017-06-23 15:59                   ` Greg KH
2017-06-23 22:47                     ` Luis R. Rodriguez
2017-06-19 22:51                 ` Li, Yi
2017-06-20  1:48                   ` AKASHI Takahiro
2017-06-20 15:20                     ` Li, Yi
2017-06-20 16:27                 ` Vikram Mulukutla
2017-06-20 17:22                   ` Luis R. Rodriguez
2017-06-21  0:49                     ` AKASHI Takahiro
2017-06-23 16:33                       ` Luis R. Rodriguez
2017-06-05 21:39         ` [PATCH v9 2/5] firmware: add extensible driver data API Luis R. Rodriguez
2017-06-05 21:39         ` [PATCH v9 3/5] test: add new driver_data load tester Luis R. Rodriguez
2017-06-05 21:39         ` [PATCH v9 4/5] firmware: document the extensible driver data API Luis R. Rodriguez
2017-06-05 21:39         ` [PATCH v9 5/5] iwlwifi: convert to use " Luis R. Rodriguez

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).