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

Message ID 1495262948-1106-2-git-send-email-yi1.li@linux.intel.com
State New
Headers show

Commit Message

Li, Yi May 20, 2017, 6:49 a.m. UTC
From: Yi Li <yi1.li@linux.intel.com>

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@linux.intel.com>
---
 drivers/base/firmware_class.c | 94 +++++++++++++++++++++++++++++++++++++------
 include/linux/driver_data.h   | 10 +++++
 2 files changed, 91 insertions(+), 13 deletions(-)

Patch
diff mbox

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;
 };
 
 /*