summaryrefslogtreecommitdiff
path: root/drivers/staging/greybus
diff options
context:
space:
mode:
authorViresh Kumar <viresh.kumar@linaro.org>2015-08-12 09:19:33 +0530
committerGreg Kroah-Hartman <gregkh@google.com>2015-08-11 22:12:59 -0700
commit90f1b617d88f145506e9061436069583cb82d101 (patch)
tree3fb47e06cc0f6f1423be189046d8c8370799d99c /drivers/staging/greybus
parent738599c0dd7fef4d28f416ff9b0b3bc1b07468d2 (diff)
greybus: Add firmware protocol driver
This adds firmware protocol driver based on the latest specs available on mailing lists. This uses the firmware framework present in kernel. Refer Documentation/firmware_class/README on how it works. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Diffstat (limited to 'drivers/staging/greybus')
-rw-r--r--drivers/staging/greybus/Makefile1
-rw-r--r--drivers/staging/greybus/core.c9
-rw-r--r--drivers/staging/greybus/firmware.c199
-rw-r--r--drivers/staging/greybus/firmware.h16
-rw-r--r--drivers/staging/greybus/greybus.h1
-rw-r--r--drivers/staging/greybus/greybus_manifest.h2
-rw-r--r--drivers/staging/greybus/greybus_protocols.h54
7 files changed, 282 insertions, 0 deletions
diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile
index 1467c5b3fcd8..3c32d1427dc0 100644
--- a/drivers/staging/greybus/Makefile
+++ b/drivers/staging/greybus/Makefile
@@ -10,6 +10,7 @@ greybus-y := core.o \
protocol.o \
control.o \
svc.o \
+ firmware.o \
operation.o
gb-phy-y := gpbridge.o \
diff --git a/drivers/staging/greybus/core.c b/drivers/staging/greybus/core.c
index 225fa4fb5268..6edeec9c1fdf 100644
--- a/drivers/staging/greybus/core.c
+++ b/drivers/staging/greybus/core.c
@@ -300,8 +300,16 @@ static int __init gb_init(void)
goto error_svc;
}
+ retval = gb_firmware_protocol_init();
+ if (retval) {
+ pr_err("gb_firmware_protocol_init failed\n");
+ goto error_firmware;
+ }
+
return 0; /* Success */
+error_firmware:
+ gb_svc_protocol_exit();
error_svc:
gb_control_protocol_exit();
error_control:
@@ -321,6 +329,7 @@ module_init(gb_init);
static void __exit gb_exit(void)
{
+ gb_firmware_protocol_exit();
gb_svc_protocol_exit();
gb_control_protocol_exit();
gb_endo_exit();
diff --git a/drivers/staging/greybus/firmware.c b/drivers/staging/greybus/firmware.c
new file mode 100644
index 000000000000..13efaabb891b
--- /dev/null
+++ b/drivers/staging/greybus/firmware.c
@@ -0,0 +1,199 @@
+/*
+ * FIRMWARE Greybus driver.
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/firmware.h>
+
+#include "greybus.h"
+
+struct gb_firmware {
+ struct gb_connection *connection;
+ const struct firmware *fw;
+};
+
+static void free_firmware(struct gb_firmware *firmware)
+{
+ release_firmware(firmware->fw);
+ firmware->fw = NULL;
+}
+
+/* This returns path of the firmware blob on the disk */
+static int download_firmware(struct gb_firmware *firmware, u8 stage)
+{
+ struct gb_connection *connection = firmware->connection;
+ struct gb_interface *intf = connection->bundle->intf;
+ char firmware_name[28];
+
+ /* Already have a firmware, free it */
+ if (firmware->fw)
+ free_firmware(firmware);
+
+ /*
+ * Create firmware name
+ *
+ * XXX Name it properly..
+ */
+ sprintf(firmware_name, "ara:%04x:%04x:%04x:%04x:%04x.fw", intf->unipro_mfg_id,
+ intf->unipro_prod_id, intf->ara_vend_id, intf->ara_prod_id,
+ stage);
+
+ return request_firmware(&firmware->fw, firmware_name, &connection->dev);
+}
+
+static int gb_firmware_size_request(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_firmware *firmware = connection->private;
+ struct gb_firmware_size_request *size_request = op->request->payload;
+ struct gb_firmware_size_response *size_response;
+ struct device *dev = &connection->dev;
+ int ret;
+
+ if (op->request->payload_size != sizeof(*size_request)) {
+ dev_err(dev, "%s: illegal size of firmware size request (%zu != %zu)\n",
+ __func__, op->request->payload_size,
+ sizeof(*size_request));
+ return -EINVAL;
+ }
+
+ ret = download_firmware(firmware, size_request->stage);
+ if (ret) {
+ dev_err(dev, "%s: failed to download firmware (%d)\n", __func__,
+ ret);
+ return ret;
+ }
+
+ if (!gb_operation_response_alloc(op, sizeof(*size_response),
+ GFP_KERNEL)) {
+ dev_err(dev, "%s: error allocating response\n", __func__);
+ free_firmware(firmware);
+ return -ENOMEM;
+ }
+
+ size_response = op->response->payload;
+ size_response->size = cpu_to_le32(firmware->fw->size);
+
+ return 0;
+}
+
+static int gb_firmware_get_firmware(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_firmware *firmware = connection->private;
+ struct gb_firmware_get_firmware_request *firmware_request = op->request->payload;
+ struct gb_firmware_get_firmware_response *firmware_response;
+ struct device *dev = &connection->dev;
+ unsigned int offset, size;
+
+ if (op->request->payload_size != sizeof(*firmware_request)) {
+ dev_err(dev, "%s: Illegal size of get firmware request (%zu %zu)\n",
+ __func__, op->request->payload_size,
+ sizeof(*firmware_request));
+ return -EINVAL;
+ }
+
+ if (!firmware->fw) {
+ dev_err(dev, "%s: firmware not available\n", __func__);
+ return -EINVAL;
+ }
+
+ offset = le32_to_cpu(firmware_request->offset);
+ size = le32_to_cpu(firmware_request->size);
+
+ if (!gb_operation_response_alloc(op, sizeof(*firmware_response) + size,
+ GFP_KERNEL)) {
+ dev_err(dev, "%s: error allocating response\n", __func__);
+ return -ENOMEM;
+ }
+
+ firmware_response = op->response->payload;
+ memcpy(firmware_response->data, firmware->fw->data + offset, size);
+
+ return 0;
+}
+
+static int gb_firmware_ready_to_boot(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_firmware_ready_to_boot_request *rtb_request = op->request->payload;
+ struct device *dev = &connection->dev;
+ u8 stage, status;
+
+ if (op->request->payload_size != sizeof(*rtb_request)) {
+ dev_err(dev, "%s: Illegal size of ready to boot request (%zu %zu)\n",
+ __func__, op->request->payload_size,
+ sizeof(*rtb_request));
+ return -EINVAL;
+ }
+
+ stage = rtb_request->stage;
+ status = rtb_request->status;
+
+ /* Return error if the blob was invalid */
+ if (status == GB_FIRMWARE_BOOT_STATUS_INVALID)
+ return -EINVAL;
+
+ /*
+ * XXX Should we return error for insecure firmware?
+ */
+
+ return 0;
+}
+
+static int gb_firmware_request_recv(u8 type, struct gb_operation *op)
+{
+ switch (type) {
+ case GB_FIRMWARE_TYPE_FIRMWARE_SIZE:
+ return gb_firmware_size_request(op);
+ case GB_FIRMWARE_TYPE_GET_FIRMWARE:
+ return gb_firmware_get_firmware(op);
+ case GB_FIRMWARE_TYPE_READY_TO_BOOT:
+ return gb_firmware_ready_to_boot(op);
+ default:
+ dev_err(&op->connection->dev,
+ "unsupported request: %hhu\n", type);
+ return -EINVAL;
+ }
+}
+
+static int gb_firmware_connection_init(struct gb_connection *connection)
+{
+ struct gb_firmware *firmware;
+
+ firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
+ if (!firmware)
+ return -ENOMEM;
+
+ firmware->connection = connection;
+ connection->private = firmware;
+
+ return 0;
+}
+
+static void gb_firmware_connection_exit(struct gb_connection *connection)
+{
+ struct gb_firmware *firmware = connection->private;
+
+ /* Release firmware */
+ if (firmware->fw)
+ free_firmware(firmware);
+
+ connection->private = NULL;
+ kfree(firmware);
+}
+
+static struct gb_protocol firmware_protocol = {
+ .name = "firmware",
+ .id = GREYBUS_PROTOCOL_FIRMWARE,
+ .major = GB_FIRMWARE_VERSION_MAJOR,
+ .minor = GB_FIRMWARE_VERSION_MINOR,
+ .connection_init = gb_firmware_connection_init,
+ .connection_exit = gb_firmware_connection_exit,
+ .request_recv = gb_firmware_request_recv,
+};
+gb_builtin_protocol_driver(firmware_protocol);
diff --git a/drivers/staging/greybus/firmware.h b/drivers/staging/greybus/firmware.h
new file mode 100644
index 000000000000..548d297eec63
--- /dev/null
+++ b/drivers/staging/greybus/firmware.h
@@ -0,0 +1,16 @@
+/*
+ * Greybus firmware code
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef __FIRMWARE_H
+#define __FIRMWARE_H
+
+int gb_firmware_protocol_init(void);
+void gb_firmware_protocol_exit(void);
+
+#endif /* __FIRMWARE_H */
diff --git a/drivers/staging/greybus/greybus.h b/drivers/staging/greybus/greybus.h
index 2214f447df2b..0d4ca700a711 100644
--- a/drivers/staging/greybus/greybus.h
+++ b/drivers/staging/greybus/greybus.h
@@ -27,6 +27,7 @@
#include "manifest.h"
#include "endo.h"
#include "svc.h"
+#include "firmware.h"
#include "module.h"
#include "control.h"
#include "interface.h"
diff --git a/drivers/staging/greybus/greybus_manifest.h b/drivers/staging/greybus/greybus_manifest.h
index 9c4d7cae9bbb..687adf2cb645 100644
--- a/drivers/staging/greybus/greybus_manifest.h
+++ b/drivers/staging/greybus/greybus_manifest.h
@@ -43,6 +43,7 @@ enum greybus_protocol {
GREYBUS_PROTOCOL_I2S_RECEIVER = 0x12,
GREYBUS_PROTOCOL_I2S_TRANSMITTER = 0x13,
GREYBUS_PROTOCOL_SVC = 0x14,
+ GREYBUS_PROTOCOL_FIRMWARE = 0x15,
/* ... */
GREYBUS_PROTOCOL_RAW = 0xfe,
GREYBUS_PROTOCOL_VENDOR = 0xff,
@@ -70,6 +71,7 @@ enum greybus_class_type {
GREYBUS_CLASS_I2S_RECEIVER = 0x12,
GREYBUS_CLASS_I2S_TRANSMITTER = 0x13,
GREYBUS_CLASS_SVC = 0x14,
+ GREYBUS_CLASS_FIRMWARE = 0x15,
/* ... */
GREYBUS_CLASS_RAW = 0xfe,
GREYBUS_CLASS_VENDOR = 0xff,
diff --git a/drivers/staging/greybus/greybus_protocols.h b/drivers/staging/greybus/greybus_protocols.h
index b95d24bd8e62..357ecd371adb 100644
--- a/drivers/staging/greybus/greybus_protocols.h
+++ b/drivers/staging/greybus/greybus_protocols.h
@@ -146,6 +146,60 @@ struct gb_control_disconnected_request {
};
/* Control protocol [dis]connected response has no payload */
+
+/* Firmware Protocol */
+
+/* Version of the Greybus firmware protocol we support */
+#define GB_FIRMWARE_VERSION_MAJOR 0x00
+#define GB_FIRMWARE_VERSION_MINOR 0x01
+
+/* Greybus firmware request types */
+#define GB_FIRMWARE_TYPE_INVALID 0x00
+#define GB_FIRMWARE_TYPE_PROTOCOL_VERSION 0x01
+#define GB_FIRMWARE_TYPE_FIRMWARE_SIZE 0x02
+#define GB_FIRMWARE_TYPE_GET_FIRMWARE 0x03
+#define GB_FIRMWARE_TYPE_READY_TO_BOOT 0x04
+
+/* Greybus firmware boot stages */
+#define GB_FIRMWARE_BOOT_STAGE_ONE 0x01 /* Reserved for the boot ROM */
+#define GB_FIRMWARE_BOOT_STAGE_TWO 0x02 /* Firmware package to be loaded by the boot ROM */
+#define GB_FIRMWARE_BOOT_STAGE_THREE 0x03 /* Module personality package loaded by Stage 2 firmware */
+
+/* Greybus firmware ready to boot status */
+#define GB_FIRMWARE_BOOT_STATUS_INVALID 0x00 /* Firmware blob could not be validated */
+#define GB_FIRMWARE_BOOT_STATUS_INSECURE 0x01 /* Firmware blob is valid but insecure */
+#define GB_FIRMWARE_BOOT_STATUS_SECURE 0x02 /* Firmware blob is valid and secure */
+
+/* Max firmware data fetch size in bytes */
+#define GB_FIRMWARE_FETCH_MAX 2000
+
+/* Firmware protocol firmware size request/response */
+struct gb_firmware_size_request {
+ __u8 stage;
+};
+
+struct gb_firmware_size_response {
+ __le32 size;
+};
+
+/* Firmware protocol get firmware request/response */
+struct gb_firmware_get_firmware_request {
+ __le32 offset;
+ __le32 size;
+};
+
+struct gb_firmware_get_firmware_response {
+ __u8 data[0];
+};
+
+/* Firmware protocol Ready to boot request */
+struct gb_firmware_ready_to_boot_request {
+ __u8 stage;
+ __u8 status;
+};
+/* Firmware protocol Ready to boot response has no payload */
+
+
/* I2C */
/* Version of the Greybus i2c protocol we support */