[PATCH v4 1/3] firmware: add new extensible firmware API - drvdata
From: Luis R. Rodriguez
Date: Thu Jan 12 2017 - 10:09:28 EST
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@xxxxxxxxxx>
---
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@xxxxxxxxxxxxx>
M: Luis R. Rodriguez <mcgrof@xxxxxxxxxx>
L: linux-kernel@xxxxxxxxxxxxxxx
@@ -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@xxxxxxxxxx>
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@xxxxxxxxxx>
*
* 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 = ¶ms->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 = ¶ms->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@xxxxxxxxxx>
+ *
+ * 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