greybus: Add firmware protocol driver
authorViresh Kumar <viresh.kumar@linaro.org>
Wed, 12 Aug 2015 03:49:33 +0000 (09:19 +0530)
committerGreg Kroah-Hartman <gregkh@google.com>
Wed, 12 Aug 2015 05:12:59 +0000 (22:12 -0700)
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>
drivers/staging/greybus/Makefile
drivers/staging/greybus/core.c
drivers/staging/greybus/firmware.c [new file with mode: 0644]
drivers/staging/greybus/firmware.h [new file with mode: 0644]
drivers/staging/greybus/greybus.h
drivers/staging/greybus/greybus_manifest.h
drivers/staging/greybus/greybus_protocols.h

index 1467c5b3fcd85e87d62ef846ba72d3078ebe44bf..3c32d1427dc07d0a06ce3519679b1a8159f8e309 100644 (file)
@@ -10,6 +10,7 @@ greybus-y :=  core.o          \
                protocol.o      \
                control.o       \
                svc.o           \
+               firmware.o      \
                operation.o
 
 gb-phy-y :=    gpbridge.o      \
index 225fa4fb5268223aab67d7a2c149f82c84963535..6edeec9c1fdfc9ab0abc0d740249f66ec5843528 100644 (file)
@@ -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 (file)
index 0000000..13efaab
--- /dev/null
@@ -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 (file)
index 0000000..548d297
--- /dev/null
@@ -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 */
index 2214f447df2bda8d5acfda1e937a563cbc2bc48e..0d4ca700a7111a27bf64f021811954c65b802537 100644 (file)
@@ -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"
index 9c4d7cae9bbb8e68ed5c0d05ba3b5ba83fe3d7f2..687adf2cb6458e64bda7e114ac71bfe3ab20e090 100644 (file)
@@ -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,
index b95d24bd8e6243a6adcae5d3dabc03d2ab5b53af..357ecd371adb9ad571cdf2b70da4bb9990fa4cfb 100644 (file)
@@ -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 */