lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20170207010854.30340-2-mcgrof@kernel.org>
Date:   Mon,  6 Feb 2017 17:08:53 -0800
From:   "Luis R. Rodriguez" <mcgrof@...nel.org>
To:     gregkh@...uxfoundation.org, ming.lei@...onical.com
Cc:     bp@...en8.de, wagi@...om.org, teg@...m.no, mchehab@....samsung.com,
        zajec5@...il.com, linux-kernel@...r.kernel.org,
        markivx@...eaurora.org, stephen.boyd@...aro.org,
        broonie@...nel.org, zohar@...ux.vnet.ibm.com, tiwai@...e.de,
        johannes@...solutions.net, chunkeey@...glemail.com,
        hauke@...ke-m.de, jwboyer@...oraproject.org,
        dmitry.torokhov@...il.com, dwmw2@...radead.org, jslaby@...e.com,
        torvalds@...ux-foundation.org, luto@...capital.net,
        fengguang.wu@...el.com, rpurdie@...ys.net,
        j.anaszewski@...sung.com, Abhay_Salunke@...l.com,
        Julia.Lawall@...6.fr, Gilles.Muller@...6.fr, nicolas.palix@...g.fr,
        dhowells@...hat.com, bjorn.andersson@...aro.org,
        arend.vanspriel@...adcom.com, kvalo@...eaurora.org,
        "Luis R. Rodriguez" <mcgrof@...nel.org>
Subject: [PATCH v5 1/2] firmware: add extensible driver data API

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@...nel.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@...onical.com>
 M:	Luis R. Rodriguez <mcgrof@...nel.org>
 L:	linux-kernel@...r.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@...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@...nel.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@...nel.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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ