[PATCHv2 1/3] firmware: Add streaming support for driver_data_request_sync API

From: yi1 . li
Date: Sat May 20 2017 - 02:55:04 EST


From: Yi Li <yi1.li@xxxxxxxxxxxxxxx>

By setting the driver_data_req_params req flag of DRIVER_DATA_REQ_STREAMING
and DRIVER_DATA_REQ_NO_CACHE, caller can streaming firmware image to the
pre-allocated buffer in small trunks. Caller also need to setup the
img_offset pointer and firmware image **path to avoid searching the
firmware folders repeatly. Details and examples please refer to the
trigger_config_stream function of test_driver_data.c and
fpga_mgr_firmware_stream function of fpga_mgr.c.

Signed-off-by: Yi Li <yi1.li@xxxxxxxxxxxxxxx>
---
drivers/base/firmware_class.c | 94 +++++++++++++++++++++++++++++++++++++------
include/linux/driver_data.h | 10 +++++
2 files changed, 91 insertions(+), 13 deletions(-)

diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index 461c7c2..9a63124 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -152,6 +152,20 @@ struct driver_data_params {
DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT, \
}

+#define __DATA_REQ_FIRMWARE_STREAM_BUF(buf, size, offset, path) \
+ .req_params = { \
+ .reqs = DRIVER_DATA_REQ_NO_CACHE | \
+ DRIVER_DATA_REQ_STREAMING, \
+ .alloc_buf = buf, \
+ .alloc_buf_size = size, \
+ .img_offset = offset, \
+ .path = path, \
+ }, \
+ .priv_params = { \
+ .priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK | \
+ DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT, \
+ }
+
#define __DATA_REQ_FIRMWARE_NOWAIT(module, uevent, gfp, async_cb, async_ctx) \
.req_params = { \
.hold_module = module, \
@@ -180,6 +194,8 @@ struct driver_data_params {
(!!((params)->priv_reqs & DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT))
#define driver_data_param_nocache(params) \
(!!((params)->reqs & DRIVER_DATA_REQ_NO_CACHE))
+#define driver_data_param_streaming(params) \
+ (!!((params)->reqs & DRIVER_DATA_REQ_STREAMING))

#define driver_data_param_optional(params) \
(!!((params)->reqs & DRIVER_DATA_REQ_OPTIONAL))
@@ -625,14 +641,18 @@ module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644);
MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path");

static int
-fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf)
+fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf,
+ struct driver_data_params *data_params)
{
loff_t size;
int i, len;
int rc = -ENOENT;
- char *path;
+ char **path;
+ char *local_path = NULL;
+ loff_t *offset = data_params->req_params.img_offset;
enum kernel_read_file_id id = READING_FIRMWARE;
size_t msize = INT_MAX;
+ struct file *file;

/* Already populated data member means we're loading into a buffer */
if (buf->data) {
@@ -640,16 +660,58 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf)
msize = buf->allocated_size;
}

- path = __getname();
- if (!path)
+ buf->size = 0;
+
+ /* Save path for streaming case */
+ if (driver_data_param_streaming(&data_params->req_params)) {
+ path = data_params->req_params.path;
+ if (!path) {
+ dev_err(device, "req_params.path not initialized\n");
+ rc = -ENOENT;
+ return rc;
+ }
+ } else {
+ path = &local_path;
+ }
+
+streaming:
+ /* Skip the repeating folder searching, direct to load*/
+ if (driver_data_param_streaming(&data_params->req_params) && *path) {
+ if (!offset) {
+ dev_err(device, "img_offset not initialized\n");
+ rc = -ENOENT;
+ return rc;
+ }
+
+ file = filp_open(*path, O_RDONLY, 0);
+ if (IS_ERR(file)) {
+ rc = -ENOENT;
+ return rc;
+ }
+
+ buf->size = kernel_read(file, *offset, (char *)buf->data,
+ msize);
+ fput(file);
+ if (buf->size < msize) {
+ fw_state_done(&buf->fw_st);
+ __putname(*path);
+ }
+ return 0;
+ }
+
+ /* First time to load the firmware */
+ *path = __getname();
+ if (!*path) {
+ dev_err(device, "cannot getname\n");
return -ENOMEM;
+ }

for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
/* skip the unset customized path */
if (!fw_path[i][0])
continue;

- len = snprintf(path, PATH_MAX, "%s/%s",
+ len = snprintf(*path, PATH_MAX, "%s/%s",
fw_path[i], buf->fw_id);
if (len >= PATH_MAX) {
rc = -ENAMETOOLONG;
@@ -657,23 +719,29 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf)
}

buf->size = 0;
- rc = kernel_read_file_from_path(path, &buf->data, &size, msize,
- id);
+ rc = kernel_read_file_from_path(*path, &buf->data, &size,
+ msize, id);
if (rc) {
- if (rc == -ENOENT)
+ if (driver_data_param_streaming(&data_params->req_params)
+ && (rc == -EFBIG)) {
+ rc = 0;
+ goto streaming;
+ } else if (rc == -ENOENT)
dev_dbg(device, "loading %s failed with error %d\n",
- path, rc);
+ *path, rc);
else
dev_warn(device, "loading %s failed with error %d\n",
- path, rc);
+ *path, rc);
continue;
}
dev_dbg(device, "direct-loading %s\n", buf->fw_id);
buf->size = size;
- fw_state_done(&buf->fw_st);
+ if (buf->size < msize) {
+ fw_state_done(&buf->fw_st);
+ __putname(*path);
+ }
break;
}
- __putname(path);

return rc;
}
@@ -1466,7 +1534,7 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
goto out;
}

- ret = fw_get_filesystem_firmware(device, fw->priv);
+ ret = fw_get_filesystem_firmware(device, fw->priv, data_params);
if (ret) {
if (!driver_data_param_optional(&data_params->req_params))
dev_warn(device,
diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h
index a3a5fbe..e9e828a 100644
--- a/include/linux/driver_data.h
+++ b/include/linux/driver_data.h
@@ -120,12 +120,16 @@ union driver_data_cbs {
* @DRIVER_DATA_REQ_NO_CACHE: indicates that the driver data request
* should not set up and use the internal caching mechanism to assist
* drivers from fetching driver data at resume time after suspend.
+ * @DRIVER_DATA_REQ_STREAMING: indicates that the driver data request
+ * is in the streaming mode, the buffer is allocated outside the
+ * firmware driver.
*/
enum driver_data_reqs {
DRIVER_DATA_REQ_OPTIONAL = 1 << 0,
DRIVER_DATA_REQ_KEEP = 1 << 1,
DRIVER_DATA_REQ_USE_API_VERSIONING = 1 << 2,
DRIVER_DATA_REQ_NO_CACHE = 1 << 3,
+ DRIVER_DATA_REQ_STREAMING = 1 << 4,
};

/**
@@ -147,6 +151,10 @@ enum driver_data_reqs {
* @alloc_buf: pointer of pointer to the buffer area allocated by the caller
* so we can place the respective driver data
* @alloc_buf_size: size of the @alloc_buf
+ * @img_offset: optional for streaming mode, define which offset address the
+ * firmware should be read from
+ * @path: optional for streaming mode, save the file path to avoid search over
+ * and over again
*
* 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
@@ -162,6 +170,8 @@ struct driver_data_req_params {
const union driver_data_cbs cbs;
void **alloc_buf;
size_t alloc_buf_size;
+ loff_t *img_offset;
+ char **path;
};

/*
--
2.7.4