All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/4] firmware: add drvdata API
@ 2016-12-16 11:46 Luis R. Rodriguez
  2016-12-16 11:46 ` [PATCH v3 1/4] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
                   ` (4 more replies)
  0 siblings, 5 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2016-12-16 11:46 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: wagi, teg, mchehab, zajec5, linux-kernel, markivx, stephen.boyd,
	broonie, zohar, tiwai, johannes, chunkeey, hauke, jwboyer,
	dmitry.torokhov, dwmw2, jslaby, torvalds, luto, fengguang.wu,
	rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

This series is intended for considering *after* the merge window.

This v3 takes off from where we I last left off [0].  I've put
aside patches not relating to this topic into its own separate
small series [1]. Given the huge amount of confusion over the
firmware API in general and also the firmware "usermode helper"
in that series I also revamped the entire firmware documentation.
Folks should stop calling it firmware "usermode helper" and
simply referring to it as "fallback mechanisms" to avoid any
further confusion.

I followed up with quite a few folks at Plumbers and over e-mail
have been having quite a bit of productive exchanges for the outlook
for the future of what a clean slate for fallback mechanisms can
look like, but one thing is clear -- the current set of fallback
mechanisms are quite a mess. The purpose of this series is to only
add an extensible firmware API onto which we can rely on in the
future for new features.

The fallback mechanism is not addressed yet given the old uevent
fallback mechanism was ripped out form systemd a while ago, and
so considering a clean solution means coordinating well with the
systemd folks. This is in no way urgent given we have only 2 upstream
drivers usign the custom fallback mechanism, and the purpose of
the new extensible API is new features. Part of the new challenges
we've faced (like the pivot root dillema) have been considered by
systemd folks and others and in particular development lead by
Tom Gundersen and Daniel Wagner are addressing the issues we
had not been able to resolve yet in kernelspace by a userspace
solution. For detalis check out their latest firmwared development [2]
work, which is still being advanced. This is what we intend on
relying on for a clean slate solution for a firmware fallback
mechanism eventually.

I've also droped the SmPL patch to help developers convert drivers
for two reasons:

1) We had some bikeshedding name changes and I haven't had time
   to port it and test it
2) It seems some folks were under the incorrect impression that
   the goal was to convert all drivers -- that's not the case, the
   goal is to add a new clean *extensible* API which lets us add
   new features without the mess we have with the current API.

As I had noted in the last series the reason for why using the
drvdata API can incur a bit more code is we require a callback
for the sync call so we can deal with freeing your firmware for
you, as an option. The older API didn't do this.

This converts two drivers just as an example for now, more examples
are available on the test_drvdata driver.

If you want to test pulling the code its up on my linux-next tree
on the 20161216-drvdata-v3 branch.

[0] https://lkml.kernel.org/r/1466121559-22363-1-git-send-email-mcgrof@kernel.org
[1] https://lkml.kernel.org/r/20161216111038.22064-1-mcgrof@kernel.org
[2] https://github.com/teg/firmwared
[3] https://git.kernel.org/cgit/linux/kernel/git/mcgrof/linux-next.git/log/?h=20161216-drvdata-v3

Luis R. Rodriguez (4):
  firmware: add new extensible firmware API - drvdata
  test: add new drvdata loader tester
  x86/microcode: convert to use sysdata API
  p54: convert to sysdata API

 Documentation/driver-api/firmware/drvdata.rst      |   91 ++
 Documentation/driver-api/firmware/index.rst        |    1 +
 Documentation/driver-api/firmware/introduction.rst |   11 +
 MAINTAINERS                                        |    3 +-
 arch/x86/kernel/cpu/microcode/amd.c                |   56 +-
 drivers/base/firmware_class.c                      |  327 +++++++
 drivers/net/wireless/intersil/p54/eeprom.c         |    2 +-
 drivers/net/wireless/intersil/p54/fwio.c           |    5 +-
 drivers/net/wireless/intersil/p54/led.c            |    2 +-
 drivers/net/wireless/intersil/p54/main.c           |    2 +-
 drivers/net/wireless/intersil/p54/p54.h            |    3 +-
 drivers/net/wireless/intersil/p54/p54pci.c         |   26 +-
 drivers/net/wireless/intersil/p54/p54pci.h         |    4 +-
 drivers/net/wireless/intersil/p54/p54spi.c         |   80 +-
 drivers/net/wireless/intersil/p54/p54spi.h         |    2 +-
 drivers/net/wireless/intersil/p54/p54usb.c         |   18 +-
 drivers/net/wireless/intersil/p54/p54usb.h         |    4 +-
 drivers/net/wireless/intersil/p54/txrx.c           |    2 +-
 include/linux/drvdata.h                            |  245 +++++
 lib/Kconfig.debug                                  |   12 +
 lib/Makefile                                       |    1 +
 lib/test_drvdata.c                                 | 1033 ++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile          |    2 +-
 tools/testing/selftests/firmware/config            |    1 +
 tools/testing/selftests/firmware/drvdata.sh        |  827 ++++++++++++++++
 25 files changed, 2679 insertions(+), 81 deletions(-)
 create mode 100644 Documentation/driver-api/firmware/drvdata.rst
 create mode 100644 include/linux/drvdata.h
 create mode 100644 lib/test_drvdata.c
 create mode 100755 tools/testing/selftests/firmware/drvdata.sh

-- 
2.10.1

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

* [PATCH v3 1/4] firmware: add new extensible firmware API - drvdata
  2016-12-16 11:46 [PATCH v3 0/4] firmware: add drvdata API Luis R. Rodriguez
@ 2016-12-16 11:46 ` Luis R. Rodriguez
  2016-12-16 11:46 ` [PATCH v3 2/4] test: add new drvdata loader tester Luis R. Rodriguez
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2016-12-16 11:46 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: wagi, teg, mchehab, zajec5, linux-kernel, markivx, stephen.boyd,
	broonie, zohar, tiwai, johannes, chunkeey, hauke, jwboyer,
	dmitry.torokhov, dwmw2, jslaby, torvalds, luto, fengguang.wu,
	rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

The firmware API has evolved over the years slowly, as it
grows we extend it by adding new routines or at times we extend
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 firmware API is also
now being used for things outside of the scope of what typically
would be considered "firmware", an example here is the p54 driver
enables users to provide a custom EEPROM through this interface.
Another example is optional CPU microcode updates. This list is
actually quite endless...

There are other subsystems which would like to make use of the
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. If different requirements are needed it would
again mean adding more arguments and making a slew of collateral
evolutions, or adding yet-another-new-API-call (TM).

Another sticking point over the current firmware API is that
some callers may need the firmware fallback mechanism when its
enabled. There are two types of fallback mechanisms and both have
shortcomings. This new API accepts the current status quo and
ignore the fallback mechanism all together. When and if we add
support for it, it will be well though out.

This new extensible firmware API enables new extensions to be added by
avoiding future unnecessary collateral evolutions as this code /
features get added. This new set of APIs leaves the old firmware API
as-is, ignores all firmware fallback mechanism, labels the new
API to reflect its broad use outside of the scope of firmware: driver
data helpers, and builds on top of the original firmware core code.
We purposely try to limit the scope of changes in this new API to
simply enable a flexible API to start off with.

The new extensible "driver data" set of helpers accepts that there
really are only two types of requests for accessing driver data:

a) synchronous requests
b) asynchronous requests

Both of these requests may have a different set of requirements which
must be met. These requirements can simply be passed as a struct
drvdata_req_params to each type of request. This struct can be extended
over time to support different requirements as the kernel evolves.

Using the new driver data helpers is only necessary if you have
requirements outside of what the existing old firmware API accepts
or alternatively if you want to ensure to avoid the old firmware
fallback mechanism at all times, regardless of what kernel your driver
might run in.

Developers with new uses should extend the new new struct drvdata_req_params
and driver data code to provide support for new features.

A *few* simple features added as part of the new set of driver data
request APIs, other than making the new API easily extensible for
the future:

 - The firmware fallback mechanism is currenlty always ignored
 - 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 descriptor. The new
   drvdata API is able to free the drvdata file for you by requiring a
   consumer callback for the driver data file.
 - You no longer need to declare and use your own completions, you
   can replace your completions with drvdata_synchronize_request() using
   the async_cookie set for you by drvdata_file_request_async(). When
   drvdata_file_request_async() completes you can rest assured all the
   work for both triggering, and processing the drvdata using any of
   your callbacks has completed.
 - Allow 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 -- although it should be
   noted another one of its goal was to also skip the fallback mechanisms.
   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.
 - The driver data request APIs currently match the old synchronous firmware
   API calls to refcounted firmware_class module, but it should be easy
   to add support now to enable also refcounting the caller's module
   should it be be needed. Likewise the driver data request APIs match the
   old asynchronous firmware API call and refcounts the caller's module.

v5 changes:

o Bike shedding:
  o sysdata/drvdata
  o sysdata_req_desc/drvdata_req_params
o Documentation conversion to Sphinx

v4 changes:

o Add SYSDATA_KEEP_SYNC() and SYSDATA_KEEP_ASYNC() macro helpers,
  drivers that want to keep the firmware are pretty common, however
  note that if we can figure out a way to avoid having drivers
  deal with releasing the firmware we're better off, that however
  can be an additional change to look forward to.

o 0-day-bot make htmldocs warning fixes

o When developing and testing the sysdata test driver I ended up
  running into tons of hairball code just to be able to come up
  with enough code to be able to tweak all possible knobs using
  a userspace test interface. This begged for a cleaner API and
  in testing found that async_schedule_domain() made life so much
  easier. This also added the sysdata_synchronize_request() helper
  which user can use to see if their async request completed. This
  should help users considerably as well. Updated code, commit log
  and documentation to reflect these changes.

o In testing found that to make semantics stronger we should
  require @optional to true on the descriptor if an optional
  callback is to be provided (with SYSDATA_SYNC_OPT_CB() or
  SYSDATA_ASYNC_OPT_CB()). Made notes to ensure to users
  that set @optional to true but are not providing a opt_fail_cb()
  should at the very least seriously consider using the returned
  using async_cookie to sysdata_synchronize_request() to ensure
  no lingering requests are kept out of bounds.

o Updated commit log to reflect how we can compartamentalize
  usermode helper code

o Adds SYSDATA_ASYNC_OPT_CB()

o Forces @optional on SYSDATA_SYNC_OPT_CB() to true

o Ensures sysdata_file_request() and sysdata_file_request_async()
  check for emptry string (name[0] == '\0') as follow up to
  Kees's check for empty string name 471b095dfe0d6 ("firmware_class:
  make sure fw requests contain a name") and later a fix by
  Brian through 715780ae4bb76d ("firmware: actually return NULL on
  failed request_firmware_nowait()).

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 Documentation/driver-api/firmware/drvdata.rst      |  91 ++++++
 Documentation/driver-api/firmware/index.rst        |   1 +
 Documentation/driver-api/firmware/introduction.rst |  11 +
 MAINTAINERS                                        |   3 +-
 drivers/base/firmware_class.c                      | 327 +++++++++++++++++++++
 include/linux/drvdata.h                            | 245 +++++++++++++++
 6 files changed, 677 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/driver-api/firmware/drvdata.rst
 create mode 100644 include/linux/drvdata.h

diff --git a/Documentation/driver-api/firmware/drvdata.rst b/Documentation/driver-api/firmware/drvdata.rst
new file mode 100644
index 000000000000..0445d49cd974
--- /dev/null
+++ b/Documentation/driver-api/firmware/drvdata.rst
@@ -0,0 +1,91 @@
+===========
+drvdata API
+===========
+
+As the kernel evolves we keep extending the firmware_class set of APIs
+with more or less arguments, this creates a slew of collateral evolutions.
+The set of users of firmware request APIs has also grown now to include
+users which are not looking for "firmware" per se, but instead general
+driver data files which for one reason or another has been decided to be
+kept oustide of the kernel, and/or to allow dynamic updates. The driver data
+request set of APIs addresses rebranding of firmware as generic driver data
+files, and provides a way to enable these APIs to easily be extended without
+much collateral evolutions.
+
+Driver data modes of operation
+==============================
+
+There are only two types of modes of operation for system data requests:
+
+  * synchronous  - drvdata_request()
+  * asynchronous - drvdata_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 can grow as with new
+fields as requirements grow. The old firmware API provides two synchronous
+requests: request_firmware() and request_firmware_direct(), the later allowing
+the caller to specify that the "driver data file" is optional.  The driver data
+request API allows a caller to set the optional nature of the driver data
+on the request parameter data structure using the same synchronous API. Since
+this requirement is part of the request paramter data structure it also allows
+asynchronous requests to specify that the driver data is optional.
+
+Reference counting and releasing the system 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 mimic this behaviour, for synchronous
+requests the firmware_class module is refcounted through the use of
+dfl_sync_reqs, although if in the future we may later enable use of
+also refcounting the caller's module as well. Likewise in the future we
+may extend 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 paramter data structure.
+
+Async cookies, replacing completions
+====================================
+
+With this new API you do not need to declare and use your own completions, you
+can replace your completions with drvdata_synchronize_request() using the
+async_cookie set for you by drvdata_file_request_async(). When
+drvdata_file_request_async() completes you can rest assured all the work for
+both triggering, and processing the drvdata using any of your callbacks has
+completed.
+
+Fallback mechanisms on the driver data API
+==========================================
+
+The old firmware API provided support for a series of fallback mechanisms. The
+new driver data API abandons all current notions of the fallback mechanisms,
+it may soon add support for one though.
+
+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..8d275c4c165b 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
+   drvdata
 
 .. only::  subproject and html
 
diff --git a/Documentation/driver-api/firmware/introduction.rst b/Documentation/driver-api/firmware/introduction.rst
index 211cb44eb972..d7d5ef846ca0 100644
--- a/Documentation/driver-api/firmware/introduction.rst
+++ b/Documentation/driver-api/firmware/introduction.rst
@@ -25,3 +25,14 @@ 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
+* drvdata API - new flexible API
+
+New features and development should be added through the new drvdata API to
+avoid unnecessary collateral evolutions when adding new features.
diff --git a/MAINTAINERS b/MAINTAINERS
index b784e4216868..2190a5e2ea6a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5038,7 +5038,7 @@ F:	include/linux/firewire.h
 F:	include/uapi/linux/firewire*.h
 F:	tools/firewire/
 
-FIRMWARE LOADER (request_firmware)
+FIRMWARE LOADER (request_firmware, drvdata_file_request)
 M:	Ming Lei <ming.lei@canonical.com>
 M:	Luis R. Rodriguez <mcgrof@kernel.org>
 L:	linux-kernel@vger.kernel.org
@@ -5046,6 +5046,7 @@ S:	Maintained
 F:	Documentation/firmware_class/
 F:	drivers/base/firmware*.c
 F:	include/linux/firmware.h
+F:	include/linux/drvdata.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 4497d263209f..1f3f2212d6e9 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) 2016 Luis R. Rodriguez <mcgrof@kernel.org>
  *
  * Please see Documentation/firmware_class/ for more information.
  *
@@ -18,6 +19,7 @@
 #include <linux/mutex.h>
 #include <linux/workqueue.h>
 #include <linux/highmem.h>
+#include <linux/drvdata.h>
 #include <linux/firmware.h>
 #include <linux/slab.h>
 #include <linux/sched.h>
@@ -40,6 +42,12 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
 MODULE_DESCRIPTION("Multi purpose firmware loading support");
 MODULE_LICENSE("GPL");
 
+static const struct drvdata_reqs dfl_sync_reqs = {
+	.mode = DRVDATA_SYNC,
+	.module = THIS_MODULE,
+	.gfp = GFP_KERNEL,
+};
+
 /* Builtin firmware support */
 
 #ifdef CONFIG_FW_LOADER
@@ -1338,6 +1346,184 @@ void release_firmware(const struct firmware *fw)
 }
 EXPORT_SYMBOL(release_firmware);
 
+static void drvdata_file_update(struct drvdata *drvdata)
+{
+	struct firmware *fw;
+	struct firmware_buf *buf;
+
+	if (!drvdata || !drvdata->priv)
+		return;
+
+	fw = drvdata->priv;
+	if (!fw->priv)
+		return;
+
+	buf = fw->priv;
+
+	drvdata->size = buf->size;
+	drvdata->data = buf->data;
+
+	pr_debug("%s: fw-%s buf=%p data=%p size=%u",
+		 __func__, buf->fw_id, buf, buf->data,
+		 (unsigned int)buf->size);
+}
+
+/*
+ * prepare firmware and firmware_buf structs;
+ * return 0 if a firmware is already assigned, 1 if need to load one,
+ * or a negative error code
+ */
+static int
+_request_drvdata_prepare(struct drvdata **drvdata_p, const char *name,
+			 struct device *device)
+{
+	struct drvdata *drvdata;
+	struct firmware *fw;
+	int ret;
+
+	*drvdata_p = drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata) {
+		dev_err(device, "%s: kmalloc(struct drvdata) failed\n",
+			__func__);
+		return -ENOMEM;
+	}
+
+	ret = _request_firmware_prepare(&fw, name, device, NULL, 0);
+	if (ret >= 0)
+		drvdata->priv = fw;
+
+	return ret;
+}
+
+/**
+ * release_drvdata_file: - release the resource associated with the drvdata file
+ * @drvdata: drvdata file resource to release
+ **/
+void release_drvdata(const struct drvdata *drvdata)
+{
+	struct firmware *fw;
+
+	if (drvdata) {
+		if (drvdata->priv) {
+			fw = drvdata->priv;
+			release_firmware(fw);
+		}
+	}
+	kfree(drvdata);
+}
+EXPORT_SYMBOL_GPL(release_drvdata);
+
+/*
+ * drvdata_p is always set to be NULL unless a proper driver
+ * data file was found.
+ */
+static int _drvdata_request(const struct drvdata **drvdata_p,
+			    const char *name,
+			    const struct drvdata_req_params *params,
+			    struct device *device)
+{
+	struct drvdata *drvdata = NULL;
+	struct firmware *fw = NULL;
+	int ret = -EINVAL;
+
+	if (!drvdata_p)
+		goto out;
+
+	if (!params)
+		goto out;
+
+	if (!name || name[0] == '\0')
+		goto out;
+
+	ret = _request_drvdata_prepare(&drvdata, name, device);
+	if (ret <= 0) /* error or already assigned */
+		goto out;
+
+	fw = drvdata->priv;
+
+	ret = fw_get_filesystem_firmware(device, fw->priv);
+	if (ret && !params->optional)
+		pr_err("Direct driver data load for %s failed with error %d\n",
+		       name, ret);
+
+	if (!ret)
+		ret = assign_firmware_buf(fw, device, FW_OPT_UEVENT);
+
+ out:
+	if (ret < 0) {
+		release_drvdata(drvdata);
+		drvdata = NULL;
+	}
+
+	drvdata_file_update(drvdata);
+
+	*drvdata_p = drvdata;
+
+	return ret;
+}
+
+/**
+ * drvdata_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 drvdata_request(const char *name,
+		    const struct drvdata_req_params *params,
+		    struct device *device)
+{
+	const struct drvdata *drvdata;
+	const struct drvdata_reqs *sync_reqs;
+	int ret;
+
+	if (!device || !params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (params->sync_reqs.mode != DRVDATA_SYNC)
+		return -EINVAL;
+
+	if (drvdata_sync_opt_cb(params) && !params->optional)
+		return -EINVAL;
+
+	sync_reqs = &dfl_sync_reqs;
+
+	__module_get(sync_reqs->module);
+	get_device(device);
+
+	ret = _drvdata_request(&drvdata, name, params, device);
+	if (ret && params->optional)
+		ret = drvdata_sync_opt_call_cb(params);
+	else
+		ret = drvdata_sync_call_cb(params, drvdata);
+
+	if (!params->keep)
+		release_drvdata(drvdata);
+
+	put_device(device);
+	module_put(sync_reqs->module);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drvdata_request);
+
 /* Async support */
 struct firmware_work {
 	struct work_struct work;
@@ -1426,6 +1612,145 @@ request_firmware_nowait(
 }
 EXPORT_SYMBOL(request_firmware_nowait);
 
+struct drvdata_file_work {
+	const char *name;
+	struct drvdata_req_params params;
+	struct device *device;
+};
+
+static ASYNC_DOMAIN(drvdata_async_domain);
+
+static void request_drvdata_file_work_func(void *data, async_cookie_t cookie)
+{
+	struct drvdata_file_work *drv_work = data;
+	const struct drvdata_req_params *params;
+	const struct drvdata_reqs *sync_reqs;
+	const struct drvdata *drvdata;
+	int ret;
+
+	params = &drv_work->params;
+	sync_reqs = &params->sync_reqs;
+
+	ret = _drvdata_request(&drvdata, drv_work->name,
+			       params, drv_work->device);
+	if (ret && params->optional)
+		drvdata_async_opt_call_cb(params);
+	else
+		drvdata_async_call_cb(drvdata, params);
+
+	if (!params->keep)
+		release_drvdata(drvdata);
+
+	put_device(drv_work->device);
+	module_put(sync_reqs->module);
+
+	kfree_const(drv_work->name);
+	kfree(drv_work);
+}
+
+/**
+ * drvdata_request_async - asynchronous request for a driver data file
+ * @name: name of the driver data file
+ * @desc: driver data file descriptor, it provides all the requirements
+ * 	which must be met for the file being requested.
+ * @device: device for which firmware is being loaded
+ * @async_cookie: used for checkpointing your async request
+ *
+ * This performs an asynchronous driver data file lookup with the requirements
+ * specified on @desc. The request for the actual driver data file lookup will
+ * be scheduled with async_schedule_domain() 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 @desc. If the driver data file is optional you must
+ * specify that on the @desc 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 @desc. 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.
+ *
+ * You should use rely on async_cookie to determine if your asynchronous work
+ * has been scheduled and completed. If you need to wait for completion of
+ * processing of your drvdata through your callbacks, or if you just want to
+ * know the hunt is over you can drvdata_synchronize_request() with the
+ * async_cookie.
+ */
+int drvdata_request_async(const char *name,
+			  const struct drvdata_req_params *params,
+			  struct device *device,
+			  async_cookie_t *async_cookie)
+{
+	struct drvdata_file_work *drv_work;
+	const struct drvdata_reqs *sync_reqs;
+
+	if (!device || !params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (params->sync_reqs.mode != DRVDATA_ASYNC)
+		return -EINVAL;
+
+	if (drvdata_async_opt_cb(params) && !params->optional)
+		return -EINVAL;
+
+	sync_reqs = &params->sync_reqs;
+
+	drv_work = kzalloc(sizeof(struct drvdata_file_work), sync_reqs->gfp);
+	if (!drv_work)
+		return -ENOMEM;
+
+	drv_work->device = device;
+	memcpy(&drv_work->params, params, sizeof(struct drvdata_req_params));
+	drv_work->name = kstrdup_const(name, sync_reqs->gfp);
+	if (!drv_work->name) {
+		kfree(drv_work);
+		return -ENOMEM;
+	}
+
+	if (!try_module_get(sync_reqs->module)) {
+		kfree_const(drv_work->name);
+		kfree(drv_work);
+		return -EFAULT;
+	}
+
+	get_device(drv_work->device);
+
+	*async_cookie = async_schedule_domain(request_drvdata_file_work_func,
+					      drv_work,
+					      &drvdata_async_domain);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(drvdata_request_async);
+
+/**
+ * drvdata_synchronize_request - wait until your async drvdata calls complete
+ * @async_cookie: async cookie
+ *
+ * Waits until all asynchronous drvdata calls prior to and up to @async_cookie
+ * have been completed. You can use this to wait for completion of your own
+ * async callback. Your wait will end after request_drvdata_file_work_func()
+ * is called for your cookie. At this point you can rest assured your
+ * series of async callbacks would have been called if supplied.
+ *
+ * async_cookie+1 is used as async_synchronize_cookie_domain() only waits
+ * until at least your own call is next in queue to be run, we want the
+ * next item after yours to be in queue, this tells us we have run already.
+ * Should there not be any other async scheduled item after yours this will
+ * simply wait until all async drvdata calls are complete.
+ */
+void drvdata_synchronize_request(async_cookie_t async_cookie)
+{
+	async_synchronize_cookie_domain(async_cookie+1, &drvdata_async_domain);
+}
+EXPORT_SYMBOL_GPL(drvdata_synchronize_request);
+
 #ifdef CONFIG_PM_SLEEP
 static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);
 
@@ -1799,6 +2124,7 @@ static int __init firmware_class_init(void)
 
 static void __exit firmware_class_exit(void)
 {
+	async_synchronize_full_domain(&drvdata_async_domain);
 #ifdef CONFIG_PM_SLEEP
 	unregister_syscore_ops(&fw_syscore_ops);
 	unregister_pm_notifier(&fw_cache.pm_notify);
@@ -1807,6 +2133,7 @@ static void __exit firmware_class_exit(void)
 	unregister_reboot_notifier(&fw_shutdown_nb);
 	class_unregister(&firmware_class);
 #endif
+	async_unregister_domain(&drvdata_async_domain);
 }
 
 fs_initcall(firmware_class_init);
diff --git a/include/linux/drvdata.h b/include/linux/drvdata.h
new file mode 100644
index 000000000000..fad7524513f6
--- /dev/null
+++ b/include/linux/drvdata.h
@@ -0,0 +1,245 @@
+#ifndef _LINUX_DRVDATA_H
+#define _LINUX_DRVDATA_H
+
+#include <linux/types.h>
+#include <linux/compiler.h>
+#include <linux/gfp.h>
+#include <linux/device.h>
+#include <linux/async.h>
+
+/*
+ * Driver Data internals
+ *
+ * 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/.
+ */
+
+struct drvdata {
+	size_t size;
+	const u8 *data;
+
+	/* drvdata loader private fields */
+	void *priv;
+};
+
+/**
+ * enum drvdata_mode - driver data mode of operation
+ *
+ * DRVDATA_SYNC: your call to request driver data is synchronous. We will
+ * 	look for the driver data file you have requested immediatley.
+ * DRVDATA_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 drvdata_mode {
+	DRVDATA_SYNC,
+	DRVDATA_ASYNC,
+};
+
+/* one per drvdata_mode */
+union drvdata_cbs {
+	struct {
+		int __must_check (*found_cb)(void *, const struct drvdata *);
+		void *found_ctx;
+
+		int __must_check (*opt_fail_cb)(void *);
+		void *opt_fail_ctx;
+	} sync;
+	struct {
+		void (*found_cb)(const struct drvdata *, void *);
+		void *found_ctx;
+
+		void (*opt_fail_cb)(void *);
+		void *opt_fail_ctx;
+	} async;
+};
+
+struct drvdata_reqs {
+	enum drvdata_mode mode;
+	struct module *module;
+	gfp_t gfp;
+};
+
+/**
+ * struct drvdata_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. You must set this to true if you have provided a opt_fail_cb
+ *	callback, DRVDATA_SYNC_OPT_CB() and DRVDATA_ASYNC_OPT_CB() ensures
+ *	this is done for you. If you set this to true and are using an
+ *	asynchronous request but not providing a opt_fail_cb() you should
+ *	seriously consider using at the very least using async_cookie provided
+ *	to you to drvdata_synchronize_request() to ensure no lingering
+ *	requests are kept out of bounds.
+ * @keep: if set the caller wants to claim ownership over the system data
+ *	through one of its callbacks, it must later free it with
+ *	release_drvdata(). By default this is set to false and the kernel
+ *	will release the system data file for you after callback processing
+ *	has completed.
+ * @sync_reqs: synchronization requirements, this will be taken care for you
+ *	by default if you are usingy drvdata_request(), otherwise you
+ *	should provide your own requirements.
+ *
+ * This structure is set the by the driver and passed to the system data
+ * file helpers drvdata_request() or drvdata_request_async().
+ * It is intended to carry all requirements and specifications required
+ * to complete the task to get the requested system date file to the caller.
+ * If you wish to extend functionality of system data file requests you
+ * should extend this data structure and make use of the extensions on
+ * the callers to avoid unnecessary collateral evolutions.
+ *
+ * You are allowed to provide a callback to handle if a system data file was
+ * found or not. You do not need to provide a callback. You may also set
+ * an optional flag which would enable you to declare that the system data
+ * file is optional and that if it is not found an alternative callback be
+ * run for you.
+ *
+ * Refer to drvdata_request() and drvdata_request_async() for more
+ * details.
+ */
+struct drvdata_req_params {
+	bool optional;
+	bool keep;
+	struct drvdata_reqs sync_reqs;
+	const union drvdata_cbs cbs;
+};
+
+/*
+ * We keep these template definitions to a minimum for the most
+ * popular requests.
+ */
+
+/* Typical sync data case */
+#define DRVDATA_SYNC_FOUND(__found_cb, __ctx)				\
+	.cbs.sync.found_cb = __found_cb,				\
+	.cbs.sync.found_ctx = __ctx
+
+#define DRVDATA_DEFAULT_SYNC(__found_cb, __ctx)				\
+	DRVDATA_SYNC_FOUND(__found_cb, __ctx)
+
+#define DRVDATA_KEEP_SYNC(__found_cb, __ctx)				\
+	DRVDATA_DEFAULT_SYNC(__found_cb, __ctx),			\
+	.keep= true
+
+/* If you have one fallback routine */
+#define DRVDATA_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
+ * drvdata_request_async(). Drivers can override.
+ */
+#define DRVDATA_DEFAULT_ASYNC(__found_cb, __ctx)			\
+	.sync_reqs = {							\
+		.mode = DRVDATA_ASYNC,					\
+		.module = THIS_MODULE,					\
+		.gfp = GFP_KERNEL,					\
+	},								\
+	.cbs.async = {							\
+		.found_cb = __found_cb,					\
+		.found_ctx = __ctx,					\
+	}
+
+#define DRVDATA_KEEP_ASYNC(__found_cb, __ctx)				\
+	DRVDATA_DEFAULT_ASYNC(__found_cb, __ctx),			\
+	.keep = true
+
+#define DRVDATA_ASYNC_OPT_CB(__fail_cb, __ctx)				\
+	.optional = true,						\
+	.cbs.async.opt_fail_cb = __fail_cb,				\
+	.cbs.async.opt_fail_ctx = __ctx
+
+#define drvdata_sync_cb(param)		((params)->cbs.sync.found_cb)
+#define drvdata_sync_ctx(params)	((params)->cbs.sync.found_ctx)
+static inline int drvdata_sync_call_cb(const struct drvdata_req_params *params,
+				       const struct drvdata *drvdata)
+{
+	if (params->sync_reqs.mode != DRVDATA_SYNC)
+		return -EINVAL;
+	if (!drvdata_sync_cb(params)) {
+		if (drvdata)
+			return 0;
+		return -ENOENT;
+	}
+	return drvdata_sync_cb(params)(drvdata_sync_ctx(params), drvdata);
+}
+
+#define drvdata_sync_opt_cb(params)	((params)->cbs.sync.opt_fail_cb)
+#define drvdata_sync_opt_ctx(params)	((params)->cbs.sync.opt_fail_ctx)
+static
+inline int drvdata_sync_opt_call_cb(const struct drvdata_req_params *params)
+{
+	if (params->sync_reqs.mode != DRVDATA_SYNC)
+		return -EINVAL;
+	if (!drvdata_sync_opt_cb(params))
+		return 0;
+	return drvdata_sync_opt_cb(params)(drvdata_sync_opt_ctx(params));
+}
+
+#define drvdata_async_cb(params)	((params)->cbs.async.found_cb)
+#define drvdata_async_ctx(params)	((params)->cbs.async.found_ctx)
+static
+inline void drvdata_async_call_cb(const struct drvdata *drvdata,
+				  const struct drvdata_req_params *params)
+{
+	if (params->sync_reqs.mode != DRVDATA_ASYNC)
+		return;
+	if (!drvdata_async_cb(params))
+		return;
+	drvdata_async_cb(params)(drvdata, drvdata_async_ctx(params));
+}
+
+#define drvdata_async_opt_cb(params)	((params)->cbs.async.opt_fail_cb)
+#define drvdata_async_opt_ctx(params)	((params)->cbs.async.opt_fail_ctx)
+static
+inline void drvdata_async_opt_call_cb(const struct drvdata_req_params *params)
+{
+	if (params->sync_reqs.mode != DRVDATA_ASYNC)
+		return;
+	if (!drvdata_async_opt_cb(params))
+		return;
+	drvdata_async_opt_cb(params)(drvdata_async_opt_ctx(params));
+}
+
+#if defined(CONFIG_FW_LOADER) || (defined(CONFIG_FW_LOADER_MODULE) && defined(MODULE))
+int drvdata_request(const char *name,
+		    const struct drvdata_req_params *params,
+		    struct device *device);
+int drvdata_request_async(const char *name,
+			  const struct drvdata_req_params *params,
+			  struct device *device,
+			  async_cookie_t *async_cookie);
+void release_drvdata(const struct drvdata *drvdata);
+void drvdata_synchronize_request(async_cookie_t async_cookie);
+#else
+static inline int drvdata_request(const char *name,
+				  const struct drvdata_req_params *params,
+				  struct device *device)
+{
+	return -EINVAL;
+}
+
+static
+inline int drvdata_request_async(const char *name,
+				 const struct drvdata_req_params *params,
+				 struct device *device,
+				 async_cookie_t *async_cookie);
+{
+	return -EINVAL;
+}
+
+static inline void release_drvdata(const struct drvdata *drvdata)
+{
+}
+
+void drvdata_synchronize_request(async_cookie_t async_cookie)
+{
+}
+#endif
+
+#endif /* _LINUX_DRVDATA_H */
-- 
2.10.1

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

* [PATCH v3 2/4] test: add new drvdata loader tester
  2016-12-16 11:46 [PATCH v3 0/4] firmware: add drvdata API Luis R. Rodriguez
  2016-12-16 11:46 ` [PATCH v3 1/4] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
@ 2016-12-16 11:46 ` Luis R. Rodriguez
  2016-12-16 11:46 ` [PATCH v3 3/4] x86/microcode: convert to use sysdata API Luis R. Rodriguez
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2016-12-16 11:46 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: wagi, teg, mchehab, zajec5, linux-kernel, markivx, stephen.boyd,
	broonie, zohar, tiwai, johannes, chunkeey, hauke, jwboyer,
	dmitry.torokhov, dwmw2, jslaby, torvalds, luto, fengguang.wu,
	rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

This adds a load tester driver test_drvdata a for the new
extensible drvdata loader APIs, part of firmware_class.
Since the fallback mechanisms are currenlty completely
ignored by the drvdata API the testing is much easier to do.

Contrary to the firmware_class tester which adds in-kernel
code for each and every single test it can think of for each
type of request, this enables you to build your tests in userspace
by exposing knobs of the exported API to userspace of the
options available in the API and then lets the trigger kick a one
time kernel API use. This lets us build any possible test case
in userspace.

The test driver also enables multiple test triggers
to be created enabling further testing to be done through
separate threads in parallel.

Both these facts should should not only help testing the
drvdata API in as many ways as possible as efficiently
as possible, but it also paves the way to later strive to
see how it might be even possible to automatically generate
test API drivers for exported symbols in the future. The
exported symbols being the test cases and attributes exposed
in userspace consisting of device attributes, the target test
driver being the desired output driver.

v5:
o expand testdrv.sh to support a series of different options
  to help with testing
o use snprintf() using PAGE_SIZE
o add a common helper config_test_show_str()
o drop test_dev_get_name() in favor for kasprintf()
o use vzalloc() instead of vmalloc()
o bike shedding sysdata/drvdata

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 lib/Kconfig.debug                           |   12 +
 lib/Makefile                                |    1 +
 lib/test_drvdata.c                          | 1033 +++++++++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile   |    2 +-
 tools/testing/selftests/firmware/config     |    1 +
 tools/testing/selftests/firmware/drvdata.sh |  827 +++++++++++++++++++++
 6 files changed, 1875 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_drvdata.c
 create mode 100755 tools/testing/selftests/firmware/drvdata.sh

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 18ffa8084eb4..5b6830dc5915 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1955,6 +1955,18 @@ config TEST_FIRMWARE
 
 	  If unsure, say N.
 
+config TEST_DRVDATA
+	tristate "Test driver data loading via drvdata APIs"
+	default n
+	depends on FW_LOADER
+	help
+	  This builds the "test_drvdata" module that creates a userspace
+	  interface for testing driver data loading using the drvdata 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 d15e235f72ea..ac266f3dbe4c 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_TEST_HEXDUMP) += test_hexdump.o
 obj-y += kstrtox.o
 obj-$(CONFIG_TEST_BPF) += test_bpf.o
 obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o
+obj-$(CONFIG_TEST_DRVDATA) += test_drvdata.o
 obj-$(CONFIG_TEST_HASH) += test_hash.o
 obj-$(CONFIG_TEST_KASAN) += test_kasan.o
 obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o
diff --git a/lib/test_drvdata.c b/lib/test_drvdata.c
new file mode 100644
index 000000000000..441b4d9af925
--- /dev/null
+++ b/lib/test_drvdata.c
@@ -0,0 +1,1033 @@
+/*
+ * Driver data test interface
+ *
+ * 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 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/drvdata.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_DRVDATA "test-drvdata.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 drvdata API
+ *
+ * @name: the name of the primary drvdata 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
+ * 	drvdata_request_async(). If false the synchronous call will
+ * 	be used, drvdata_request().
+ * @optional: whether or not the drvdata is optional refer to the
+ *	struct drvdata_reg_params @optional field for more information.
+ * @keep: whether or not we wish to free the drvdata on our own, refer to
+ *	the struct drvdata_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
+ *	drvdata_req_params as this is implementation specific, and in
+ *	in drvdata API its explicit if you had defined an optional call
+ *	back for your descriptor with either DRVDATA_SYNC_OPT_CB() or
+ *	DRVDATA_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.
+ * @test_result: a test may use this to collect the result from the call
+ *	of the drvdata_request_async() or drvdata_request() 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;
+
+	int test_result;
+};
+
+/**
+ * test_drvdata_private - private device driver drvdata representation
+ *
+ * @size: size of the data copied, in bytes
+ * @data: the actual data we copied over from drvdata
+ * @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_drvdata_private {
+	size_t size;
+	u8 *data;
+	bool written;
+};
+
+/**
+ * drvdata_test_device - test device to help test drvdata
+ *
+ * @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
+ *	drvdata API and we to let you tune them in userspace. We then just
+ *	provide one trigger.
+ * @test_drvdata: internal private representation of a storage area
+ *	a driver might typically use to stuff firmware / drvdata.
+ * @misc_dev: we use a misc device under the hood
+ * @dev: pointer to misc_dev's own struct device
+ * @drvdata_mutex: for access into the @drvdata, the fake storage location for
+ * 	the system data we copy.
+ * @config_mutex:
+ * @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.
+ * list: needed to be part of the reg_test_devs
+ */
+struct drvdata_test_device {
+	int dev_idx;
+	struct test_config config;
+	struct test_drvdata_private test_drvdata;
+	struct miscdevice misc_dev;
+	struct device *dev;
+
+	struct mutex drvdata_mutex;
+	struct mutex config_mutex;
+	struct mutex trigger_mutex;
+	struct list_head list;
+};
+
+static struct miscdevice *dev_to_misc_dev(struct device *dev)
+{
+	return dev_get_drvdata(dev);
+}
+
+static
+struct drvdata_test_device *misc_dev_to_test_dev(struct miscdevice *misc_dev)
+{
+	return container_of(misc_dev, struct drvdata_test_device, misc_dev);
+}
+
+static struct drvdata_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 drvdata_test_device *test_dev = misc_dev_to_test_dev(misc_dev);
+	struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
+	ssize_t rc = 0;
+
+	mutex_lock(&test_dev->drvdata_mutex);
+	if (test_drvdata->written)
+		rc = simple_read_from_buffer(buf, size, offset,
+					     test_drvdata->data,
+					     test_drvdata->size);
+	mutex_unlock(&test_dev->drvdata_mutex);
+
+	return rc;
+}
+
+static const struct file_operations test_fw_fops = {
+	.owner          = THIS_MODULE,
+	.read           = test_fw_misc_read,
+};
+
+static void free_test_drvdata(struct test_drvdata_private *test_drvdata)
+{
+	kfree(test_drvdata->data);
+	test_drvdata->data = NULL;
+	test_drvdata->size = 0;
+	test_drvdata->written = false;
+}
+
+static int test_load_drvdata(struct drvdata_test_device *test_dev,
+			     const struct drvdata *drvdata)
+{
+	struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
+	int ret = 0;
+
+	if (!drvdata)
+		return -ENOENT;
+
+	mutex_lock(&test_dev->drvdata_mutex);
+
+	free_test_drvdata(test_drvdata);
+
+	test_drvdata->data = kzalloc(drvdata->size, GFP_KERNEL);
+	if (!test_drvdata->data) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	memcpy(test_drvdata->data, drvdata->data, drvdata->size);
+	test_drvdata->size = drvdata->size;
+	test_drvdata->written = true;
+
+	dev_info(test_dev->dev, "loaded: %zu\n", test_drvdata->size);
+
+out:
+	mutex_unlock(&test_dev->drvdata_mutex);
+
+	return ret;
+}
+
+static int sync_found_cb(void *context, const struct drvdata *drvdata)
+{
+	struct drvdata_test_device *test_dev = context;
+	int ret;
+
+	ret = test_load_drvdata(test_dev, drvdata);
+	if (ret)
+		dev_info(test_dev->dev, "unable to write drvdata: %d\n", ret);
+	return ret;
+}
+
+static ssize_t config_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	struct drvdata_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,
+			"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 drvdata_test_device *test_dev,
+			    const struct drvdata *drvdata)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	ret = test_load_drvdata(test_dev, drvdata);
+	if (ret) {
+		if (!config->optional)
+			dev_info(test_dev->dev, "unable to write drvdata\n");
+	}
+	if (config->keep) {
+		release_drvdata(drvdata);
+		drvdata = NULL;
+	}
+	return ret;
+}
+
+static int config_req_default(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int rc;
+	/*
+	 * 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 drvdata_req_params req_params = {
+		DRVDATA_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);
+
+	rc = drvdata_request(config->default_name,
+			     &req_params, test_dev->dev);
+	if (rc)
+		dev_info(test_dev->dev,
+			 "load of default '%s' failed: %d\n",
+			 config->default_name, rc);
+
+	return rc;
+}
+
+/*
+ * 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 drvdata_test_device *test_dev = context;
+	int rc;
+
+	rc = config_req_default(test_dev);
+
+	return rc;
+
+	/* 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 drvdata_test_device *test_dev = context;
+
+	config_req_default(test_dev);
+
+	/* Leave all the error checking for the main caller */
+}
+
+static int config_sync_req_cb(void *context,
+			      const struct drvdata *drvdata)
+{
+	struct drvdata_test_device *test_dev = context;
+
+	return config_load_data(test_dev, drvdata);
+}
+
+static int trigger_config_sync(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int rc;
+	const struct drvdata_req_params req_params_default = {
+		DRVDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params req_params_opt_cb = {
+		DRVDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+		DRVDATA_SYNC_OPT_CB(config_sync_req_default_cb, test_dev),
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params *req_params;
+
+	if (config->enable_opt_cb)
+		req_params = &req_params_opt_cb;
+	else
+		req_params= &req_params_default;
+
+	rc = drvdata_request(config->name, req_params, test_dev->dev);
+	if (rc)
+		dev_err(test_dev->dev, "sync load of '%s' failed: %d\n",
+			config->name, rc);
+
+	return rc;
+}
+
+static void config_async_req_cb(const struct drvdata *drvdata,
+				void *context)
+{
+	struct drvdata_test_device *test_dev = context;
+	config_load_data(test_dev, drvdata);
+}
+
+static int trigger_config_async(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int rc;
+	const struct drvdata_req_params req_params_default = {
+		DRVDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+		.sync_reqs.mode = config->async ?
+			DRVDATA_ASYNC : DRVDATA_SYNC,
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params req_params_opt_cb = {
+		DRVDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+		DRVDATA_ASYNC_OPT_CB(config_async_req_default_cb, test_dev),
+		.sync_reqs.mode = config->async ?
+			DRVDATA_ASYNC : DRVDATA_SYNC,
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params *req_params;
+	async_cookie_t async_cookie;
+
+	if (config->enable_opt_cb)
+		req_params = &req_params_opt_cb;
+	else
+		req_params = &req_params_default;
+
+	rc = drvdata_request_async(config->name, req_params,
+				   test_dev->dev, &async_cookie);
+	if (rc) {
+		dev_err(test_dev->dev, "async load of '%s' failed: %d\n",
+			config->name, rc);
+		goto out;
+	}
+
+	drvdata_synchronize_request(async_cookie);
+out:
+	return rc;
+}
+
+static ssize_t
+trigger_config_store(struct device *dev,
+		     struct device_attribute *attr,
+		     const char *buf, size_t count)
+{
+	struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
+	struct test_config *config = &test_dev->config;
+	int rc;
+
+	mutex_lock(&test_dev->trigger_mutex);
+	mutex_lock(&test_dev->config_mutex);
+
+	dev_info(dev, "loading '%s'\n", config->name);
+
+	if (config->async)
+		rc = trigger_config_async(test_dev);
+	else
+		rc = trigger_config_sync(test_dev);
+
+	config->test_result = rc;
+
+	if (rc)
+		goto out;
+
+	if (test_drvdata->written) {
+		dev_info(dev, "loaded: %zu\n", test_drvdata->size);
+		rc = count;
+	} else {
+		dev_err(dev, "failed to load firmware\n");
+		rc = -ENODEV;
+	}
+
+out:
+	mutex_unlock(&test_dev->config_mutex);
+	mutex_unlock(&test_dev->trigger_mutex);
+
+	return rc;
+}
+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 int config_copy_name(struct test_config *config,
+			    const char *name,
+			    size_t count)
+{
+	return __kstrncpy(&config->name, name, count, GFP_KERNEL);
+}
+
+static int config_copy_default_name(struct test_config *config,
+				    const char *name,
+				    size_t count)
+{
+	return __kstrncpy(&config->default_name, name, count, GFP_KERNEL);
+}
+
+static void __drvdata_config_free(struct test_config *config)
+{
+	kfree_const(config->name);
+	config->name = NULL;
+	kfree_const(config->default_name);
+	config->default_name = NULL;
+}
+
+static void drvdata_config_free(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+
+	mutex_lock(&test_dev->config_mutex);
+	__drvdata_config_free(config);
+	mutex_unlock(&test_dev->config_mutex);
+}
+
+static int __drvdata_config_init(struct test_config *config)
+{
+	int ret;
+
+	ret = config_copy_name(config, TEST_DRVDATA, strlen(TEST_DRVDATA));
+	if (ret < 0)
+		goto out;
+
+	ret = config_copy_default_name(config, TEST_DRVDATA,
+				       strlen(TEST_DRVDATA));
+	if (ret < 0)
+		goto out;
+
+	config->async = false;
+	config->optional = false;
+	config->keep = false;
+	config->enable_opt_cb = false;
+	config->test_result = 0;
+
+out:
+	return ret;
+}
+
+int drvdata_config_init(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	ret = __drvdata_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 drvdata_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int rc;
+
+	mutex_lock(&test_dev->config_mutex);
+	rc = config_copy_name(config, buf, count);
+	mutex_unlock(&test_dev->config_mutex);
+
+	return rc;
+}
+
+/*
+ * 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 drvdata_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 drvdata_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int rc;
+
+	mutex_lock(&test_dev->config_mutex);
+	rc = config_copy_default_name(config, buf, count);
+	mutex_unlock(&test_dev->config_mutex);
+
+	return rc;
+}
+
+static ssize_t config_default_name_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct drvdata_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 drvdata_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->drvdata_mutex);
+	free_test_drvdata(&test_dev->test_drvdata);
+	mutex_unlock(&test_dev->drvdata_mutex);
+
+	mutex_lock(&test_dev->config_mutex);
+
+	__drvdata_config_free(config);
+
+	ret = __drvdata_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 drvdata_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 drvdata_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 drvdata_test_device *test_dev,
+				      const char *buf, size_t size,
+				      int *config)
+{
+	char *end;
+	long new = simple_strtol(buf, &end, 0);
+	if (end == buf || 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 drvdata_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 ssize_t config_async_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 test_result_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct drvdata_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 drvdata_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 DRVDATA_DEV_ATTR(name)		&dev_attr_##name.attr
+
+static struct attribute *test_dev_attrs[] = {
+	DRVDATA_DEV_ATTR(trigger_config),
+	DRVDATA_DEV_ATTR(config),
+	DRVDATA_DEV_ATTR(reset),
+
+	DRVDATA_DEV_ATTR(config_name),
+	DRVDATA_DEV_ATTR(config_default_name),
+	DRVDATA_DEV_ATTR(config_async),
+	DRVDATA_DEV_ATTR(config_optional),
+	DRVDATA_DEV_ATTR(config_keep),
+	DRVDATA_DEV_ATTR(config_enable_opt_cb),
+	DRVDATA_DEV_ATTR(test_result),
+
+	NULL,
+};
+
+ATTRIBUTE_GROUPS(test_dev);
+
+void free_test_dev_drvdata(struct drvdata_test_device *test_dev)
+{
+	kfree_const(test_dev->misc_dev.name);
+	test_dev->misc_dev.name = NULL;
+	vfree(test_dev);
+	test_dev = NULL;
+	drvdata_config_free(test_dev);
+}
+
+void unregister_test_dev_drvdata(struct drvdata_test_device *test_dev)
+{
+	dev_info(test_dev->dev, "removing interface\n");
+	misc_deregister(&test_dev->misc_dev);
+	kfree(&test_dev->misc_dev.name);
+	free_test_dev_drvdata(test_dev);
+}
+
+struct drvdata_test_device *alloc_test_dev_drvdata(int idx)
+{
+	int rc;
+	struct drvdata_test_device *test_dev;
+	struct miscdevice *misc_dev;
+
+	test_dev = vzalloc(sizeof(struct drvdata_test_device));
+	if (!test_dev) {
+		pr_err("Cannot alloc test_dev\n");
+		goto err_out;
+	}
+
+	mutex_init(&test_dev->drvdata_mutex);
+	mutex_init(&test_dev->config_mutex);
+	mutex_init(&test_dev->trigger_mutex);
+
+	rc = drvdata_config_init(test_dev);
+	if (rc < 0) {
+		pr_err("Cannot alloc drvdata_config_init()\n");
+		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_drvdata%d", idx);
+	if (!misc_dev->name) {
+		pr_err("Cannot alloc misc_dev->name\n");
+		goto err_out_free_config;
+	}
+	misc_dev->fops = &test_fw_fops;
+	misc_dev->groups = test_dev_groups;
+
+	return test_dev;
+
+err_out_free_config:
+	__drvdata_config_free(&test_dev->config);
+err_out_free:
+	kfree(test_dev);
+err_out:
+	return NULL;
+}
+
+static int register_test_dev_drvdata(void)
+{
+	struct drvdata_test_device *test_dev = NULL;
+	int rc = -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_drvdata(num_test_devs);
+	if (!test_dev) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	rc = misc_register(&test_dev->misc_dev);
+	if (rc) {
+		pr_err("could not register misc device: %d\n", rc);
+		free_test_dev_drvdata(test_dev);
+		return rc;
+	}
+
+	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++;
+
+	mutex_unlock(&reg_dev_mutex);
+
+out:
+	return rc;
+}
+
+static int __init test_drvdata_init(void)
+{
+	int rc;
+
+	rc = register_test_dev_drvdata();
+	if (rc)
+		pr_err("Cannot add first test drvdata device\n");
+
+	return rc;
+}
+late_initcall(test_drvdata_init);
+
+static void __exit test_drvdata_exit(void)
+{
+	struct drvdata_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_drvdata(test_dev);
+	}
+	mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_drvdata_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 9bf82234855b..7d4f6808bf1c 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_userhelper.sh
+TEST_PROGS := fw_filesystem.sh fw_userhelper.sh drvdata.sh
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config
index c8137f70e291..0a07fccd8850 100644
--- a/tools/testing/selftests/firmware/config
+++ b/tools/testing/selftests/firmware/config
@@ -1 +1,2 @@
 CONFIG_TEST_FIRMWARE=y
+CONFIG_TEST_DRVDATA=y
diff --git a/tools/testing/selftests/firmware/drvdata.sh b/tools/testing/selftests/firmware/drvdata.sh
new file mode 100755
index 000000000000..aae23e61e3a4
--- /dev/null
+++ b/tools/testing/selftests/firmware/drvdata.sh
@@ -0,0 +1,827 @@
+#!/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="drvdata"
+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"
+
+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 $DIR ]; then
+		DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"
+	fi
+
+	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_DRVDATA ]; then
+		DEFAULT_DRVDATA="test-drvdata.bin"
+	fi
+
+	if [ -z $FW ]; then
+		FW="$FWPATH/$DEFAULT_DRVDATA"
+	fi
+
+	# Set the kernel search path.
+	echo -n "$FWPATH" > $FW_SYSFSPATH
+
+	# This is an unlikely real-world firmware content. :)
+	echo "ABCD0123" >"$FW"
+
+	NAME=$(basename "$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 [ ! -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_disable_opt_cb()
+{
+	if ! echo -n 0 >$DIR/config_enable_opt_cb; then
+		echo "$0: Unable to disable keep option" >&2
+		exit 1
+	fi
+}
+
+
+# For special characters use printf directly,
+# refer to drvdata_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 drvdata_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
+}
+
+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 drvdata"
+}
+
+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)
+	# On this one we expect the file to exist so leave stderr in
+	if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_drvdata0 > /dev/null) > /dev/null; then
+		echo "$1: FAIL - file $FILE did not match contents in /dev/test_drvdata0" >&2
+		config_show_config >&2
+		exit 1
+	fi
+	echo "$1: OK! - $FILE == /dev/test_drvdata0"
+}
+
+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_drvdata0 > /dev/null) > /dev/null; then
+		echo "$1: FAIL - file $FILE did not match contents in /dev/test_drvdata0" >&2
+		config_show_config >&2
+		exit 1
+	fi
+	echo "$1: OK! - $FILE == /dev/test_drvdata0"
+}
+
+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_drvdata0 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_drvdata0"
+}
+
+config_default_file_should_match()
+{
+	FILE=$(config_get_default_name)
+	diff -q $FWPATH/$FILE /dev/test_drvdata0 2> /dev/null
+	if ! $? ; then
+		echo "$1: FAIL - file $FILE expected to match /dev/test_drvdata0" >&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_drvdata0 2> /dev/null
+	if $? 2> /dev/null ; then
+		echo "$1: FAIL - file $FILE was not expected to match test_drvdata0" >&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"
+}
+
+drvdata_set_sync_defaults()
+{
+	config_reset
+}
+
+drvdata_set_async_defaults()
+{
+	config_reset
+	config_set_async
+}
+
+drvdata_test_0001s()
+{
+	NAME='\000'
+
+	drvdata_set_sync_defaults
+	config_set_name $NAME
+	printf '\000' >"$DIR"/config_name
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+drvdata_test_0001a()
+{
+	NAME='\000'
+
+	drvdata_set_async_defaults
+	printf '\000' >"$DIR"/config_name
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+drvdata_test_0001()
+{
+	drvdata_test_0001s
+	drvdata_test_0001a
+}
+
+drvdata_test_0002s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_set_sync_defaults
+	config_set_name ${FUNCNAME[0]}
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+drvdata_test_0002a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0002()
+{
+	#drvdata_test_0002s
+	drvdata_test_0002a
+}
+
+drvdata_test_0003()
+{
+	config_reset
+	config_file_should_not_match ${FUNCNAME[0]}
+}
+
+drvdata_test_0004s()
+{
+	TEST="drvdata_test_0004s"
+
+	drvdata_set_sync_defaults
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0004a()
+{
+	drvdata_set_async_defaults
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0004()
+{
+	drvdata_test_0004s
+	drvdata_test_0004a
+}
+
+drvdata_test_0005s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0005a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0005()
+{
+	drvdata_test_0005s
+	drvdata_test_0005a
+}
+
+drvdata_test_0006s()
+{
+	drvdata_set_sync_defaults
+	config_set_optional
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0006a()
+{
+	drvdata_set_async_defaults
+	config_set_optional
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0006()
+{
+	drvdata_test_0006s
+	drvdata_test_0006a
+}
+
+drvdata_test_0007s()
+{
+	drvdata_set_sync_defaults
+	config_set_keep
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0007a()
+{
+	drvdata_set_async_defaults
+	config_set_keep
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0007()
+{
+	drvdata_test_0007s
+	drvdata_test_0007a
+}
+
+drvdata_test_0008s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0008a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0008()
+{
+	drvdata_test_0008s
+	drvdata_test_0008a
+}
+
+drvdata_test_0009s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0009a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0009()
+{
+	drvdata_test_0009s
+	drvdata_test_0009a
+}
+
+drvdata_test_0010s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0010a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0010()
+{
+	drvdata_test_0010s
+	drvdata_test_0010a
+}
+
+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_drvdata0 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"
+}
+
+test_reqs
+
+usage()
+{
+	NUM_TESTS=$(grep -o ':' <<<"$ALL_TESTS" | grep -c .)
+	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
+allow_user_defaults
+load_req_mod
+
+trap "test_finish" EXIT
+
+parse_args $@
+
+exit 0
-- 
2.10.1

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

* [PATCH v3 3/4] x86/microcode: convert to use sysdata API
  2016-12-16 11:46 [PATCH v3 0/4] firmware: add drvdata API Luis R. Rodriguez
  2016-12-16 11:46 ` [PATCH v3 1/4] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
  2016-12-16 11:46 ` [PATCH v3 2/4] test: add new drvdata loader tester Luis R. Rodriguez
@ 2016-12-16 11:46 ` Luis R. Rodriguez
  2016-12-16 11:46 ` [PATCH v3 4/4] p54: convert to " Luis R. Rodriguez
  2017-01-12 15:02 ` [PATCH v4 0/3] firmware: add drvdata API Luis R. Rodriguez
  4 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2016-12-16 11:46 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: wagi, teg, mchehab, zajec5, linux-kernel, markivx, stephen.boyd,
	broonie, zohar, tiwai, johannes, chunkeey, hauke, jwboyer,
	dmitry.torokhov, dwmw2, jslaby, torvalds, luto, fengguang.wu,
	rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

This uses the new flexible firmware API, since we don't
have to keep the firmware around the sysdata API does the
freeing for us safely.

v2: was not present
v5: bike shed changes: sysdata/drvdata

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 arch/x86/kernel/cpu/microcode/amd.c | 56 +++++++++++++++++++++++++------------
 1 file changed, 38 insertions(+), 18 deletions(-)

diff --git a/arch/x86/kernel/cpu/microcode/amd.c b/arch/x86/kernel/cpu/microcode/amd.c
index 6f353bdb3a25..5d08cd7284e5 100644
--- a/arch/x86/kernel/cpu/microcode/amd.c
+++ b/arch/x86/kernel/cpu/microcode/amd.c
@@ -24,7 +24,7 @@
 #define pr_fmt(fmt) "microcode: " fmt
 
 #include <linux/earlycpio.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/uaccess.h>
 #include <linux/vmalloc.h>
 #include <linux/initrd.h>
@@ -877,6 +877,31 @@ load_microcode_amd(int cpu, u8 family, const u8 *data, size_t size)
 	return ret;
 }
 
+struct amd_ucode_req {
+	int cpu;
+	struct cpuinfo_x86 *c;
+	enum ucode_state state;
+	const char *name;
+};
+
+static int request_microcode_amd_cb(void *context,
+				    const struct drvdata *drvdata)
+{
+	struct amd_ucode_req *req = context;
+
+	if (*(u32 *)drvdata->data != UCODE_MAGIC) {
+		pr_err("invalid magic value (0x%08x)\n",
+		       *(u32 *)drvdata->data);
+		req->state = UCODE_ERROR;
+		return -EINVAL;
+	}
+
+	req->state = load_microcode_amd(req->cpu, req->c->x86,
+					drvdata->data, drvdata->size);
+
+	return 0;
+}
+
 /*
  * AMD microcode firmware naming convention, up to family 15h they are in
  * the legacy file:
@@ -896,10 +921,13 @@ load_microcode_amd(int cpu, u8 family, const u8 *data, size_t size)
 static enum ucode_state request_microcode_amd(int cpu, struct device *device,
 					      bool refresh_fw)
 {
+	struct amd_ucode_req req;
 	char fw_name[36] = "amd-ucode/microcode_amd.bin";
 	struct cpuinfo_x86 *c = &cpu_data(cpu);
-	enum ucode_state ret = UCODE_NFOUND;
-	const struct firmware *fw;
+	const struct drvdata_req_params req_params = {
+			DRVDATA_DEFAULT_SYNC(request_microcode_amd_cb, &req),
+			.optional = true,
+	};
 
 	/* reload ucode container only on the boot cpu */
 	if (!refresh_fw || c->cpu_index != boot_cpu_data.cpu_index)
@@ -908,24 +936,16 @@ static enum ucode_state request_microcode_amd(int cpu, struct device *device,
 	if (c->x86 >= 0x15)
 		snprintf(fw_name, sizeof(fw_name), "amd-ucode/microcode_amd_fam%.2xh.bin", c->x86);
 
-	if (request_firmware_direct(&fw, (const char *)fw_name, device)) {
-		pr_debug("failed to load file %s\n", fw_name);
-		goto out;
-	}
+	req.cpu = cpu;
+	req.c = c;
+	req.name = fw_name;
+	req.state = UCODE_NFOUND;
 
-	ret = UCODE_ERROR;
-	if (*(u32 *)fw->data != UCODE_MAGIC) {
-		pr_err("invalid magic value (0x%08x)\n", *(u32 *)fw->data);
-		goto fw_release;
+	if (drvdata_request((const char *)fw_name, &req_params, device)) {
+		pr_debug("failed to load file %s\n", fw_name);
 	}
 
-	ret = load_microcode_amd(cpu, c->x86, fw->data, fw->size);
-
- fw_release:
-	release_firmware(fw);
-
- out:
-	return ret;
+	return req.state;
 }
 
 static enum ucode_state
-- 
2.10.1

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

* [PATCH v3 4/4] p54: convert to sysdata API
  2016-12-16 11:46 [PATCH v3 0/4] firmware: add drvdata API Luis R. Rodriguez
                   ` (2 preceding siblings ...)
  2016-12-16 11:46 ` [PATCH v3 3/4] x86/microcode: convert to use sysdata API Luis R. Rodriguez
@ 2016-12-16 11:46 ` Luis R. Rodriguez
  2016-12-16 17:14   ` Luis R. Rodriguez
  2017-01-12 15:02 ` [PATCH v4 0/3] firmware: add drvdata API Luis R. Rodriguez
  4 siblings, 1 reply; 33+ messages in thread
From: Luis R. Rodriguez @ 2016-12-16 11:46 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: wagi, teg, mchehab, zajec5, linux-kernel, markivx, stephen.boyd,
	broonie, zohar, tiwai, johannes, chunkeey, hauke, jwboyer,
	dmitry.torokhov, dwmw2, jslaby, torvalds, luto, fengguang.wu,
	rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

The Coccinelle sysdata patches were used to help with
this transition. The changes have been carefully manually
vetted for. With the conversion we modify the cases that do
not need the firmware to be kept so that the sysdata API
can release it for us. Using the new sysdata API also means
we can get rid of our own completions.

v2: was not present
v3: initial release
v4: small cosmetic fixes
v5: bike shed changes

Generated-by: Coccinelle SmPL
Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/net/wireless/intersil/p54/eeprom.c |  2 +-
 drivers/net/wireless/intersil/p54/fwio.c   |  5 +-
 drivers/net/wireless/intersil/p54/led.c    |  2 +-
 drivers/net/wireless/intersil/p54/main.c   |  2 +-
 drivers/net/wireless/intersil/p54/p54.h    |  3 +-
 drivers/net/wireless/intersil/p54/p54pci.c | 26 ++++++----
 drivers/net/wireless/intersil/p54/p54pci.h |  4 +-
 drivers/net/wireless/intersil/p54/p54spi.c | 80 +++++++++++++++++++-----------
 drivers/net/wireless/intersil/p54/p54spi.h |  2 +-
 drivers/net/wireless/intersil/p54/p54usb.c | 18 +++----
 drivers/net/wireless/intersil/p54/p54usb.h |  4 +-
 drivers/net/wireless/intersil/p54/txrx.c   |  2 +-
 12 files changed, 89 insertions(+), 61 deletions(-)

diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c
index d4c73d39336f..b8184cbc6770 100644
--- a/drivers/net/wireless/intersil/p54/eeprom.c
+++ b/drivers/net/wireless/intersil/p54/eeprom.c
@@ -16,7 +16,7 @@
  * published by the Free Software Foundation.
  */
 
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/sort.h>
 #include <linux/slab.h>
diff --git a/drivers/net/wireless/intersil/p54/fwio.c b/drivers/net/wireless/intersil/p54/fwio.c
index 4ac6764f4897..dc27049e4533 100644
--- a/drivers/net/wireless/intersil/p54/fwio.c
+++ b/drivers/net/wireless/intersil/p54/fwio.c
@@ -17,7 +17,7 @@
  */
 
 #include <linux/slab.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/export.h>
 
@@ -27,7 +27,8 @@
 #include "eeprom.h"
 #include "lmac.h"
 
-int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw)
+int p54_parse_firmware(struct ieee80211_hw *dev,
+		       const struct drvdata *fw)
 {
 	struct p54_common *priv = dev->priv;
 	struct exp_if *exp_if;
diff --git a/drivers/net/wireless/intersil/p54/led.c b/drivers/net/wireless/intersil/p54/led.c
index 9a8fedd3c0f5..4d13598d3968 100644
--- a/drivers/net/wireless/intersil/p54/led.c
+++ b/drivers/net/wireless/intersil/p54/led.c
@@ -16,7 +16,7 @@
  * published by the Free Software Foundation.
  */
 
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 
 #include <net/mac80211.h>
diff --git a/drivers/net/wireless/intersil/p54/main.c b/drivers/net/wireless/intersil/p54/main.c
index d5a3bf91a03e..a1c546cd232c 100644
--- a/drivers/net/wireless/intersil/p54/main.c
+++ b/drivers/net/wireless/intersil/p54/main.c
@@ -17,7 +17,7 @@
  */
 
 #include <linux/slab.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/module.h>
 
diff --git a/drivers/net/wireless/intersil/p54/p54.h b/drivers/net/wireless/intersil/p54/p54.h
index 529939e611cd..5bbe9d77e5fc 100644
--- a/drivers/net/wireless/intersil/p54/p54.h
+++ b/drivers/net/wireless/intersil/p54/p54.h
@@ -268,7 +268,8 @@ struct p54_common {
 /* interfaces for the drivers */
 int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb);
 void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb);
-int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw);
+int p54_parse_firmware(struct ieee80211_hw *dev,
+		       const struct drvdata *fw);
 int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len);
 int p54_read_eeprom(struct ieee80211_hw *dev);
 
diff --git a/drivers/net/wireless/intersil/p54/p54pci.c b/drivers/net/wireless/intersil/p54/p54pci.c
index 27a49068d32d..0e7fd9ba7186 100644
--- a/drivers/net/wireless/intersil/p54/p54pci.c
+++ b/drivers/net/wireless/intersil/p54/p54pci.c
@@ -15,7 +15,7 @@
 
 #include <linux/pci.h>
 #include <linux/slab.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/delay.h>
 #include <linux/completion.h>
@@ -490,7 +490,7 @@ static int p54p_open(struct ieee80211_hw *dev)
 	return 0;
 }
 
-static void p54p_firmware_step2(const struct firmware *fw,
+static void p54p_firmware_step2(const struct drvdata *fw,
 				void *context)
 {
 	struct p54p_priv *priv = context;
@@ -520,8 +520,6 @@ static void p54p_firmware_step2(const struct firmware *fw,
 
 out:
 
-	complete(&priv->fw_loaded);
-
 	if (err) {
 		struct device *parent = pdev->dev.parent;
 
@@ -542,6 +540,17 @@ static void p54p_firmware_step2(const struct firmware *fw,
 	pci_dev_put(pdev);
 }
 
+static int p54p_load_firmware(struct p54p_priv *priv)
+{
+	const struct drvdata_req_params req_params = {
+		DRVDATA_KEEP_ASYNC(p54p_firmware_step2, priv),
+	};
+
+	return drvdata_request_async("isl3886pci", &req_params,
+				     &priv->pdev->dev,
+				     &priv->fw_async_cookie);
+}
+
 static int p54p_probe(struct pci_dev *pdev,
 				const struct pci_device_id *id)
 {
@@ -595,7 +604,6 @@ static int p54p_probe(struct pci_dev *pdev,
 	priv = dev->priv;
 	priv->pdev = pdev;
 
-	init_completion(&priv->fw_loaded);
 	SET_IEEE80211_DEV(dev, &pdev->dev);
 	pci_set_drvdata(pdev, dev);
 
@@ -620,9 +628,7 @@ static int p54p_probe(struct pci_dev *pdev,
 	spin_lock_init(&priv->lock);
 	tasklet_init(&priv->tasklet, p54p_tasklet, (unsigned long)dev);
 
-	err = request_firmware_nowait(THIS_MODULE, 1, "isl3886pci",
-				      &priv->pdev->dev, GFP_KERNEL,
-				      priv, p54p_firmware_step2);
+	err = p54p_load_firmware(priv);
 	if (!err)
 		return 0;
 
@@ -652,9 +658,9 @@ static void p54p_remove(struct pci_dev *pdev)
 		return;
 
 	priv = dev->priv;
-	wait_for_completion(&priv->fw_loaded);
+	drvdata_synchronize_request(priv->fw_async_cookie);
 	p54_unregister_common(dev);
-	release_firmware(priv->firmware);
+	release_drvdata(priv->firmware);
 	pci_free_consistent(pdev, sizeof(*priv->ring_control),
 			    priv->ring_control, priv->ring_control_dma);
 	iounmap(priv->map);
diff --git a/drivers/net/wireless/intersil/p54/p54pci.h b/drivers/net/wireless/intersil/p54/p54pci.h
index 68405c142f97..00c30e1fc60b 100644
--- a/drivers/net/wireless/intersil/p54/p54pci.h
+++ b/drivers/net/wireless/intersil/p54/p54pci.h
@@ -94,7 +94,7 @@ struct p54p_priv {
 	struct pci_dev *pdev;
 	struct p54p_csr __iomem *map;
 	struct tasklet_struct tasklet;
-	const struct firmware *firmware;
+	const struct drvdata *firmware;
 	spinlock_t lock;
 	struct p54p_ring_control *ring_control;
 	dma_addr_t ring_control_dma;
@@ -105,7 +105,7 @@ struct p54p_priv {
 	struct sk_buff *tx_buf_data[32];
 	struct sk_buff *tx_buf_mgmt[4];
 	struct completion boot_comp;
-	struct completion fw_loaded;
+	async_cookie_t fw_async_cookie;
 };
 
 #endif /* P54USB_H */
diff --git a/drivers/net/wireless/intersil/p54/p54spi.c b/drivers/net/wireless/intersil/p54/p54spi.c
index 7ab2f43ab425..c0118048c01f 100644
--- a/drivers/net/wireless/intersil/p54/p54spi.c
+++ b/drivers/net/wireless/intersil/p54/p54spi.c
@@ -23,7 +23,7 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/interrupt.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/delay.h>
 #include <linux/irq.h>
 #include <linux/spi/spi.h>
@@ -162,53 +162,73 @@ static int p54spi_spi_write_dma(struct p54s_priv *priv, __le32 base,
 	return 0;
 }
 
+static int p54spi_request_firmware_found_cb(void *context,
+					    const struct drvdata *drvdata)
+{
+	int ret;
+	struct p54s_priv *priv = context;
+
+	priv->firmware = drvdata;
+	ret = p54_parse_firmware(priv->hw, priv->firmware);
+	if (ret)
+		release_drvdata(priv->firmware);
+
+	return ret;
+}
+
 static int p54spi_request_firmware(struct ieee80211_hw *dev)
 {
 	struct p54s_priv *priv = dev->priv;
+	const struct drvdata_req_params req_params = {
+		DRVDATA_KEEP_SYNC(p54spi_request_firmware_found_cb, priv),
+	};
 	int ret;
 
 	/* FIXME: should driver use it's own struct device? */
-	ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
-
+	ret = drvdata_request("3826.arm", &req_params, &priv->spi->dev);
 	if (ret < 0) {
-		dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
-		return ret;
+		dev_err(&priv->spi->dev,
+			"firmware request failed: %d", ret);
 	}
+	return ret;
+}
 
-	ret = p54_parse_firmware(dev, priv->firmware);
-	if (ret) {
-		release_firmware(priv->firmware);
-		return ret;
-	}
+#ifdef CONFIG_P54_SPI_DEFAULT_EEPROM
+static int p54spi_load_eeprom_default(void *context)
+{
+	struct p54s_priv *priv = context;
+	struct ieee80211_hw *dev = priv->hw;
 
-	return 0;
+	dev_info(&priv->spi->dev, "loading default eeprom...\n");
+	return p54_parse_eeprom(dev, (void *) p54spi_eeprom,
+				sizeof(p54spi_eeprom));
 }
+#endif
+
+static int p54spi_load_eeprom_cb(void *context,
+				 const struct drvdata *drvdata)
+{
+	struct p54s_priv *priv = context;
+	struct ieee80211_hw *dev = priv->hw;
 
+	dev_info(&priv->spi->dev, "loading user eeprom...\n");
+	return p54_parse_eeprom(dev, (void *) drvdata->data,
+				(int)drvdata->size);
+}
 static int p54spi_request_eeprom(struct ieee80211_hw *dev)
 {
 	struct p54s_priv *priv = dev->priv;
-	const struct firmware *eeprom;
-	int ret;
+	const struct drvdata_req_params req_params = {
+		DRVDATA_DEFAULT_SYNC(p54spi_load_eeprom_cb, priv),
+#ifdef CONFIG_P54_SPI_DEFAULT_EEPROM
+		DRVDATA_SYNC_OPT_CB(p54spi_load_eeprom_default, priv),
+#endif
+	};
 
 	/* allow users to customize their eeprom.
 	 */
 
-	ret = request_firmware_direct(&eeprom, "3826.eeprom", &priv->spi->dev);
-	if (ret < 0) {
-#ifdef CONFIG_P54_SPI_DEFAULT_EEPROM
-		dev_info(&priv->spi->dev, "loading default eeprom...\n");
-		ret = p54_parse_eeprom(dev, (void *) p54spi_eeprom,
-				       sizeof(p54spi_eeprom));
-#else
-		dev_err(&priv->spi->dev, "Failed to request user eeprom\n");
-#endif /* CONFIG_P54_SPI_DEFAULT_EEPROM */
-	} else {
-		dev_info(&priv->spi->dev, "loading user eeprom...\n");
-		ret = p54_parse_eeprom(dev, (void *) eeprom->data,
-				       (int)eeprom->size);
-		release_firmware(eeprom);
-	}
-	return ret;
+	return drvdata_request("3826.eeprom", &req_params, &priv->spi->dev);
 }
 
 static int p54spi_upload_firmware(struct ieee80211_hw *dev)
@@ -692,7 +712,7 @@ static int p54spi_remove(struct spi_device *spi)
 
 	gpio_free(p54spi_gpio_power);
 	gpio_free(p54spi_gpio_irq);
-	release_firmware(priv->firmware);
+	release_drvdata(priv->firmware);
 
 	mutex_destroy(&priv->mutex);
 
diff --git a/drivers/net/wireless/intersil/p54/p54spi.h b/drivers/net/wireless/intersil/p54/p54spi.h
index dfaa62aaeb07..c484536be900 100644
--- a/drivers/net/wireless/intersil/p54/p54spi.h
+++ b/drivers/net/wireless/intersil/p54/p54spi.h
@@ -119,7 +119,7 @@ struct p54s_priv {
 	struct list_head tx_pending;
 
 	enum fw_state fw_state;
-	const struct firmware *firmware;
+	const struct drvdata *firmware;
 };
 
 #endif /* P54SPI_H */
diff --git a/drivers/net/wireless/intersil/p54/p54usb.c b/drivers/net/wireless/intersil/p54/p54usb.c
index 043bd1c23c19..8870d177b7d5 100644
--- a/drivers/net/wireless/intersil/p54/p54usb.c
+++ b/drivers/net/wireless/intersil/p54/p54usb.c
@@ -15,7 +15,7 @@
 #include <linux/usb.h>
 #include <linux/pci.h>
 #include <linux/slab.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/delay.h>
 #include <linux/crc32.h>
@@ -916,14 +916,13 @@ static int p54u_start_ops(struct p54u_priv *priv)
 	return ret;
 }
 
-static void p54u_load_firmware_cb(const struct firmware *firmware,
+static void p54u_load_firmware_cb(const struct drvdata *firmware,
 				  void *context)
 {
 	struct p54u_priv *priv = context;
 	struct usb_device *udev = priv->udev;
 	int err;
 
-	complete(&priv->fw_wait_load);
 	if (firmware) {
 		priv->fw = firmware;
 		err = p54u_start_ops(priv);
@@ -959,12 +958,14 @@ static int p54u_load_firmware(struct ieee80211_hw *dev,
 {
 	struct usb_device *udev = interface_to_usbdev(intf);
 	struct p54u_priv *priv = dev->priv;
+	const struct drvdata_req_params req_params = {
+		SYSDATA_KEEP_ASYNC(p54u_load_firmware_cb, priv),
+	};
 	struct device *device = &udev->dev;
 	int err, i;
 
 	BUILD_BUG_ON(ARRAY_SIZE(p54u_fwlist) != __NUM_P54U_HWTYPES);
 
-	init_completion(&priv->fw_wait_load);
 	i = p54_find_type(priv);
 	if (i < 0)
 		return i;
@@ -973,9 +974,8 @@ static int p54u_load_firmware(struct ieee80211_hw *dev,
 	       p54u_fwlist[i].fw);
 
 	usb_get_dev(udev);
-	err = request_firmware_nowait(THIS_MODULE, 1, p54u_fwlist[i].fw,
-				      device, GFP_KERNEL, priv,
-				      p54u_load_firmware_cb);
+	err = drvdata_request_async(p54u_fwlist[i].fw, &req_params,
+				    device, &priv->fw_async_cookie);
 	if (err) {
 		dev_err(&priv->udev->dev, "(p54usb) cannot load firmware %s "
 					  "(%d)!\n", p54u_fwlist[i].fw, err);
@@ -1069,11 +1069,11 @@ static void p54u_disconnect(struct usb_interface *intf)
 		return;
 
 	priv = dev->priv;
-	wait_for_completion(&priv->fw_wait_load);
+	drvdata_synchronize_request(priv->fw_async_cookie);
 	p54_unregister_common(dev);
 
 	usb_put_dev(interface_to_usbdev(intf));
-	release_firmware(priv->fw);
+	release_drvdata(priv->fw);
 	p54_free_common(dev);
 }
 
diff --git a/drivers/net/wireless/intersil/p54/p54usb.h b/drivers/net/wireless/intersil/p54/p54usb.h
index a5f5f0fea3bd..56b58a1c41c1 100644
--- a/drivers/net/wireless/intersil/p54/p54usb.h
+++ b/drivers/net/wireless/intersil/p54/p54usb.h
@@ -153,10 +153,10 @@ struct p54u_priv {
 	spinlock_t lock;
 	struct sk_buff_head rx_queue;
 	struct usb_anchor submitted;
-	const struct firmware *fw;
+	const struct drvdata *fw;
 
 	/* asynchronous firmware callback */
-	struct completion fw_wait_load;
+	async_cookie_t fw_async_cookie;
 };
 
 #endif /* P54USB_H */
diff --git a/drivers/net/wireless/intersil/p54/txrx.c b/drivers/net/wireless/intersil/p54/txrx.c
index 1af7da0b386e..fd52af4735e7 100644
--- a/drivers/net/wireless/intersil/p54/txrx.c
+++ b/drivers/net/wireless/intersil/p54/txrx.c
@@ -17,7 +17,7 @@
  */
 
 #include <linux/export.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <asm/div64.h>
 
-- 
2.10.1

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

* Re: [PATCH v3 4/4] p54: convert to sysdata API
  2016-12-16 11:46 ` [PATCH v3 4/4] p54: convert to " Luis R. Rodriguez
@ 2016-12-16 17:14   ` Luis R. Rodriguez
  0 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2016-12-16 17:14 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, ming.lei, wagi, teg, mchehab, zajec5, linux-kernel,
	markivx, stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey,
	hauke, jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo

On Fri, Dec 16, 2016 at 03:46:32AM -0800, Luis R. Rodriguez wrote:
> diff --git a/drivers/net/wireless/intersil/p54/p54usb.c b/drivers/net/wireless/intersil/p54/p54usb.c
> index 043bd1c23c19..8870d177b7d5 100644
> --- a/drivers/net/wireless/intersil/p54/p54usb.c
> +++ b/drivers/net/wireless/intersil/p54/p54usb.c
> @@ -959,12 +958,14 @@ static int p54u_load_firmware(struct ieee80211_hw *dev,
>  {
>  	struct usb_device *udev = interface_to_usbdev(intf);
>  	struct p54u_priv *priv = dev->priv;
> +	const struct drvdata_req_params req_params = {
> +		SYSDATA_KEEP_ASYNC(p54u_load_firmware_cb, priv),
> +	};

Whoops forgot to convert here due to the bikeshed, sorry thought I had
compile tested the entire driver... Can send a follow up later after
the other patches get reviewed.

  Luis

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

* [PATCH v4 0/3] firmware: add drvdata API
  2016-12-16 11:46 [PATCH v3 0/4] firmware: add drvdata API Luis R. Rodriguez
                   ` (3 preceding siblings ...)
  2016-12-16 11:46 ` [PATCH v3 4/4] p54: convert to " Luis R. Rodriguez
@ 2017-01-12 15:02 ` Luis R. Rodriguez
  2017-01-12 15:02   ` [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
                     ` (3 more replies)
  4 siblings, 4 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-12 15:02 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

This v4 fixes a small typo on the p54 driver conversion after the
sysdata/drvdata rename caught by 0-day. It also drops the microcode
firmware conversion as we only need one driver to demo the API for now.

Once we get more bells and whistles we might see more drivers convert over,
if they want, specially once we get firmware signing -- but that may take a
bit more time.

The drvdata goes without any fallback mechanism as that is a royal mess
right now, we *do* plan on merging support for it once we iron out a clean
solution on the old API which addresses all known shortcomings. A clean
fallback mechanism will then be available to both old an new the new API.

*New* features however will be encouraged using the new drvdata API moving
forward. This series is intended to apply after the two documentation patch
updates in the series: "[PATCH v4 0/2] firmware: fw doc revamp follow up" [0].

[0] https://lkml.kernel.org/r/20170112144250.12376-1-mcgrof@kernel.org

Luis R. Rodriguez (3):
  firmware: add new extensible firmware API - drvdata
  test: add new drvdata loader tester
  p54: convert to sysdata API

 Documentation/driver-api/firmware/drvdata.rst      |   91 ++
 Documentation/driver-api/firmware/index.rst        |    1 +
 Documentation/driver-api/firmware/introduction.rst |   11 +
 MAINTAINERS                                        |    3 +-
 drivers/base/firmware_class.c                      |  327 +++++++
 drivers/net/wireless/intersil/p54/eeprom.c         |    2 +-
 drivers/net/wireless/intersil/p54/fwio.c           |    5 +-
 drivers/net/wireless/intersil/p54/led.c            |    2 +-
 drivers/net/wireless/intersil/p54/main.c           |    2 +-
 drivers/net/wireless/intersil/p54/p54.h            |    3 +-
 drivers/net/wireless/intersil/p54/p54pci.c         |   26 +-
 drivers/net/wireless/intersil/p54/p54pci.h         |    4 +-
 drivers/net/wireless/intersil/p54/p54spi.c         |   80 +-
 drivers/net/wireless/intersil/p54/p54spi.h         |    2 +-
 drivers/net/wireless/intersil/p54/p54usb.c         |   18 +-
 drivers/net/wireless/intersil/p54/p54usb.h         |    4 +-
 drivers/net/wireless/intersil/p54/txrx.c           |    2 +-
 include/linux/drvdata.h                            |  245 +++++
 lib/Kconfig.debug                                  |   12 +
 lib/Makefile                                       |    1 +
 lib/test_drvdata.c                                 | 1033 ++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile          |    2 +-
 tools/testing/selftests/firmware/config            |    1 +
 tools/testing/selftests/firmware/drvdata.sh        |  827 ++++++++++++++++
 24 files changed, 2641 insertions(+), 63 deletions(-)
 create mode 100644 Documentation/driver-api/firmware/drvdata.rst
 create mode 100644 include/linux/drvdata.h
 create mode 100644 lib/test_drvdata.c
 create mode 100755 tools/testing/selftests/firmware/drvdata.sh

-- 
2.11.0

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

* [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata
  2017-01-12 15:02 ` [PATCH v4 0/3] firmware: add drvdata API Luis R. Rodriguez
@ 2017-01-12 15:02   ` Luis R. Rodriguez
  2017-01-19 11:36     ` Greg KH
  2017-01-19 18:58     ` Bjorn Andersson
  2017-01-12 15:02   ` [PATCH v4 2/3] test: add new drvdata loader tester Luis R. Rodriguez
                     ` (2 subsequent siblings)
  3 siblings, 2 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-12 15:02 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

The firmware API has evolved over the years slowly, as it
grows we extend it by adding new routines or at times we extend
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 firmware API is also
now being used for things outside of the scope of what typically
would be considered "firmware", an example here is the p54 driver
enables users to provide a custom EEPROM through this interface.
Another example is optional CPU microcode updates. This list is
actually quite endless...

There are other subsystems which would like to make use of the
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. If different requirements are needed it would
again mean adding more arguments and making a slew of collateral
evolutions, or adding yet-another-new-API-call (TM).

Another sticking point over the current firmware API is that
some callers may need the firmware fallback mechanism when its
enabled. There are two types of fallback mechanisms and both have
shortcomings. This new API accepts the current status quo and
ignore the fallback mechanism all together. When and if we add
support for it, it will be well though out.

This new extensible firmware API enables new extensions to be added by
avoiding future unnecessary collateral evolutions as this code /
features get added. This new set of APIs leaves the old firmware API
as-is, ignores all firmware fallback mechanism, labels the new
API to reflect its broad use outside of the scope of firmware: driver
data helpers, and builds on top of the original firmware core code.
We purposely try to limit the scope of changes in this new API to
simply enable a flexible API to start off with.

The new extensible "driver data" set of helpers accepts that there
really are only two types of requests for accessing driver data:

a) synchronous requests
b) asynchronous requests

Both of these requests may have a different set of requirements which
must be met. These requirements can simply be passed as a struct
drvdata_req_params to each type of request. This struct can be extended
over time to support different requirements as the kernel evolves.

Using the new driver data helpers is only necessary if you have
requirements outside of what the existing old firmware API accepts
or alternatively if you want to ensure to avoid the old firmware
fallback mechanism at all times, regardless of what kernel your driver
might run in.

Developers with new uses should extend the new new struct drvdata_req_params
and driver data code to provide support for new features.

A *few* simple features added as part of the new set of driver data
request APIs, other than making the new API easily extensible for
the future:

 - The firmware fallback mechanism is currenlty always ignored
 - 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 descriptor. The new
   drvdata API is able to free the drvdata file for you by requiring a
   consumer callback for the driver data file.
 - You no longer need to declare and use your own completions, you
   can replace your completions with drvdata_synchronize_request() using
   the async_cookie set for you by drvdata_file_request_async(). When
   drvdata_file_request_async() completes you can rest assured all the
   work for both triggering, and processing the drvdata using any of
   your callbacks has completed.
 - Allow 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 -- although it should be
   noted another one of its goal was to also skip the fallback mechanisms.
   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.
 - The driver data request APIs currently match the old synchronous firmware
   API calls to refcounted firmware_class module, but it should be easy
   to add support now to enable also refcounting the caller's module
   should it be be needed. Likewise the driver data request APIs match the
   old asynchronous firmware API call and refcounts the caller's module.

v5 changes:

o Bike shedding:
  o sysdata/drvdata
  o sysdata_req_desc/drvdata_req_params
o Documentation conversion to Sphinx

v4 changes:

o Add SYSDATA_KEEP_SYNC() and SYSDATA_KEEP_ASYNC() macro helpers,
  drivers that want to keep the firmware are pretty common, however
  note that if we can figure out a way to avoid having drivers
  deal with releasing the firmware we're better off, that however
  can be an additional change to look forward to.

o 0-day-bot make htmldocs warning fixes

o When developing and testing the sysdata test driver I ended up
  running into tons of hairball code just to be able to come up
  with enough code to be able to tweak all possible knobs using
  a userspace test interface. This begged for a cleaner API and
  in testing found that async_schedule_domain() made life so much
  easier. This also added the sysdata_synchronize_request() helper
  which user can use to see if their async request completed. This
  should help users considerably as well. Updated code, commit log
  and documentation to reflect these changes.

o In testing found that to make semantics stronger we should
  require @optional to true on the descriptor if an optional
  callback is to be provided (with SYSDATA_SYNC_OPT_CB() or
  SYSDATA_ASYNC_OPT_CB()). Made notes to ensure to users
  that set @optional to true but are not providing a opt_fail_cb()
  should at the very least seriously consider using the returned
  using async_cookie to sysdata_synchronize_request() to ensure
  no lingering requests are kept out of bounds.

o Updated commit log to reflect how we can compartamentalize
  usermode helper code

o Adds SYSDATA_ASYNC_OPT_CB()

o Forces @optional on SYSDATA_SYNC_OPT_CB() to true

o Ensures sysdata_file_request() and sysdata_file_request_async()
  check for emptry string (name[0] == '\0') as follow up to
  Kees's check for empty string name 471b095dfe0d6 ("firmware_class:
  make sure fw requests contain a name") and later a fix by
  Brian through 715780ae4bb76d ("firmware: actually return NULL on
  failed request_firmware_nowait()).

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 Documentation/driver-api/firmware/drvdata.rst      |  91 ++++++
 Documentation/driver-api/firmware/index.rst        |   1 +
 Documentation/driver-api/firmware/introduction.rst |  11 +
 MAINTAINERS                                        |   3 +-
 drivers/base/firmware_class.c                      | 327 +++++++++++++++++++++
 include/linux/drvdata.h                            | 245 +++++++++++++++
 6 files changed, 677 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/driver-api/firmware/drvdata.rst
 create mode 100644 include/linux/drvdata.h

diff --git a/Documentation/driver-api/firmware/drvdata.rst b/Documentation/driver-api/firmware/drvdata.rst
new file mode 100644
index 000000000000..0445d49cd974
--- /dev/null
+++ b/Documentation/driver-api/firmware/drvdata.rst
@@ -0,0 +1,91 @@
+===========
+drvdata API
+===========
+
+As the kernel evolves we keep extending the firmware_class set of APIs
+with more or less arguments, this creates a slew of collateral evolutions.
+The set of users of firmware request APIs has also grown now to include
+users which are not looking for "firmware" per se, but instead general
+driver data files which for one reason or another has been decided to be
+kept oustide of the kernel, and/or to allow dynamic updates. The driver data
+request set of APIs addresses rebranding of firmware as generic driver data
+files, and provides a way to enable these APIs to easily be extended without
+much collateral evolutions.
+
+Driver data modes of operation
+==============================
+
+There are only two types of modes of operation for system data requests:
+
+  * synchronous  - drvdata_request()
+  * asynchronous - drvdata_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 can grow as with new
+fields as requirements grow. The old firmware API provides two synchronous
+requests: request_firmware() and request_firmware_direct(), the later allowing
+the caller to specify that the "driver data file" is optional.  The driver data
+request API allows a caller to set the optional nature of the driver data
+on the request parameter data structure using the same synchronous API. Since
+this requirement is part of the request paramter data structure it also allows
+asynchronous requests to specify that the driver data is optional.
+
+Reference counting and releasing the system 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 mimic this behaviour, for synchronous
+requests the firmware_class module is refcounted through the use of
+dfl_sync_reqs, although if in the future we may later enable use of
+also refcounting the caller's module as well. Likewise in the future we
+may extend 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 paramter data structure.
+
+Async cookies, replacing completions
+====================================
+
+With this new API you do not need to declare and use your own completions, you
+can replace your completions with drvdata_synchronize_request() using the
+async_cookie set for you by drvdata_file_request_async(). When
+drvdata_file_request_async() completes you can rest assured all the work for
+both triggering, and processing the drvdata using any of your callbacks has
+completed.
+
+Fallback mechanisms on the driver data API
+==========================================
+
+The old firmware API provided support for a series of fallback mechanisms. The
+new driver data API abandons all current notions of the fallback mechanisms,
+it may soon add support for one though.
+
+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..8d275c4c165b 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
+   drvdata
 
 .. only::  subproject and html
 
diff --git a/Documentation/driver-api/firmware/introduction.rst b/Documentation/driver-api/firmware/introduction.rst
index 211cb44eb972..d7d5ef846ca0 100644
--- a/Documentation/driver-api/firmware/introduction.rst
+++ b/Documentation/driver-api/firmware/introduction.rst
@@ -25,3 +25,14 @@ 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
+* drvdata API - new flexible API
+
+New features and development should be added through the new drvdata API to
+avoid unnecessary collateral evolutions when adding new features.
diff --git a/MAINTAINERS b/MAINTAINERS
index 5f0420a0da5b..f7752e4a2de8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5028,7 +5028,7 @@ F:	include/linux/firewire.h
 F:	include/uapi/linux/firewire*.h
 F:	tools/firewire/
 
-FIRMWARE LOADER (request_firmware)
+FIRMWARE LOADER (request_firmware, drvdata_file_request)
 M:	Ming Lei <ming.lei@canonical.com>
 M:	Luis R. Rodriguez <mcgrof@kernel.org>
 L:	linux-kernel@vger.kernel.org
@@ -5036,6 +5036,7 @@ S:	Maintained
 F:	Documentation/firmware_class/
 F:	drivers/base/firmware*.c
 F:	include/linux/firmware.h
+F:	include/linux/drvdata.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 4497d263209f..1f3f2212d6e9 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) 2016 Luis R. Rodriguez <mcgrof@kernel.org>
  *
  * Please see Documentation/firmware_class/ for more information.
  *
@@ -18,6 +19,7 @@
 #include <linux/mutex.h>
 #include <linux/workqueue.h>
 #include <linux/highmem.h>
+#include <linux/drvdata.h>
 #include <linux/firmware.h>
 #include <linux/slab.h>
 #include <linux/sched.h>
@@ -40,6 +42,12 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
 MODULE_DESCRIPTION("Multi purpose firmware loading support");
 MODULE_LICENSE("GPL");
 
+static const struct drvdata_reqs dfl_sync_reqs = {
+	.mode = DRVDATA_SYNC,
+	.module = THIS_MODULE,
+	.gfp = GFP_KERNEL,
+};
+
 /* Builtin firmware support */
 
 #ifdef CONFIG_FW_LOADER
@@ -1338,6 +1346,184 @@ void release_firmware(const struct firmware *fw)
 }
 EXPORT_SYMBOL(release_firmware);
 
+static void drvdata_file_update(struct drvdata *drvdata)
+{
+	struct firmware *fw;
+	struct firmware_buf *buf;
+
+	if (!drvdata || !drvdata->priv)
+		return;
+
+	fw = drvdata->priv;
+	if (!fw->priv)
+		return;
+
+	buf = fw->priv;
+
+	drvdata->size = buf->size;
+	drvdata->data = buf->data;
+
+	pr_debug("%s: fw-%s buf=%p data=%p size=%u",
+		 __func__, buf->fw_id, buf, buf->data,
+		 (unsigned int)buf->size);
+}
+
+/*
+ * prepare firmware and firmware_buf structs;
+ * return 0 if a firmware is already assigned, 1 if need to load one,
+ * or a negative error code
+ */
+static int
+_request_drvdata_prepare(struct drvdata **drvdata_p, const char *name,
+			 struct device *device)
+{
+	struct drvdata *drvdata;
+	struct firmware *fw;
+	int ret;
+
+	*drvdata_p = drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata) {
+		dev_err(device, "%s: kmalloc(struct drvdata) failed\n",
+			__func__);
+		return -ENOMEM;
+	}
+
+	ret = _request_firmware_prepare(&fw, name, device, NULL, 0);
+	if (ret >= 0)
+		drvdata->priv = fw;
+
+	return ret;
+}
+
+/**
+ * release_drvdata_file: - release the resource associated with the drvdata file
+ * @drvdata: drvdata file resource to release
+ **/
+void release_drvdata(const struct drvdata *drvdata)
+{
+	struct firmware *fw;
+
+	if (drvdata) {
+		if (drvdata->priv) {
+			fw = drvdata->priv;
+			release_firmware(fw);
+		}
+	}
+	kfree(drvdata);
+}
+EXPORT_SYMBOL_GPL(release_drvdata);
+
+/*
+ * drvdata_p is always set to be NULL unless a proper driver
+ * data file was found.
+ */
+static int _drvdata_request(const struct drvdata **drvdata_p,
+			    const char *name,
+			    const struct drvdata_req_params *params,
+			    struct device *device)
+{
+	struct drvdata *drvdata = NULL;
+	struct firmware *fw = NULL;
+	int ret = -EINVAL;
+
+	if (!drvdata_p)
+		goto out;
+
+	if (!params)
+		goto out;
+
+	if (!name || name[0] == '\0')
+		goto out;
+
+	ret = _request_drvdata_prepare(&drvdata, name, device);
+	if (ret <= 0) /* error or already assigned */
+		goto out;
+
+	fw = drvdata->priv;
+
+	ret = fw_get_filesystem_firmware(device, fw->priv);
+	if (ret && !params->optional)
+		pr_err("Direct driver data load for %s failed with error %d\n",
+		       name, ret);
+
+	if (!ret)
+		ret = assign_firmware_buf(fw, device, FW_OPT_UEVENT);
+
+ out:
+	if (ret < 0) {
+		release_drvdata(drvdata);
+		drvdata = NULL;
+	}
+
+	drvdata_file_update(drvdata);
+
+	*drvdata_p = drvdata;
+
+	return ret;
+}
+
+/**
+ * drvdata_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 drvdata_request(const char *name,
+		    const struct drvdata_req_params *params,
+		    struct device *device)
+{
+	const struct drvdata *drvdata;
+	const struct drvdata_reqs *sync_reqs;
+	int ret;
+
+	if (!device || !params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (params->sync_reqs.mode != DRVDATA_SYNC)
+		return -EINVAL;
+
+	if (drvdata_sync_opt_cb(params) && !params->optional)
+		return -EINVAL;
+
+	sync_reqs = &dfl_sync_reqs;
+
+	__module_get(sync_reqs->module);
+	get_device(device);
+
+	ret = _drvdata_request(&drvdata, name, params, device);
+	if (ret && params->optional)
+		ret = drvdata_sync_opt_call_cb(params);
+	else
+		ret = drvdata_sync_call_cb(params, drvdata);
+
+	if (!params->keep)
+		release_drvdata(drvdata);
+
+	put_device(device);
+	module_put(sync_reqs->module);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drvdata_request);
+
 /* Async support */
 struct firmware_work {
 	struct work_struct work;
@@ -1426,6 +1612,145 @@ request_firmware_nowait(
 }
 EXPORT_SYMBOL(request_firmware_nowait);
 
+struct drvdata_file_work {
+	const char *name;
+	struct drvdata_req_params params;
+	struct device *device;
+};
+
+static ASYNC_DOMAIN(drvdata_async_domain);
+
+static void request_drvdata_file_work_func(void *data, async_cookie_t cookie)
+{
+	struct drvdata_file_work *drv_work = data;
+	const struct drvdata_req_params *params;
+	const struct drvdata_reqs *sync_reqs;
+	const struct drvdata *drvdata;
+	int ret;
+
+	params = &drv_work->params;
+	sync_reqs = &params->sync_reqs;
+
+	ret = _drvdata_request(&drvdata, drv_work->name,
+			       params, drv_work->device);
+	if (ret && params->optional)
+		drvdata_async_opt_call_cb(params);
+	else
+		drvdata_async_call_cb(drvdata, params);
+
+	if (!params->keep)
+		release_drvdata(drvdata);
+
+	put_device(drv_work->device);
+	module_put(sync_reqs->module);
+
+	kfree_const(drv_work->name);
+	kfree(drv_work);
+}
+
+/**
+ * drvdata_request_async - asynchronous request for a driver data file
+ * @name: name of the driver data file
+ * @desc: driver data file descriptor, it provides all the requirements
+ * 	which must be met for the file being requested.
+ * @device: device for which firmware is being loaded
+ * @async_cookie: used for checkpointing your async request
+ *
+ * This performs an asynchronous driver data file lookup with the requirements
+ * specified on @desc. The request for the actual driver data file lookup will
+ * be scheduled with async_schedule_domain() 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 @desc. If the driver data file is optional you must
+ * specify that on the @desc 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 @desc. 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.
+ *
+ * You should use rely on async_cookie to determine if your asynchronous work
+ * has been scheduled and completed. If you need to wait for completion of
+ * processing of your drvdata through your callbacks, or if you just want to
+ * know the hunt is over you can drvdata_synchronize_request() with the
+ * async_cookie.
+ */
+int drvdata_request_async(const char *name,
+			  const struct drvdata_req_params *params,
+			  struct device *device,
+			  async_cookie_t *async_cookie)
+{
+	struct drvdata_file_work *drv_work;
+	const struct drvdata_reqs *sync_reqs;
+
+	if (!device || !params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (params->sync_reqs.mode != DRVDATA_ASYNC)
+		return -EINVAL;
+
+	if (drvdata_async_opt_cb(params) && !params->optional)
+		return -EINVAL;
+
+	sync_reqs = &params->sync_reqs;
+
+	drv_work = kzalloc(sizeof(struct drvdata_file_work), sync_reqs->gfp);
+	if (!drv_work)
+		return -ENOMEM;
+
+	drv_work->device = device;
+	memcpy(&drv_work->params, params, sizeof(struct drvdata_req_params));
+	drv_work->name = kstrdup_const(name, sync_reqs->gfp);
+	if (!drv_work->name) {
+		kfree(drv_work);
+		return -ENOMEM;
+	}
+
+	if (!try_module_get(sync_reqs->module)) {
+		kfree_const(drv_work->name);
+		kfree(drv_work);
+		return -EFAULT;
+	}
+
+	get_device(drv_work->device);
+
+	*async_cookie = async_schedule_domain(request_drvdata_file_work_func,
+					      drv_work,
+					      &drvdata_async_domain);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(drvdata_request_async);
+
+/**
+ * drvdata_synchronize_request - wait until your async drvdata calls complete
+ * @async_cookie: async cookie
+ *
+ * Waits until all asynchronous drvdata calls prior to and up to @async_cookie
+ * have been completed. You can use this to wait for completion of your own
+ * async callback. Your wait will end after request_drvdata_file_work_func()
+ * is called for your cookie. At this point you can rest assured your
+ * series of async callbacks would have been called if supplied.
+ *
+ * async_cookie+1 is used as async_synchronize_cookie_domain() only waits
+ * until at least your own call is next in queue to be run, we want the
+ * next item after yours to be in queue, this tells us we have run already.
+ * Should there not be any other async scheduled item after yours this will
+ * simply wait until all async drvdata calls are complete.
+ */
+void drvdata_synchronize_request(async_cookie_t async_cookie)
+{
+	async_synchronize_cookie_domain(async_cookie+1, &drvdata_async_domain);
+}
+EXPORT_SYMBOL_GPL(drvdata_synchronize_request);
+
 #ifdef CONFIG_PM_SLEEP
 static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);
 
@@ -1799,6 +2124,7 @@ static int __init firmware_class_init(void)
 
 static void __exit firmware_class_exit(void)
 {
+	async_synchronize_full_domain(&drvdata_async_domain);
 #ifdef CONFIG_PM_SLEEP
 	unregister_syscore_ops(&fw_syscore_ops);
 	unregister_pm_notifier(&fw_cache.pm_notify);
@@ -1807,6 +2133,7 @@ static void __exit firmware_class_exit(void)
 	unregister_reboot_notifier(&fw_shutdown_nb);
 	class_unregister(&firmware_class);
 #endif
+	async_unregister_domain(&drvdata_async_domain);
 }
 
 fs_initcall(firmware_class_init);
diff --git a/include/linux/drvdata.h b/include/linux/drvdata.h
new file mode 100644
index 000000000000..fad7524513f6
--- /dev/null
+++ b/include/linux/drvdata.h
@@ -0,0 +1,245 @@
+#ifndef _LINUX_DRVDATA_H
+#define _LINUX_DRVDATA_H
+
+#include <linux/types.h>
+#include <linux/compiler.h>
+#include <linux/gfp.h>
+#include <linux/device.h>
+#include <linux/async.h>
+
+/*
+ * Driver Data internals
+ *
+ * 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/.
+ */
+
+struct drvdata {
+	size_t size;
+	const u8 *data;
+
+	/* drvdata loader private fields */
+	void *priv;
+};
+
+/**
+ * enum drvdata_mode - driver data mode of operation
+ *
+ * DRVDATA_SYNC: your call to request driver data is synchronous. We will
+ * 	look for the driver data file you have requested immediatley.
+ * DRVDATA_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 drvdata_mode {
+	DRVDATA_SYNC,
+	DRVDATA_ASYNC,
+};
+
+/* one per drvdata_mode */
+union drvdata_cbs {
+	struct {
+		int __must_check (*found_cb)(void *, const struct drvdata *);
+		void *found_ctx;
+
+		int __must_check (*opt_fail_cb)(void *);
+		void *opt_fail_ctx;
+	} sync;
+	struct {
+		void (*found_cb)(const struct drvdata *, void *);
+		void *found_ctx;
+
+		void (*opt_fail_cb)(void *);
+		void *opt_fail_ctx;
+	} async;
+};
+
+struct drvdata_reqs {
+	enum drvdata_mode mode;
+	struct module *module;
+	gfp_t gfp;
+};
+
+/**
+ * struct drvdata_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. You must set this to true if you have provided a opt_fail_cb
+ *	callback, DRVDATA_SYNC_OPT_CB() and DRVDATA_ASYNC_OPT_CB() ensures
+ *	this is done for you. If you set this to true and are using an
+ *	asynchronous request but not providing a opt_fail_cb() you should
+ *	seriously consider using at the very least using async_cookie provided
+ *	to you to drvdata_synchronize_request() to ensure no lingering
+ *	requests are kept out of bounds.
+ * @keep: if set the caller wants to claim ownership over the system data
+ *	through one of its callbacks, it must later free it with
+ *	release_drvdata(). By default this is set to false and the kernel
+ *	will release the system data file for you after callback processing
+ *	has completed.
+ * @sync_reqs: synchronization requirements, this will be taken care for you
+ *	by default if you are usingy drvdata_request(), otherwise you
+ *	should provide your own requirements.
+ *
+ * This structure is set the by the driver and passed to the system data
+ * file helpers drvdata_request() or drvdata_request_async().
+ * It is intended to carry all requirements and specifications required
+ * to complete the task to get the requested system date file to the caller.
+ * If you wish to extend functionality of system data file requests you
+ * should extend this data structure and make use of the extensions on
+ * the callers to avoid unnecessary collateral evolutions.
+ *
+ * You are allowed to provide a callback to handle if a system data file was
+ * found or not. You do not need to provide a callback. You may also set
+ * an optional flag which would enable you to declare that the system data
+ * file is optional and that if it is not found an alternative callback be
+ * run for you.
+ *
+ * Refer to drvdata_request() and drvdata_request_async() for more
+ * details.
+ */
+struct drvdata_req_params {
+	bool optional;
+	bool keep;
+	struct drvdata_reqs sync_reqs;
+	const union drvdata_cbs cbs;
+};
+
+/*
+ * We keep these template definitions to a minimum for the most
+ * popular requests.
+ */
+
+/* Typical sync data case */
+#define DRVDATA_SYNC_FOUND(__found_cb, __ctx)				\
+	.cbs.sync.found_cb = __found_cb,				\
+	.cbs.sync.found_ctx = __ctx
+
+#define DRVDATA_DEFAULT_SYNC(__found_cb, __ctx)				\
+	DRVDATA_SYNC_FOUND(__found_cb, __ctx)
+
+#define DRVDATA_KEEP_SYNC(__found_cb, __ctx)				\
+	DRVDATA_DEFAULT_SYNC(__found_cb, __ctx),			\
+	.keep= true
+
+/* If you have one fallback routine */
+#define DRVDATA_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
+ * drvdata_request_async(). Drivers can override.
+ */
+#define DRVDATA_DEFAULT_ASYNC(__found_cb, __ctx)			\
+	.sync_reqs = {							\
+		.mode = DRVDATA_ASYNC,					\
+		.module = THIS_MODULE,					\
+		.gfp = GFP_KERNEL,					\
+	},								\
+	.cbs.async = {							\
+		.found_cb = __found_cb,					\
+		.found_ctx = __ctx,					\
+	}
+
+#define DRVDATA_KEEP_ASYNC(__found_cb, __ctx)				\
+	DRVDATA_DEFAULT_ASYNC(__found_cb, __ctx),			\
+	.keep = true
+
+#define DRVDATA_ASYNC_OPT_CB(__fail_cb, __ctx)				\
+	.optional = true,						\
+	.cbs.async.opt_fail_cb = __fail_cb,				\
+	.cbs.async.opt_fail_ctx = __ctx
+
+#define drvdata_sync_cb(param)		((params)->cbs.sync.found_cb)
+#define drvdata_sync_ctx(params)	((params)->cbs.sync.found_ctx)
+static inline int drvdata_sync_call_cb(const struct drvdata_req_params *params,
+				       const struct drvdata *drvdata)
+{
+	if (params->sync_reqs.mode != DRVDATA_SYNC)
+		return -EINVAL;
+	if (!drvdata_sync_cb(params)) {
+		if (drvdata)
+			return 0;
+		return -ENOENT;
+	}
+	return drvdata_sync_cb(params)(drvdata_sync_ctx(params), drvdata);
+}
+
+#define drvdata_sync_opt_cb(params)	((params)->cbs.sync.opt_fail_cb)
+#define drvdata_sync_opt_ctx(params)	((params)->cbs.sync.opt_fail_ctx)
+static
+inline int drvdata_sync_opt_call_cb(const struct drvdata_req_params *params)
+{
+	if (params->sync_reqs.mode != DRVDATA_SYNC)
+		return -EINVAL;
+	if (!drvdata_sync_opt_cb(params))
+		return 0;
+	return drvdata_sync_opt_cb(params)(drvdata_sync_opt_ctx(params));
+}
+
+#define drvdata_async_cb(params)	((params)->cbs.async.found_cb)
+#define drvdata_async_ctx(params)	((params)->cbs.async.found_ctx)
+static
+inline void drvdata_async_call_cb(const struct drvdata *drvdata,
+				  const struct drvdata_req_params *params)
+{
+	if (params->sync_reqs.mode != DRVDATA_ASYNC)
+		return;
+	if (!drvdata_async_cb(params))
+		return;
+	drvdata_async_cb(params)(drvdata, drvdata_async_ctx(params));
+}
+
+#define drvdata_async_opt_cb(params)	((params)->cbs.async.opt_fail_cb)
+#define drvdata_async_opt_ctx(params)	((params)->cbs.async.opt_fail_ctx)
+static
+inline void drvdata_async_opt_call_cb(const struct drvdata_req_params *params)
+{
+	if (params->sync_reqs.mode != DRVDATA_ASYNC)
+		return;
+	if (!drvdata_async_opt_cb(params))
+		return;
+	drvdata_async_opt_cb(params)(drvdata_async_opt_ctx(params));
+}
+
+#if defined(CONFIG_FW_LOADER) || (defined(CONFIG_FW_LOADER_MODULE) && defined(MODULE))
+int drvdata_request(const char *name,
+		    const struct drvdata_req_params *params,
+		    struct device *device);
+int drvdata_request_async(const char *name,
+			  const struct drvdata_req_params *params,
+			  struct device *device,
+			  async_cookie_t *async_cookie);
+void release_drvdata(const struct drvdata *drvdata);
+void drvdata_synchronize_request(async_cookie_t async_cookie);
+#else
+static inline int drvdata_request(const char *name,
+				  const struct drvdata_req_params *params,
+				  struct device *device)
+{
+	return -EINVAL;
+}
+
+static
+inline int drvdata_request_async(const char *name,
+				 const struct drvdata_req_params *params,
+				 struct device *device,
+				 async_cookie_t *async_cookie);
+{
+	return -EINVAL;
+}
+
+static inline void release_drvdata(const struct drvdata *drvdata)
+{
+}
+
+void drvdata_synchronize_request(async_cookie_t async_cookie)
+{
+}
+#endif
+
+#endif /* _LINUX_DRVDATA_H */
-- 
2.11.0

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

* [PATCH v4 2/3] test: add new drvdata loader tester
  2017-01-12 15:02 ` [PATCH v4 0/3] firmware: add drvdata API Luis R. Rodriguez
  2017-01-12 15:02   ` [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
@ 2017-01-12 15:02   ` Luis R. Rodriguez
  2017-01-12 15:02   ` [PATCH v4 3/3] p54: convert to sysdata API Luis R. Rodriguez
  2017-02-07  1:08   ` [PATCH v5 0/2] firmware: add driver data API Luis R. Rodriguez
  3 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-12 15:02 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

This adds a load tester driver test_drvdata a for the new
extensible drvdata loader APIs, part of firmware_class.
Since the fallback mechanisms are currenlty completely
ignored by the drvdata API the testing is much easier to do.

Contrary to the firmware_class tester which adds in-kernel
code for each and every single test it can think of for each
type of request, this enables you to build your tests in userspace
by exposing knobs of the exported API to userspace of the
options available in the API and then lets the trigger kick a one
time kernel API use. This lets us build any possible test case
in userspace.

The test driver also enables multiple test triggers
to be created enabling further testing to be done through
separate threads in parallel.

Both these facts should should not only help testing the
drvdata API in as many ways as possible as efficiently
as possible, but it also paves the way to later strive to
see how it might be even possible to automatically generate
test API drivers for exported symbols in the future. The
exported symbols being the test cases and attributes exposed
in userspace consisting of device attributes, the target test
driver being the desired output driver.

v5:
o 0-day build complaint lock fix on lib/test_drvdata.c:9991
o expand testdrv.sh to support a series of different options
  to help with testing
o use snprintf() using PAGE_SIZE
o add a common helper config_test_show_str()
o drop test_dev_get_name() in favor for kasprintf()
o use vzalloc() instead of vmalloc()
o bike shedding sysdata/drvdata

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 lib/Kconfig.debug                           |   12 +
 lib/Makefile                                |    1 +
 lib/test_drvdata.c                          | 1033 +++++++++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile   |    2 +-
 tools/testing/selftests/firmware/config     |    1 +
 tools/testing/selftests/firmware/drvdata.sh |  827 +++++++++++++++++++++
 6 files changed, 1875 insertions(+), 1 deletion(-)
 create mode 100644 lib/test_drvdata.c
 create mode 100755 tools/testing/selftests/firmware/drvdata.sh

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index b06848a104e6..88d807eb1efe 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1930,6 +1930,18 @@ config TEST_FIRMWARE
 
 	  If unsure, say N.
 
+config TEST_DRVDATA
+	tristate "Test driver data loading via drvdata APIs"
+	default n
+	depends on FW_LOADER
+	help
+	  This builds the "test_drvdata" module that creates a userspace
+	  interface for testing driver data loading using the drvdata 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 bc4073a8cd08..d456f59e6ae0 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -45,6 +45,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
+obj-$(CONFIG_TEST_DRVDATA) += test_drvdata.o
 obj-$(CONFIG_TEST_KASAN) += test_kasan.o
 obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o
 obj-$(CONFIG_TEST_LKM) += test_module.o
diff --git a/lib/test_drvdata.c b/lib/test_drvdata.c
new file mode 100644
index 000000000000..a924ed7d85b3
--- /dev/null
+++ b/lib/test_drvdata.c
@@ -0,0 +1,1033 @@
+/*
+ * Driver data test interface
+ *
+ * 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 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/drvdata.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_DRVDATA "test-drvdata.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 drvdata API
+ *
+ * @name: the name of the primary drvdata 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
+ * 	drvdata_request_async(). If false the synchronous call will
+ * 	be used, drvdata_request().
+ * @optional: whether or not the drvdata is optional refer to the
+ *	struct drvdata_reg_params @optional field for more information.
+ * @keep: whether or not we wish to free the drvdata on our own, refer to
+ *	the struct drvdata_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
+ *	drvdata_req_params as this is implementation specific, and in
+ *	in drvdata API its explicit if you had defined an optional call
+ *	back for your descriptor with either DRVDATA_SYNC_OPT_CB() or
+ *	DRVDATA_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.
+ * @test_result: a test may use this to collect the result from the call
+ *	of the drvdata_request_async() or drvdata_request() 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;
+
+	int test_result;
+};
+
+/**
+ * test_drvdata_private - private device driver drvdata representation
+ *
+ * @size: size of the data copied, in bytes
+ * @data: the actual data we copied over from drvdata
+ * @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_drvdata_private {
+	size_t size;
+	u8 *data;
+	bool written;
+};
+
+/**
+ * drvdata_test_device - test device to help test drvdata
+ *
+ * @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
+ *	drvdata API and we to let you tune them in userspace. We then just
+ *	provide one trigger.
+ * @test_drvdata: internal private representation of a storage area
+ *	a driver might typically use to stuff firmware / drvdata.
+ * @misc_dev: we use a misc device under the hood
+ * @dev: pointer to misc_dev's own struct device
+ * @drvdata_mutex: for access into the @drvdata, the fake storage location for
+ * 	the system data we copy.
+ * @config_mutex:
+ * @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.
+ * list: needed to be part of the reg_test_devs
+ */
+struct drvdata_test_device {
+	int dev_idx;
+	struct test_config config;
+	struct test_drvdata_private test_drvdata;
+	struct miscdevice misc_dev;
+	struct device *dev;
+
+	struct mutex drvdata_mutex;
+	struct mutex config_mutex;
+	struct mutex trigger_mutex;
+	struct list_head list;
+};
+
+static struct miscdevice *dev_to_misc_dev(struct device *dev)
+{
+	return dev_get_drvdata(dev);
+}
+
+static
+struct drvdata_test_device *misc_dev_to_test_dev(struct miscdevice *misc_dev)
+{
+	return container_of(misc_dev, struct drvdata_test_device, misc_dev);
+}
+
+static struct drvdata_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 drvdata_test_device *test_dev = misc_dev_to_test_dev(misc_dev);
+	struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
+	ssize_t ret = 0;
+
+	mutex_lock(&test_dev->drvdata_mutex);
+	if (test_drvdata->written)
+		ret = simple_read_from_buffer(buf, size, offset,
+					      test_drvdata->data,
+					      test_drvdata->size);
+	mutex_unlock(&test_dev->drvdata_mutex);
+
+	return ret;
+}
+
+static const struct file_operations test_fw_fops = {
+	.owner          = THIS_MODULE,
+	.read           = test_fw_misc_read,
+};
+
+static void free_test_drvdata(struct test_drvdata_private *test_drvdata)
+{
+	kfree(test_drvdata->data);
+	test_drvdata->data = NULL;
+	test_drvdata->size = 0;
+	test_drvdata->written = false;
+}
+
+static int test_load_drvdata(struct drvdata_test_device *test_dev,
+			     const struct drvdata *drvdata)
+{
+	struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
+	int ret = 0;
+
+	if (!drvdata)
+		return -ENOENT;
+
+	mutex_lock(&test_dev->drvdata_mutex);
+
+	free_test_drvdata(test_drvdata);
+
+	test_drvdata->data = kzalloc(drvdata->size, GFP_KERNEL);
+	if (!test_drvdata->data) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	memcpy(test_drvdata->data, drvdata->data, drvdata->size);
+	test_drvdata->size = drvdata->size;
+	test_drvdata->written = true;
+
+	dev_info(test_dev->dev, "loaded: %zu\n", test_drvdata->size);
+
+out:
+	mutex_unlock(&test_dev->drvdata_mutex);
+
+	return ret;
+}
+
+static int sync_found_cb(void *context, const struct drvdata *drvdata)
+{
+	struct drvdata_test_device *test_dev = context;
+	int ret;
+
+	ret = test_load_drvdata(test_dev, drvdata);
+	if (ret)
+		dev_info(test_dev->dev, "unable to write drvdata: %d\n", ret);
+	return ret;
+}
+
+static ssize_t config_show(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	struct drvdata_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,
+			"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 drvdata_test_device *test_dev,
+			    const struct drvdata *drvdata)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	ret = test_load_drvdata(test_dev, drvdata);
+	if (ret) {
+		if (!config->optional)
+			dev_info(test_dev->dev, "unable to write drvdata\n");
+	}
+	if (config->keep) {
+		release_drvdata(drvdata);
+		drvdata = NULL;
+	}
+	return ret;
+}
+
+static int config_req_default(struct drvdata_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 drvdata_req_params req_params = {
+		DRVDATA_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 = drvdata_request(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 drvdata_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 drvdata_test_device *test_dev = context;
+
+	config_req_default(test_dev);
+
+	/* Leave all the error checking for the main caller */
+}
+
+static int config_sync_req_cb(void *context,
+			      const struct drvdata *drvdata)
+{
+	struct drvdata_test_device *test_dev = context;
+
+	return config_load_data(test_dev, drvdata);
+}
+
+static int trigger_config_sync(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+	const struct drvdata_req_params req_params_default = {
+		DRVDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params req_params_opt_cb = {
+		DRVDATA_DEFAULT_SYNC(config_sync_req_cb, test_dev),
+		DRVDATA_SYNC_OPT_CB(config_sync_req_default_cb, test_dev),
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params *req_params;
+
+	if (config->enable_opt_cb)
+		req_params = &req_params_opt_cb;
+	else
+		req_params= &req_params_default;
+
+	ret = drvdata_request(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 drvdata *drvdata,
+				void *context)
+{
+	struct drvdata_test_device *test_dev = context;
+	config_load_data(test_dev, drvdata);
+}
+
+static int trigger_config_async(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+	const struct drvdata_req_params req_params_default = {
+		DRVDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+		.sync_reqs.mode = config->async ?
+			DRVDATA_ASYNC : DRVDATA_SYNC,
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params req_params_opt_cb = {
+		DRVDATA_DEFAULT_ASYNC(config_async_req_cb, test_dev),
+		DRVDATA_ASYNC_OPT_CB(config_async_req_default_cb, test_dev),
+		.sync_reqs.mode = config->async ?
+			DRVDATA_ASYNC : DRVDATA_SYNC,
+		.optional = config->optional,
+		.keep = config->keep,
+	};
+	const struct drvdata_req_params *req_params;
+	async_cookie_t async_cookie;
+
+	if (config->enable_opt_cb)
+		req_params = &req_params_opt_cb;
+	else
+		req_params = &req_params_default;
+
+	ret = drvdata_request_async(config->name, req_params,
+				   test_dev->dev, &async_cookie);
+	if (ret) {
+		dev_err(test_dev->dev, "async load of '%s' failed: %d\n",
+			config->name, ret);
+		goto out;
+	}
+
+	drvdata_synchronize_request(async_cookie);
+out:
+	return ret;
+}
+
+static ssize_t
+trigger_config_store(struct device *dev,
+		     struct device_attribute *attr,
+		     const char *buf, size_t count)
+{
+	struct drvdata_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_drvdata_private *test_drvdata = &test_dev->test_drvdata;
+	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_drvdata->written) {
+		dev_info(dev, "loaded: %zu\n", test_drvdata->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 int config_copy_name(struct test_config *config,
+			    const char *name,
+			    size_t count)
+{
+	return __kstrncpy(&config->name, name, count, GFP_KERNEL);
+}
+
+static int config_copy_default_name(struct test_config *config,
+				    const char *name,
+				    size_t count)
+{
+	return __kstrncpy(&config->default_name, name, count, GFP_KERNEL);
+}
+
+static void __drvdata_config_free(struct test_config *config)
+{
+	kfree_const(config->name);
+	config->name = NULL;
+	kfree_const(config->default_name);
+	config->default_name = NULL;
+}
+
+static void drvdata_config_free(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+
+	mutex_lock(&test_dev->config_mutex);
+	__drvdata_config_free(config);
+	mutex_unlock(&test_dev->config_mutex);
+}
+
+static int __drvdata_config_init(struct test_config *config)
+{
+	int ret;
+
+	ret = config_copy_name(config, TEST_DRVDATA, strlen(TEST_DRVDATA));
+	if (ret < 0)
+		goto out;
+
+	ret = config_copy_default_name(config, TEST_DRVDATA,
+				       strlen(TEST_DRVDATA));
+	if (ret < 0)
+		goto out;
+
+	config->async = false;
+	config->optional = false;
+	config->keep = false;
+	config->enable_opt_cb = false;
+	config->test_result = 0;
+
+out:
+	return ret;
+}
+
+int drvdata_config_init(struct drvdata_test_device *test_dev)
+{
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	ret = __drvdata_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 drvdata_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	ret = config_copy_name(config, buf, count);
+	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 drvdata_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 drvdata_test_device *test_dev = dev_to_test_dev(dev);
+	struct test_config *config = &test_dev->config;
+	int ret;
+
+	mutex_lock(&test_dev->config_mutex);
+	ret = config_copy_default_name(config, buf, count);
+	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 drvdata_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 drvdata_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->drvdata_mutex);
+	free_test_drvdata(&test_dev->test_drvdata);
+	mutex_unlock(&test_dev->drvdata_mutex);
+
+	mutex_lock(&test_dev->config_mutex);
+
+	__drvdata_config_free(config);
+
+	ret = __drvdata_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 drvdata_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 drvdata_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 drvdata_test_device *test_dev,
+				      const char *buf, size_t size,
+				      int *config)
+{
+	char *end;
+	long new = simple_strtol(buf, &end, 0);
+	if (end == buf || 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 drvdata_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 ssize_t config_async_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 drvdata_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 test_result_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct drvdata_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 drvdata_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 DRVDATA_DEV_ATTR(name)		&dev_attr_##name.attr
+
+static struct attribute *test_dev_attrs[] = {
+	DRVDATA_DEV_ATTR(trigger_config),
+	DRVDATA_DEV_ATTR(config),
+	DRVDATA_DEV_ATTR(reset),
+
+	DRVDATA_DEV_ATTR(config_name),
+	DRVDATA_DEV_ATTR(config_default_name),
+	DRVDATA_DEV_ATTR(config_async),
+	DRVDATA_DEV_ATTR(config_optional),
+	DRVDATA_DEV_ATTR(config_keep),
+	DRVDATA_DEV_ATTR(config_enable_opt_cb),
+	DRVDATA_DEV_ATTR(test_result),
+
+	NULL,
+};
+
+ATTRIBUTE_GROUPS(test_dev);
+
+void free_test_dev_drvdata(struct drvdata_test_device *test_dev)
+{
+	kfree_const(test_dev->misc_dev.name);
+	test_dev->misc_dev.name = NULL;
+	vfree(test_dev);
+	test_dev = NULL;
+	drvdata_config_free(test_dev);
+}
+
+void unregister_test_dev_drvdata(struct drvdata_test_device *test_dev)
+{
+	dev_info(test_dev->dev, "removing interface\n");
+	misc_deregister(&test_dev->misc_dev);
+	kfree(&test_dev->misc_dev.name);
+	free_test_dev_drvdata(test_dev);
+}
+
+struct drvdata_test_device *alloc_test_dev_drvdata(int idx)
+{
+	int ret;
+	struct drvdata_test_device *test_dev;
+	struct miscdevice *misc_dev;
+
+	test_dev = vzalloc(sizeof(struct drvdata_test_device));
+	if (!test_dev) {
+		pr_err("Cannot alloc test_dev\n");
+		goto err_out;
+	}
+
+	mutex_init(&test_dev->drvdata_mutex);
+	mutex_init(&test_dev->config_mutex);
+	mutex_init(&test_dev->trigger_mutex);
+
+	ret = drvdata_config_init(test_dev);
+	if (ret < 0) {
+		pr_err("Cannot alloc drvdata_config_init()\n");
+		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_drvdata%d", idx);
+	if (!misc_dev->name) {
+		pr_err("Cannot alloc misc_dev->name\n");
+		goto err_out_free_config;
+	}
+	misc_dev->fops = &test_fw_fops;
+	misc_dev->groups = test_dev_groups;
+
+	return test_dev;
+
+err_out_free_config:
+	__drvdata_config_free(&test_dev->config);
+err_out_free:
+	kfree(test_dev);
+err_out:
+	return NULL;
+}
+
+static int register_test_dev_drvdata(void)
+{
+	struct drvdata_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_drvdata(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_drvdata(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_drvdata_init(void)
+{
+	int ret;
+
+	ret = register_test_dev_drvdata();
+	if (ret)
+		pr_err("Cannot add first test drvdata device\n");
+
+	return ret;
+}
+late_initcall(test_drvdata_init);
+
+static void __exit test_drvdata_exit(void)
+{
+	struct drvdata_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_drvdata(test_dev);
+	}
+	mutex_unlock(&reg_dev_mutex);
+}
+
+module_exit(test_drvdata_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 9bf82234855b..7d4f6808bf1c 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_userhelper.sh
+TEST_PROGS := fw_filesystem.sh fw_userhelper.sh drvdata.sh
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/firmware/config b/tools/testing/selftests/firmware/config
index c8137f70e291..0a07fccd8850 100644
--- a/tools/testing/selftests/firmware/config
+++ b/tools/testing/selftests/firmware/config
@@ -1 +1,2 @@
 CONFIG_TEST_FIRMWARE=y
+CONFIG_TEST_DRVDATA=y
diff --git a/tools/testing/selftests/firmware/drvdata.sh b/tools/testing/selftests/firmware/drvdata.sh
new file mode 100755
index 000000000000..aae23e61e3a4
--- /dev/null
+++ b/tools/testing/selftests/firmware/drvdata.sh
@@ -0,0 +1,827 @@
+#!/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="drvdata"
+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"
+
+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 $DIR ]; then
+		DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"
+	fi
+
+	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_DRVDATA ]; then
+		DEFAULT_DRVDATA="test-drvdata.bin"
+	fi
+
+	if [ -z $FW ]; then
+		FW="$FWPATH/$DEFAULT_DRVDATA"
+	fi
+
+	# Set the kernel search path.
+	echo -n "$FWPATH" > $FW_SYSFSPATH
+
+	# This is an unlikely real-world firmware content. :)
+	echo "ABCD0123" >"$FW"
+
+	NAME=$(basename "$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 [ ! -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_disable_opt_cb()
+{
+	if ! echo -n 0 >$DIR/config_enable_opt_cb; then
+		echo "$0: Unable to disable keep option" >&2
+		exit 1
+	fi
+}
+
+
+# For special characters use printf directly,
+# refer to drvdata_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 drvdata_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
+}
+
+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 drvdata"
+}
+
+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)
+	# On this one we expect the file to exist so leave stderr in
+	if ! $(diff -q "$FWPATH"/"$FILE" /dev/test_drvdata0 > /dev/null) > /dev/null; then
+		echo "$1: FAIL - file $FILE did not match contents in /dev/test_drvdata0" >&2
+		config_show_config >&2
+		exit 1
+	fi
+	echo "$1: OK! - $FILE == /dev/test_drvdata0"
+}
+
+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_drvdata0 > /dev/null) > /dev/null; then
+		echo "$1: FAIL - file $FILE did not match contents in /dev/test_drvdata0" >&2
+		config_show_config >&2
+		exit 1
+	fi
+	echo "$1: OK! - $FILE == /dev/test_drvdata0"
+}
+
+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_drvdata0 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_drvdata0"
+}
+
+config_default_file_should_match()
+{
+	FILE=$(config_get_default_name)
+	diff -q $FWPATH/$FILE /dev/test_drvdata0 2> /dev/null
+	if ! $? ; then
+		echo "$1: FAIL - file $FILE expected to match /dev/test_drvdata0" >&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_drvdata0 2> /dev/null
+	if $? 2> /dev/null ; then
+		echo "$1: FAIL - file $FILE was not expected to match test_drvdata0" >&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"
+}
+
+drvdata_set_sync_defaults()
+{
+	config_reset
+}
+
+drvdata_set_async_defaults()
+{
+	config_reset
+	config_set_async
+}
+
+drvdata_test_0001s()
+{
+	NAME='\000'
+
+	drvdata_set_sync_defaults
+	config_set_name $NAME
+	printf '\000' >"$DIR"/config_name
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+drvdata_test_0001a()
+{
+	NAME='\000'
+
+	drvdata_set_async_defaults
+	printf '\000' >"$DIR"/config_name
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -EINVAL
+}
+
+drvdata_test_0001()
+{
+	drvdata_test_0001s
+	drvdata_test_0001a
+}
+
+drvdata_test_0002s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_set_sync_defaults
+	config_set_name ${FUNCNAME[0]}
+	config_trigger_want_fail ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} -ENOENT
+}
+
+drvdata_test_0002a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0002()
+{
+	#drvdata_test_0002s
+	drvdata_test_0002a
+}
+
+drvdata_test_0003()
+{
+	config_reset
+	config_file_should_not_match ${FUNCNAME[0]}
+}
+
+drvdata_test_0004s()
+{
+	TEST="drvdata_test_0004s"
+
+	drvdata_set_sync_defaults
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0004a()
+{
+	drvdata_set_async_defaults
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0004()
+{
+	drvdata_test_0004s
+	drvdata_test_0004a
+}
+
+drvdata_test_0005s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0005a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0005()
+{
+	drvdata_test_0005s
+	drvdata_test_0005a
+}
+
+drvdata_test_0006s()
+{
+	drvdata_set_sync_defaults
+	config_set_optional
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0006a()
+{
+	drvdata_set_async_defaults
+	config_set_optional
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0006()
+{
+	drvdata_test_0006s
+	drvdata_test_0006a
+}
+
+drvdata_test_0007s()
+{
+	drvdata_set_sync_defaults
+	config_set_keep
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0007a()
+{
+	drvdata_set_async_defaults
+	config_set_keep
+	config_trigger ${FUNCNAME[0]}
+	config_file_should_match ${FUNCNAME[0]}
+	config_expect_result ${FUNCNAME[0]} SUCCESS
+}
+
+drvdata_test_0007()
+{
+	drvdata_test_0007s
+	drvdata_test_0007a
+}
+
+drvdata_test_0008s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0008a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0008()
+{
+	drvdata_test_0008s
+	drvdata_test_0008a
+}
+
+drvdata_test_0009s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0009a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0009()
+{
+	drvdata_test_0009s
+	drvdata_test_0009a
+}
+
+drvdata_test_0010s()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0010a()
+{
+	NAME="nope-$DEFAULT_DRVDATA"
+
+	drvdata_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
+}
+
+drvdata_test_0010()
+{
+	drvdata_test_0010s
+	drvdata_test_0010a
+}
+
+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_drvdata0 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"
+}
+
+test_reqs
+
+usage()
+{
+	NUM_TESTS=$(grep -o ':' <<<"$ALL_TESTS" | grep -c .)
+	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
+allow_user_defaults
+load_req_mod
+
+trap "test_finish" EXIT
+
+parse_args $@
+
+exit 0
-- 
2.11.0

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

* [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-12 15:02 ` [PATCH v4 0/3] firmware: add drvdata API Luis R. Rodriguez
  2017-01-12 15:02   ` [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
  2017-01-12 15:02   ` [PATCH v4 2/3] test: add new drvdata loader tester Luis R. Rodriguez
@ 2017-01-12 15:02   ` Luis R. Rodriguez
  2017-01-16 11:32     ` Christian Lamparter
  2017-01-19 11:38     ` Greg KH
  2017-02-07  1:08   ` [PATCH v5 0/2] firmware: add driver data API Luis R. Rodriguez
  3 siblings, 2 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-12 15:02 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

The Coccinelle sysdata patches were used to help with
this transition. The changes have been carefully manually
vetted for. With the conversion we modify the cases that do
not need the firmware to be kept so that the sysdata API
can release it for us. Using the new sysdata API also means
we can get rid of our own completions.

v2: was not present
v3: initial release
v4: small cosmetic fixes
v5: bike shed changes
v6: forgot to change one piece of code during the bikeshed name change

Generated-by: Coccinelle SmPL
Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/net/wireless/intersil/p54/eeprom.c |  2 +-
 drivers/net/wireless/intersil/p54/fwio.c   |  5 +-
 drivers/net/wireless/intersil/p54/led.c    |  2 +-
 drivers/net/wireless/intersil/p54/main.c   |  2 +-
 drivers/net/wireless/intersil/p54/p54.h    |  3 +-
 drivers/net/wireless/intersil/p54/p54pci.c | 26 ++++++----
 drivers/net/wireless/intersil/p54/p54pci.h |  4 +-
 drivers/net/wireless/intersil/p54/p54spi.c | 80 +++++++++++++++++++-----------
 drivers/net/wireless/intersil/p54/p54spi.h |  2 +-
 drivers/net/wireless/intersil/p54/p54usb.c | 18 +++----
 drivers/net/wireless/intersil/p54/p54usb.h |  4 +-
 drivers/net/wireless/intersil/p54/txrx.c   |  2 +-
 12 files changed, 89 insertions(+), 61 deletions(-)

diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c
index d4c73d39336f..b8184cbc6770 100644
--- a/drivers/net/wireless/intersil/p54/eeprom.c
+++ b/drivers/net/wireless/intersil/p54/eeprom.c
@@ -16,7 +16,7 @@
  * published by the Free Software Foundation.
  */
 
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/sort.h>
 #include <linux/slab.h>
diff --git a/drivers/net/wireless/intersil/p54/fwio.c b/drivers/net/wireless/intersil/p54/fwio.c
index 4ac6764f4897..dc27049e4533 100644
--- a/drivers/net/wireless/intersil/p54/fwio.c
+++ b/drivers/net/wireless/intersil/p54/fwio.c
@@ -17,7 +17,7 @@
  */
 
 #include <linux/slab.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/export.h>
 
@@ -27,7 +27,8 @@
 #include "eeprom.h"
 #include "lmac.h"
 
-int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw)
+int p54_parse_firmware(struct ieee80211_hw *dev,
+		       const struct drvdata *fw)
 {
 	struct p54_common *priv = dev->priv;
 	struct exp_if *exp_if;
diff --git a/drivers/net/wireless/intersil/p54/led.c b/drivers/net/wireless/intersil/p54/led.c
index 9a8fedd3c0f5..4d13598d3968 100644
--- a/drivers/net/wireless/intersil/p54/led.c
+++ b/drivers/net/wireless/intersil/p54/led.c
@@ -16,7 +16,7 @@
  * published by the Free Software Foundation.
  */
 
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 
 #include <net/mac80211.h>
diff --git a/drivers/net/wireless/intersil/p54/main.c b/drivers/net/wireless/intersil/p54/main.c
index d5a3bf91a03e..a1c546cd232c 100644
--- a/drivers/net/wireless/intersil/p54/main.c
+++ b/drivers/net/wireless/intersil/p54/main.c
@@ -17,7 +17,7 @@
  */
 
 #include <linux/slab.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/module.h>
 
diff --git a/drivers/net/wireless/intersil/p54/p54.h b/drivers/net/wireless/intersil/p54/p54.h
index 529939e611cd..5bbe9d77e5fc 100644
--- a/drivers/net/wireless/intersil/p54/p54.h
+++ b/drivers/net/wireless/intersil/p54/p54.h
@@ -268,7 +268,8 @@ struct p54_common {
 /* interfaces for the drivers */
 int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb);
 void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb);
-int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw);
+int p54_parse_firmware(struct ieee80211_hw *dev,
+		       const struct drvdata *fw);
 int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len);
 int p54_read_eeprom(struct ieee80211_hw *dev);
 
diff --git a/drivers/net/wireless/intersil/p54/p54pci.c b/drivers/net/wireless/intersil/p54/p54pci.c
index 27a49068d32d..0e7fd9ba7186 100644
--- a/drivers/net/wireless/intersil/p54/p54pci.c
+++ b/drivers/net/wireless/intersil/p54/p54pci.c
@@ -15,7 +15,7 @@
 
 #include <linux/pci.h>
 #include <linux/slab.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/delay.h>
 #include <linux/completion.h>
@@ -490,7 +490,7 @@ static int p54p_open(struct ieee80211_hw *dev)
 	return 0;
 }
 
-static void p54p_firmware_step2(const struct firmware *fw,
+static void p54p_firmware_step2(const struct drvdata *fw,
 				void *context)
 {
 	struct p54p_priv *priv = context;
@@ -520,8 +520,6 @@ static void p54p_firmware_step2(const struct firmware *fw,
 
 out:
 
-	complete(&priv->fw_loaded);
-
 	if (err) {
 		struct device *parent = pdev->dev.parent;
 
@@ -542,6 +540,17 @@ static void p54p_firmware_step2(const struct firmware *fw,
 	pci_dev_put(pdev);
 }
 
+static int p54p_load_firmware(struct p54p_priv *priv)
+{
+	const struct drvdata_req_params req_params = {
+		DRVDATA_KEEP_ASYNC(p54p_firmware_step2, priv),
+	};
+
+	return drvdata_request_async("isl3886pci", &req_params,
+				     &priv->pdev->dev,
+				     &priv->fw_async_cookie);
+}
+
 static int p54p_probe(struct pci_dev *pdev,
 				const struct pci_device_id *id)
 {
@@ -595,7 +604,6 @@ static int p54p_probe(struct pci_dev *pdev,
 	priv = dev->priv;
 	priv->pdev = pdev;
 
-	init_completion(&priv->fw_loaded);
 	SET_IEEE80211_DEV(dev, &pdev->dev);
 	pci_set_drvdata(pdev, dev);
 
@@ -620,9 +628,7 @@ static int p54p_probe(struct pci_dev *pdev,
 	spin_lock_init(&priv->lock);
 	tasklet_init(&priv->tasklet, p54p_tasklet, (unsigned long)dev);
 
-	err = request_firmware_nowait(THIS_MODULE, 1, "isl3886pci",
-				      &priv->pdev->dev, GFP_KERNEL,
-				      priv, p54p_firmware_step2);
+	err = p54p_load_firmware(priv);
 	if (!err)
 		return 0;
 
@@ -652,9 +658,9 @@ static void p54p_remove(struct pci_dev *pdev)
 		return;
 
 	priv = dev->priv;
-	wait_for_completion(&priv->fw_loaded);
+	drvdata_synchronize_request(priv->fw_async_cookie);
 	p54_unregister_common(dev);
-	release_firmware(priv->firmware);
+	release_drvdata(priv->firmware);
 	pci_free_consistent(pdev, sizeof(*priv->ring_control),
 			    priv->ring_control, priv->ring_control_dma);
 	iounmap(priv->map);
diff --git a/drivers/net/wireless/intersil/p54/p54pci.h b/drivers/net/wireless/intersil/p54/p54pci.h
index 68405c142f97..00c30e1fc60b 100644
--- a/drivers/net/wireless/intersil/p54/p54pci.h
+++ b/drivers/net/wireless/intersil/p54/p54pci.h
@@ -94,7 +94,7 @@ struct p54p_priv {
 	struct pci_dev *pdev;
 	struct p54p_csr __iomem *map;
 	struct tasklet_struct tasklet;
-	const struct firmware *firmware;
+	const struct drvdata *firmware;
 	spinlock_t lock;
 	struct p54p_ring_control *ring_control;
 	dma_addr_t ring_control_dma;
@@ -105,7 +105,7 @@ struct p54p_priv {
 	struct sk_buff *tx_buf_data[32];
 	struct sk_buff *tx_buf_mgmt[4];
 	struct completion boot_comp;
-	struct completion fw_loaded;
+	async_cookie_t fw_async_cookie;
 };
 
 #endif /* P54USB_H */
diff --git a/drivers/net/wireless/intersil/p54/p54spi.c b/drivers/net/wireless/intersil/p54/p54spi.c
index 7ab2f43ab425..c0118048c01f 100644
--- a/drivers/net/wireless/intersil/p54/p54spi.c
+++ b/drivers/net/wireless/intersil/p54/p54spi.c
@@ -23,7 +23,7 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/interrupt.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/delay.h>
 #include <linux/irq.h>
 #include <linux/spi/spi.h>
@@ -162,53 +162,73 @@ static int p54spi_spi_write_dma(struct p54s_priv *priv, __le32 base,
 	return 0;
 }
 
+static int p54spi_request_firmware_found_cb(void *context,
+					    const struct drvdata *drvdata)
+{
+	int ret;
+	struct p54s_priv *priv = context;
+
+	priv->firmware = drvdata;
+	ret = p54_parse_firmware(priv->hw, priv->firmware);
+	if (ret)
+		release_drvdata(priv->firmware);
+
+	return ret;
+}
+
 static int p54spi_request_firmware(struct ieee80211_hw *dev)
 {
 	struct p54s_priv *priv = dev->priv;
+	const struct drvdata_req_params req_params = {
+		DRVDATA_KEEP_SYNC(p54spi_request_firmware_found_cb, priv),
+	};
 	int ret;
 
 	/* FIXME: should driver use it's own struct device? */
-	ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
-
+	ret = drvdata_request("3826.arm", &req_params, &priv->spi->dev);
 	if (ret < 0) {
-		dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
-		return ret;
+		dev_err(&priv->spi->dev,
+			"firmware request failed: %d", ret);
 	}
+	return ret;
+}
 
-	ret = p54_parse_firmware(dev, priv->firmware);
-	if (ret) {
-		release_firmware(priv->firmware);
-		return ret;
-	}
+#ifdef CONFIG_P54_SPI_DEFAULT_EEPROM
+static int p54spi_load_eeprom_default(void *context)
+{
+	struct p54s_priv *priv = context;
+	struct ieee80211_hw *dev = priv->hw;
 
-	return 0;
+	dev_info(&priv->spi->dev, "loading default eeprom...\n");
+	return p54_parse_eeprom(dev, (void *) p54spi_eeprom,
+				sizeof(p54spi_eeprom));
 }
+#endif
+
+static int p54spi_load_eeprom_cb(void *context,
+				 const struct drvdata *drvdata)
+{
+	struct p54s_priv *priv = context;
+	struct ieee80211_hw *dev = priv->hw;
 
+	dev_info(&priv->spi->dev, "loading user eeprom...\n");
+	return p54_parse_eeprom(dev, (void *) drvdata->data,
+				(int)drvdata->size);
+}
 static int p54spi_request_eeprom(struct ieee80211_hw *dev)
 {
 	struct p54s_priv *priv = dev->priv;
-	const struct firmware *eeprom;
-	int ret;
+	const struct drvdata_req_params req_params = {
+		DRVDATA_DEFAULT_SYNC(p54spi_load_eeprom_cb, priv),
+#ifdef CONFIG_P54_SPI_DEFAULT_EEPROM
+		DRVDATA_SYNC_OPT_CB(p54spi_load_eeprom_default, priv),
+#endif
+	};
 
 	/* allow users to customize their eeprom.
 	 */
 
-	ret = request_firmware_direct(&eeprom, "3826.eeprom", &priv->spi->dev);
-	if (ret < 0) {
-#ifdef CONFIG_P54_SPI_DEFAULT_EEPROM
-		dev_info(&priv->spi->dev, "loading default eeprom...\n");
-		ret = p54_parse_eeprom(dev, (void *) p54spi_eeprom,
-				       sizeof(p54spi_eeprom));
-#else
-		dev_err(&priv->spi->dev, "Failed to request user eeprom\n");
-#endif /* CONFIG_P54_SPI_DEFAULT_EEPROM */
-	} else {
-		dev_info(&priv->spi->dev, "loading user eeprom...\n");
-		ret = p54_parse_eeprom(dev, (void *) eeprom->data,
-				       (int)eeprom->size);
-		release_firmware(eeprom);
-	}
-	return ret;
+	return drvdata_request("3826.eeprom", &req_params, &priv->spi->dev);
 }
 
 static int p54spi_upload_firmware(struct ieee80211_hw *dev)
@@ -692,7 +712,7 @@ static int p54spi_remove(struct spi_device *spi)
 
 	gpio_free(p54spi_gpio_power);
 	gpio_free(p54spi_gpio_irq);
-	release_firmware(priv->firmware);
+	release_drvdata(priv->firmware);
 
 	mutex_destroy(&priv->mutex);
 
diff --git a/drivers/net/wireless/intersil/p54/p54spi.h b/drivers/net/wireless/intersil/p54/p54spi.h
index dfaa62aaeb07..c484536be900 100644
--- a/drivers/net/wireless/intersil/p54/p54spi.h
+++ b/drivers/net/wireless/intersil/p54/p54spi.h
@@ -119,7 +119,7 @@ struct p54s_priv {
 	struct list_head tx_pending;
 
 	enum fw_state fw_state;
-	const struct firmware *firmware;
+	const struct drvdata *firmware;
 };
 
 #endif /* P54SPI_H */
diff --git a/drivers/net/wireless/intersil/p54/p54usb.c b/drivers/net/wireless/intersil/p54/p54usb.c
index 043bd1c23c19..f698c4acfb49 100644
--- a/drivers/net/wireless/intersil/p54/p54usb.c
+++ b/drivers/net/wireless/intersil/p54/p54usb.c
@@ -15,7 +15,7 @@
 #include <linux/usb.h>
 #include <linux/pci.h>
 #include <linux/slab.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <linux/delay.h>
 #include <linux/crc32.h>
@@ -916,14 +916,13 @@ static int p54u_start_ops(struct p54u_priv *priv)
 	return ret;
 }
 
-static void p54u_load_firmware_cb(const struct firmware *firmware,
+static void p54u_load_firmware_cb(const struct drvdata *firmware,
 				  void *context)
 {
 	struct p54u_priv *priv = context;
 	struct usb_device *udev = priv->udev;
 	int err;
 
-	complete(&priv->fw_wait_load);
 	if (firmware) {
 		priv->fw = firmware;
 		err = p54u_start_ops(priv);
@@ -959,12 +958,14 @@ static int p54u_load_firmware(struct ieee80211_hw *dev,
 {
 	struct usb_device *udev = interface_to_usbdev(intf);
 	struct p54u_priv *priv = dev->priv;
+	const struct drvdata_req_params req_params = {
+		DRVDATA_KEEP_ASYNC(p54u_load_firmware_cb, priv),
+	};
 	struct device *device = &udev->dev;
 	int err, i;
 
 	BUILD_BUG_ON(ARRAY_SIZE(p54u_fwlist) != __NUM_P54U_HWTYPES);
 
-	init_completion(&priv->fw_wait_load);
 	i = p54_find_type(priv);
 	if (i < 0)
 		return i;
@@ -973,9 +974,8 @@ static int p54u_load_firmware(struct ieee80211_hw *dev,
 	       p54u_fwlist[i].fw);
 
 	usb_get_dev(udev);
-	err = request_firmware_nowait(THIS_MODULE, 1, p54u_fwlist[i].fw,
-				      device, GFP_KERNEL, priv,
-				      p54u_load_firmware_cb);
+	err = drvdata_request_async(p54u_fwlist[i].fw, &req_params,
+				    device, &priv->fw_async_cookie);
 	if (err) {
 		dev_err(&priv->udev->dev, "(p54usb) cannot load firmware %s "
 					  "(%d)!\n", p54u_fwlist[i].fw, err);
@@ -1069,11 +1069,11 @@ static void p54u_disconnect(struct usb_interface *intf)
 		return;
 
 	priv = dev->priv;
-	wait_for_completion(&priv->fw_wait_load);
+	drvdata_synchronize_request(priv->fw_async_cookie);
 	p54_unregister_common(dev);
 
 	usb_put_dev(interface_to_usbdev(intf));
-	release_firmware(priv->fw);
+	release_drvdata(priv->fw);
 	p54_free_common(dev);
 }
 
diff --git a/drivers/net/wireless/intersil/p54/p54usb.h b/drivers/net/wireless/intersil/p54/p54usb.h
index a5f5f0fea3bd..56b58a1c41c1 100644
--- a/drivers/net/wireless/intersil/p54/p54usb.h
+++ b/drivers/net/wireless/intersil/p54/p54usb.h
@@ -153,10 +153,10 @@ struct p54u_priv {
 	spinlock_t lock;
 	struct sk_buff_head rx_queue;
 	struct usb_anchor submitted;
-	const struct firmware *fw;
+	const struct drvdata *fw;
 
 	/* asynchronous firmware callback */
-	struct completion fw_wait_load;
+	async_cookie_t fw_async_cookie;
 };
 
 #endif /* P54USB_H */
diff --git a/drivers/net/wireless/intersil/p54/txrx.c b/drivers/net/wireless/intersil/p54/txrx.c
index 1af7da0b386e..fd52af4735e7 100644
--- a/drivers/net/wireless/intersil/p54/txrx.c
+++ b/drivers/net/wireless/intersil/p54/txrx.c
@@ -17,7 +17,7 @@
  */
 
 #include <linux/export.h>
-#include <linux/firmware.h>
+#include <linux/drvdata.h>
 #include <linux/etherdevice.h>
 #include <asm/div64.h>
 
-- 
2.11.0

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-12 15:02   ` [PATCH v4 3/3] p54: convert to sysdata API Luis R. Rodriguez
@ 2017-01-16 11:32     ` Christian Lamparter
  2017-01-19 11:38     ` Greg KH
  1 sibling, 0 replies; 33+ messages in thread
From: Christian Lamparter @ 2017-01-16 11:32 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, ming.lei, bp, wagi, teg, mchehab, zajec5, linux-kernel,
	markivx, stephen.boyd, broonie, zohar, tiwai, johannes, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo

On Thursday, January 12, 2017 7:02:44 AM CET Luis R. Rodriguez wrote:
> The Coccinelle sysdata patches were used to help with
> this transition. The changes have been carefully manually
> vetted for. With the conversion we modify the cases that do
> not need the firmware to be kept so that the sysdata API
> can release it for us. Using the new sysdata API also means
> we can get rid of our own completions.
> 
> v2: was not present
> v3: initial release
> v4: small cosmetic fixes
> v5: bike shed changes
> v6: forgot to change one piece of code during the bikeshed name change
> 
> Generated-by: Coccinelle SmPL
> Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
Acked-by: Christian Lamparter <chunkeey@googlemail.com>

I've also tested it with the ISL3887USB and a ISL3880PCI.
I hope the p54spi will work as well. But I don't have the
HW to test it.

Regards,
Christian

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

* Re: [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata
  2017-01-12 15:02   ` [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
@ 2017-01-19 11:36     ` Greg KH
  2017-01-19 16:54       ` Luis R. Rodriguez
  2017-01-19 18:58     ` Bjorn Andersson
  1 sibling, 1 reply; 33+ messages in thread
From: Greg KH @ 2017-01-19 11:36 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: ming.lei, bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo

On Thu, Jan 12, 2017 at 07:02:42AM -0800, Luis R. Rodriguez wrote:
> The firmware API has evolved over the years slowly, as it
> grows we extend it by adding new routines or at times we extend
> 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 firmware API is also
> now being used for things outside of the scope of what typically
> would be considered "firmware", an example here is the p54 driver
> enables users to provide a custom EEPROM through this interface.
> Another example is optional CPU microcode updates. This list is
> actually quite endless...
> 
> There are other subsystems which would like to make use of the
> 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. If different requirements are needed it would
> again mean adding more arguments and making a slew of collateral
> evolutions, or adding yet-another-new-API-call (TM).
> 
> Another sticking point over the current firmware API is that
> some callers may need the firmware fallback mechanism when its
> enabled. There are two types of fallback mechanisms and both have
> shortcomings. This new API accepts the current status quo and
> ignore the fallback mechanism all together. When and if we add
> support for it, it will be well though out.
> 
> This new extensible firmware API enables new extensions to be added by
> avoiding future unnecessary collateral evolutions as this code /
> features get added. This new set of APIs leaves the old firmware API
> as-is, ignores all firmware fallback mechanism, labels the new
> API to reflect its broad use outside of the scope of firmware: driver
> data helpers, and builds on top of the original firmware core code.
> We purposely try to limit the scope of changes in this new API to
> simply enable a flexible API to start off with.
> 
> The new extensible "driver data" set of helpers accepts that there
> really are only two types of requests for accessing driver data:
> 
> a) synchronous requests
> b) asynchronous requests
> 
> Both of these requests may have a different set of requirements which
> must be met. These requirements can simply be passed as a struct
> drvdata_req_params to each type of request. This struct can be extended
> over time to support different requirements as the kernel evolves.
> 
> Using the new driver data helpers is only necessary if you have
> requirements outside of what the existing old firmware API accepts
> or alternatively if you want to ensure to avoid the old firmware
> fallback mechanism at all times, regardless of what kernel your driver
> might run in.
> 
> Developers with new uses should extend the new new struct drvdata_req_params
> and driver data code to provide support for new features.
> 
> A *few* simple features added as part of the new set of driver data
> request APIs, other than making the new API easily extensible for
> the future:
> 
>  - The firmware fallback mechanism is currenlty always ignored
>  - 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 descriptor. The new
>    drvdata API is able to free the drvdata file for you by requiring a
>    consumer callback for the driver data file.
>  - You no longer need to declare and use your own completions, you
>    can replace your completions with drvdata_synchronize_request() using
>    the async_cookie set for you by drvdata_file_request_async(). When
>    drvdata_file_request_async() completes you can rest assured all the
>    work for both triggering, and processing the drvdata using any of
>    your callbacks has completed.
>  - Allow 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 -- although it should be
>    noted another one of its goal was to also skip the fallback mechanisms.
>    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.
>  - The driver data request APIs currently match the old synchronous firmware
>    API calls to refcounted firmware_class module, but it should be easy
>    to add support now to enable also refcounting the caller's module
>    should it be be needed. Likewise the driver data request APIs match the
>    old asynchronous firmware API call and refcounts the caller's module.

I think this changelog novel is longer than the documentation you added
to the kernel :(

> --- /dev/null
> +++ b/Documentation/driver-api/firmware/drvdata.rst
> @@ -0,0 +1,91 @@
> +===========
> +drvdata API

Here kid, have a few vowels, we have plenty...

Please spell this out "driver_data", there's no need to shorten it for
no reason at all except to confuse people / non-native speakers for a
while before they figure it out.

> +===========
> +
> +As the kernel evolves we keep extending the firmware_class set of APIs
> +with more or less arguments, this creates a slew of collateral evolutions.

Why is this sentance here?

> +The set of users of firmware request APIs has also grown now to include
> +users which are not looking for "firmware" per se, but instead general
> +driver data files which for one reason or another has been decided to be
> +kept oustide of the kernel, and/or to allow dynamic updates. The driver data
> +request set of APIs addresses rebranding of firmware as generic driver data
> +files, and provides a way to enable these APIs to easily be extended without
> +much collateral evolutions.
> +
> +Driver data modes of operation
> +==============================
> +
> +There are only two types of modes of operation for system data requests:
> +
> +  * synchronous  - drvdata_request()
> +  * asynchronous - drvdata_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 can grow as with new
> +fields as requirements grow. The old firmware API provides two synchronous
> +requests: request_firmware() and request_firmware_direct(), the later allowing
> +the caller to specify that the "driver data file" is optional.  The driver data
> +request API allows a caller to set the optional nature of the driver data
> +on the request parameter data structure using the same synchronous API. Since
> +this requirement is part of the request paramter data structure it also allows
> +asynchronous requests to specify that the driver data is optional.
> +
> +Reference counting and releasing the system 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 mimic this behaviour, for synchronous
> +requests the firmware_class module is refcounted through the use of
> +dfl_sync_reqs, although if in the future we may later enable use of
> +also refcounting the caller's module as well. Likewise in the future we
> +may extend 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 paramter data structure.
> +
> +Async cookies, replacing completions
> +====================================
> +
> +With this new API you do not need to declare and use your own completions, you

It's not going to be "new" in a year, are you going to go and change the
documentation here?

And if you want to provide a "how to convert from firmware to
driver_data" document, great, but to constantly compare the two seems a
bit like you are trying too hard.  It should stand on it's own without
needing to do that.

> +can replace your completions with drvdata_synchronize_request() using the
> +async_cookie set for you by drvdata_file_request_async(). When
> +drvdata_file_request_async() completes you can rest assured all the work for
> +both triggering, and processing the drvdata using any of your callbacks has
> +completed.
> +
> +Fallback mechanisms on the driver data API
> +==========================================
> +
> +The old firmware API provided support for a series of fallback mechanisms. The
> +new driver data API abandons all current notions of the fallback mechanisms,
> +it may soon add support for one though.

Oh come on, is this paragraph really needed at all?  "soon"?  Hah.


> +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..8d275c4c165b 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
> +   drvdata
>  
>  .. only::  subproject and html
>  
> diff --git a/Documentation/driver-api/firmware/introduction.rst b/Documentation/driver-api/firmware/introduction.rst
> index 211cb44eb972..d7d5ef846ca0 100644
> --- a/Documentation/driver-api/firmware/introduction.rst
> +++ b/Documentation/driver-api/firmware/introduction.rst
> @@ -25,3 +25,14 @@ 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
> +* drvdata API - new flexible API

"new" isn't "new" in a few months.

thanks,

greg k-h

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-12 15:02   ` [PATCH v4 3/3] p54: convert to sysdata API Luis R. Rodriguez
  2017-01-16 11:32     ` Christian Lamparter
@ 2017-01-19 11:38     ` Greg KH
  2017-01-19 16:27       ` Luis R. Rodriguez
  1 sibling, 1 reply; 33+ messages in thread
From: Greg KH @ 2017-01-19 11:38 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: ming.lei, bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo

On Thu, Jan 12, 2017 at 07:02:44AM -0800, Luis R. Rodriguez wrote:
> The Coccinelle sysdata patches were used to help with
> this transition. The changes have been carefully manually
> vetted for. With the conversion we modify the cases that do
> not need the firmware to be kept so that the sysdata API
> can release it for us. Using the new sysdata API also means
> we can get rid of our own completions.
> 
> v2: was not present
> v3: initial release
> v4: small cosmetic fixes
> v5: bike shed changes
> v6: forgot to change one piece of code during the bikeshed name change
> 
> Generated-by: Coccinelle SmPL

What is this tag for?

Also, meta-comment, put your vN: lines below the --- line like the
kernel documentation says to do.

> Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
> ---
>  drivers/net/wireless/intersil/p54/eeprom.c |  2 +-
>  drivers/net/wireless/intersil/p54/fwio.c   |  5 +-
>  drivers/net/wireless/intersil/p54/led.c    |  2 +-
>  drivers/net/wireless/intersil/p54/main.c   |  2 +-
>  drivers/net/wireless/intersil/p54/p54.h    |  3 +-
>  drivers/net/wireless/intersil/p54/p54pci.c | 26 ++++++----
>  drivers/net/wireless/intersil/p54/p54pci.h |  4 +-
>  drivers/net/wireless/intersil/p54/p54spi.c | 80 +++++++++++++++++++-----------
>  drivers/net/wireless/intersil/p54/p54spi.h |  2 +-
>  drivers/net/wireless/intersil/p54/p54usb.c | 18 +++----
>  drivers/net/wireless/intersil/p54/p54usb.h |  4 +-
>  drivers/net/wireless/intersil/p54/txrx.c   |  2 +-
>  12 files changed, 89 insertions(+), 61 deletions(-)

why does the "new" api require more lines?


> 
> diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c
> index d4c73d39336f..b8184cbc6770 100644
> --- a/drivers/net/wireless/intersil/p54/eeprom.c
> +++ b/drivers/net/wireless/intersil/p54/eeprom.c
> @@ -16,7 +16,7 @@
>   * published by the Free Software Foundation.
>   */
>  
> -#include <linux/firmware.h>
> +#include <linux/drvdata.h>
>  #include <linux/etherdevice.h>
>  #include <linux/sort.h>
>  #include <linux/slab.h>
> diff --git a/drivers/net/wireless/intersil/p54/fwio.c b/drivers/net/wireless/intersil/p54/fwio.c
> index 4ac6764f4897..dc27049e4533 100644
> --- a/drivers/net/wireless/intersil/p54/fwio.c
> +++ b/drivers/net/wireless/intersil/p54/fwio.c
> @@ -17,7 +17,7 @@
>   */
>  
>  #include <linux/slab.h>
> -#include <linux/firmware.h>
> +#include <linux/drvdata.h>
>  #include <linux/etherdevice.h>
>  #include <linux/export.h>
>  
> @@ -27,7 +27,8 @@
>  #include "eeprom.h"
>  #include "lmac.h"
>  
> -int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw)
> +int p54_parse_firmware(struct ieee80211_hw *dev,
> +		       const struct drvdata *fw)
>  {
>  	struct p54_common *priv = dev->priv;
>  	struct exp_if *exp_if;
> diff --git a/drivers/net/wireless/intersil/p54/led.c b/drivers/net/wireless/intersil/p54/led.c
> index 9a8fedd3c0f5..4d13598d3968 100644
> --- a/drivers/net/wireless/intersil/p54/led.c
> +++ b/drivers/net/wireless/intersil/p54/led.c
> @@ -16,7 +16,7 @@
>   * published by the Free Software Foundation.
>   */
>  
> -#include <linux/firmware.h>
> +#include <linux/drvdata.h>
>  #include <linux/etherdevice.h>
>  
>  #include <net/mac80211.h>
> diff --git a/drivers/net/wireless/intersil/p54/main.c b/drivers/net/wireless/intersil/p54/main.c
> index d5a3bf91a03e..a1c546cd232c 100644
> --- a/drivers/net/wireless/intersil/p54/main.c
> +++ b/drivers/net/wireless/intersil/p54/main.c
> @@ -17,7 +17,7 @@
>   */
>  
>  #include <linux/slab.h>
> -#include <linux/firmware.h>
> +#include <linux/drvdata.h>
>  #include <linux/etherdevice.h>
>  #include <linux/module.h>
>  
> diff --git a/drivers/net/wireless/intersil/p54/p54.h b/drivers/net/wireless/intersil/p54/p54.h
> index 529939e611cd..5bbe9d77e5fc 100644
> --- a/drivers/net/wireless/intersil/p54/p54.h
> +++ b/drivers/net/wireless/intersil/p54/p54.h
> @@ -268,7 +268,8 @@ struct p54_common {
>  /* interfaces for the drivers */
>  int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb);
>  void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb);
> -int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw);
> +int p54_parse_firmware(struct ieee80211_hw *dev,
> +		       const struct drvdata *fw);
>  int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len);
>  int p54_read_eeprom(struct ieee80211_hw *dev);
>  
> diff --git a/drivers/net/wireless/intersil/p54/p54pci.c b/drivers/net/wireless/intersil/p54/p54pci.c
> index 27a49068d32d..0e7fd9ba7186 100644
> --- a/drivers/net/wireless/intersil/p54/p54pci.c
> +++ b/drivers/net/wireless/intersil/p54/p54pci.c
> @@ -15,7 +15,7 @@
>  
>  #include <linux/pci.h>
>  #include <linux/slab.h>
> -#include <linux/firmware.h>
> +#include <linux/drvdata.h>
>  #include <linux/etherdevice.h>
>  #include <linux/delay.h>
>  #include <linux/completion.h>
> @@ -490,7 +490,7 @@ static int p54p_open(struct ieee80211_hw *dev)
>  	return 0;
>  }
>  
> -static void p54p_firmware_step2(const struct firmware *fw,
> +static void p54p_firmware_step2(const struct drvdata *fw,
>  				void *context)
>  {
>  	struct p54p_priv *priv = context;
> @@ -520,8 +520,6 @@ static void p54p_firmware_step2(const struct firmware *fw,
>  
>  out:
>  
> -	complete(&priv->fw_loaded);
> -
>  	if (err) {
>  		struct device *parent = pdev->dev.parent;
>  
> @@ -542,6 +540,17 @@ static void p54p_firmware_step2(const struct firmware *fw,
>  	pci_dev_put(pdev);
>  }
>  
> +static int p54p_load_firmware(struct p54p_priv *priv)
> +{
> +	const struct drvdata_req_params req_params = {
> +		DRVDATA_KEEP_ASYNC(p54p_firmware_step2, priv),
> +	};
> +
> +	return drvdata_request_async("isl3886pci", &req_params,
> +				     &priv->pdev->dev,
> +				     &priv->fw_async_cookie);
> +}
> +
>  static int p54p_probe(struct pci_dev *pdev,
>  				const struct pci_device_id *id)
>  {
> @@ -595,7 +604,6 @@ static int p54p_probe(struct pci_dev *pdev,
>  	priv = dev->priv;
>  	priv->pdev = pdev;
>  
> -	init_completion(&priv->fw_loaded);
>  	SET_IEEE80211_DEV(dev, &pdev->dev);
>  	pci_set_drvdata(pdev, dev);
>  
> @@ -620,9 +628,7 @@ static int p54p_probe(struct pci_dev *pdev,
>  	spin_lock_init(&priv->lock);
>  	tasklet_init(&priv->tasklet, p54p_tasklet, (unsigned long)dev);
>  
> -	err = request_firmware_nowait(THIS_MODULE, 1, "isl3886pci",
> -				      &priv->pdev->dev, GFP_KERNEL,
> -				      priv, p54p_firmware_step2);
> +	err = p54p_load_firmware(priv);
>  	if (!err)
>  		return 0;
>  
> @@ -652,9 +658,9 @@ static void p54p_remove(struct pci_dev *pdev)
>  		return;
>  
>  	priv = dev->priv;
> -	wait_for_completion(&priv->fw_loaded);
> +	drvdata_synchronize_request(priv->fw_async_cookie);
>  	p54_unregister_common(dev);
> -	release_firmware(priv->firmware);
> +	release_drvdata(priv->firmware);
>  	pci_free_consistent(pdev, sizeof(*priv->ring_control),
>  			    priv->ring_control, priv->ring_control_dma);
>  	iounmap(priv->map);
> diff --git a/drivers/net/wireless/intersil/p54/p54pci.h b/drivers/net/wireless/intersil/p54/p54pci.h
> index 68405c142f97..00c30e1fc60b 100644
> --- a/drivers/net/wireless/intersil/p54/p54pci.h
> +++ b/drivers/net/wireless/intersil/p54/p54pci.h
> @@ -94,7 +94,7 @@ struct p54p_priv {
>  	struct pci_dev *pdev;
>  	struct p54p_csr __iomem *map;
>  	struct tasklet_struct tasklet;
> -	const struct firmware *firmware;
> +	const struct drvdata *firmware;
>  	spinlock_t lock;
>  	struct p54p_ring_control *ring_control;
>  	dma_addr_t ring_control_dma;
> @@ -105,7 +105,7 @@ struct p54p_priv {
>  	struct sk_buff *tx_buf_data[32];
>  	struct sk_buff *tx_buf_mgmt[4];
>  	struct completion boot_comp;
> -	struct completion fw_loaded;
> +	async_cookie_t fw_async_cookie;
>  };
>  
>  #endif /* P54USB_H */
> diff --git a/drivers/net/wireless/intersil/p54/p54spi.c b/drivers/net/wireless/intersil/p54/p54spi.c
> index 7ab2f43ab425..c0118048c01f 100644
> --- a/drivers/net/wireless/intersil/p54/p54spi.c
> +++ b/drivers/net/wireless/intersil/p54/p54spi.c
> @@ -23,7 +23,7 @@
>  #include <linux/module.h>
>  #include <linux/platform_device.h>
>  #include <linux/interrupt.h>
> -#include <linux/firmware.h>
> +#include <linux/drvdata.h>
>  #include <linux/delay.h>
>  #include <linux/irq.h>
>  #include <linux/spi/spi.h>
> @@ -162,53 +162,73 @@ static int p54spi_spi_write_dma(struct p54s_priv *priv, __le32 base,
>  	return 0;
>  }
>  
> +static int p54spi_request_firmware_found_cb(void *context,
> +					    const struct drvdata *drvdata)
> +{
> +	int ret;
> +	struct p54s_priv *priv = context;
> +
> +	priv->firmware = drvdata;
> +	ret = p54_parse_firmware(priv->hw, priv->firmware);
> +	if (ret)
> +		release_drvdata(priv->firmware);
> +
> +	return ret;
> +}
> +
>  static int p54spi_request_firmware(struct ieee80211_hw *dev)
>  {
>  	struct p54s_priv *priv = dev->priv;
> +	const struct drvdata_req_params req_params = {
> +		DRVDATA_KEEP_SYNC(p54spi_request_firmware_found_cb, priv),
> +	};
>  	int ret;
>  
>  	/* FIXME: should driver use it's own struct device? */
> -	ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
> -
> +	ret = drvdata_request("3826.arm", &req_params, &priv->spi->dev);
>  	if (ret < 0) {
> -		dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
> -		return ret;
> +		dev_err(&priv->spi->dev,
> +			"firmware request failed: %d", ret);

shouldn't the call report this error to the kernel log?  Why must each
user print it out themselves again?

thanks,

greg k-h

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-19 11:38     ` Greg KH
@ 2017-01-19 16:27       ` Luis R. Rodriguez
  2017-01-26 21:50         ` Luis R. Rodriguez
  0 siblings, 1 reply; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-19 16:27 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, ming.lei, bp, wagi, teg, mchehab, zajec5,
	linux-kernel, markivx, stephen.boyd, broonie, zohar, tiwai,
	johannes, chunkeey, hauke, jwboyer, dmitry.torokhov, dwmw2,
	jslaby, torvalds, luto, fengguang.wu, rpurdie, j.anaszewski,
	Abhay_Salunke, Julia.Lawall, Gilles.Muller, nicolas.palix,
	dhowells, bjorn.andersson, arend.vanspriel, kvalo

On Thu, Jan 19, 2017 at 12:38:57PM +0100, Greg KH wrote:
> On Thu, Jan 12, 2017 at 07:02:44AM -0800, Luis R. Rodriguez wrote:
> > The Coccinelle sysdata patches were used to help with
> > this transition. The changes have been carefully manually
> > vetted for. With the conversion we modify the cases that do
> > not need the firmware to be kept so that the sysdata API
> > can release it for us. Using the new sysdata API also means
> > we can get rid of our own completions.
> > 
> > v2: was not present
> > v3: initial release
> > v4: small cosmetic fixes
> > v5: bike shed changes
> > v6: forgot to change one piece of code during the bikeshed name change
> > 
> > Generated-by: Coccinelle SmPL
> 
> What is this tag for?

Every no wand then some tool scrapes for commit logs to see if
Coccinelle was used to help with a kernel commit. There are different
heuristics, this tag is to help make a more unique search more easily
identifiable as I used Coccinelle to do the original port.

> Also, meta-comment, put your vN: lines below the --- line like the
> kernel documentation says to do.

Oh, OK sounds good.

> > Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
> > ---
> >  drivers/net/wireless/intersil/p54/eeprom.c |  2 +-
> >  drivers/net/wireless/intersil/p54/fwio.c   |  5 +-
> >  drivers/net/wireless/intersil/p54/led.c    |  2 +-
> >  drivers/net/wireless/intersil/p54/main.c   |  2 +-
> >  drivers/net/wireless/intersil/p54/p54.h    |  3 +-
> >  drivers/net/wireless/intersil/p54/p54pci.c | 26 ++++++----
> >  drivers/net/wireless/intersil/p54/p54pci.h |  4 +-
> >  drivers/net/wireless/intersil/p54/p54spi.c | 80 +++++++++++++++++++-----------
> >  drivers/net/wireless/intersil/p54/p54spi.h |  2 +-
> >  drivers/net/wireless/intersil/p54/p54usb.c | 18 +++----
> >  drivers/net/wireless/intersil/p54/p54usb.h |  4 +-
> >  drivers/net/wireless/intersil/p54/txrx.c   |  2 +-
> >  12 files changed, 89 insertions(+), 61 deletions(-)
> 
> why does the "new" api require more lines?

This is a bare bones flexible API with only a few new tiny features to start
with, one of them was to enable the API do the freeing of the driver data for
you. In the kernel we have devres to help with this but devres only helps if
you would use the API call on probe. We want to support the ability to let the
API free the driver data for you even if your call is outside of probe, for this
to work we need a callback. For async calls this is rather trivial given we
already have a callback, for sync calls this means a new routine is needed.
Freeing the data for you is an option, but I decided to keep the callback
requirement even if you didn't want the free'ing to be done for you. The
addition of a callback is what accounts for the slight increase on this driver.

I could try avoiding the callback if no freeing is needed.

> > --- a/drivers/net/wireless/intersil/p54/p54spi.c
> > +++ b/drivers/net/wireless/intersil/p54/p54spi.c
> > @@ -162,53 +162,73 @@ static int p54spi_spi_write_dma(struct p54s_priv *priv, __le32 base,
> >  	return 0;
> >  }
> >  
> > +static int p54spi_request_firmware_found_cb(void *context,
> > +					    const struct drvdata *drvdata)
> > +{
> > +	int ret;
> > +	struct p54s_priv *priv = context;
> > +
> > +	priv->firmware = drvdata;
> > +	ret = p54_parse_firmware(priv->hw, priv->firmware);
> > +	if (ret)
> > +		release_drvdata(priv->firmware);
> > +
> > +	return ret;
> > +}
> > +
> >  static int p54spi_request_firmware(struct ieee80211_hw *dev)
> >  {
> >  	struct p54s_priv *priv = dev->priv;
> > +	const struct drvdata_req_params req_params = {
> > +		DRVDATA_KEEP_SYNC(p54spi_request_firmware_found_cb, priv),
> > +	};
> >  	int ret;
> >  
> >  	/* FIXME: should driver use it's own struct device? */
> > -	ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
> > -
> > +	ret = drvdata_request("3826.arm", &req_params, &priv->spi->dev);
> >  	if (ret < 0) {
> > -		dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
> > -		return ret;
> > +		dev_err(&priv->spi->dev,
> > +			"firmware request failed: %d", ret);
> 
> shouldn't the call report this error to the kernel log?  Why must each
> user print it out themselves again?

Great point. The API already has:

static int _drvdata_request(const struct drvdata **drvdata_p,
                           const char *name,
                           const struct drvdata_req_params *params,
                           struct device *device)
{
...
       if (ret && !params->optional)
               pr_err("Direct driver data load for %s failed with error %d\n",
                      name, ret);
...
}

So it is not needed for driver to moan about failures here.

  Luis

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

* Re: [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata
  2017-01-19 11:36     ` Greg KH
@ 2017-01-19 16:54       ` Luis R. Rodriguez
  0 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-19 16:54 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, ming.lei, bp, wagi, teg, mchehab, zajec5,
	linux-kernel, markivx, stephen.boyd, broonie, zohar, tiwai,
	johannes, chunkeey, hauke, jwboyer, dmitry.torokhov, dwmw2,
	jslaby, torvalds, luto, fengguang.wu, rpurdie, j.anaszewski,
	Abhay_Salunke, Julia.Lawall, Gilles.Muller, nicolas.palix,
	dhowells, bjorn.andersson, arend.vanspriel, kvalo

On Thu, Jan 19, 2017 at 12:36:52PM +0100, Greg KH wrote:
> On Thu, Jan 12, 2017 at 07:02:42AM -0800, Luis R. Rodriguez wrote:
> 
> I think this changelog novel is longer than the documentation you added
> to the kernel :(

Heh, OK will work on that.

> > --- /dev/null
> > +++ b/Documentation/driver-api/firmware/drvdata.rst
> > @@ -0,0 +1,91 @@
> > +===========
> > +drvdata API
> 
> Here kid, have a few vowels, we have plenty...
> 
> Please spell this out "driver_data", there's no need to shorten it for
> no reason at all except to confuse people / non-native speakers for a
> while before they figure it out.

Sure thing'o.

> > +===========
> > +
> > +As the kernel evolves we keep extending the firmware_class set of APIs
> > +with more or less arguments, this creates a slew of collateral evolutions.
> 
> Why is this sentance here?

Hm yeah it was not clear I was trying to explain *why* we ended up implementing
a flexible API. Will work on that.

> > +The set of users of firmware request APIs has also grown now to include
> > +users which are not looking for "firmware" per se, but instead general
> > +driver data files which for one reason or another has been decided to be
> > +kept oustide of the kernel, and/or to allow dynamic updates. The driver data
> > +request set of APIs addresses rebranding of firmware as generic driver data
> > +files, and provides a way to enable these APIs to easily be extended without
> > +much collateral evolutions.
> > +
> > +Driver data modes of operation
> > +==============================
> > +
> > +There are only two types of modes of operation for system data requests:
> > +
> > +  * synchronous  - drvdata_request()
> > +  * asynchronous - drvdata_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 can grow as with new
> > +fields as requirements grow. The old firmware API provides two synchronous
> > +requests: request_firmware() and request_firmware_direct(), the later allowing
> > +the caller to specify that the "driver data file" is optional.  The driver data
> > +request API allows a caller to set the optional nature of the driver data
> > +on the request parameter data structure using the same synchronous API. Since
> > +this requirement is part of the request paramter data structure it also allows
> > +asynchronous requests to specify that the driver data is optional.
> > +
> > +Reference counting and releasing the system 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 mimic this behaviour, for synchronous
> > +requests the firmware_class module is refcounted through the use of
> > +dfl_sync_reqs, although if in the future we may later enable use of
> > +also refcounting the caller's module as well. Likewise in the future we
> > +may extend 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 paramter data structure.
> > +
> > +Async cookies, replacing completions
> > +====================================
> > +
> > +With this new API you do not need to declare and use your own completions, you
> 
> It's not going to be "new" in a year, are you going to go and change the
> documentation here?

Will fix.

> And if you want to provide a "how to convert from firmware to
> driver_data" document, great, but to constantly compare the two seems a
> bit like you are trying too hard.  It should stand on it's own without
> needing to do that.

Sure.

> > +can replace your completions with drvdata_synchronize_request() using the
> > +async_cookie set for you by drvdata_file_request_async(). When
> > +drvdata_file_request_async() completes you can rest assured all the work for
> > +both triggering, and processing the drvdata using any of your callbacks has
> > +completed.
> > +
> > +Fallback mechanisms on the driver data API
> > +==========================================
> > +
> > +The old firmware API provided support for a series of fallback mechanisms. The
> > +new driver data API abandons all current notions of the fallback mechanisms,
> > +it may soon add support for one though.
> 
> Oh come on, is this paragraph really needed at all?  "soon"?  Hah.

As recent fixes for the fallback mechanism show the fallback mechanism
is pretty hairy. I want to clean up all that mess, add respective test
units for it. There are also some more longer term things to consider
which I'd like to address as well, for instance the firmware code is the
only code using the general UMH lock, although it was originally added
to help warn for firmware APi uses on suspend/resume the firmware cache
mechanism now helps resolve the issues -- the only case which cannot take
advantage of this is the custom fallback mechanism. One also needs to
consider if the UMH lock or a replacement should be considered for other
kernel UMH. A related effort here which can also help is a kernel
functionality to help address races with block devices freeze_super() can be
used to queue superblock filesystem operations. Getting this resolved
(as discussed at kernel summit by Jiri) could help resolve some of the corner
case suspend/resume race concerns for to help replace the lock for the
old API (custom fallback mechanism).

There are *real* more practical races on init which we will be able to soon
fix with the new firmwared effort by Daniel Wagner and Tom Gunderson. Once
most of this is lined up I'd feel much more comfortable with a fallback
mechanism.

I really am trying hard to make that fallback mechanism *work* fine, right now
its just hairballs. More patches to follow up to help with that..

> > +Two APIs
> > +========
> > +
> > +Two APIs are provided for firmware:
> > +
> > +* request_firmware API - old firmware API
> > +* drvdata API - new flexible API
> 
> "new" isn't "new" in a few months.

Will fix.

  Luis

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

* Re: [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata
  2017-01-12 15:02   ` [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
  2017-01-19 11:36     ` Greg KH
@ 2017-01-19 18:58     ` Bjorn Andersson
  2017-02-03 21:56       ` Luis R. Rodriguez
  1 sibling, 1 reply; 33+ messages in thread
From: Bjorn Andersson @ 2017-01-19 18:58 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: gregkh, ming.lei, bp, wagi, teg, mchehab, zajec5, linux-kernel,
	markivx, stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey,
	hauke, jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, arend.vanspriel, kvalo

On Thu 12 Jan 07:02 PST 2017, Luis R. Rodriguez wrote:
[..]
> +Fallback mechanisms on the driver data API
> +==========================================
> +
> +The old firmware API provided support for a series of fallback mechanisms. The
> +new driver data API abandons all current notions of the fallback mechanisms,
> +it may soon add support for one though.
> +

What will this fallback mechanism look like? Will it be fully compatible
with the current userspace interfaces or will we forever have two
duplicate systems for loading "firmware" in the kernel?

Will the "sysdata" (or is it named "driver_data"?!) replace the
firmware_class at any point in the future?

Regards,
Bjorn

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-19 16:27       ` Luis R. Rodriguez
@ 2017-01-26 21:50         ` Luis R. Rodriguez
  2017-01-26 21:54           ` Linus Torvalds
  2017-01-27  7:47           ` Greg KH
  0 siblings, 2 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-26 21:50 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Greg KH, ming.lei, bp, wagi, teg, mchehab, zajec5, linux-kernel,
	markivx, stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey,
	hauke, jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo

On Thu, Jan 19, 2017 at 05:27:51PM +0100, Luis R. Rodriguez wrote:
> On Thu, Jan 19, 2017 at 12:38:57PM +0100, Greg KH wrote:
> > On Thu, Jan 12, 2017 at 07:02:44AM -0800, Luis R. Rodriguez wrote:
> > > ---
> > >  drivers/net/wireless/intersil/p54/eeprom.c |  2 +-
> > >  drivers/net/wireless/intersil/p54/fwio.c   |  5 +-
> > >  drivers/net/wireless/intersil/p54/led.c    |  2 +-
> > >  drivers/net/wireless/intersil/p54/main.c   |  2 +-
> > >  drivers/net/wireless/intersil/p54/p54.h    |  3 +-
> > >  drivers/net/wireless/intersil/p54/p54pci.c | 26 ++++++----
> > >  drivers/net/wireless/intersil/p54/p54pci.h |  4 +-
> > >  drivers/net/wireless/intersil/p54/p54spi.c | 80 +++++++++++++++++++-----------
> > >  drivers/net/wireless/intersil/p54/p54spi.h |  2 +-
> > >  drivers/net/wireless/intersil/p54/p54usb.c | 18 +++----
> > >  drivers/net/wireless/intersil/p54/p54usb.h |  4 +-
> > >  drivers/net/wireless/intersil/p54/txrx.c   |  2 +-
> > >  12 files changed, 89 insertions(+), 61 deletions(-)
> > 
> > why does the "new" api require more lines?
> 
> This is a bare bones flexible API with only a few new tiny features to start
> with, one of them was to enable the API do the freeing of the driver data for
> you. In the kernel we have devres to help with this but devres only helps if
> you would use the API call on probe. We want to support the ability to let the
> API free the driver data for you even if your call is outside of probe, for this
> to work we need a callback. For async calls this is rather trivial given we
> already have a callback, for sync calls this means a new routine is needed.
> Freeing the data for you is an option, but I decided to keep the callback
> requirement even if you didn't want the free'ing to be done for you. The
> addition of a callback is what accounts for the slight increase on this driver.
> 
> I could try avoiding the callback if no freeing is needed.

OK I've added a respective helper call which would map 1-1 with the
old sync mechanism to enable a 1-1 change, this will be called
driver_data_request_simple(), but let me know if there is a preference
for something else.

With this the only visible delta now is from taking advantage of new
features. In p54's case this would re-organize the mess in
drivers/net/wireless/intersil/p54/p54spi.c, the diff stat is a bit
larger for that file just because of this but I think in this case
its very much worth the small additions. In this case two routines are
added for handling the work through callbacks on a sync call.

 1 file changed, 38 insertions(+), 30 deletions(-)

diff --git a/drivers/net/wireless/intersil/p54/p54spi.c b/drivers/net/wireless/intersil/p54/p54spi.c
index 7ab2f43ab425..6183a8bfa149 100644
--- a/drivers/net/wireless/intersil/p54/p54spi.c
+++ b/drivers/net/wireless/intersil/p54/p54spi.c
@@ -23,7 +23,7 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/interrupt.h>
-#include <linux/firmware.h>
+#include <linux/driver_data.h>
 #include <linux/delay.h>
 #include <linux/irq.h>
 #include <linux/spi/spi.h>
@@ -168,47 +168,55 @@ static int p54spi_request_firmware(struct ieee80211_hw *dev)
 	int ret;
 
 	/* FIXME: should driver use it's own struct device? */
-	ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
-
-	if (ret < 0) {
-		dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
+	ret = driver_data_request_simple("3826.arm", &priv->spi->dev,
+					 &priv->firmware);
+	if (ret < 0)
 		return ret;
-	}
 
 	ret = p54_parse_firmware(dev, priv->firmware);
 	if (ret) {
-		release_firmware(priv->firmware);
+		release_driver_data(priv->firmware);
 		return ret;
 	}
-
 	return 0;
 }
 
-static int p54spi_request_eeprom(struct ieee80211_hw *dev)
+#ifdef CONFIG_P54_SPI_DEFAULT_EEPROM
+static int p54spi_load_eeprom_default(void *context)
 {
-	struct p54s_priv *priv = dev->priv;
-	const struct firmware *eeprom;
-	int ret;
+	struct p54s_priv *priv = context;
+	struct ieee80211_hw *dev = priv->hw;
 
-	/* allow users to customize their eeprom.
-	 */
+	dev_info(&priv->spi->dev, "loading default eeprom...\n");
+	return p54_parse_eeprom(dev, (void *) p54spi_eeprom,
+				sizeof(p54spi_eeprom));
+}
+#endif
 
-	ret = request_firmware_direct(&eeprom, "3826.eeprom", &priv->spi->dev);
-	if (ret < 0) {
+static int p54spi_load_eeprom_cb(void *context,
+				 const struct driver_data *driver_data)
+{
+	struct p54s_priv *priv = context;
+	struct ieee80211_hw *dev = priv->hw;
+
+	dev_info(&priv->spi->dev, "loading user eeprom...\n");
+	return p54_parse_eeprom(dev, (void *) driver_data->data,
+				(int)driver_data->size);
+}
+static int p54spi_request_eeprom(struct ieee80211_hw *dev)
+{
+	struct p54s_priv *priv = dev->priv;
+	const struct driver_data_req_params req_params = {
+		DRIVER_DATA_DEFAULT_SYNC(p54spi_load_eeprom_cb, priv),
 #ifdef CONFIG_P54_SPI_DEFAULT_EEPROM
-		dev_info(&priv->spi->dev, "loading default eeprom...\n");
-		ret = p54_parse_eeprom(dev, (void *) p54spi_eeprom,
-				       sizeof(p54spi_eeprom));
-#else
-		dev_err(&priv->spi->dev, "Failed to request user eeprom\n");
-#endif /* CONFIG_P54_SPI_DEFAULT_EEPROM */
-	} else {
-		dev_info(&priv->spi->dev, "loading user eeprom...\n");
-		ret = p54_parse_eeprom(dev, (void *) eeprom->data,
-				       (int)eeprom->size);
-		release_firmware(eeprom);
-	}
-	return ret;
+		DRIVER_DATA_SYNC_OPT_CB(p54spi_load_eeprom_default, priv),
+#endif
+	};
+	/*
+	 * allow users to customize their eeprom.
+	 */
+	return driver_data_request("3826.eeprom", &req_params,
+				   &priv->spi->dev);
 }
 
 static int p54spi_upload_firmware(struct ieee80211_hw *dev)
@@ -692,7 +700,7 @@ static int p54spi_remove(struct spi_device *spi)
 
 	gpio_free(p54spi_gpio_power);
 	gpio_free(p54spi_gpio_irq);
-	release_firmware(priv->firmware);
+	release_driver_data(priv->firmware);
 
 	mutex_destroy(&priv->mutex);
 

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-26 21:50         ` Luis R. Rodriguez
@ 2017-01-26 21:54           ` Linus Torvalds
  2017-01-27 18:23             ` Luis R. Rodriguez
  2017-01-27  7:47           ` Greg KH
  1 sibling, 1 reply; 33+ messages in thread
From: Linus Torvalds @ 2017-01-26 21:54 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Greg KH, Ming Lei, Borislav Petkov, Daniel Wagner, Tom Gundersen,
	Mauro Carvalho Chehab, Rafał Miłecki,
	Linux Kernel Mailing List, Vikram Mulukutla, Stephen Boyd,
	Mark Brown, Mimi Zohar, Takashi Iwai, Johannes Berg,
	Christian Lamparter, Hauke Mehrtens, Josh Boyer, Dmitry Torokhov,
	David Woodhouse, Jiri Slaby, Andy Lutomirski, Wu Fengguang,
	Richard Purdie, Jacek Anaszewski, Abhay_Salunke, Julia Lawall,
	Gilles.Muller, nicolas.palix, David Howells, Bjorn Andersson,
	arend.vanspriel, Kalle Valo

On Thu, Jan 26, 2017 at 1:50 PM, Luis R. Rodriguez <mcgrof@kernel.org> wrote:
>
> OK I've added a respective helper call which would map 1-1 with the
> old sync mechanism to enable a 1-1 change, this will be called
> driver_data_request_simple(), but let me know if there is a preference
> for something else.

So just looking at this patch, what's the *advantage* to the driver writer?

Apart from the actual new feature, this patch seems to actively make
the driver uglier.

I mentioned this before, but replacing "request_firmware()" with
"driver_data_request_simple()" is SIMPLY NOT AN IMPROVEMENT.

The new name is longer and _less_ descriptive.

So I'm really not seeing why you want to make these conversions that
just make code worse.

                Linus

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-26 21:50         ` Luis R. Rodriguez
  2017-01-26 21:54           ` Linus Torvalds
@ 2017-01-27  7:47           ` Greg KH
  2017-01-27 11:25             ` Rafał Miłecki
  1 sibling, 1 reply; 33+ messages in thread
From: Greg KH @ 2017-01-27  7:47 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: ming.lei, bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo

On Thu, Jan 26, 2017 at 10:50:05PM +0100, Luis R. Rodriguez wrote:
> On Thu, Jan 19, 2017 at 05:27:51PM +0100, Luis R. Rodriguez wrote:
> > On Thu, Jan 19, 2017 at 12:38:57PM +0100, Greg KH wrote:
> > > On Thu, Jan 12, 2017 at 07:02:44AM -0800, Luis R. Rodriguez wrote:
> > > > ---
> > > >  drivers/net/wireless/intersil/p54/eeprom.c |  2 +-
> > > >  drivers/net/wireless/intersil/p54/fwio.c   |  5 +-
> > > >  drivers/net/wireless/intersil/p54/led.c    |  2 +-
> > > >  drivers/net/wireless/intersil/p54/main.c   |  2 +-
> > > >  drivers/net/wireless/intersil/p54/p54.h    |  3 +-
> > > >  drivers/net/wireless/intersil/p54/p54pci.c | 26 ++++++----
> > > >  drivers/net/wireless/intersil/p54/p54pci.h |  4 +-
> > > >  drivers/net/wireless/intersil/p54/p54spi.c | 80 +++++++++++++++++++-----------
> > > >  drivers/net/wireless/intersil/p54/p54spi.h |  2 +-
> > > >  drivers/net/wireless/intersil/p54/p54usb.c | 18 +++----
> > > >  drivers/net/wireless/intersil/p54/p54usb.h |  4 +-
> > > >  drivers/net/wireless/intersil/p54/txrx.c   |  2 +-
> > > >  12 files changed, 89 insertions(+), 61 deletions(-)
> > > 
> > > why does the "new" api require more lines?
> > 
> > This is a bare bones flexible API with only a few new tiny features to start
> > with, one of them was to enable the API do the freeing of the driver data for
> > you. In the kernel we have devres to help with this but devres only helps if
> > you would use the API call on probe. We want to support the ability to let the
> > API free the driver data for you even if your call is outside of probe, for this
> > to work we need a callback. For async calls this is rather trivial given we
> > already have a callback, for sync calls this means a new routine is needed.
> > Freeing the data for you is an option, but I decided to keep the callback
> > requirement even if you didn't want the free'ing to be done for you. The
> > addition of a callback is what accounts for the slight increase on this driver.
> > 
> > I could try avoiding the callback if no freeing is needed.
> 
> OK I've added a respective helper call which would map 1-1 with the
> old sync mechanism to enable a 1-1 change, this will be called
> driver_data_request_simple(), but let me know if there is a preference
> for something else.
> 
> With this the only visible delta now is from taking advantage of new
> features. In p54's case this would re-organize the mess in
> drivers/net/wireless/intersil/p54/p54spi.c, the diff stat is a bit
> larger for that file just because of this but I think in this case
> its very much worth the small additions. In this case two routines are
> added for handling the work through callbacks on a sync call.
> 
>  1 file changed, 38 insertions(+), 30 deletions(-)

I agree with Linus, as well as, look, it's still bigger, so you are
making driver developers do more work :(

>  	/* FIXME: should driver use it's own struct device? */
> -	ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
> -
> -	if (ret < 0) {
> -		dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
> +	ret = driver_data_request_simple("3826.arm", &priv->spi->dev,
> +					 &priv->firmware);
> +	if (ret < 0)
>  		return ret;
> -	}

Hm, a FIXME that you aren't fixing :(

I still fail to see why this new api is worth it at all, sorry.

greg k-h

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-27  7:47           ` Greg KH
@ 2017-01-27 11:25             ` Rafał Miłecki
  2017-01-27 14:07               ` Greg KH
  0 siblings, 1 reply; 33+ messages in thread
From: Rafał Miłecki @ 2017-01-27 11:25 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, Ming Lei, Borislav Petkov, wagi,
	Tom Gundersen, Mauro Carvalho Chehab, Linux Kernel Mailing List,
	Vikram Mulukutla, Stephen Boyd, Mark Brown, zohar, Takashi Iwai,
	Johannes Berg, Christian Lamparter, Hauke Mehrtens, Josh Boyer,
	Dmitry Torokhov, David Woodhouse, jslaby, Linus Torvalds, luto,
	Fengguang Wu, Richard Purdie, Jacek Anaszewski, Abhay_Salunke,
	Julia Lawall, Gilles.Muller, nicolas.palix, dhowells,
	bjorn.andersson, Arend Van Spriel, Kalle Valo

On 27 January 2017 at 08:47, Greg KH <gregkh@linuxfoundation.org> wrote:
> On Thu, Jan 26, 2017 at 10:50:05PM +0100, Luis R. Rodriguez wrote:
>> On Thu, Jan 19, 2017 at 05:27:51PM +0100, Luis R. Rodriguez wrote:
>> > On Thu, Jan 19, 2017 at 12:38:57PM +0100, Greg KH wrote:
>> > > On Thu, Jan 12, 2017 at 07:02:44AM -0800, Luis R. Rodriguez wrote:
>> > > > ---
>> > > >  drivers/net/wireless/intersil/p54/eeprom.c |  2 +-
>> > > >  drivers/net/wireless/intersil/p54/fwio.c   |  5 +-
>> > > >  drivers/net/wireless/intersil/p54/led.c    |  2 +-
>> > > >  drivers/net/wireless/intersil/p54/main.c   |  2 +-
>> > > >  drivers/net/wireless/intersil/p54/p54.h    |  3 +-
>> > > >  drivers/net/wireless/intersil/p54/p54pci.c | 26 ++++++----
>> > > >  drivers/net/wireless/intersil/p54/p54pci.h |  4 +-
>> > > >  drivers/net/wireless/intersil/p54/p54spi.c | 80 +++++++++++++++++++-----------
>> > > >  drivers/net/wireless/intersil/p54/p54spi.h |  2 +-
>> > > >  drivers/net/wireless/intersil/p54/p54usb.c | 18 +++----
>> > > >  drivers/net/wireless/intersil/p54/p54usb.h |  4 +-
>> > > >  drivers/net/wireless/intersil/p54/txrx.c   |  2 +-
>> > > >  12 files changed, 89 insertions(+), 61 deletions(-)
>> > >
>> > > why does the "new" api require more lines?
>> >
>> > This is a bare bones flexible API with only a few new tiny features to start
>> > with, one of them was to enable the API do the freeing of the driver data for
>> > you. In the kernel we have devres to help with this but devres only helps if
>> > you would use the API call on probe. We want to support the ability to let the
>> > API free the driver data for you even if your call is outside of probe, for this
>> > to work we need a callback. For async calls this is rather trivial given we
>> > already have a callback, for sync calls this means a new routine is needed.
>> > Freeing the data for you is an option, but I decided to keep the callback
>> > requirement even if you didn't want the free'ing to be done for you. The
>> > addition of a callback is what accounts for the slight increase on this driver.
>> >
>> > I could try avoiding the callback if no freeing is needed.
>>
>> OK I've added a respective helper call which would map 1-1 with the
>> old sync mechanism to enable a 1-1 change, this will be called
>> driver_data_request_simple(), but let me know if there is a preference
>> for something else.
>>
>> With this the only visible delta now is from taking advantage of new
>> features. In p54's case this would re-organize the mess in
>> drivers/net/wireless/intersil/p54/p54spi.c, the diff stat is a bit
>> larger for that file just because of this but I think in this case
>> its very much worth the small additions. In this case two routines are
>> added for handling the work through callbacks on a sync call.
>>
>>  1 file changed, 38 insertions(+), 30 deletions(-)
>
> I agree with Linus, as well as, look, it's still bigger, so you are
> making driver developers do more work :(
>
>>       /* FIXME: should driver use it's own struct device? */
>> -     ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
>> -
>> -     if (ret < 0) {
>> -             dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
>> +     ret = driver_data_request_simple("3826.arm", &priv->spi->dev,
>> +                                      &priv->firmware);
>> +     if (ret < 0)
>>               return ret;
>> -     }
>
> Hm, a FIXME that you aren't fixing :(
>
> I still fail to see why this new api is worth it at all, sorry.

Maybe we could try cleaning up existing firmware API and see if we
really hit something that can't be solved in any sane way? What do you
think?

I'd love to help with that, I started with a trivial cleaning patch:
[PATCH V2] firmware: simplify defining and handling FW_OPT_FALLBACK
https://patchwork.kernel.org/patch/9469875/

It didn't receive any real negative comments but I also have no idea
how could pick it up for me and send in some pull request. Any
suggestions?

-- 
Rafał

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-27 11:25             ` Rafał Miłecki
@ 2017-01-27 14:07               ` Greg KH
  2017-01-27 14:14                 ` Rafał Miłecki
  0 siblings, 1 reply; 33+ messages in thread
From: Greg KH @ 2017-01-27 14:07 UTC (permalink / raw)
  To: Rafał Miłecki
  Cc: Luis R. Rodriguez, Ming Lei, Borislav Petkov, wagi,
	Tom Gundersen, Mauro Carvalho Chehab, Linux Kernel Mailing List,
	Vikram Mulukutla, Stephen Boyd, Mark Brown, zohar, Takashi Iwai,
	Johannes Berg, Christian Lamparter, Hauke Mehrtens, Josh Boyer,
	Dmitry Torokhov, David Woodhouse, jslaby, Linus Torvalds, luto,
	Fengguang Wu, Richard Purdie, Jacek Anaszewski, Abhay_Salunke,
	Julia Lawall, Gilles.Muller, nicolas.palix, dhowells,
	bjorn.andersson, Arend Van Spriel, Kalle Valo

On Fri, Jan 27, 2017 at 12:25:48PM +0100, Rafał Miłecki wrote:
> On 27 January 2017 at 08:47, Greg KH <gregkh@linuxfoundation.org> wrote:
> > On Thu, Jan 26, 2017 at 10:50:05PM +0100, Luis R. Rodriguez wrote:
> >> On Thu, Jan 19, 2017 at 05:27:51PM +0100, Luis R. Rodriguez wrote:
> >> > On Thu, Jan 19, 2017 at 12:38:57PM +0100, Greg KH wrote:
> >> > > On Thu, Jan 12, 2017 at 07:02:44AM -0800, Luis R. Rodriguez wrote:
> >> > > > ---
> >> > > >  drivers/net/wireless/intersil/p54/eeprom.c |  2 +-
> >> > > >  drivers/net/wireless/intersil/p54/fwio.c   |  5 +-
> >> > > >  drivers/net/wireless/intersil/p54/led.c    |  2 +-
> >> > > >  drivers/net/wireless/intersil/p54/main.c   |  2 +-
> >> > > >  drivers/net/wireless/intersil/p54/p54.h    |  3 +-
> >> > > >  drivers/net/wireless/intersil/p54/p54pci.c | 26 ++++++----
> >> > > >  drivers/net/wireless/intersil/p54/p54pci.h |  4 +-
> >> > > >  drivers/net/wireless/intersil/p54/p54spi.c | 80 +++++++++++++++++++-----------
> >> > > >  drivers/net/wireless/intersil/p54/p54spi.h |  2 +-
> >> > > >  drivers/net/wireless/intersil/p54/p54usb.c | 18 +++----
> >> > > >  drivers/net/wireless/intersil/p54/p54usb.h |  4 +-
> >> > > >  drivers/net/wireless/intersil/p54/txrx.c   |  2 +-
> >> > > >  12 files changed, 89 insertions(+), 61 deletions(-)
> >> > >
> >> > > why does the "new" api require more lines?
> >> >
> >> > This is a bare bones flexible API with only a few new tiny features to start
> >> > with, one of them was to enable the API do the freeing of the driver data for
> >> > you. In the kernel we have devres to help with this but devres only helps if
> >> > you would use the API call on probe. We want to support the ability to let the
> >> > API free the driver data for you even if your call is outside of probe, for this
> >> > to work we need a callback. For async calls this is rather trivial given we
> >> > already have a callback, for sync calls this means a new routine is needed.
> >> > Freeing the data for you is an option, but I decided to keep the callback
> >> > requirement even if you didn't want the free'ing to be done for you. The
> >> > addition of a callback is what accounts for the slight increase on this driver.
> >> >
> >> > I could try avoiding the callback if no freeing is needed.
> >>
> >> OK I've added a respective helper call which would map 1-1 with the
> >> old sync mechanism to enable a 1-1 change, this will be called
> >> driver_data_request_simple(), but let me know if there is a preference
> >> for something else.
> >>
> >> With this the only visible delta now is from taking advantage of new
> >> features. In p54's case this would re-organize the mess in
> >> drivers/net/wireless/intersil/p54/p54spi.c, the diff stat is a bit
> >> larger for that file just because of this but I think in this case
> >> its very much worth the small additions. In this case two routines are
> >> added for handling the work through callbacks on a sync call.
> >>
> >>  1 file changed, 38 insertions(+), 30 deletions(-)
> >
> > I agree with Linus, as well as, look, it's still bigger, so you are
> > making driver developers do more work :(
> >
> >>       /* FIXME: should driver use it's own struct device? */
> >> -     ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
> >> -
> >> -     if (ret < 0) {
> >> -             dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
> >> +     ret = driver_data_request_simple("3826.arm", &priv->spi->dev,
> >> +                                      &priv->firmware);
> >> +     if (ret < 0)
> >>               return ret;
> >> -     }
> >
> > Hm, a FIXME that you aren't fixing :(
> >
> > I still fail to see why this new api is worth it at all, sorry.
> 
> Maybe we could try cleaning up existing firmware API and see if we
> really hit something that can't be solved in any sane way? What do you
> think?
> 
> I'd love to help with that, I started with a trivial cleaning patch:
> [PATCH V2] firmware: simplify defining and handling FW_OPT_FALLBACK
> https://patchwork.kernel.org/patch/9469875/
> 
> It didn't receive any real negative comments but I also have no idea
> how could pick it up for me and send in some pull request. Any
> suggestions?

Does that patch really "simplify" anything?  Anyway, resend it if the
maintainer of the subsystem ignores it (you did cc: the correct people,
right?)

And yes, I always like seeing things that simplify apis, and the
firmware interface could really use that, which I thought is what Luis
was trying to do here, which is why I keep pushing back on this
patchset as it doesn't seem to be happening.

thanks,

greg k-h

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-27 14:07               ` Greg KH
@ 2017-01-27 14:14                 ` Rafał Miłecki
  2017-01-27 14:30                   ` Greg KH
  0 siblings, 1 reply; 33+ messages in thread
From: Rafał Miłecki @ 2017-01-27 14:14 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, Ming Lei, Borislav Petkov, wagi,
	Tom Gundersen, Mauro Carvalho Chehab, Linux Kernel Mailing List,
	Vikram Mulukutla, Stephen Boyd, Mark Brown, zohar, Takashi Iwai,
	Johannes Berg, Christian Lamparter, Hauke Mehrtens, Josh Boyer,
	Dmitry Torokhov, David Woodhouse, jslaby, Linus Torvalds, luto,
	Fengguang Wu, Richard Purdie, Jacek Anaszewski, Abhay_Salunke,
	Julia Lawall, Gilles.Muller, nicolas.palix, dhowells,
	bjorn.andersson, Arend Van Spriel, Kalle Valo

On 27 January 2017 at 15:07, Greg KH <gregkh@linuxfoundation.org> wrote:
> On Fri, Jan 27, 2017 at 12:25:48PM +0100, Rafał Miłecki wrote:
>> On 27 January 2017 at 08:47, Greg KH <gregkh@linuxfoundation.org> wrote:
>> > On Thu, Jan 26, 2017 at 10:50:05PM +0100, Luis R. Rodriguez wrote:
>> >> On Thu, Jan 19, 2017 at 05:27:51PM +0100, Luis R. Rodriguez wrote:
>> >> > On Thu, Jan 19, 2017 at 12:38:57PM +0100, Greg KH wrote:
>> >> > > On Thu, Jan 12, 2017 at 07:02:44AM -0800, Luis R. Rodriguez wrote:
>> >> > > > ---
>> >> > > >  drivers/net/wireless/intersil/p54/eeprom.c |  2 +-
>> >> > > >  drivers/net/wireless/intersil/p54/fwio.c   |  5 +-
>> >> > > >  drivers/net/wireless/intersil/p54/led.c    |  2 +-
>> >> > > >  drivers/net/wireless/intersil/p54/main.c   |  2 +-
>> >> > > >  drivers/net/wireless/intersil/p54/p54.h    |  3 +-
>> >> > > >  drivers/net/wireless/intersil/p54/p54pci.c | 26 ++++++----
>> >> > > >  drivers/net/wireless/intersil/p54/p54pci.h |  4 +-
>> >> > > >  drivers/net/wireless/intersil/p54/p54spi.c | 80 +++++++++++++++++++-----------
>> >> > > >  drivers/net/wireless/intersil/p54/p54spi.h |  2 +-
>> >> > > >  drivers/net/wireless/intersil/p54/p54usb.c | 18 +++----
>> >> > > >  drivers/net/wireless/intersil/p54/p54usb.h |  4 +-
>> >> > > >  drivers/net/wireless/intersil/p54/txrx.c   |  2 +-
>> >> > > >  12 files changed, 89 insertions(+), 61 deletions(-)
>> >> > >
>> >> > > why does the "new" api require more lines?
>> >> >
>> >> > This is a bare bones flexible API with only a few new tiny features to start
>> >> > with, one of them was to enable the API do the freeing of the driver data for
>> >> > you. In the kernel we have devres to help with this but devres only helps if
>> >> > you would use the API call on probe. We want to support the ability to let the
>> >> > API free the driver data for you even if your call is outside of probe, for this
>> >> > to work we need a callback. For async calls this is rather trivial given we
>> >> > already have a callback, for sync calls this means a new routine is needed.
>> >> > Freeing the data for you is an option, but I decided to keep the callback
>> >> > requirement even if you didn't want the free'ing to be done for you. The
>> >> > addition of a callback is what accounts for the slight increase on this driver.
>> >> >
>> >> > I could try avoiding the callback if no freeing is needed.
>> >>
>> >> OK I've added a respective helper call which would map 1-1 with the
>> >> old sync mechanism to enable a 1-1 change, this will be called
>> >> driver_data_request_simple(), but let me know if there is a preference
>> >> for something else.
>> >>
>> >> With this the only visible delta now is from taking advantage of new
>> >> features. In p54's case this would re-organize the mess in
>> >> drivers/net/wireless/intersil/p54/p54spi.c, the diff stat is a bit
>> >> larger for that file just because of this but I think in this case
>> >> its very much worth the small additions. In this case two routines are
>> >> added for handling the work through callbacks on a sync call.
>> >>
>> >>  1 file changed, 38 insertions(+), 30 deletions(-)
>> >
>> > I agree with Linus, as well as, look, it's still bigger, so you are
>> > making driver developers do more work :(
>> >
>> >>       /* FIXME: should driver use it's own struct device? */
>> >> -     ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
>> >> -
>> >> -     if (ret < 0) {
>> >> -             dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
>> >> +     ret = driver_data_request_simple("3826.arm", &priv->spi->dev,
>> >> +                                      &priv->firmware);
>> >> +     if (ret < 0)
>> >>               return ret;
>> >> -     }
>> >
>> > Hm, a FIXME that you aren't fixing :(
>> >
>> > I still fail to see why this new api is worth it at all, sorry.
>>
>> Maybe we could try cleaning up existing firmware API and see if we
>> really hit something that can't be solved in any sane way? What do you
>> think?
>>
>> I'd love to help with that, I started with a trivial cleaning patch:
>> [PATCH V2] firmware: simplify defining and handling FW_OPT_FALLBACK
>> https://patchwork.kernel.org/patch/9469875/
>>
>> It didn't receive any real negative comments but I also have no idea
>> how could pick it up for me and send in some pull request. Any
>> suggestions?
>
> Does that patch really "simplify" anything?  Anyway, resend it if the
> maintainer of the subsystem ignores it (you did cc: the correct people,
> right?)

According to the MAINTAINERS there isn't firmware API tree /
maintainer. Also this is just a cleanup so I don't know if I should
expect some random maintainer (e.g. wireless tree) to pick it.

-- 
Rafał

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-27 14:14                 ` Rafał Miłecki
@ 2017-01-27 14:30                   ` Greg KH
  2017-01-27 14:39                     ` Rafał Miłecki
  0 siblings, 1 reply; 33+ messages in thread
From: Greg KH @ 2017-01-27 14:30 UTC (permalink / raw)
  To: Rafał Miłecki
  Cc: Luis R. Rodriguez, Ming Lei, Borislav Petkov, wagi,
	Tom Gundersen, Mauro Carvalho Chehab, Linux Kernel Mailing List,
	Vikram Mulukutla, Stephen Boyd, Mark Brown, zohar, Takashi Iwai,
	Johannes Berg, Christian Lamparter, Hauke Mehrtens, Josh Boyer,
	Dmitry Torokhov, David Woodhouse, jslaby, Linus Torvalds, luto,
	Fengguang Wu, Richard Purdie, Jacek Anaszewski, Abhay_Salunke,
	Julia Lawall, Gilles.Muller, nicolas.palix, dhowells,
	bjorn.andersson, Arend Van Spriel, Kalle Valo

On Fri, Jan 27, 2017 at 03:14:14PM +0100, Rafał Miłecki wrote:
> > Does that patch really "simplify" anything?  Anyway, resend it if the
> > maintainer of the subsystem ignores it (you did cc: the correct people,
> > right?)
> 
> According to the MAINTAINERS there isn't firmware API tree /
> maintainer. Also this is just a cleanup so I don't know if I should
> expect some random maintainer (e.g. wireless tree) to pick it.

I don't think you looked very hard:

 $ ./scripts/get_maintainer.pl --file drivers/base/firmware_class.c
 Ming Lei <ming.lei@canonical.com> (maintainer:FIRMWARE LOADER (request_firmware))
 "Luis R. Rodriguez" <mcgrof@kernel.org> (maintainer:FIRMWARE LOADER (request_firmware))
 Greg Kroah-Hartman <gregkh@linuxfoundation.org> (supporter:DRIVER CORE, KOBJECTS, DEBUGFS, KERNFS AND SYSFS)
 linux-kernel@vger.kernel.org (open list:FIRMWARE LOADER (request_firmware))

Please try again...

greg k-h

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-27 14:30                   ` Greg KH
@ 2017-01-27 14:39                     ` Rafał Miłecki
  2017-01-27 21:27                       ` Luis R. Rodriguez
  0 siblings, 1 reply; 33+ messages in thread
From: Rafał Miłecki @ 2017-01-27 14:39 UTC (permalink / raw)
  To: Greg KH
  Cc: Luis R. Rodriguez, Ming Lei, Borislav Petkov, wagi,
	Tom Gundersen, Mauro Carvalho Chehab, Linux Kernel Mailing List,
	Vikram Mulukutla, Stephen Boyd, Mark Brown, zohar, Takashi Iwai,
	Johannes Berg, Christian Lamparter, Hauke Mehrtens, Josh Boyer,
	Dmitry Torokhov, David Woodhouse, jslaby, Linus Torvalds, luto,
	Fengguang Wu, Richard Purdie, Jacek Anaszewski, Abhay_Salunke,
	Julia Lawall, Gilles.Muller, nicolas.palix, dhowells,
	bjorn.andersson, Arend Van Spriel, Kalle Valo

On 27 January 2017 at 15:30, Greg KH <gregkh@linuxfoundation.org> wrote:
> On Fri, Jan 27, 2017 at 03:14:14PM +0100, Rafał Miłecki wrote:
>> > Does that patch really "simplify" anything?  Anyway, resend it if the
>> > maintainer of the subsystem ignores it (you did cc: the correct people,
>> > right?)
>>
>> According to the MAINTAINERS there isn't firmware API tree /
>> maintainer. Also this is just a cleanup so I don't know if I should
>> expect some random maintainer (e.g. wireless tree) to pick it.
>
> I don't think you looked very hard:
>
>  $ ./scripts/get_maintainer.pl --file drivers/base/firmware_class.c
>  Ming Lei <ming.lei@canonical.com> (maintainer:FIRMWARE LOADER (request_firmware))
>  "Luis R. Rodriguez" <mcgrof@kernel.org> (maintainer:FIRMWARE LOADER (request_firmware))
>  Greg Kroah-Hartman <gregkh@linuxfoundation.org> (supporter:DRIVER CORE, KOBJECTS, DEBUGFS, KERNFS AND SYSFS)
>  linux-kernel@vger.kernel.org (open list:FIRMWARE LOADER (request_firmware))
>
> Please try again...

My memory totally failed me :( Sorry

-- 
Rafał

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-26 21:54           ` Linus Torvalds
@ 2017-01-27 18:23             ` Luis R. Rodriguez
  2017-01-27 20:53               ` Linus Torvalds
  0 siblings, 1 reply; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-27 18:23 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Luis R. Rodriguez, Greg KH, Ming Lei, Borislav Petkov,
	Daniel Wagner, Tom Gundersen, Mauro Carvalho Chehab,
	Rafał Miłecki, Linux Kernel Mailing List,
	Vikram Mulukutla, Stephen Boyd, Mark Brown, Mimi Zohar,
	Takashi Iwai, Johannes Berg, Christian Lamparter, Hauke Mehrtens,
	Josh Boyer, Dmitry Torokhov, David Woodhouse, Jiri Slaby,
	Andy Lutomirski, Wu Fengguang, Richard Purdie, Jacek Anaszewski,
	Abhay_Salunke, Julia Lawall, Gilles.Muller, nicolas.palix,
	David Howells, Bjorn Andersson, arend.vanspriel, Kalle Valo

On Thu, Jan 26, 2017 at 01:54:20PM -0800, Linus Torvalds wrote:
> On Thu, Jan 26, 2017 at 1:50 PM, Luis R. Rodriguez <mcgrof@kernel.org> wrote:
> >
> > OK I've added a respective helper call which would map 1-1 with the
> > old sync mechanism to enable a 1-1 change, this will be called
> > driver_data_request_simple(), but let me know if there is a preference
> > for something else.
> 
> So just looking at this patch, what's the *advantage* to the driver writer?

So for the driver writer it provides a clean way to logically split up what is
to be done for certain situations if the firmware is not present or is present.
Without this the code is a bit unruly, and this is actually a mild case. There
are much crazier chained conditionals (iwlwifi is one that has a long chain of
firmwares) but a goal here was to just provide only the most basic bump in
logic so that further enhancements/functionality is added later.

> Apart from the actual new feature, this patch seems to actively make
> the driver uglier.
> 
> I mentioned this before, but replacing "request_firmware()" with
> "driver_data_request_simple()" is SIMPLY NOT AN IMPROVEMENT.

I strongly agree with this.

> The new name is longer and _less_ descriptive.
> 
> So I'm really not seeing why you want to make these conversions that
> just make code worse.

The real goal here was first to actually provide a flexible API to enable
more advanced features to be added without having to affect existing
callers, as has been done before. So hence the const struct driver_data_req_params
approach and only two basic calls -- a sync and async call. This was after long ago we
had revised how we would go about adding firmware signing support to the kernel.

My first approach in addressing firmware signing was to mimic how we handle
have module signing: everyone gets it (even those on the old API), using one
default key. The flexible API was then a secondary step, to enable users to
customize signature requirements.  As we discussed things it was clear that we
wanted the ability to support firmware signing with the ability to provide
alternative key requirements from the very start. Having an extensible firmware API
in place first would enable the flexibility to let us decide what requirements
we want to put in place for firmware signing without concern for making a slew
of collateral evolutions as requirements change.

The flexible API then would be, as is in this series, completely optional.
Only if you want to reap benefit of some of the new features would you use it.

So unless the flexible API is reproachable in and of itself perhaps the thing
to do is leave all drivers as-is (without no conversion) and only convert
once we have a full gain value-add. For instance later adding support to easily
chain a series of firmware requests (not just 2), or once we have firmware
signing support and a driver want to reap benefit from it.

Thoughts ?

  Luis

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-27 18:23             ` Luis R. Rodriguez
@ 2017-01-27 20:53               ` Linus Torvalds
  2017-01-27 21:34                 ` Luis R. Rodriguez
  0 siblings, 1 reply; 33+ messages in thread
From: Linus Torvalds @ 2017-01-27 20:53 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: Greg KH, Ming Lei, Borislav Petkov, Daniel Wagner, Tom Gundersen,
	Mauro Carvalho Chehab, Rafał Miłecki,
	Linux Kernel Mailing List, Vikram Mulukutla, Stephen Boyd,
	Mark Brown, Mimi Zohar, Takashi Iwai, Johannes Berg,
	Christian Lamparter, Hauke Mehrtens, Josh Boyer, Dmitry Torokhov,
	David Woodhouse, Jiri Slaby, Andy Lutomirski, Wu Fengguang,
	Richard Purdie, Jacek Anaszewski, Abhay_Salunke, Julia Lawall,
	Gilles.Muller, nicolas.palix, David Howells, Bjorn Andersson,
	arend.vanspriel, Kalle Valo

On Fri, Jan 27, 2017 at 10:23 AM, Luis R. Rodriguez <mcgrof@kernel.org> wrote:
>> So I'm really not seeing why you want to make these conversions that
>> just make code worse.
>
> The real goal here was first to actually provide a flexible API to enable
> more advanced features to be added without having to affect existing
> callers, as has been done before.

So I've said this before, and I'll say this one more time:

It's fine if we make the internal implementation of some generic "load
data from the filesystem or user" be this kind of new flexible API
that is internally called "driver_data_request()" or whatever.

But dammit, that is NOT AN EXCUSE for then making crap patches that
just replace the existing firmware users.

If the new interface cannot be wrapped in the old names (and the old
semantics) the new interface is shit and should never ever go
anywhere.

So leave the existing users alone. Concentrate on _only_ the parts
where  there is actual and real need of new features. Don't try to
rename or extend current drivers. Don't send out these patches that
make drivers actively uglier. Really.

I really question the whole thing when there are things like this going on.

            Linus

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-27 14:39                     ` Rafał Miłecki
@ 2017-01-27 21:27                       ` Luis R. Rodriguez
  0 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-27 21:27 UTC (permalink / raw)
  To: Rafał Miłecki
  Cc: Greg KH, Luis R. Rodriguez, Ming Lei, Borislav Petkov, wagi,
	Tom Gundersen, Mauro Carvalho Chehab, Linux Kernel Mailing List,
	Vikram Mulukutla, Stephen Boyd, Mark Brown, zohar, Takashi Iwai,
	Johannes Berg, Christian Lamparter, Hauke Mehrtens, Josh Boyer,
	Dmitry Torokhov, David Woodhouse, jslaby, Linus Torvalds, luto,
	Fengguang Wu, Richard Purdie, Jacek Anaszewski, Abhay_Salunke,
	Julia Lawall, Gilles.Muller, nicolas.palix, dhowells,
	bjorn.andersson, Arend Van Spriel, Kalle Valo

On Fri, Jan 27, 2017 at 03:39:36PM +0100, Rafał Miłecki wrote:
> On 27 January 2017 at 15:30, Greg KH <gregkh@linuxfoundation.org> wrote:
> > On Fri, Jan 27, 2017 at 03:14:14PM +0100, Rafał Miłecki wrote:
> >> > Does that patch really "simplify" anything?  Anyway, resend it if the
> >> > maintainer of the subsystem ignores it (you did cc: the correct people,
> >> > right?)
> >>
> >> According to the MAINTAINERS there isn't firmware API tree /
> >> maintainer. Also this is just a cleanup so I don't know if I should
> >> expect some random maintainer (e.g. wireless tree) to pick it.
> >
> > I don't think you looked very hard:
> >
> >  $ ./scripts/get_maintainer.pl --file drivers/base/firmware_class.c
> >  Ming Lei <ming.lei@canonical.com> (maintainer:FIRMWARE LOADER (request_firmware))
> >  "Luis R. Rodriguez" <mcgrof@kernel.org> (maintainer:FIRMWARE LOADER (request_firmware))
> >  Greg Kroah-Hartman <gregkh@linuxfoundation.org> (supporter:DRIVER CORE, KOBJECTS, DEBUGFS, KERNFS AND SYSFS)
> >  linux-kernel@vger.kernel.org (open list:FIRMWARE LOADER (request_firmware))
> >
> > Please try again...
> 
> My memory totally failed me :( Sorry

I was eventually copied on the patch but there was also a side discussion on
you wanting FW_OPT_NO_WARN. Can you review if the proposed driver_data API does
what I think you wanted. Although request_firmware_direct() only avoids warning
on a sync all the diver_data API also supports this for async calls. If its
something else I'd prefer we evaluate extending the driver_data API before
loosely adding yet another API call using the old API.

  Luis

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

* Re: [PATCH v4 3/3] p54: convert to sysdata API
  2017-01-27 20:53               ` Linus Torvalds
@ 2017-01-27 21:34                 ` Luis R. Rodriguez
  0 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-01-27 21:34 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Luis R. Rodriguez, Greg KH, Ming Lei, Borislav Petkov,
	Daniel Wagner, Tom Gundersen, Mauro Carvalho Chehab,
	Rafał Miłecki, Linux Kernel Mailing List,
	Vikram Mulukutla, Stephen Boyd, Mark Brown, Mimi Zohar,
	Takashi Iwai, Johannes Berg, Christian Lamparter, Hauke Mehrtens,
	Josh Boyer, Dmitry Torokhov, David Woodhouse, Jiri Slaby,
	Andy Lutomirski, Wu Fengguang, Richard Purdie, Jacek Anaszewski,
	Abhay_Salunke, Julia Lawall, Gilles.Muller, nicolas.palix,
	David Howells, Bjorn Andersson, arend.vanspriel, Kalle Valo

On Fri, Jan 27, 2017 at 12:53:38PM -0800, Linus Torvalds wrote:
> On Fri, Jan 27, 2017 at 10:23 AM, Luis R. Rodriguez <mcgrof@kernel.org> wrote:
> >> So I'm really not seeing why you want to make these conversions that
> >> just make code worse.
> >
> > The real goal here was first to actually provide a flexible API to enable
> > more advanced features to be added without having to affect existing
> > callers, as has been done before.
> 
> So I've said this before, and I'll say this one more time:
> 
> It's fine if we make the internal implementation of some generic "load
> data from the filesystem or user" be this kind of new flexible API
> that is internally called "driver_data_request()" or whatever.
> 
> But dammit, that is NOT AN EXCUSE for then making crap patches that
> just replace the existing firmware users.

Works with me.

> If the new interface cannot be wrapped in the old names (and the old
> semantics) the new interface is shit and should never ever go
> anywhere.

There's a few questionable things part of the old API which (UMH lock is one
used even if no UMH is used, the fallback mechanism another) so I've taken out
what I can truly vouch for and its all being shared on the driver_data API.
Extending the old API with yet-more flags is a big concern on my part so will
also recommend new functionality to be focused on the newer API.

> So leave the existing users alone. Concentrate on _only_ the parts
> where  there is actual and real need of new features. Don't try to
> rename or extend current drivers. Don't send out these patches that
> make drivers actively uglier. Really.

Right on.

  Luis

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

* Re: [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata
  2017-01-19 18:58     ` Bjorn Andersson
@ 2017-02-03 21:56       ` Luis R. Rodriguez
  0 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-02-03 21:56 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: Luis R. Rodriguez, gregkh, ming.lei, bp, wagi, teg, mchehab,
	zajec5, linux-kernel, markivx, stephen.boyd, broonie, zohar,
	tiwai, johannes, chunkeey, hauke, jwboyer, dmitry.torokhov,
	dwmw2, jslaby, torvalds, luto, fengguang.wu, rpurdie,
	j.anaszewski, Abhay_Salunke, Julia.Lawall, Gilles.Muller,
	nicolas.palix, dhowells, arend.vanspriel, kvalo

On Thu, Jan 19, 2017 at 10:58:56AM -0800, Bjorn Andersson wrote:
> On Thu 12 Jan 07:02 PST 2017, Luis R. Rodriguez wrote:
> [..]
> > +Fallback mechanisms on the driver data API
> > +==========================================
> > +
> > +The old firmware API provided support for a series of fallback mechanisms. The
> > +new driver data API abandons all current notions of the fallback mechanisms,
> > +it may soon add support for one though.
> > +
> 
> What will this fallback mechanism look like? Will it be fully compatible
> with the current userspace interfaces 

I think its important to strive towards this as we get to fix up the old
stuff as much as possible that way. As you could tell from the surge of
fixes on the fallback mechanism it is rather fragile and has not had much
love. With time some of us have been giving it some love, just recently we
added test interfaces for it. We need to address some few more cobwebs before
being 100% certain the precise old interface is the ideal solution for a
fallback mechanism. I haven't personally found any roadblocks from keeping
it compatible on the newer API.

> or will we forever have two
> duplicate systems for loading "firmware" in the kernel?

We need flexibility, that's where the new API comes from, the
old API will remain but new features should go in through the
new API.

> Will the "driver_data" API replace the firmware_class at any point in the future?

The idea is to keep the old API as-is, users requiring new features
would use the newer API. That's all.

  Luis

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

* [PATCH v5 0/2] firmware: add driver data API
  2017-01-12 15:02 ` [PATCH v4 0/3] firmware: add drvdata API Luis R. Rodriguez
                     ` (2 preceding siblings ...)
  2017-01-12 15:02   ` [PATCH v4 3/3] p54: convert to sysdata API Luis R. Rodriguez
@ 2017-02-07  1:08   ` Luis R. Rodriguez
  2017-02-07  1:08     ` [PATCH v5 1/2] firmware: add extensible " Luis R. Rodriguez
                       ` (2 more replies)
  3 siblings, 3 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-02-07  1:08 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, Luis R. Rodriguez

Greg,

This v5 drops the driver porting examples in light of the fact simple
cases do not yet benefit from use of the newer API. The API is purposely
introduced bare bones so new features are easier to review.

This v5 embraces the proposed "driver data" name, extends documentation
to cover the driver data API stress test driver, and simplifies
documentation a bit more as requested. I've taken the time to also
review all checkpatch annoyances.

On the v4 series Linus clarified he's of the position the driver data
API could be a generic API for a generic "load data from the filesystem
or user" requests and that it should be possible to wrap the new
interface in the old firmware API. There's a few questionable things
part of the old firmware API which upon review I could not stomache in
keeping though as either they are very likely not needed anymore or
require more thought to properly implement. As such the new API only
carries on what is sensible from the old API. The questionable things
from the old API are currently not carried forward are:

  o The usermode helper lock: is currently used on the older firmware API
    even if the kernel UMH is not used. The UMH lock was added to help
    avoid a suspend and resume face issue, which the firmware cache fixed.

  o The fallback mechanism: the fallback mechanism is rather complex and
    supports a custom fallback mechanism. As recent patches reveal this
    was not well tested for a while. We also know systemd udev has long
    had the fallback mechanism ripped out, and some distributions simply
    ignore these requestions all together (Debian). A few new users of
    the fallback mechanism have come to light however and upon review it
    would seem we can address support for these users using the default
    fallback mechanism with kobject uevents even if a custom path is
    required, and a new userspace solution which replaces the old
    systemd udev fallback helper. Long term then the custom fallback
    mechanism proves pointless to extend.

Lastly, we now have the new common kernel_read_file_from_fd() and
kernel_read_file_from_path() with respective LSM hooks and identifiers
for LSMs for reading files from the kernel for:

  o READING_UNKNOWN
  o READING_FIRMWARE
  o READING_FIRMWARE_PREALLOC_BUFFER
  o READING_MODULE
  o READING_KEXEC_IMAGE
  o READING_KEXEC_INITRAMFS
  o READING_POLICY

If we want to support a fallback mechanism and/or custom paths on the
driver data API to generalize it more we'd need to revise how we'd want
to expand on context to feed LSMs. For now though the driver data API
only supports and uses kernel_read_file_from_path() and uses the
READING_FIRMWARE id only as this is where the code grew from.

Luis R. Rodriguez (2):
  firmware: add extensible driver data API
  test: add new driver_data load tester

 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                      |  333 +++++++
 include/linux/driver_data.h                        |  253 +++++
 lib/Kconfig.debug                                  |   12 +
 lib/Makefile                                       |    1 +
 lib/test_driver_data.c                             | 1039 ++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile          |    2 +-
 tools/testing/selftests/firmware/config            |    1 +
 tools/testing/selftests/firmware/driver_data.sh    |  826 ++++++++++++++++
 12 files changed, 2595 insertions(+), 2 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] 33+ messages in thread

* [PATCH v5 1/2] firmware: add extensible driver data API
  2017-02-07  1:08   ` [PATCH v5 0/2] firmware: add driver data API Luis R. Rodriguez
@ 2017-02-07  1:08     ` Luis R. Rodriguez
  2017-02-07  1:08     ` [PATCH v5 2/2] test: add new driver_data load tester Luis R. Rodriguez
  2017-02-10 14:31     ` [PATCH v5 0/2] firmware: add driver data API Greg KH
  2 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-02-07  1:08 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, 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. While reviewing the existing API
the firmware fallback mechansism stands out as requiring more
review before folding forward as such it has been kept out of the
new API.

We purposely try to limit the scope of changes in this new API to
enable a flexible API to start off with as base. 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. Other than providing a flexible API
a few minor features are are introduced:

 - 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.
 - You no longer need to declare and use your own completions, you
   can replace your completions with driver_data_synchronize_request()
   using the async_cookie given back by the async driver data call.

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                      | 333 +++++++++++++++++++++
 include/linux/driver_data.h                        | 253 ++++++++++++++++
 6 files changed, 682 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/driver-api/firmware/driver_data.rst
 create mode 100644 include/linux/driver_data.h

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 2c171ad9ea07..f1dd84bb21ed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5065,7 +5065,7 @@ F:	include/linux/firewire.h
 F:	include/uapi/linux/firewire*.h
 F:	tools/firewire/
 
-FIRMWARE LOADER (request_firmware)
+FIRMWARE LOADER (request_firmware, driver_data_request)
 M:	Ming Lei <ming.lei@canonical.com>
 M:	Luis R. Rodriguez <mcgrof@kernel.org>
 L:	linux-kernel@vger.kernel.org
@@ -5073,6 +5073,7 @@ 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 ac350c518e0c..c4286d41df9b 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.
  *
@@ -18,6 +19,7 @@
 #include <linux/mutex.h>
 #include <linux/workqueue.h>
 #include <linux/highmem.h>
+#include <linux/driver_data.h>
 #include <linux/firmware.h>
 #include <linux/slab.h>
 #include <linux/sched.h>
@@ -40,6 +42,12 @@ 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,
+};
+
 /* Builtin firmware support */
 
 #ifdef CONFIG_FW_LOADER
@@ -1335,6 +1343,186 @@ void release_firmware(const struct firmware *fw)
 }
 EXPORT_SYMBOL(release_firmware);
 
+static void driver_data_file_update(struct driver_data *driver_data)
+{
+	struct firmware *fw;
+	struct firmware_buf *buf;
+
+	if (!driver_data || !driver_data->priv)
+		return;
+
+	fw = driver_data->priv;
+	if (!fw->priv)
+		return;
+
+	buf = fw->priv;
+
+	driver_data->size = buf->size;
+	driver_data->data = buf->data;
+
+	pr_debug("%s: fw-%s buf=%p data=%p size=%u",
+		 __func__, buf->fw_id, buf, buf->data,
+		 (unsigned int)buf->size);
+}
+
+/*
+ * prepare firmware and firmware_buf structs;
+ * return 0 if a firmware is already assigned, 1 if need to load one,
+ * or a negative error code
+ */
+static int
+_request_driver_data_prepare(struct driver_data **driver_data_p,
+			     const char *name,
+			     struct device *device)
+{
+	struct driver_data *driver_data;
+	struct firmware *fw;
+	int ret;
+
+	*driver_data_p = driver_data =
+		kzalloc(sizeof(*driver_data), GFP_KERNEL);
+	if (!driver_data) {
+		dev_err(device, "%s: kmalloc(struct driver_data) failed\n",
+			__func__);
+		return -ENOMEM;
+	}
+
+	ret = _request_firmware_prepare(&fw, name, device, NULL, 0);
+	if (ret >= 0)
+		driver_data->priv = fw;
+
+	return ret;
+}
+
+/**
+ * release_driver_data_file: - release driver_data file resources
+ * @driver_data: driver_data file resource to release
+ **/
+void release_driver_data(const struct driver_data *driver_data)
+{
+	struct firmware *fw;
+
+	if (driver_data) {
+		if (driver_data->priv) {
+			fw = driver_data->priv;
+			release_firmware(fw);
+		}
+	}
+	kfree(driver_data);
+}
+EXPORT_SYMBOL_GPL(release_driver_data);
+
+/*
+ * driver_data_p is always set to be NULL unless a proper driver
+ * data file was found.
+ */
+static int _driver_data_request(const struct driver_data **driver_data_p,
+				const char *name,
+				const struct driver_data_req_params *params,
+				struct device *device)
+{
+	struct driver_data *driver_data = NULL;
+	struct firmware *fw = NULL;
+	int ret = -EINVAL;
+
+	if (!driver_data_p)
+		goto out;
+
+	if (!params)
+		goto out;
+
+	if (!name || name[0] == '\0')
+		goto out;
+
+	ret = _request_driver_data_prepare(&driver_data, name, device);
+	if (ret <= 0) /* error or already assigned */
+		goto out;
+
+	fw = driver_data->priv;
+
+	ret = fw_get_filesystem_firmware(device, fw->priv);
+	if (ret && !params->optional)
+		pr_err("Direct driver data load for %s failed with error %d\n",
+		       name, ret);
+
+	if (!ret)
+		ret = assign_firmware_buf(fw, device, FW_OPT_UEVENT);
+
+ out:
+	if (ret < 0) {
+		release_driver_data(driver_data);
+		driver_data = NULL;
+	}
+
+	driver_data_file_update(driver_data);
+
+	*driver_data_p = driver_data;
+
+	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(const char *name,
+			const struct driver_data_req_params *params,
+			struct device *device)
+{
+	const struct driver_data *driver_data;
+	const struct driver_data_reqs *sync_reqs;
+	int ret;
+
+	if (!device || !params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (params->sync_reqs.mode != DRIVER_DATA_SYNC)
+		return -EINVAL;
+
+	if (driver_data_sync_opt_cb(params) && !params->optional)
+		return -EINVAL;
+
+	sync_reqs = &dfl_sync_reqs;
+
+	__module_get(sync_reqs->module);
+	get_device(device);
+
+	ret = _driver_data_request(&driver_data, name, params, device);
+	if (ret && params->optional)
+		ret = driver_data_sync_opt_call_cb(params);
+	else
+		ret = driver_data_sync_call_cb(params, driver_data);
+
+	if (!params->keep)
+		release_driver_data(driver_data);
+
+	put_device(device);
+	module_put(sync_reqs->module);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(driver_data_request);
+
 /* Async support */
 struct firmware_work {
 	struct work_struct work;
@@ -1423,6 +1611,149 @@ request_firmware_nowait(
 }
 EXPORT_SYMBOL(request_firmware_nowait);
 
+struct driver_data_file_work {
+	const char *name;
+	struct driver_data_req_params params;
+	struct device *device;
+};
+
+static ASYNC_DOMAIN(driver_data_async_domain);
+
+static void request_driver_data_work_func(void *data, async_cookie_t cookie)
+{
+	struct driver_data_file_work *drv_work = data;
+	const struct driver_data_req_params *params;
+	const struct driver_data_reqs *sync_reqs;
+	const struct driver_data *driver_data;
+	int ret;
+
+	params = &drv_work->params;
+	sync_reqs = &params->sync_reqs;
+
+	ret = _driver_data_request(&driver_data, drv_work->name,
+				   params, drv_work->device);
+	if (ret && params->optional)
+		driver_data_async_opt_call_cb(params);
+	else
+		driver_data_async_call_cb(driver_data, params);
+
+	if (!params->keep)
+		release_driver_data(driver_data);
+
+	put_device(drv_work->device);
+	module_put(sync_reqs->module);
+
+	kfree_const(drv_work->name);
+	kfree(drv_work);
+}
+
+/**
+ * driver_data_request_async - asynchronous request for a driver data file
+ * @name: name of the driver data file
+ * @desc: driver data file descriptor, it provides all the requirements
+ *	which must be met for the file being requested.
+ * @device: device for which firmware is being loaded
+ * @async_cookie: used for checkpointing your async request
+ *
+ * This performs an asynchronous driver data file lookup with the requirements
+ * specified on @desc. The request for the actual driver data file lookup will
+ * be scheduled with async_schedule_domain() 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 @desc. If the driver data file is optional you must
+ * specify that on the @desc 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 @desc. 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.
+ *
+ * You should use rely on async_cookie to determine if your asynchronous work
+ * has been scheduled and completed. If you need to wait for completion of
+ * processing of your driver_data through your callbacks, or if you just want
+ * to know the hunt is over you can driver_data_synchronize_request() with the
+ * async_cookie.
+ */
+int driver_data_request_async(const char *name,
+			      const struct driver_data_req_params *params,
+			      struct device *device,
+			      async_cookie_t *async_cookie)
+{
+	struct driver_data_file_work *drv_work;
+	const struct driver_data_reqs *sync_reqs;
+
+	if (!device || !params || !name || name[0] == '\0')
+		return -EINVAL;
+
+	if (params->sync_reqs.mode != DRIVER_DATA_ASYNC)
+		return -EINVAL;
+
+	if (driver_data_async_opt_cb(params) && !params->optional)
+		return -EINVAL;
+
+	sync_reqs = &params->sync_reqs;
+
+	drv_work = kzalloc(sizeof(struct driver_data_file_work),
+			   sync_reqs->gfp);
+	if (!drv_work)
+		return -ENOMEM;
+
+	drv_work->device = device;
+	memcpy(&drv_work->params, params,
+	       sizeof(struct driver_data_req_params));
+	drv_work->name = kstrdup_const(name, sync_reqs->gfp);
+	if (!drv_work->name) {
+		kfree(drv_work);
+		return -ENOMEM;
+	}
+
+	if (!try_module_get(sync_reqs->module)) {
+		kfree_const(drv_work->name);
+		kfree(drv_work);
+		return -EFAULT;
+	}
+
+	get_device(drv_work->device);
+
+	*async_cookie = async_schedule_domain(request_driver_data_work_func,
+					      drv_work,
+					      &driver_data_async_domain);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(driver_data_request_async);
+
+/**
+ * driver_data_synchronize_request - wait until async calls complete
+ * @async_cookie: async cookie
+ *
+ * Waits until all asynchronous driver_data calls prior to and up to
+ * @async_cookie have been completed. You can use this to wait for completion
+ * of your own async callback. Your wait will end after
+ * request_driver_data_work_func() is called for your cookie. At this point you
+ * can rest assured your series of async callbacks would have been called if
+ * supplied.
+ *
+ * async_cookie+1 is used as async_synchronize_cookie_domain() only waits
+ * until at least your own call is next in queue to be run, we want the
+ * next item after yours to be in queue, this tells us we have run already.
+ * Should there not be any other async scheduled item after yours this will
+ * simply wait until all async driver_data calls are complete.
+ */
+void driver_data_synchronize_request(async_cookie_t async_cookie)
+{
+	async_synchronize_cookie_domain(async_cookie+1,
+					&driver_data_async_domain);
+}
+EXPORT_SYMBOL_GPL(driver_data_synchronize_request);
+
 #ifdef CONFIG_PM_SLEEP
 static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);
 
@@ -1796,6 +2127,7 @@ static int __init firmware_class_init(void)
 
 static void __exit firmware_class_exit(void)
 {
+	async_synchronize_full_domain(&driver_data_async_domain);
 #ifdef CONFIG_PM_SLEEP
 	unregister_syscore_ops(&fw_syscore_ops);
 	unregister_pm_notifier(&fw_cache.pm_notify);
@@ -1804,6 +2136,7 @@ static void __exit firmware_class_exit(void)
 	unregister_reboot_notifier(&fw_shutdown_nb);
 	class_unregister(&firmware_class);
 #endif
+	async_unregister_domain(&driver_data_async_domain);
 }
 
 fs_initcall(firmware_class_init);
diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
new file mode 100644
index 000000000000..7dee055df41e
--- /dev/null
+++ b/include/linux/driver_data.h
@@ -0,0 +1,253 @@
+#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>
+#include <linux/async.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 {
+	size_t size;
+	const u8 *data;
+
+	/* driver_data loader private fields */
+	void *priv;
+};
+
+/**
+ * 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 {
+		int __must_check
+			(*found_cb)(void *context,
+				    const struct driver_data *driver_data);
+		void *found_ctx;
+
+		int __must_check (*opt_fail_cb)(void *context);
+		void *opt_fail_ctx;
+	} sync;
+	struct {
+		void (*found_cb)(const struct driver_data *driver_data,
+				 void *context);
+		void *found_ctx;
+
+		void (*opt_fail_cb)(void *context);
+		void *opt_fail_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. You must set this to true if you have provided a opt_fail_cb
+ *	callback, DRIVER_DATA_SYNC_OPT_CB() and DRIVER_DATA_ASYNC_OPT_CB()
+ *	ensures this is done for you. If you set this to true and are using an
+ *	asynchronous request but not providing a opt_fail_cb() you should
+ *	seriously consider using at the very least using async_cookie provided
+ *	to you to driver_data_synchronize_request() to ensure no lingering
+ *	requests are kept out of bounds.
+ * @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.
+ * @sync_reqs: synchronization requirements, this will be taken care for you
+ *	by default if you are usingy driver_data_request(), otherwise you
+ *	should provide your own requirements.
+ *
+ * This structure is set the by the driver and passed to the driver data
+ * file helpers driver_data_request() or driver_data_request_async().
+ * It is intended to carry all requirements and specifications required
+ * to complete the task to get the requested driver date file to the caller.
+ * If you wish to extend functionality of driver data file requests you
+ * should extend this data structure and make use of the extensions on
+ * the callers to avoid unnecessary collateral evolutions.
+ *
+ * You are allowed to provide a callback to handle if a driver data file was
+ * found or not. You do not need to provide a callback. You may also set
+ * an optional flag which would enable you to declare that the driver data
+ * file is optional and that if it is not found an alternative callback be
+ * run for you.
+ *
+ * Refer to driver_data_request() and driver_data_request_async() for more
+ * details.
+ */
+struct driver_data_req_params {
+	bool optional;
+	bool keep;
+	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_KEEP_ASYNC(__found_cb, __ctx)			\
+	DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx),			\
+	.keep = 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_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 driver_data *driver_data)
+{
+	if (params->sync_reqs.mode != DRIVER_DATA_SYNC)
+		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 driver_data *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));
+}
+
+#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));
+}
+
+#if defined(CONFIG_FW_LOADER) || \
+	(defined(CONFIG_FW_LOADER_MODULE) && defined(MODULE))
+int driver_data_request(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,
+			  async_cookie_t *async_cookie);
+void release_driver_data(const struct driver_data *driver_data);
+void driver_data_synchronize_request(async_cookie_t async_cookie);
+#else
+static inline int driver_data_request(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,
+				 async_cookie_t *async_cookie);
+{
+	return -EINVAL;
+}
+
+static inline void release_driver_data(const struct driver_data *driver_data)
+{
+}
+
+void driver_data_synchronize_request(async_cookie_t async_cookie)
+{
+}
+#endif
+
+#endif /* _LINUX_DRIVER_DATA_H */
-- 
2.11.0

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

* [PATCH v5 2/2] test: add new driver_data load tester
  2017-02-07  1:08   ` [PATCH v5 0/2] firmware: add driver data API Luis R. Rodriguez
  2017-02-07  1:08     ` [PATCH v5 1/2] firmware: add extensible " Luis R. Rodriguez
@ 2017-02-07  1:08     ` Luis R. Rodriguez
  2017-02-10 14:31     ` [PATCH v5 0/2] firmware: add driver data API Greg KH
  2 siblings, 0 replies; 33+ messages in thread
From: Luis R. Rodriguez @ 2017-02-07  1:08 UTC (permalink / raw)
  To: gregkh, ming.lei
  Cc: bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo, 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                            | 1039 +++++++++++++++++++++
 tools/testing/selftests/firmware/Makefile         |    2 +-
 tools/testing/selftests/firmware/config           |    1 +
 tools/testing/selftests/firmware/driver_data.sh   |  826 ++++++++++++++++
 8 files changed, 1913 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 f1dd84bb21ed..96449804ec9d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5072,6 +5072,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 64c03b07ad2f..68495327ca49 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1975,6 +1975,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 fbbe2e2dba8f..79eecd0ce6fb 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -46,6 +46,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_LKM) += test_module.o
diff --git a/lib/test_driver_data.c b/lib/test_driver_data.c
new file mode 100644
index 000000000000..0c2670b356aa
--- /dev/null
+++ b/lib/test_driver_data.c
@@ -0,0 +1,1039 @@
+/*
+ * 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().
+ * @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.
+ * @test_result: a test may use this to collect the result from the call
+ *	of the driver_data_request_async() or driver_data_request() 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;
+
+	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;
+	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
+ * @driver_data_mutex: for access into the @driver_data, the fake storage
+ *	location for the system data we copy.
+ * @config_mutex:
+ * @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.
+ * 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;
+
+	struct mutex driver_data_mutex;
+	struct mutex config_mutex;
+	struct mutex trigger_mutex;
+	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->written = false;
+}
+
+static int test_load_driver_data(struct driver_data_test_device *test_dev,
+				 const struct driver_data *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;
+
+	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 driver_data *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,
+			"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 driver_data *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_driver_data(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(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);
+
+	/* Leave all the error checking for the main caller */
+}
+
+static int config_sync_req_cb(void *context,
+			      const struct driver_data *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(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 driver_data *driver_data,
+				void *context)
+{
+	struct driver_data_test_device *test_dev = context;
+
+	config_load_data(test_dev, driver_data);
+}
+
+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;
+	async_cookie_t async_cookie;
+
+	if (config->enable_opt_cb)
+		req_params = &req_params_opt_cb;
+	else
+		req_params = &req_params_default;
+
+	ret = driver_data_request_async(config->name, req_params,
+				   test_dev->dev, &async_cookie);
+	if (ret) {
+		dev_err(test_dev->dev, "async load of '%s' failed: %d\n",
+			config->name, ret);
+		goto out;
+	}
+
+	driver_data_synchronize_request(async_cookie);
+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;
+}
+
+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->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);
+	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 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 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_enable_opt_cb),
+	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)
+{
+	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);
+
+	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..7884cda3dd16
--- /dev/null
+++ b/tools/testing/selftests/firmware/driver_data.sh
@@ -0,0 +1,826 @@
+#!/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"
+
+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 $DIR ]; then
+		DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"
+	fi
+
+	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
+		DEFAULT_DRIVER_DATA="test-driver_data.bin"
+	fi
+
+	if [ -z $FW ]; then
+		FW="$FWPATH/$DEFAULT_DRIVER_DATA"
+	fi
+
+	# Set the kernel search path.
+	echo -n "$FWPATH" > $FW_SYSFSPATH
+
+	# This is an unlikely real-world firmware content. :)
+	echo "ABCD0123" >"$FW"
+
+	NAME=$(basename "$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 [ ! -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_disable_opt_cb()
+{
+	if ! echo -n 0 >$DIR/config_enable_opt_cb; then
+		echo "$0: Unable to disable keep option" >&2
+		exit 1
+	fi
+}
+
+# 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
+}
+
+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)
+	# 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
+}
+
+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 ${FUNCNAME[0]}
+	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
+}
+
+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 requesting module after driver data works"
+}
+
+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
+allow_user_defaults
+load_req_mod
+
+trap "test_finish" EXIT
+
+parse_args $@
+
+exit 0
-- 
2.11.0

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

* Re: [PATCH v5 0/2] firmware: add driver data API
  2017-02-07  1:08   ` [PATCH v5 0/2] firmware: add driver data API Luis R. Rodriguez
  2017-02-07  1:08     ` [PATCH v5 1/2] firmware: add extensible " Luis R. Rodriguez
  2017-02-07  1:08     ` [PATCH v5 2/2] test: add new driver_data load tester Luis R. Rodriguez
@ 2017-02-10 14:31     ` Greg KH
  2 siblings, 0 replies; 33+ messages in thread
From: Greg KH @ 2017-02-10 14:31 UTC (permalink / raw)
  To: Luis R. Rodriguez
  Cc: ming.lei, bp, wagi, teg, mchehab, zajec5, linux-kernel, markivx,
	stephen.boyd, broonie, zohar, tiwai, johannes, chunkeey, hauke,
	jwboyer, dmitry.torokhov, dwmw2, jslaby, torvalds, luto,
	fengguang.wu, rpurdie, j.anaszewski, Abhay_Salunke, Julia.Lawall,
	Gilles.Muller, nicolas.palix, dhowells, bjorn.andersson,
	arend.vanspriel, kvalo

On Mon, Feb 06, 2017 at 05:08:52PM -0800, Luis R. Rodriguez wrote:
> Greg,
> 
> This v5 drops the driver porting examples in light of the fact simple
> cases do not yet benefit from use of the newer API. The API is purposely
> introduced bare bones so new features are easier to review.

I am not going to review this until you actually convert some existing
code, or do something with it.  A test-module isn't proof that this is a
good api or not.  As it is, I can't add new apis with no users.

sorry,

greg k-h

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

end of thread, other threads:[~2017-02-10 14:31 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-12-16 11:46 [PATCH v3 0/4] firmware: add drvdata API Luis R. Rodriguez
2016-12-16 11:46 ` [PATCH v3 1/4] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
2016-12-16 11:46 ` [PATCH v3 2/4] test: add new drvdata loader tester Luis R. Rodriguez
2016-12-16 11:46 ` [PATCH v3 3/4] x86/microcode: convert to use sysdata API Luis R. Rodriguez
2016-12-16 11:46 ` [PATCH v3 4/4] p54: convert to " Luis R. Rodriguez
2016-12-16 17:14   ` Luis R. Rodriguez
2017-01-12 15:02 ` [PATCH v4 0/3] firmware: add drvdata API Luis R. Rodriguez
2017-01-12 15:02   ` [PATCH v4 1/3] firmware: add new extensible firmware API - drvdata Luis R. Rodriguez
2017-01-19 11:36     ` Greg KH
2017-01-19 16:54       ` Luis R. Rodriguez
2017-01-19 18:58     ` Bjorn Andersson
2017-02-03 21:56       ` Luis R. Rodriguez
2017-01-12 15:02   ` [PATCH v4 2/3] test: add new drvdata loader tester Luis R. Rodriguez
2017-01-12 15:02   ` [PATCH v4 3/3] p54: convert to sysdata API Luis R. Rodriguez
2017-01-16 11:32     ` Christian Lamparter
2017-01-19 11:38     ` Greg KH
2017-01-19 16:27       ` Luis R. Rodriguez
2017-01-26 21:50         ` Luis R. Rodriguez
2017-01-26 21:54           ` Linus Torvalds
2017-01-27 18:23             ` Luis R. Rodriguez
2017-01-27 20:53               ` Linus Torvalds
2017-01-27 21:34                 ` Luis R. Rodriguez
2017-01-27  7:47           ` Greg KH
2017-01-27 11:25             ` Rafał Miłecki
2017-01-27 14:07               ` Greg KH
2017-01-27 14:14                 ` Rafał Miłecki
2017-01-27 14:30                   ` Greg KH
2017-01-27 14:39                     ` Rafał Miłecki
2017-01-27 21:27                       ` Luis R. Rodriguez
2017-02-07  1:08   ` [PATCH v5 0/2] firmware: add driver data API Luis R. Rodriguez
2017-02-07  1:08     ` [PATCH v5 1/2] firmware: add extensible " Luis R. Rodriguez
2017-02-07  1:08     ` [PATCH v5 2/2] test: add new driver_data load tester Luis R. Rodriguez
2017-02-10 14:31     ` [PATCH v5 0/2] firmware: add driver data API Greg KH

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.