summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorViresh Kumar <viresh.kumar@linaro.org>2016-04-28 10:06:38 +0530
committerGreg Kroah-Hartman <gregkh@google.com>2016-04-29 14:34:32 -0700
commitcca22207673896262443c72f19b049552d65f88e (patch)
tree210b5f5de80bdd89340301f5cf95574ef42ac5cd /drivers
parent7adb32b429ce38bae39e277ae2cc37c93770104f (diff)
downloadlinux-cca22207673896262443c72f19b049552d65f88e.tar.gz
linux-cca22207673896262443c72f19b049552d65f88e.tar.bz2
linux-cca22207673896262443c72f19b049552d65f88e.zip
greybus: firmware: Add firmware-download protocol driver
This patch adds Firmware Download Protocol support to firmware core, which allows an Interface to download a firmware package over Unipro. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> Reviewed-by: Jun Li <li_jun@projectara.com> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/staging/greybus/Makefile2
-rw-r--r--drivers/staging/greybus/firmware.h20
-rw-r--r--drivers/staging/greybus/fw-core.c37
-rw-r--r--drivers/staging/greybus/fw-download.c299
-rw-r--r--drivers/staging/greybus/greybus_protocols.h37
5 files changed, 392 insertions, 3 deletions
diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile
index 1a120d928485..43c0c9910de1 100644
--- a/drivers/staging/greybus/Makefile
+++ b/drivers/staging/greybus/Makefile
@@ -39,7 +39,7 @@ gb-audio-apbridgea-y := audio_apbridgea.o
gb-audio-manager-y += audio_manager.o
gb-audio-manager-y += audio_manager_module.o
gb-camera-y := camera.o
-gb-firmware-y := fw-core.o
+gb-firmware-y := fw-core.o fw-download.o
obj-m += greybus.o
obj-m += gb-phy.o
diff --git a/drivers/staging/greybus/firmware.h b/drivers/staging/greybus/firmware.h
new file mode 100644
index 000000000000..c5736d5f96a2
--- /dev/null
+++ b/drivers/staging/greybus/firmware.h
@@ -0,0 +1,20 @@
+/*
+ * Greybus Firmware Management Header
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef __FIRMWARE_H
+#define __FIRMWARE_H
+
+#include "greybus.h"
+
+/* Firmware Download Protocol specific functions */
+int gb_fw_download_request_handler(struct gb_operation *op);
+int gb_fw_download_connection_init(struct gb_connection *connection);
+void gb_fw_download_connection_exit(struct gb_connection *connection);
+
+#endif /* __FIRMWARE_H */
diff --git a/drivers/staging/greybus/fw-core.c b/drivers/staging/greybus/fw-core.c
index 4720d595cc2b..a6865276855a 100644
--- a/drivers/staging/greybus/fw-core.c
+++ b/drivers/staging/greybus/fw-core.c
@@ -9,9 +9,11 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/firmware.h>
+#include "firmware.h"
#include "greybus.h"
struct gb_fw_core {
+ struct gb_connection *download_connection;
struct gb_connection *mgmt_connection;
};
@@ -52,11 +54,30 @@ static int gb_fw_core_probe(struct gb_bundle *bundle,
dev_err(&bundle->dev,
"failed to create management connection (%d)\n",
ret);
- goto err_free_fw_core;
+ goto err_destroy_connections;
}
fw_core->mgmt_connection = connection;
break;
+ case GREYBUS_PROTOCOL_FW_DOWNLOAD:
+ /* Disallow multiple Firmware Download CPorts */
+ if (fw_core->download_connection) {
+ dev_err(&bundle->dev,
+ "multiple download CPorts found\n");
+ ret = -EINVAL;
+ goto err_destroy_connections;
+ }
+
+ connection = gb_connection_create(bundle, cport_id,
+ gb_fw_download_request_handler);
+ if (IS_ERR(connection)) {
+ dev_err(&bundle->dev, "failed to create download connection (%ld)\n",
+ PTR_ERR(connection));
+ } else {
+ fw_core->download_connection = connection;
+ }
+
+ break;
default:
dev_err(&bundle->dev, "invalid protocol id (0x%02x)\n",
protocol_id);
@@ -69,7 +90,16 @@ static int gb_fw_core_probe(struct gb_bundle *bundle,
if (!fw_core->mgmt_connection) {
dev_err(&bundle->dev, "missing management connection\n");
ret = -ENODEV;
- goto err_free_fw_core;
+ goto err_destroy_connections;
+ }
+
+ ret = gb_fw_download_connection_init(fw_core->download_connection);
+ if (ret) {
+ /* We may still be able to work with the Interface */
+ dev_err(&bundle->dev, "failed to initialize firmware download connection, disable it (%d)\n",
+ ret);
+ gb_connection_destroy(fw_core->download_connection);
+ fw_core->download_connection = NULL;
}
greybus_set_drvdata(bundle, fw_core);
@@ -77,6 +107,7 @@ static int gb_fw_core_probe(struct gb_bundle *bundle,
return 0;
err_destroy_connections:
+ gb_connection_destroy(fw_core->download_connection);
gb_connection_destroy(fw_core->mgmt_connection);
err_free_fw_core:
kfree(fw_core);
@@ -88,6 +119,8 @@ static void gb_fw_core_disconnect(struct gb_bundle *bundle)
{
struct gb_fw_core *fw_core = greybus_get_drvdata(bundle);
+ gb_fw_download_connection_exit(fw_core->download_connection);
+ gb_connection_destroy(fw_core->download_connection);
gb_connection_destroy(fw_core->mgmt_connection);
kfree(fw_core);
diff --git a/drivers/staging/greybus/fw-download.c b/drivers/staging/greybus/fw-download.c
new file mode 100644
index 000000000000..836b02b9d7c6
--- /dev/null
+++ b/drivers/staging/greybus/fw-download.c
@@ -0,0 +1,299 @@
+/*
+ * Greybus Firmware Download Protocol Driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/firmware.h>
+#include "firmware.h"
+#include "greybus.h"
+
+struct fw_request {
+ u8 firmware_id;
+ char name[56]; /* ara_%08x_%08x_%08x_%08x_%s.tftf */
+ const struct firmware *fw;
+ struct list_head node;
+};
+
+struct fw_download {
+ struct device *parent;
+ struct gb_connection *connection;
+ struct list_head fw_requests;
+ struct ida id_map;
+};
+
+static struct fw_request *match_firmware(struct fw_download *fw_download,
+ u8 firmware_id)
+{
+ struct fw_request *fw_req;
+
+ list_for_each_entry(fw_req, &fw_download->fw_requests, node) {
+ if (fw_req->firmware_id == firmware_id)
+ return fw_req;
+ }
+
+ return NULL;
+}
+
+static void free_firmware(struct fw_download *fw_download,
+ struct fw_request *fw_req)
+{
+ list_del(&fw_req->node);
+ release_firmware(fw_req->fw);
+ ida_simple_remove(&fw_download->id_map, fw_req->firmware_id);
+ kfree(fw_req);
+}
+
+/* This returns path of the firmware blob on the disk */
+static struct fw_request *find_firmware(struct fw_download *fw_download,
+ const char *tag)
+{
+ struct gb_interface *intf = fw_download->connection->bundle->intf;
+ struct fw_request *fw_req;
+ int ret;
+
+ fw_req = kzalloc(sizeof(*fw_req), GFP_KERNEL);
+ if (!fw_req)
+ return ERR_PTR(-ENOMEM);
+
+ /* Allocate ids from 1 to 255 (u8-max), 0 is an invalid id */
+ ret = ida_simple_get(&fw_download->id_map, 1, 256, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(fw_download->parent,
+ "failed to allocate firmware id (%d)\n", ret);
+ goto err_free_req;
+ }
+ fw_req->firmware_id = ret;
+
+ snprintf(fw_req->name, sizeof(fw_req->name),
+ "ara_%08x_%08x_%08x_%08x_%s.tftf",
+ intf->ddbl1_manufacturer_id, intf->ddbl1_product_id,
+ intf->vendor_id, intf->product_id, tag);
+
+ dev_info(fw_download->parent, "Requested firmware package '%s'\n",
+ fw_req->name);
+
+ ret = request_firmware(&fw_req->fw, fw_req->name, fw_download->parent);
+ if (ret) {
+ dev_err(fw_download->parent,
+ "firmware request failed for %s (%d)\n", fw_req->name,
+ ret);
+ goto err_free_id;
+ }
+
+ list_add(&fw_req->node, &fw_download->fw_requests);
+
+ return fw_req;
+
+err_free_id:
+ ida_simple_remove(&fw_download->id_map, fw_req->firmware_id);
+err_free_req:
+ kfree(fw_req);
+
+ return ERR_PTR(ret);
+}
+
+static int fw_download_find_firmware(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct fw_download *fw_download = gb_connection_get_data(connection);
+ struct gb_fw_download_find_firmware_request *request;
+ struct gb_fw_download_find_firmware_response *response;
+ struct fw_request *fw_req;
+ const char *tag;
+
+ if (op->request->payload_size != sizeof(*request)) {
+ dev_err(fw_download->parent,
+ "illegal size of find firmware request (%zu != %zu)\n",
+ op->request->payload_size, sizeof(*request));
+ return -EINVAL;
+ }
+
+ request = op->request->payload;
+ tag = (const char *)(request->firmware_tag);
+
+ /* firmware_tag should be null-terminated */
+ if (strnlen(tag, GB_FIRMWARE_TAG_MAX_LEN) == GB_FIRMWARE_TAG_MAX_LEN) {
+ dev_err(fw_download->parent,
+ "firmware-tag is not null-terminated\n");
+ return -EINVAL;
+ }
+
+ fw_req = find_firmware(fw_download, request->firmware_tag);
+ if (IS_ERR(fw_req))
+ return PTR_ERR(fw_req);
+
+ if (!gb_operation_response_alloc(op, sizeof(*response), GFP_KERNEL)) {
+ dev_err(fw_download->parent, "error allocating response\n");
+ free_firmware(fw_download, fw_req);
+ return -ENOMEM;
+ }
+
+ response = op->response->payload;
+ response->firmware_id = fw_req->firmware_id;
+ response->size = cpu_to_le32(fw_req->fw->size);
+
+ dev_dbg(fw_download->parent,
+ "firmware size is %zu bytes\n", fw_req->fw->size);
+
+ return 0;
+}
+
+static int fw_download_fetch_firmware(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct fw_download *fw_download = gb_connection_get_data(connection);
+ struct gb_fw_download_fetch_firmware_request *request;
+ struct gb_fw_download_fetch_firmware_response *response;
+ struct fw_request *fw_req;
+ const struct firmware *fw;
+ unsigned int offset, size;
+ u8 firmware_id;
+
+ if (op->request->payload_size != sizeof(*request)) {
+ dev_err(fw_download->parent,
+ "Illegal size of fetch firmware request (%zu %zu)\n",
+ op->request->payload_size, sizeof(*request));
+ return -EINVAL;
+ }
+
+ request = op->request->payload;
+ offset = le32_to_cpu(request->offset);
+ size = le32_to_cpu(request->size);
+ firmware_id = request->firmware_id;
+
+ fw_req = match_firmware(fw_download, firmware_id);
+ if (!fw_req) {
+ dev_err(fw_download->parent,
+ "firmware not available for id: %02u\n", firmware_id);
+ return -EINVAL;
+ }
+
+ fw = fw_req->fw;
+
+ if (offset >= fw->size || size > fw->size - offset) {
+ dev_err(fw_download->parent,
+ "bad fetch firmware request (offs = %u, size = %u)\n",
+ offset, size);
+ return -EINVAL;
+ }
+
+ if (!gb_operation_response_alloc(op, sizeof(*response) + size,
+ GFP_KERNEL)) {
+ dev_err(fw_download->parent,
+ "error allocating fetch firmware response\n");
+ return -ENOMEM;
+ }
+
+ response = op->response->payload;
+ memcpy(response->data, fw->data + offset, size);
+
+ dev_dbg(fw_download->parent,
+ "responding with firmware (offs = %u, size = %u)\n", offset,
+ size);
+
+ return 0;
+}
+
+static int fw_download_release_firmware(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct fw_download *fw_download = gb_connection_get_data(connection);
+ struct gb_fw_download_release_firmware_request *request;
+ struct fw_request *fw_req;
+ u8 firmware_id;
+
+ if (op->request->payload_size != sizeof(*request)) {
+ dev_err(fw_download->parent,
+ "Illegal size of release firmware request (%zu %zu)\n",
+ op->request->payload_size, sizeof(*request));
+ return -EINVAL;
+ }
+
+ request = op->request->payload;
+ firmware_id = request->firmware_id;
+
+ fw_req = match_firmware(fw_download, firmware_id);
+ if (!fw_req) {
+ dev_err(fw_download->parent,
+ "firmware not available for id: %02u\n", firmware_id);
+ return -EINVAL;
+ }
+
+ free_firmware(fw_download, fw_req);
+
+ dev_dbg(fw_download->parent, "release firmware\n");
+
+ return 0;
+}
+
+int gb_fw_download_request_handler(struct gb_operation *op)
+{
+ u8 type = op->type;
+
+ switch (type) {
+ case GB_FW_DOWNLOAD_TYPE_FIND_FIRMWARE:
+ return fw_download_find_firmware(op);
+ case GB_FW_DOWNLOAD_TYPE_FETCH_FIRMWARE:
+ return fw_download_fetch_firmware(op);
+ case GB_FW_DOWNLOAD_TYPE_RELEASE_FIRMWARE:
+ return fw_download_release_firmware(op);
+ default:
+ dev_err(&op->connection->bundle->dev,
+ "unsupported request: %u\n", type);
+ return -EINVAL;
+ }
+}
+
+int gb_fw_download_connection_init(struct gb_connection *connection)
+{
+ struct fw_download *fw_download;
+ int ret;
+
+ if (!connection)
+ return 0;
+
+ fw_download = kzalloc(sizeof(*fw_download), GFP_KERNEL);
+ if (!fw_download)
+ return -ENOMEM;
+
+ fw_download->parent = &connection->bundle->dev;
+ INIT_LIST_HEAD(&fw_download->fw_requests);
+ ida_init(&fw_download->id_map);
+ gb_connection_set_data(connection, fw_download);
+ fw_download->connection = connection;
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto err_destroy_id_map;
+
+ return 0;
+
+err_destroy_id_map:
+ ida_destroy(&fw_download->id_map);
+ kfree(fw_download);
+
+ return ret;
+}
+
+void gb_fw_download_connection_exit(struct gb_connection *connection)
+{
+ struct fw_download *fw_download;
+ struct fw_request *fw_req, *tmp;
+
+ if (!connection)
+ return;
+
+ fw_download = gb_connection_get_data(connection);
+ gb_connection_disable(fw_download->connection);
+
+ /* Release pending firmware packages */
+ list_for_each_entry_safe(fw_req, tmp, &fw_download->fw_requests, node)
+ free_firmware(fw_download, fw_req);
+
+ ida_destroy(&fw_download->id_map);
+ kfree(fw_download);
+}
diff --git a/drivers/staging/greybus/greybus_protocols.h b/drivers/staging/greybus/greybus_protocols.h
index f1e8b345a761..b73acc0c3ee5 100644
--- a/drivers/staging/greybus/greybus_protocols.h
+++ b/drivers/staging/greybus/greybus_protocols.h
@@ -213,6 +213,43 @@ struct gb_control_timesync_authoritative_request {
#define GB_APB_REQUEST_CPORT_FEAT_EN 0x0b
#define GB_APB_REQUEST_CPORT_FEAT_DIS 0x0c
+/* Firmware Download Protocol */
+
+/* Request Types */
+#define GB_FW_DOWNLOAD_TYPE_FIND_FIRMWARE 0x01
+#define GB_FW_DOWNLOAD_TYPE_FETCH_FIRMWARE 0x02
+#define GB_FW_DOWNLOAD_TYPE_RELEASE_FIRMWARE 0x03
+
+#define GB_FIRMWARE_TAG_MAX_LEN 10
+
+/* firmware download find firmware request/response */
+struct gb_fw_download_find_firmware_request {
+ __u8 firmware_tag[GB_FIRMWARE_TAG_MAX_LEN];
+} __packed;
+
+struct gb_fw_download_find_firmware_response {
+ __u8 firmware_id;
+ __le32 size;
+} __packed;
+
+/* firmware download fetch firmware request/response */
+struct gb_fw_download_fetch_firmware_request {
+ __u8 firmware_id;
+ __le32 offset;
+ __le32 size;
+} __packed;
+
+struct gb_fw_download_fetch_firmware_response {
+ __u8 data[0];
+} __packed;
+
+/* firmware download release firmware request */
+struct gb_fw_download_release_firmware_request {
+ __u8 firmware_id;
+} __packed;
+/* firmware download release firmware response has no payload */
+
+
/* Bootrom Protocol */
/* Version of the Greybus bootrom protocol we support */