Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1645850 > unrolled thread

[PATCH v8 0/5] firmware: add driver data API

Started by"Luis R. Rodriguez" <mcgrof@kernel.org>
First post2017-05-19 21:20 +0200
Last post2017-05-19 21:20 +0200
Articles 5 — 1 participant

Back to article view | Back to linux.kernel

This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by below is the oldest one visible, not the original post.


Contents

  [PATCH v8 0/5] firmware: add driver data API "Luis R. Rodriguez" <mcgrof@kernel.org> - 2017-05-19 21:20 +0200
    [PATCH v8 5/5] iwlwifi: convert to use driver data API "Luis R. Rodriguez" <mcgrof@kernel.org> - 2017-05-19 21:20 +0200
    [PATCH v8 1/5] firmware: add extensible driver data params "Luis R. Rodriguez" <mcgrof@kernel.org> - 2017-05-19 21:20 +0200
    [PATCH v8 2/5] firmware: add extensible driver data API "Luis R. Rodriguez" <mcgrof@kernel.org> - 2017-05-19 21:20 +0200
    [PATCH v8 4/5] firmware: document the extensible driver data API "Luis R. Rodriguez" <mcgrof@kernel.org> - 2017-05-19 21:20 +0200

#1645850 — [PATCH v8 0/5] firmware: add driver data API

From"Luis R. Rodriguez" <mcgrof@kernel.org>
Date2017-05-19 21:20 +0200
Subject[PATCH v8 0/5] firmware: add driver data API
Message-ID<tIVBo-6Cu-5@gated-at.bofh.it>
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

[toc] | [next] | [standalone]


#1645851 — [PATCH v8 5/5] iwlwifi: convert to use driver data API

From"Luis R. Rodriguez" <mcgrof@kernel.org>
Date2017-05-19 21:20 +0200
Subject[PATCH v8 5/5] iwlwifi: convert to use driver data API
Message-ID<tIVBo-6Cu-11@gated-at.bofh.it>
In reply to#1645850
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

[toc] | [prev] | [next] | [standalone]


#1645853 — [PATCH v8 1/5] firmware: add extensible driver data params

From"Luis R. Rodriguez" <mcgrof@kernel.org>
Date2017-05-19 21:20 +0200
Subject[PATCH v8 1/5] firmware: add extensible driver data params
Message-ID<tIVBo-6Cu-19@gated-at.bofh.it>
In reply to#1645850
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

[toc] | [prev] | [next] | [standalone]


#1645854 — [PATCH v8 2/5] firmware: add extensible driver data API

From"Luis R. Rodriguez" <mcgrof@kernel.org>
Date2017-05-19 21:20 +0200
Subject[PATCH v8 2/5] firmware: add extensible driver data API
Message-ID<tIVBo-6Cu-21@gated-at.bofh.it>
In reply to#1645850
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

[toc] | [prev] | [next] | [standalone]


#1645856 — [PATCH v8 4/5] firmware: document the extensible driver data API

From"Luis R. Rodriguez" <mcgrof@kernel.org>
Date2017-05-19 21:20 +0200
Subject[PATCH v8 4/5] firmware: document the extensible driver data API
Message-ID<tIVBp-6Cu-25@gated-at.bofh.it>
In reply to#1645850
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

[toc] | [prev] | [standalone]


Back to top | Article view | linux.kernel


csiph-web