[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20170519191040.25165-3-mcgrof@kernel.org>
Date: Fri, 19 May 2017 12:10:37 -0700
From: "Luis R. Rodriguez" <mcgrof@...nel.org>
To: gregkh@...uxfoundation.org
Cc: wagi@...om.org, dwmw2@...radead.org, rafal@...ecki.pl,
arend.vanspriel@...adcom.com, rjw@...ysocki.net,
yi1.li@...ux.intel.com, atull@...nsource.altera.com,
moritz.fischer@...us.com, pmladek@...e.com,
johannes.berg@...el.com, emmanuel.grumbach@...el.com,
luciano.coelho@...el.com, kvalo@...eaurora.org, luto@...nel.org,
torvalds@...ux-foundation.org, keescook@...omium.org,
takahiro.akashi@...aro.org, dhowells@...hat.com, pjones@...hat.com,
hdegoede@...hat.com, alan@...ux.intel.com, tytso@....edu,
linux-kernel@...r.kernel.org,
"Luis R. Rodriguez" <mcgrof@...nel.org>
Subject: [PATCH v8 2/5] 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:
The driver data API accepts that there are only two types of requests:
a) synchronous requests
b) asynchronous requests
Both requests may have a different requirements which must be met. These
requirements can be described in the struct driver_data_req_params.
This struct is expected to be extended over time to support different
requirements as the kernel evolves.
After a bit of hard work the new interface has been wrapped onto the
functionality. The fallback mechanism has been kept out of the new API
currently because it requires just a bit more grooming and documentation
given new considerations and requirements. Adding support for it will
be rather easy now that the new API sits ontop of the old one. The
request_firmware_into_buf() API also is not enabled on the new API but
it is rather easy to do so -- this call has no current existing users
upstream though. Support will be provided once we add a respective
series of test cases against it and find a proper upstream user for it.
The flexible API also adds a few new bells and whistles:
- By default the kernel will free the driver data file for you after
your callbacks are called, you however are allowed to request that
you wish to keep the driver data file on the requirements params. The
new driver data API is able to free the driver data file for you by
requiring a consumer callback for the driver data file.
- Allows both asynchronous and synchronous request to specify that
driver data files are optional. With the old APIs we had added one
full API call, request_firmware_direct() just for this purpose --
the driver data request APIs allow for you to annotate that a driver
data file is optional for both synchronous or asynchronous requests
through the same two basic set of APIs.
- A firmware API framework is provided to enable daisy chaining a
series of requests for firmware on a range of supported APIs.
Signed-off-by: Luis R. Rodriguez <mcgrof@...nel.org>
---
MAINTAINERS | 3 +-
drivers/base/firmware_class.c | 420 ++++++++++++++++++++++++++++++++++++++++++
include/linux/driver_data.h | 178 ++++++++++++++++++
include/linux/firmware.h | 2 +
4 files changed, 602 insertions(+), 1 deletion(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index f8d77c888cfe..148d032e9401 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5226,13 +5226,14 @@ F: include/linux/firewire.h
F: include/uapi/linux/firewire*.h
F: tools/firewire/
-FIRMWARE LOADER (request_firmware)
+FIRMWARE LOADER (request_firmware, driver_data)
M: Luis R. Rodriguez <mcgrof@...nel.org>
L: linux-kernel@...r.kernel.org
S: Maintained
F: Documentation/firmware_class/
F: drivers/base/firmware*.c
F: include/linux/firmware.h
+F: include/linux/driver_data.h
FLASH ADAPTER DRIVER (IBM Flash Adapter 900GB Full Height PCI Flash Card)
M: Joshua Morris <josh.h.morris@...ibm.com>
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index db7c0bc0ed98..e87e91bcd8f8 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -2,6 +2,17 @@
* firmware_class.c - Multi purpose firmware loading support
*
* Copyright (c) 2003 Manuel Estrada Sainz
+ * Copyright (c) 2017 Luis R. Rodriguez <mcgrof@...nel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or at your option) any
+ * later version; or, when distributed separately from the Linux kernel or
+ * incorporated into other software packages, subject to the following license:
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of copyleft-next (version 0.3.1 or later) as published
+ * at http://copyleft-next.org/.
*
* Please see Documentation/firmware_class/ for more information.
*
@@ -91,6 +102,12 @@ enum driver_data_priv_reqs {
* @alloc_buf_size: size of the @alloc_buf
* @old_async_cb: used only for request_firmware_nowait() since we won't change
* all async callbacks to get the return value on failure
+ * @api: used internally for keeping track of the currently evaluated API
+ * versioned file as we iterate between min API and max API.
+ * @retry_api: if the driver replied with -EAGAIN, we must ignore the passed
+ * driver data file, and retry again on the hunt from where we left off,
+ * this lets us know an attempt to look for more API driver data files
+ * is a retry.
*/
struct driver_data_priv_params {
enum driver_data_mode mode;
@@ -98,6 +115,8 @@ struct driver_data_priv_params {
void *alloc_buf;
size_t alloc_buf_size;
void (*old_async_cb)(const struct firmware *driver_data, void *context);
+ u8 api;
+ bool retry_api;
};
/**
@@ -181,8 +200,68 @@ struct driver_data_params {
#define driver_data_param_optional(params) \
(!!((params)->reqs & DRIVER_DATA_REQ_OPTIONAL))
+#define driver_data_param_keep(params) \
+ (!!((params)->reqs & DRIVER_DATA_REQ_KEEP))
+#define driver_data_param_uses_api(params) \
+ (!!((params)->reqs & DRIVER_DATA_REQ_USE_API_VERSIONING))
+
+#define driver_data_sync_cb(param) ((params)->cbs.sync.found_cb)
+#define driver_data_sync_ctx(params) ((params)->cbs.sync.found_ctx)
+static inline
+int driver_data_sync_call_cb(const struct driver_data_req_params *params,
+ const struct firmware *driver_data, int error)
+{
+ if (!driver_data_sync_cb(params))
+ return error;
+ return driver_data_sync_cb(params)(driver_data_sync_ctx(params),
+ driver_data, error);
+}
+
+#define driver_data_sync_opt_cb(params) ((params)->cbs.sync.opt_fail_cb)
+#define driver_data_sync_opt_ctx(params) ((params)->cbs.sync.opt_fail_ctx)
+static inline
+int driver_data_sync_opt_call_cb(const struct driver_data_req_params *params,
+ int error)
+{
+ if (!driver_data_sync_opt_cb(params))
+ return error;
+ return driver_data_sync_opt_cb(params)
+ (driver_data_sync_opt_ctx(params), error);
+}
+#define driver_data_async_cb(params) ((params)->cbs.async.found_cb)
#define driver_data_async_ctx(params) ((params)->cbs.async.found_ctx)
+static inline
+void driver_data_async_call_cb(const struct firmware *driver_data,
+ const struct driver_data_req_params *params,
+ int error)
+{
+ BUG_ON(!driver_data_async_cb(params));
+ driver_data_async_cb(params)(driver_data,
+ driver_data_async_ctx(params),
+ error);
+}
+
+#define driver_data_async_opt_cb(params) ((params)->cbs.async.opt_fail_cb)
+#define driver_data_async_opt_ctx(params) ((params)->cbs.async.opt_fail_ctx)
+static inline
+void driver_data_async_opt_call_cb(const struct driver_data_req_params *params,
+ int error)
+{
+ driver_data_async_opt_cb(params)(driver_data_async_opt_ctx(params),
+ error);
+}
+
+#define driver_data_async_api_cb(params) ((params)->cbs.async.found_api_cb)
+static inline
+int driver_data_async_call_api_cb(const struct firmware *driver_data,
+ const struct driver_data_req_params *params,
+ int error)
+{
+ return driver_data_async_api_cb(params)(driver_data,
+ driver_data_async_ctx(params),
+ error);
+}
/* Builtin firmware support */
@@ -1316,6 +1395,7 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
struct device *device,
struct driver_data_params *data_params)
{
+ struct driver_data_priv_params *priv_params = &data_params->priv_params;
struct firmware *firmware;
struct firmware_buf *buf;
int ret;
@@ -1339,6 +1419,7 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
* of requesting firmware.
*/
firmware->priv = buf;
+ firmware->api = priv_params->api;
if (ret > 0) {
ret = fw_state_wait(&buf->fw_st);
@@ -1531,6 +1612,131 @@ void release_firmware(const struct firmware *fw)
}
EXPORT_SYMBOL(release_firmware);
+static int _driver_data_request_api(struct driver_data_params *params,
+ struct device *device,
+ const char *name)
+{
+ struct driver_data_priv_params *priv_params = ¶ms->priv_params;
+ const struct driver_data_req_params *req_params = ¶ms->req_params;
+ int ret;
+ char *try_name;
+ u8 api_max;
+
+ if (priv_params->retry_api) {
+ if (!priv_params->api)
+ return -ENOENT;
+ api_max = priv_params->api - 1;
+ } else {
+ api_max = req_params->api_max;
+ }
+
+ for (priv_params->api = api_max;
+ priv_params->api >= req_params->api_min;
+ priv_params->api--) {
+ if (req_params->api_name_postfix)
+ try_name = kasprintf(GFP_KERNEL, "%s%d%s",
+ name,
+ priv_params->api,
+ req_params->api_name_postfix);
+ else
+ try_name = kasprintf(GFP_KERNEL, "%s%d",
+ name,
+ priv_params->api);
+ if (!try_name)
+ return -ENOMEM;
+ ret = _request_firmware(¶ms->driver_data, try_name,
+ params, device);
+ kfree(try_name);
+
+ if (!ret)
+ break;
+
+ release_firmware(params->driver_data);
+
+ /*
+ * Only chug on with the API revision hunt if the file we
+ * looked for really was not present. In case of memory issues
+ * or other related system issues we want to bail right away
+ * to not put strain on the system.
+ */
+ if (ret != -ENOENT)
+ break;
+
+ if (!priv_params->api)
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * driver_data_request_sync - synchronous request for a driver data file
+ * @name: name of the driver data file
+ * @req_params: driver data parameters, it provides all the requirements
+ * parameters which must be met for the file being requested.
+ * @device: device for which firmware is being loaded
+ *
+ * This performs a synchronous driver data lookup with the requirements
+ * specified on @params, if the file was found meeting the criteria requested 0
+ * is returned. Callers get access to any found driver data meeting the
+ * specified criteria through an optional callback set on @params. If the
+ * driver data is optional you must specify that on @params and if set you may
+ * provide an alternative callback which if set would be run if the driver data
+ * was not found.
+ *
+ * The driver data passed to the callbacks will be NULL unless it was
+ * found matching all the criteria on @params. 0 is always returned if the file
+ * was found unless a callback was provided, in which case the callback's
+ * return value will be passed. Unless the params->keep was set the kernel will
+ * release the driver data for you after your callbacks were processed.
+ *
+ * Reference counting is used during the duration of this call on both the
+ * device and module that made the request. This prevents any callers from
+ * freeing either the device or module prior to completion of this call.
+ */
+int driver_data_request_sync(const char *name,
+ const struct driver_data_req_params *req_params,
+ struct device *device)
+{
+ const struct firmware *driver_data;
+ struct module *hold_module;
+ struct driver_data_params params = {
+ .req_params = *req_params,
+ .priv_params = {
+ .mode = DRIVER_DATA_SYNC,
+ },
+ };
+ int ret;
+
+ if (!device || !req_params || !name || name[0] == '\0')
+ return -EINVAL;
+
+ if (driver_data_sync_opt_cb(req_params) &&
+ !driver_data_param_optional(req_params))
+ return -EINVAL;
+
+ hold_module = req_params->hold_module ? req_params->hold_module :
+ THIS_MODULE;
+
+ __module_get(hold_module);
+ get_device(device);
+
+ ret = _request_firmware(&driver_data, name, ¶ms, device);
+ if (ret && driver_data_param_optional(req_params))
+ ret = driver_data_sync_opt_call_cb(req_params, ret);
+ else
+ ret = driver_data_sync_call_cb(req_params, driver_data, ret);
+
+ if (!driver_data_param_keep(req_params))
+ release_firmware(driver_data);
+
+ put_device(device);
+ module_put(hold_module);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(driver_data_request_sync);
+
/* Async support */
struct firmware_work {
struct work_struct work;
@@ -1625,6 +1831,220 @@ request_firmware_nowait(
}
EXPORT_SYMBOL(request_firmware_nowait);
+static bool
+driver_data_api_versioning_ok(const struct driver_data_req_params *req_params)
+{
+ if (!req_params->api_max ||
+ (req_params->api_max < req_params->api_min) ||
+ (driver_data_async_cb(req_params)) ||
+ (!driver_data_async_api_cb(req_params)))
+ return false;
+
+ return true;
+}
+
+static int __request_driver_data_api(struct firmware_work *driver_work)
+{
+ struct driver_data_params *params = &driver_work->data_params;
+ const struct driver_data_req_params *req_params = ¶ms->req_params;
+ int ret;
+
+ ret = _driver_data_request_api(params, driver_work->device,
+ driver_work->name);
+ return driver_data_async_call_api_cb(params->driver_data, req_params,
+ ret);
+}
+
+static void request_driver_data_single(struct firmware_work *driver_work)
+{
+ struct driver_data_params *params = &driver_work->data_params;
+ const struct driver_data_req_params *req_params = ¶ms->req_params;
+ int ret;
+
+ ret = _request_firmware(¶ms->driver_data, driver_work->name,
+ params, driver_work->device);
+ if (ret &&
+ driver_data_param_optional(req_params) &&
+ driver_data_async_opt_cb(req_params))
+ driver_data_async_opt_call_cb(req_params, ret);
+ else
+ driver_data_async_call_cb(params->driver_data, req_params, ret);
+
+ if (!driver_data_param_keep(req_params))
+ release_firmware(params->driver_data);
+
+ put_device(driver_work->device);
+ module_put(req_params->hold_module);
+
+ kfree_const(driver_work->name);
+ kfree(driver_work);
+}
+
+/*
+ * Instead of recursion provide a deterministic limit based on the parameters,
+ * and consume less memory.
+ */
+static void request_driver_data_api(struct firmware_work *driver_work)
+{
+ struct driver_data_params *params = &driver_work->data_params;
+ struct driver_data_priv_params *priv_params = ¶ms->priv_params;
+ const struct driver_data_req_params *req_params = ¶ms->req_params;
+ int ret;
+ u8 i, limit;
+
+ limit = req_params->api_max - req_params->api_min;
+
+ for (i=0; i <= limit; i++) {
+ /*
+ * This does the real work of fetching the driver data through
+ * all the API revisions possible. If found the api and its
+ * return value are passed. If a value of 0 is passed then
+ * *really* does mean everything was peachy. If we catch
+ * -EAGAIN here it means the driver's API callback asked us to
+ * try again.
+ */
+ ret = __request_driver_data_api(driver_work);
+ if (!ret)
+ break;
+
+ priv_params->retry_api = true;
+
+ release_firmware(params->driver_data);
+
+ if (ret != -EAGAIN)
+ break;
+ }
+
+ /*
+ * Note special case:
+ *
+ * If the driver didn't like any of the driver data we gave it, it
+ * may return -EAGAIN for everything that we fed it. We will treat
+ * this as non-fatal so optional callbacks can work to address this
+ * if enabled.
+ *
+ * All non -ENOENT and -EAGAIN errors are treated as fatal, so we must
+ * return immediately. Only -ENONENT and -EAGAIN errors are treated as
+ * graceful and enables the optional callback.
+ */
+ if (ret) {
+ if (!driver_data_param_optional(req_params))
+ dev_err(driver_work->device,
+ "No API file in range %u - %u could be found, error: %d\n",
+ req_params->api_min, req_params->api_max, ret);
+ if ((ret == -ENOENT || ret == -EAGAIN) &&
+ driver_data_async_opt_cb(req_params))
+ driver_data_async_opt_call_cb(req_params, ret);
+ }
+
+ if (!driver_data_param_keep(req_params))
+ release_firmware(params->driver_data);
+
+ put_device(driver_work->device);
+ module_put(req_params->hold_module);
+
+ kfree_const(driver_work->name);
+ kfree(driver_work);
+}
+
+static void request_driver_data_work_func(struct work_struct *work)
+{
+ struct firmware_work *driver_work;
+ struct driver_data_params *data_params;
+ const struct driver_data_req_params *req_params;
+
+ driver_work = container_of(work, struct firmware_work, work);
+ data_params = &driver_work->data_params;
+ req_params = &data_params->req_params;
+
+ if (driver_data_param_uses_api(req_params))
+ request_driver_data_api(driver_work);
+ else
+ request_driver_data_single(driver_work);
+}
+
+/**
+ * driver_data_request_async - asynchronous request for a driver data file
+ * @name: name of the driver data file
+ * @req_params: driver data file request parameters, it provides all the
+ * requirements which must be met for the file being requested.
+ * @device: device for which firmware is being loaded
+ *
+ * This performs an asynchronous driver data file lookup with the requirements
+ * specified on @req_params. The request for the actual driver data file lookup
+ * will be scheduled with schedule_work() to be run at a later time. 0 is
+ * returned if we were able to asynchronously schedlue your work to be run.
+ *
+ * Reference counting is used during the duration of this scheduled call on
+ * both the device and module that made the request. This prevents any callers
+ * from freeing either the device or module prior to completion of the
+ * scheduled work.
+ *
+ * Access to the driver data file data can be accessed through an optional
+ * callback set on the @req_params. If the driver data file is optional you
+ * must specify that on @req_params and if set you may provide an alternative
+ * callback which if set would be run if the driver data file was not found.
+ *
+ * The driver data file passed to the callbacks will always be NULL unless it
+ * was found matching all the criteria on @req_params. Unless the desc->keep
+ * was set the kernel will release the driver data file for you after your
+ * callbacks were processed on the scheduled work.
+ */
+int driver_data_request_async(const char *name,
+ const struct driver_data_req_params *req_params,
+ struct device *device)
+{
+ struct firmware_work *driver_work;
+ struct firmware_work driver_work_stack = {
+ .data_params.req_params = *req_params,
+ .data_params.priv_params = {
+ .mode = DRIVER_DATA_ASYNC,
+ },
+ };
+
+ if (!device || !req_params || !name || name[0] == '\0')
+ return -EINVAL;
+
+ if (driver_data_async_opt_cb(req_params) &&
+ !driver_data_param_optional(req_params))
+ return -EINVAL;
+
+ if (!driver_data_async_cb(req_params) &&
+ !driver_data_async_api_cb(req_params))
+ return -EINVAL;
+
+ if (driver_data_param_uses_api(req_params) &&
+ !driver_data_api_versioning_ok(req_params))
+ return -EINVAL;
+
+ driver_work = kzalloc(sizeof(struct firmware_work), req_params->gfp);
+ if (!driver_work)
+ return -ENOMEM;
+
+ memcpy(driver_work, &driver_work_stack, sizeof(struct firmware_work));
+
+ driver_work->name = kstrdup_const(name, req_params->gfp);
+ if (!driver_work->name) {
+ kfree(driver_work);
+ return -ENOMEM;
+ }
+ driver_work->device = device;
+
+ if (!try_module_get(req_params->hold_module)) {
+ kfree_const(driver_work->name);
+ kfree(driver_work);
+ return -EFAULT;
+ }
+
+ get_device(driver_work->device);
+
+ INIT_WORK(&driver_work->work, request_driver_data_work_func);
+ schedule_work(&driver_work->work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(driver_data_request_async);
+
#ifdef CONFIG_PM_SLEEP
static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);
diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
index 272d618a5719..cfeaacfd74d3 100644
--- a/include/linux/driver_data.h
+++ b/include/linux/driver_data.h
@@ -5,6 +5,7 @@
#include <linux/compiler.h>
#include <linux/gfp.h>
#include <linux/device.h>
+#include <linux/firmware.h>
/*
* Driver Data internals
@@ -23,12 +24,47 @@
*/
/**
+ * struct driver_data_sync_cbs - synchronous driver data callbacks
+ * @found_cb: optional callback to be used when the driver data has been found.
+ * A callback is useful if you wish to take advantage of the feature of
+ * having your driver_data be released immediately after the callback is
+ * called, this feature is enabled by default can can be disabled by
+ * setting the flag %DRIVER_DATA_REQ_KEEP.
+ * @found_ctx: preferred context to be used as the second argument to
+ * @found_cb.
+ * @opt_fail_cb: if your driver data is optional and you have a viable approach
+ * to remedy the lack of finding a driver data with original requirements
+ * you can implement your solution on this callback.
+ * @opt_fail_ctx: context to use for @opt_fail_cb
+ *
+ * Used for specifying callbacks and contexts used for when synchronous driver
+ * data requests have completed. If no driver data is found the error will be
+ * passed on the respective callback.
+ */
+struct driver_data_sync_cbs {
+ int __must_check
+ (*found_cb)(void *context,
+ const struct firmware *driver_data,
+ int error);
+ void *found_ctx;
+
+ int __must_check (*opt_fail_cb)(void *context, int error);
+ void *opt_fail_ctx;
+};
+
+/**
* struct driver_data_async_cbs - callbacks for handling driver data requests
* @found_cb: callback to be used when the driver data has been found. A
* callback is required. If the requested driver data is found it will
* passed on the callback, using the context set on @found_ctx.
* @found_ctx: preferred context to be used as the second argument to
* @found_cb.
+ * @opt_fail_cb: if your driver data is optional and you have a viable approach
+ * to remedy the lack of finding a driver data with original requirements
+ * you can implement your solution on this callback.
+ * @opt_fail_ctx: context to use for @opt_fail_cb
+ * @found_api_cb: callback for the supported version API framework, refer to
+ * %DRIVER_DATA_REQ_USE_API_VERSIONING for details.
*
* Used for specifying callbacks and contexts used for when asynchronous driver
* data requests have completed. If no driver data is found the error will be
@@ -39,18 +75,28 @@ struct driver_data_async_cbs {
void *context,
int error);
void *found_ctx;
+
+ void (*opt_fail_cb)(void *context, int error);
+ void *opt_fail_ctx;
+
+ int __must_check
+ (*found_api_cb)(const struct firmware *driver_data,
+ void *context, int error);
};
/**
* union driver_data_cbs - callbacks for driver data request
* @async: callbacks for handling driver data when asynchronous requests
* are made.
+ * @sync: callbacks for handling driver data when synchronous requests are
+ * made.
*
* Used for placement of callbacks used for handling results from driver
* data requests.
*/
union driver_data_cbs {
struct driver_data_async_cbs async;
+ struct driver_data_sync_cbs sync;
};
/**
@@ -58,9 +104,30 @@ union driver_data_cbs {
* @DRIVER_DATA_REQ_OPTIONAL: if set it is not a hard requirement by the
* caller that the file requested be present. An error will not be recorded
* if the file is not found.
+ * @DRIVER_DATA_REQ_KEEP: by default the kernel will release the driver data
+ * for you immediately after your respective sync or async callback is
+ * called. Use this flag to annotate your requirement is for you to keep
+ * and free the driver data on your own. You must free the driver data
+ * using release_driver_data().
+ * @DRIVER_DATA_REQ_USE_API_VERSIONING: indicates that the caller has an API
+ * revision system for the the files being requested using a simple
+ * numeric scheme: there is a max API version supported and the lowest API
+ * version supported. The search starts using the filename requested on
+ * driver_data_request_async(), appending the
+ * &driver_data_req_params->api_max to it, and ending with a postfix if
+ * &driver_data_req_params->api_name_postfix is specified. If that is not
+ * available it will look for any files with API version lower than this
+ * until it reaches &driver_data_req_params->api_min. This enables
+ * chaining driver data requests easily on behalf of device drivers using
+ * a simple one digit versioning scheme. This feature requires only one
+ * file to be present given the API range, it is only required for one
+ * file in the API range to be present. If the %DRIVER_DATA_REQ_OPTIONAL
+ * flag is also enabled then all files are treated as optional.
*/
enum driver_data_reqs {
DRIVER_DATA_REQ_OPTIONAL = 1 << 0,
+ DRIVER_DATA_REQ_KEEP = 1 << 1,
+ DRIVER_DATA_REQ_USE_API_VERSIONING = 1 << 2,
};
/**
@@ -73,6 +140,12 @@ enum driver_data_reqs {
* @reqs: set of &enum driver_data_reqs flags used to configure the driver
* data request. All of the specified requirements must be met.
* @cbs: set of callbacks to use for the driver data request.
+ * @api_min: if %DRIVER_DATA_REQ_USE_API_VERSIONING is set, this represents the
+ * lowest version of API supported by the caller.
+ * @api_max: if %DRIVER_DATA_REQ_USE_API_VERSIONING is set, this represents the
+ * highest version of API supported by the caller.
+ * @api_name_postfix: optional, indicates to use this as the driver data name
+ * postfix when %DRIVER_DATA_REQ_USE_API_VERSIONING is enabled.
*
* This data structure is intended to carry all requirements and specifications
* required to complete the task to get the requested driver date file to the
@@ -82,7 +155,112 @@ struct driver_data_req_params {
struct module *hold_module;
gfp_t gfp;
u64 reqs;
+ u8 api_min;
+ u8 api_max;
+ const char *api_name_postfix;
const union driver_data_cbs cbs;
};
+/*
+ * We keep these template definitions to a minimum for the most
+ * popular requests.
+ */
+
+/* Typical sync data case */
+#define DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx) \
+ .cbs.sync.found_cb = __found_cb, \
+ .cbs.sync.found_ctx = __ctx
+
+#define DRIVER_DATA_DEFAULT_SYNC(__found_cb, __ctx) \
+ DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx)
+
+#define DRIVER_DATA_DEFAULT_SYNC_REQS(__found_cb, __ctx, __reqs) \
+ DRIVER_DATA_SYNC_FOUND(__found_cb, __ctx), \
+ .reqs = (__reqs)
+
+#define DRIVER_DATA_KEEP_SYNC(__found_cb, __ctx) \
+ DRIVER_DATA_DEFAULT_SYNC(__found_cb, __ctx), \
+ .reqs = DRIVER_DATA_REQ_KEEP
+
+/* If you have one fallback routine */
+#define DRIVER_DATA_SYNC_OPT_CB(__fail_cb, __ctx) \
+ .reqs = DRIVER_DATA_REQ_OPTIONAL, \
+ .cbs.sync.opt_fail_cb = __fail_cb, \
+ .cbs.sync.opt_fail_ctx = __ctx
+
+#define DRIVER_DATA_SYNC_OPT_CB_REQS(__fail_cb, __ctx, __reqs) \
+ .reqs = DRIVER_DATA_REQ_OPTIONAL | __reqs, \
+ .cbs.sync.opt_fail_cb = __fail_cb, \
+ .cbs.sync.opt_fail_ctx = __ctx
+
+/*
+ * Used to define the default asynchronization requirements for
+ * driver_data_request_async(). Drivers can override.
+ */
+#define DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx) \
+ .hold_module = THIS_MODULE, \
+ .gfp = GFP_KERNEL, \
+ .cbs.async = { \
+ .found_cb = __found_cb, \
+ .found_ctx = __ctx, \
+ }
+
+#define DRIVER_DATA_DEFAULT_ASYNC_OPT(__found_cb, __ctx) \
+ DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx), \
+ .reqs = DRIVER_DATA_REQ_OPTIONAL
+
+#define DRIVER_DATA_KEEP_ASYNC(__found_cb, __ctx) \
+ DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx), \
+ .reqs = DRIVER_DATA_PRIV_REQ_KEEP
+
+#define DRIVER_DATA_KEEP_ASYNC_OPT(__found_cb, __ctx) \
+ DRIVER_DATA_DEFAULT_ASYNC(__found_cb, __ctx), \
+ .reqs = DRIVER_DATA_PRIV_REQ_KEEP | \
+ DRIVER_DATA_REQ_OPTIONAL
+
+#define DRIVER_DATA_ASYNC_OPT_CB(__fail_cb, __ctx) \
+ .reqs = DRIVER_DATA_REQ_OPTIONAL, \
+ .cbs.async.opt_fail_cb = __fail_cb, \
+ .cbs.async.opt_fail_ctx = __ctx
+
+#define DRIVER_DATA_API_CB(__found_api_cb, __ctx) \
+ .hold_module = THIS_MODULE, \
+ .gfp = GFP_KERNEL, \
+ .cbs.async = { \
+ .found_api_cb = __found_api_cb, \
+ .found_ctx = __ctx, \
+ }
+
+#define DRIVER_DATA_API(__min, __max, __postfix) \
+ .reqs = DRIVER_DATA_REQ_USE_API_VERSIONING, \
+ .api_min = __min, \
+ .api_max = __max, \
+ .api_name_postfix = __postfix
+
+#if defined(CONFIG_FW_LOADER) || \
+ (defined(CONFIG_FW_LOADER_MODULE) && defined(MODULE))
+int driver_data_request_sync(const char *name,
+ const struct driver_data_req_params *params,
+ struct device *device);
+int driver_data_request_async(const char *name,
+ const struct driver_data_req_params *params,
+ struct device *device);
+#else
+static
+inline int driver_data_request_sync(const char *name,
+ const struct driver_data_req_params *params,
+ struct device *device)
+{
+ return -EINVAL;
+}
+
+static
+inline int driver_data_request_async(const char *name,
+ const struct driver_data_req_params *params,
+ struct device *device)
+{
+ return -EINVAL;
+}
+#endif
+
#endif /* _LINUX_DRIVER_DATA_H */
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index b1f9f0ccb8ac..3a71924d35d7 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -13,6 +13,8 @@ struct firmware {
const u8 *data;
struct page **pages;
+ u8 api;
+
/* firmware loader private fields */
void *priv;
};
--
2.11.0
Powered by blists - more mailing lists