greybus: fw-management: Add firmware-management protocol driver
authorViresh Kumar <viresh.kumar@linaro.org>
Sat, 14 May 2016 18:12:21 +0000 (23:42 +0530)
committerGreg Kroah-Hartman <gregkh@google.com>
Sat, 14 May 2016 22:23:52 +0000 (00:23 +0200)
This patch adds Firmware Management Protocol support to firmware core,
which allows the AP to manage firmware on an Interface.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Reviewed-by: Jun Li <li_jun@projectara.com>
Tested-by: Karthik Ravi Shankar <karthikrs@google.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
drivers/staging/greybus/Makefile
drivers/staging/greybus/firmware.h
drivers/staging/greybus/fw-core.c
drivers/staging/greybus/fw-management.c [new file with mode: 0644]
drivers/staging/greybus/greybus_firmware.h [new file with mode: 0644]
drivers/staging/greybus/greybus_protocols.h

index 35fc6bf..608bd51 100644 (file)
@@ -32,7 +32,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 fw-download.o
+gb-firmware-y := fw-core.o fw-download.o fw-management.o
 gb-spilib-y := spilib.o
 gb-sdio-y := sdio.o
 gb-uart-y := uart.o
index c5736d5..a82d020 100644 (file)
 
 #include "greybus.h"
 
+/* Firmware Management Protocol specific functions */
+int fw_mgmt_init(void);
+void fw_mgmt_exit(void);
+struct gb_connection *to_fw_mgmt_connection(struct device *dev);
+int gb_fw_mgmt_request_handler(struct gb_operation *op);
+int gb_fw_mgmt_connection_init(struct gb_connection *connection);
+void gb_fw_mgmt_connection_exit(struct gb_connection *connection);
+
 /* 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);
index a686527..0a456c5 100644 (file)
@@ -17,6 +17,13 @@ struct gb_fw_core {
        struct gb_connection    *mgmt_connection;
 };
 
+struct gb_connection *to_fw_mgmt_connection(struct device *dev)
+{
+       struct gb_fw_core *fw_core = dev_get_drvdata(dev);
+
+       return fw_core->mgmt_connection;
+}
+
 static int gb_fw_core_probe(struct gb_bundle *bundle,
                            const struct greybus_bundle_id *id)
 {
@@ -48,7 +55,7 @@ static int gb_fw_core_probe(struct gb_bundle *bundle,
                        }
 
                        connection = gb_connection_create(bundle, cport_id,
-                                                         NULL);
+                                               gb_fw_mgmt_request_handler);
                        if (IS_ERR(connection)) {
                                ret = PTR_ERR(connection);
                                dev_err(&bundle->dev,
@@ -102,13 +109,23 @@ static int gb_fw_core_probe(struct gb_bundle *bundle,
                fw_core->download_connection = NULL;
        }
 
+       ret = gb_fw_mgmt_connection_init(fw_core->mgmt_connection);
+       if (ret) {
+               /* We may still be able to work with the Interface */
+               dev_err(&bundle->dev, "failed to initialize firmware management connection, disable it (%d)\n",
+                       ret);
+               goto err_exit_connections;
+       }
+
        greybus_set_drvdata(bundle, fw_core);
 
        return 0;
 
+err_exit_connections:
+       gb_fw_download_connection_exit(fw_core->download_connection);
 err_destroy_connections:
-       gb_connection_destroy(fw_core->download_connection);
        gb_connection_destroy(fw_core->mgmt_connection);
+       gb_connection_destroy(fw_core->download_connection);
 err_free_fw_core:
        kfree(fw_core);
 
@@ -119,9 +136,11 @@ static void gb_fw_core_disconnect(struct gb_bundle *bundle)
 {
        struct gb_fw_core *fw_core = greybus_get_drvdata(bundle);
 
+       gb_fw_mgmt_connection_exit(fw_core->mgmt_connection);
        gb_fw_download_connection_exit(fw_core->download_connection);
-       gb_connection_destroy(fw_core->download_connection);
+
        gb_connection_destroy(fw_core->mgmt_connection);
+       gb_connection_destroy(fw_core->download_connection);
 
        kfree(fw_core);
 }
@@ -140,13 +159,28 @@ static struct greybus_driver gb_fw_core_driver = {
 
 static int fw_core_init(void)
 {
-       return greybus_register(&gb_fw_core_driver);
+       int ret;
+
+       ret = fw_mgmt_init();
+       if (ret) {
+               pr_err("Failed to initialize fw-mgmt core (%d)\n", ret);
+               return ret;
+       }
+
+       ret = greybus_register(&gb_fw_core_driver);
+       if (ret) {
+               fw_mgmt_exit();
+               return ret;
+       }
+
+       return 0;
 }
 module_init(fw_core_init);
 
 static void __exit fw_core_exit(void)
 {
        greybus_deregister(&gb_fw_core_driver);
+       fw_mgmt_exit();
 }
 module_exit(fw_core_exit);
 
diff --git a/drivers/staging/greybus/fw-management.c b/drivers/staging/greybus/fw-management.c
new file mode 100644 (file)
index 0000000..2db5a11
--- /dev/null
@@ -0,0 +1,564 @@
+/*
+ * Greybus Firmware Management Protocol Driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/cdev.h>
+#include <linux/completion.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+
+#include "firmware.h"
+#include "greybus_firmware.h"
+#include "greybus.h"
+
+#define FW_MGMT_TIMEOUT_MS             1000
+
+struct fw_mgmt {
+       struct device           *parent;
+       struct gb_connection    *connection;
+       /* Common id-map for interface and backend firmware requests */
+       struct ida              id_map;
+       struct mutex            mutex;
+       struct completion       completion;
+       struct cdev             cdev;
+       struct device           *class_device;
+       dev_t                   dev_num;
+       unsigned int            timeout_jiffies;
+
+       /* Interface Firmware specific fields */
+       u8                      intf_fw_request_id;
+       u8                      intf_fw_status;
+       u16                     intf_fw_major;
+       u16                     intf_fw_minor;
+
+       /* Backend Firmware specific fields */
+       u8                      backend_fw_request_id;
+       u8                      backend_fw_status;
+};
+
+/*
+ * Number of minor devices this driver supports.
+ * There will be exactly one required per Interface.
+ */
+#define NUM_MINORS             U8_MAX
+
+static struct class *fw_mgmt_class;
+static dev_t fw_mgmt_dev_num;
+static DEFINE_IDA(fw_mgmt_minors_map);
+
+static int fw_mgmt_interface_fw_version_operation(struct fw_mgmt *fw_mgmt,
+               struct fw_mgmt_ioc_get_fw *fw_info)
+{
+       struct gb_connection *connection = fw_mgmt->connection;
+       struct gb_fw_mgmt_interface_fw_version_response response;
+       int ret;
+
+       ret = gb_operation_sync(connection,
+                               GB_FW_MGMT_TYPE_INTERFACE_FW_VERSION, NULL, 0,
+                               &response, sizeof(response));
+       if (ret) {
+               dev_err(fw_mgmt->parent,
+                       "failed to get interface firmware version (%d)\n", ret);
+               return ret;
+       }
+
+       fw_info->major = le16_to_cpu(response.major);
+       fw_info->minor = le16_to_cpu(response.minor);
+
+       strncpy(fw_info->firmware_tag, response.firmware_tag,
+               GB_FIRMWARE_TAG_MAX_LEN);
+
+       /*
+        * The firmware-tag should be NULL terminated, otherwise throw error but
+        * don't fail.
+        */
+       if (fw_info->firmware_tag[GB_FIRMWARE_TAG_MAX_LEN - 1] != '\0') {
+               dev_err(fw_mgmt->parent,
+                       "fw-version: firmware-tag is not NULL terminated\n");
+               fw_info->firmware_tag[GB_FIRMWARE_TAG_MAX_LEN - 1] = '\0';
+       }
+
+       return 0;
+}
+
+static int fw_mgmt_load_and_validate_operation(struct fw_mgmt *fw_mgmt,
+                                              u8 load_method, const char *tag)
+{
+       struct gb_fw_mgmt_load_and_validate_fw_request request;
+       int ret;
+
+       if (load_method != GB_FW_LOAD_METHOD_UNIPRO &&
+           load_method != GB_FW_LOAD_METHOD_INTERNAL) {
+               dev_err(fw_mgmt->parent,
+                       "invalid load-method (%d)\n", load_method);
+               return -EINVAL;
+       }
+
+       request.load_method = load_method;
+       strncpy(request.firmware_tag, tag, GB_FIRMWARE_TAG_MAX_LEN);
+
+       /*
+        * The firmware-tag should be NULL terminated, otherwise throw error and
+        * fail.
+        */
+       if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_LEN - 1] != '\0') {
+               dev_err(fw_mgmt->parent, "load-and-validate: firmware-tag is not NULL terminated\n");
+               return -EINVAL;
+       }
+
+       /* Allocate ids from 1 to 255 (u8-max), 0 is an invalid id */
+       ret = ida_simple_get(&fw_mgmt->id_map, 1, 256, GFP_KERNEL);
+       if (ret < 0) {
+               dev_err(fw_mgmt->parent, "failed to allocate request id (%d)\n",
+                       ret);
+               return ret;
+       }
+
+       fw_mgmt->intf_fw_request_id = ret;
+       request.request_id = ret;
+
+       ret = gb_operation_sync(fw_mgmt->connection,
+                               GB_FW_MGMT_TYPE_LOAD_AND_VALIDATE_FW, &request,
+                               sizeof(request), NULL, 0);
+       if (ret) {
+               ida_simple_remove(&fw_mgmt->id_map,
+                                 fw_mgmt->intf_fw_request_id);
+               fw_mgmt->intf_fw_request_id = 0;
+               dev_err(fw_mgmt->parent,
+                       "load and validate firmware request failed (%d)\n",
+                       ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int fw_mgmt_interface_fw_loaded_operation(struct gb_operation *op)
+{
+       struct gb_connection *connection = op->connection;
+       struct fw_mgmt *fw_mgmt = gb_connection_get_data(connection);
+       struct gb_fw_mgmt_loaded_fw_request *request;
+
+       /* No pending load and validate request ? */
+       if (!fw_mgmt->intf_fw_request_id) {
+               dev_err(fw_mgmt->parent,
+                       "unexpected firmware loaded request received\n");
+               return -ENODEV;
+       }
+
+       if (op->request->payload_size != sizeof(*request)) {
+               dev_err(fw_mgmt->parent, "illegal size of firmware loaded request (%zu != %zu)\n",
+                       op->request->payload_size, sizeof(*request));
+               return -EINVAL;
+       }
+
+       request = op->request->payload;
+
+       /* Invalid request-id ? */
+       if (request->request_id != fw_mgmt->intf_fw_request_id) {
+               dev_err(fw_mgmt->parent, "invalid request id for firmware loaded request (%02u != %02u)\n",
+                       fw_mgmt->intf_fw_request_id, request->request_id);
+               return -ENODEV;
+       }
+
+       ida_simple_remove(&fw_mgmt->id_map, fw_mgmt->intf_fw_request_id);
+       fw_mgmt->intf_fw_request_id = 0;
+       fw_mgmt->intf_fw_status = request->status;
+       fw_mgmt->intf_fw_major = le16_to_cpu(request->major);
+       fw_mgmt->intf_fw_minor = le16_to_cpu(request->minor);
+
+       if (fw_mgmt->intf_fw_status == GB_FW_LOAD_STATUS_FAILED)
+               dev_err(fw_mgmt->parent,
+                       "failed to load interface firmware, status:%02x\n",
+                       fw_mgmt->intf_fw_status);
+       else if (fw_mgmt->intf_fw_status == GB_FW_LOAD_STATUS_VALIDATION_FAILED)
+               dev_err(fw_mgmt->parent,
+                       "failed to validate interface firmware, status:%02x\n",
+                       fw_mgmt->intf_fw_status);
+
+       complete(&fw_mgmt->completion);
+
+       return 0;
+}
+
+static int fw_mgmt_backend_fw_version_operation(struct fw_mgmt *fw_mgmt,
+               struct fw_mgmt_ioc_get_fw *fw_info)
+{
+       struct gb_connection *connection = fw_mgmt->connection;
+       struct gb_fw_mgmt_backend_fw_version_request request;
+       struct gb_fw_mgmt_backend_fw_version_response response;
+       int ret;
+
+       strncpy(request.firmware_tag, fw_info->firmware_tag,
+               GB_FIRMWARE_TAG_MAX_LEN);
+
+       /*
+        * The firmware-tag should be NULL terminated, otherwise throw error and
+        * fail.
+        */
+       if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_LEN - 1] != '\0') {
+               dev_err(fw_mgmt->parent, "backend-version: firmware-tag is not NULL terminated\n");
+               return -EINVAL;
+       }
+
+       ret = gb_operation_sync(connection,
+                               GB_FW_MGMT_TYPE_BACKEND_FW_VERSION, &request,
+                               sizeof(request), &response, sizeof(response));
+       if (ret) {
+               dev_err(fw_mgmt->parent, "failed to get version of %s backend firmware (%d)\n",
+                       fw_info->firmware_tag, ret);
+               return ret;
+       }
+
+       fw_info->major = le16_to_cpu(response.major);
+       fw_info->minor = le16_to_cpu(response.minor);
+
+       return 0;
+}
+
+static int fw_mgmt_backend_fw_update_operation(struct fw_mgmt *fw_mgmt,
+                                              char *tag)
+{
+       struct gb_fw_mgmt_backend_fw_update_request request;
+       int ret;
+
+       strncpy(request.firmware_tag, tag, GB_FIRMWARE_TAG_MAX_LEN);
+
+       /*
+        * The firmware-tag should be NULL terminated, otherwise throw error and
+        * fail.
+        */
+       if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_LEN - 1] != '\0') {
+               dev_err(fw_mgmt->parent, "backend-update: firmware-tag is not NULL terminated\n");
+               return -EINVAL;
+       }
+
+       /* Allocate ids from 1 to 255 (u8-max), 0 is an invalid id */
+       ret = ida_simple_get(&fw_mgmt->id_map, 1, 256, GFP_KERNEL);
+       if (ret < 0) {
+               dev_err(fw_mgmt->parent, "failed to allocate request id (%d)\n",
+                       ret);
+               return ret;
+       }
+
+       fw_mgmt->backend_fw_request_id = ret;
+       request.request_id = ret;
+
+       ret = gb_operation_sync(fw_mgmt->connection,
+                               GB_FW_MGMT_TYPE_BACKEND_FW_UPDATE, &request,
+                               sizeof(request), NULL, 0);
+       if (ret) {
+               ida_simple_remove(&fw_mgmt->id_map,
+                                 fw_mgmt->backend_fw_request_id);
+               fw_mgmt->backend_fw_request_id = 0;
+               dev_err(fw_mgmt->parent,
+                       "backend %s firmware update request failed (%d)\n", tag,
+                       ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int fw_mgmt_backend_fw_updated_operation(struct gb_operation *op)
+{
+       struct gb_connection *connection = op->connection;
+       struct fw_mgmt *fw_mgmt = gb_connection_get_data(connection);
+       struct gb_fw_mgmt_backend_fw_updated_request *request;
+
+       /* No pending load and validate request ? */
+       if (!fw_mgmt->backend_fw_request_id) {
+               dev_err(fw_mgmt->parent, "unexpected backend firmware updated request received\n");
+               return -ENODEV;
+       }
+
+       if (op->request->payload_size != sizeof(*request)) {
+               dev_err(fw_mgmt->parent, "illegal size of backend firmware updated request (%zu != %zu)\n",
+                       op->request->payload_size, sizeof(*request));
+               return -EINVAL;
+       }
+
+       request = op->request->payload;
+
+       /* Invalid request-id ? */
+       if (request->request_id != fw_mgmt->backend_fw_request_id) {
+               dev_err(fw_mgmt->parent, "invalid request id for backend firmware updated request (%02u != %02u)\n",
+                       fw_mgmt->backend_fw_request_id, request->request_id);
+               return -ENODEV;
+       }
+
+       ida_simple_remove(&fw_mgmt->id_map, fw_mgmt->backend_fw_request_id);
+       fw_mgmt->backend_fw_request_id = 0;
+       fw_mgmt->backend_fw_status = request->status;
+
+       if (fw_mgmt->backend_fw_status != GB_FW_BACKEND_FW_STATUS_SUCCESS)
+               dev_err(fw_mgmt->parent,
+                       "failed to backend load firmware, status:%02x\n",
+                       fw_mgmt->backend_fw_status);
+
+       complete(&fw_mgmt->completion);
+
+       return 0;
+}
+
+/* Char device fops */
+
+static int fw_mgmt_open(struct inode *inode, struct file *file)
+{
+       struct fw_mgmt *fw_mgmt = container_of(inode->i_cdev, struct fw_mgmt,
+                                              cdev);
+
+       file->private_data = fw_mgmt;
+       return 0;
+}
+
+static int fw_mgmt_ioctl(struct fw_mgmt *fw_mgmt, unsigned int cmd,
+                        void __user *buf)
+{
+       struct fw_mgmt_ioc_get_fw fw_info;
+       struct fw_mgmt_ioc_intf_load_and_validate intf_load;
+       struct fw_mgmt_ioc_backend_fw_update backend_update;
+       unsigned int timeout;
+       int ret;
+
+       switch (cmd) {
+       case FW_MGMT_IOC_GET_INTF_FW:
+               ret = fw_mgmt_interface_fw_version_operation(fw_mgmt, &fw_info);
+               if (ret)
+                       return ret;
+
+               if (copy_to_user(buf, &fw_info, sizeof(fw_info)))
+                       return -EFAULT;
+
+               return 0;
+       case FW_MGMT_IOC_GET_BACKEND_FW:
+               if (copy_from_user(&fw_info, buf, sizeof(fw_info)))
+                       return -EFAULT;
+
+               ret = fw_mgmt_backend_fw_version_operation(fw_mgmt, &fw_info);
+               if (ret)
+                       return ret;
+
+               if (copy_to_user(buf, &fw_info, sizeof(fw_info)))
+                       return -EFAULT;
+
+               return 0;
+       case FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE:
+               if (copy_from_user(&intf_load, buf, sizeof(intf_load)))
+                       return -EFAULT;
+
+               ret = fw_mgmt_load_and_validate_operation(fw_mgmt,
+                               intf_load.load_method, intf_load.firmware_tag);
+               if (ret)
+                       return ret;
+
+               if (!wait_for_completion_timeout(&fw_mgmt->completion,
+                                                fw_mgmt->timeout_jiffies)) {
+                       dev_err(fw_mgmt->parent, "timed out waiting for firmware load and validation to finish\n");
+                       return -ETIMEDOUT;
+               }
+
+               intf_load.status = fw_mgmt->intf_fw_status;
+               intf_load.major = cpu_to_le16(fw_mgmt->intf_fw_major);
+               intf_load.minor = cpu_to_le16(fw_mgmt->intf_fw_minor);
+
+               if (copy_to_user(buf, &intf_load, sizeof(intf_load)))
+                       return -EFAULT;
+
+               return 0;
+       case FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE:
+               if (copy_from_user(&backend_update, buf,
+                                  sizeof(backend_update)))
+                       return -EFAULT;
+
+               ret = fw_mgmt_backend_fw_update_operation(fw_mgmt,
+                               backend_update.firmware_tag);
+               if (ret)
+                       return ret;
+
+               if (!wait_for_completion_timeout(&fw_mgmt->completion,
+                                                fw_mgmt->timeout_jiffies)) {
+                       dev_err(fw_mgmt->parent, "timed out waiting for backend firmware update to finish\n");
+                       return -ETIMEDOUT;
+               }
+
+               backend_update.status = fw_mgmt->backend_fw_status;
+
+               if (copy_to_user(buf, &backend_update, sizeof(backend_update)))
+                       return -EFAULT;
+
+               return 0;
+       case FW_MGMT_IOC_SET_TIMEOUT_MS:
+               if (get_user(timeout, (unsigned int __user *)buf))
+                       return -EFAULT;
+
+               if (!timeout) {
+                       dev_err(fw_mgmt->parent, "timeout can't be zero\n");
+                       return -EINVAL;
+               }
+
+               fw_mgmt->timeout_jiffies = msecs_to_jiffies(timeout);
+
+               return 0;
+       default:
+               return -ENOTTY;
+       }
+}
+
+static long fw_mgmt_ioctl_unlocked(struct file *file, unsigned int cmd,
+                                  unsigned long arg)
+{
+       struct fw_mgmt *fw_mgmt = file->private_data;
+       int ret;
+
+       /*
+        * Serialize ioctls
+        *
+        * We don't want the user to do few operations in parallel. For example,
+        * updating Interface firmware in parallel for the same Interface. There
+        * is no need to do things in parallel for speed and we can avoid having
+        * complicated for now.
+        */
+       mutex_lock(&fw_mgmt->mutex);
+       ret = fw_mgmt_ioctl(fw_mgmt, cmd, (void __user *)arg);
+       mutex_unlock(&fw_mgmt->mutex);
+
+       return ret;
+}
+
+static const struct file_operations fw_mgmt_fops = {
+       .owner          = THIS_MODULE,
+       .open           = fw_mgmt_open,
+       .unlocked_ioctl = fw_mgmt_ioctl_unlocked,
+};
+
+int gb_fw_mgmt_request_handler(struct gb_operation *op)
+{
+       u8 type = op->type;
+
+       switch (type) {
+       case GB_FW_MGMT_TYPE_LOADED_FW:
+               return fw_mgmt_interface_fw_loaded_operation(op);
+       case GB_FW_MGMT_TYPE_BACKEND_FW_UPDATED:
+               return fw_mgmt_backend_fw_updated_operation(op);
+       default:
+               dev_err(&op->connection->bundle->dev,
+                       "unsupported request: %u\n", type);
+               return -EINVAL;
+       }
+}
+
+int gb_fw_mgmt_connection_init(struct gb_connection *connection)
+{
+       struct fw_mgmt *fw_mgmt;
+       int ret, minor;
+
+       if (!connection)
+               return 0;
+
+       fw_mgmt = kzalloc(sizeof(*fw_mgmt), GFP_KERNEL);
+       if (!fw_mgmt)
+               return -ENOMEM;
+
+       fw_mgmt->parent = &connection->bundle->dev;
+       fw_mgmt->timeout_jiffies = msecs_to_jiffies(FW_MGMT_TIMEOUT_MS);
+       fw_mgmt->connection = connection;
+
+       gb_connection_set_data(connection, fw_mgmt);
+       init_completion(&fw_mgmt->completion);
+       ida_init(&fw_mgmt->id_map);
+       mutex_init(&fw_mgmt->mutex);
+
+       ret = gb_connection_enable(connection);
+       if (ret)
+               goto err_destroy_ida;
+
+       minor = ida_simple_get(&fw_mgmt_minors_map, 0, NUM_MINORS, GFP_KERNEL);
+       if (minor < 0) {
+               ret = minor;
+               goto err_connection_disable;
+       }
+
+       /* Add a char device to allow userspace to interact with fw-mgmt */
+       fw_mgmt->dev_num = MKDEV(MAJOR(fw_mgmt_dev_num), minor);
+       cdev_init(&fw_mgmt->cdev, &fw_mgmt_fops);
+
+       ret = cdev_add(&fw_mgmt->cdev, fw_mgmt->dev_num, 1);
+       if (ret)
+               goto err_remove_ida;
+
+       /* Add a soft link to the previously added char-dev within the bundle */
+       fw_mgmt->class_device = device_create(fw_mgmt_class, fw_mgmt->parent,
+                                             fw_mgmt->dev_num, NULL,
+                                             "fw-mgmt-%d", minor);
+       if (IS_ERR(fw_mgmt->class_device)) {
+               ret = PTR_ERR(fw_mgmt->class_device);
+               goto err_del_cdev;
+       }
+
+       return 0;
+
+err_del_cdev:
+       cdev_del(&fw_mgmt->cdev);
+err_remove_ida:
+       ida_simple_remove(&fw_mgmt_minors_map, minor);
+err_connection_disable:
+       gb_connection_disable(connection);
+err_destroy_ida:
+       ida_destroy(&fw_mgmt->id_map);
+       kfree(fw_mgmt);
+
+       return ret;
+}
+
+void gb_fw_mgmt_connection_exit(struct gb_connection *connection)
+{
+       struct fw_mgmt *fw_mgmt;
+
+       if (!connection)
+               return;
+
+       fw_mgmt = gb_connection_get_data(connection);
+       device_destroy(fw_mgmt_class, fw_mgmt->dev_num);
+       cdev_del(&fw_mgmt->cdev);
+       ida_simple_remove(&fw_mgmt_minors_map, MINOR(fw_mgmt->dev_num));
+       gb_connection_disable(fw_mgmt->connection);
+       ida_destroy(&fw_mgmt->id_map);
+
+       kfree(fw_mgmt);
+}
+
+int fw_mgmt_init(void)
+{
+       int ret;
+
+       fw_mgmt_class = class_create(THIS_MODULE, "gb_fw_mgmt");
+       if (IS_ERR(fw_mgmt_class))
+               return PTR_ERR(fw_mgmt_class);
+
+       ret = alloc_chrdev_region(&fw_mgmt_dev_num, 0, NUM_MINORS,
+                                 "gb_fw_mgmt");
+       if (ret)
+               goto err_remove_class;
+
+       return 0;
+
+err_remove_class:
+       class_destroy(fw_mgmt_class);
+       return ret;
+}
+
+void fw_mgmt_exit(void)
+{
+       unregister_chrdev_region(fw_mgmt_dev_num, NUM_MINORS);
+       class_destroy(fw_mgmt_class);
+       ida_destroy(&fw_mgmt_minors_map);
+}
diff --git a/drivers/staging/greybus/greybus_firmware.h b/drivers/staging/greybus/greybus_firmware.h
new file mode 100644 (file)
index 0000000..9c5ad75
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Greybus Firmware Management User Header
+ *
+ * This file is provided under the GPLv2 license.  When using or
+ * redistributing this file, you may do so under that license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License version 2 for more details.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. OR
+ * LINARO LTD. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __GREYBUS_FIRMWARE_USER_H
+#define __GREYBUS_FIRMWARE_USER_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define GB_FIRMWARE_U_TAG_MAX_LEN              10
+
+#define GB_FW_U_LOAD_METHOD_UNIPRO             0x01
+#define GB_FW_U_LOAD_METHOD_INTERNAL           0x02
+
+#define GB_FW_U_LOAD_STATUS_FAILED             0x00
+#define GB_FW_U_LOAD_STATUS_UNVALIDATED                0x01
+#define GB_FW_U_LOAD_STATUS_VALIDATED          0x02
+#define GB_FW_U_LOAD_STATUS_VALIDATION_FAILED  0x03
+
+#define GB_FW_U_BACKEND_FW_STATUS_SUCCESS      0x01
+#define GB_FW_U_BACKEND_FW_STATUS_FAIL_FIND    0x02
+#define GB_FW_U_BACKEND_FW_STATUS_FAIL_FETCH   0x03
+#define GB_FW_U_BACKEND_FW_STATUS_FAIL_WRITE   0x04
+#define GB_FW_U_BACKEND_FW_STATUS_INT          0x05
+
+/* IOCTL support */
+struct fw_mgmt_ioc_get_fw {
+       __u8                    firmware_tag[GB_FIRMWARE_U_TAG_MAX_LEN];
+       __u16                   major;
+       __u16                   minor;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_intf_load_and_validate {
+       __u8                    firmware_tag[GB_FIRMWARE_U_TAG_MAX_LEN];
+       __u8                    load_method;
+       __u8                    status;
+       __u16                   major;
+       __u16                   minor;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_backend_fw_update {
+       __u8                    firmware_tag[GB_FIRMWARE_U_TAG_MAX_LEN];
+       __u8                    status;
+} __attribute__ ((__packed__));
+
+#define FW_MGMT_IOCTL_BASE                     'F'
+#define FW_MGMT_IOC_GET_INTF_FW                        _IOR(FW_MGMT_IOCTL_BASE, 0, struct fw_mgmt_ioc_get_fw)
+#define FW_MGMT_IOC_GET_BACKEND_FW             _IOWR(FW_MGMT_IOCTL_BASE, 1, struct fw_mgmt_ioc_get_fw)
+#define FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE     _IOWR(FW_MGMT_IOCTL_BASE, 2, struct fw_mgmt_ioc_intf_load_and_validate)
+#define FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE     _IOWR(FW_MGMT_IOCTL_BASE, 3, struct fw_mgmt_ioc_backend_fw_update)
+#define FW_MGMT_IOC_SET_TIMEOUT_MS             _IOW(FW_MGMT_IOCTL_BASE, 4, unsigned int)
+
+#endif /* __GREYBUS_FIRMWARE_USER_H */
+
index 40bd6c0..e3ad5d7 100644 (file)
@@ -250,6 +250,79 @@ struct gb_fw_download_release_firmware_request {
 /* firmware download release firmware response has no payload */
 
 
+/* Firmware Management Protocol */
+
+/* Request Types */
+#define GB_FW_MGMT_TYPE_INTERFACE_FW_VERSION   0x01
+#define GB_FW_MGMT_TYPE_LOAD_AND_VALIDATE_FW   0x02
+#define GB_FW_MGMT_TYPE_LOADED_FW              0x03
+#define GB_FW_MGMT_TYPE_BACKEND_FW_VERSION     0x04
+#define GB_FW_MGMT_TYPE_BACKEND_FW_UPDATE      0x05
+#define GB_FW_MGMT_TYPE_BACKEND_FW_UPDATED     0x06
+
+#define GB_FW_LOAD_METHOD_UNIPRO               0x01
+#define GB_FW_LOAD_METHOD_INTERNAL             0x02
+
+#define GB_FW_LOAD_STATUS_FAILED               0x00
+#define GB_FW_LOAD_STATUS_UNVALIDATED          0x01
+#define GB_FW_LOAD_STATUS_VALIDATED            0x02
+#define GB_FW_LOAD_STATUS_VALIDATION_FAILED    0x03
+
+#define GB_FW_BACKEND_FW_STATUS_SUCCESS                0x01
+#define GB_FW_BACKEND_FW_STATUS_FAIL_FIND      0x02
+#define GB_FW_BACKEND_FW_STATUS_FAIL_FETCH     0x03
+#define GB_FW_BACKEND_FW_STATUS_FAIL_WRITE     0x04
+#define GB_FW_BACKEND_FW_STATUS_INT            0x05
+
+/* firmware management interface firmware version request has no payload */
+struct gb_fw_mgmt_interface_fw_version_response {
+       __u8                    firmware_tag[GB_FIRMWARE_TAG_MAX_LEN];
+       __le16                  major;
+       __le16                  minor;
+} __packed;
+
+/* firmware management load and validate firmware request/response */
+struct gb_fw_mgmt_load_and_validate_fw_request {
+       __u8                    request_id;
+       __u8                    load_method;
+       __u8                    firmware_tag[GB_FIRMWARE_TAG_MAX_LEN];
+} __packed;
+/* firmware management load and validate firmware response has no payload*/
+
+/* firmware management loaded firmware request */
+struct gb_fw_mgmt_loaded_fw_request {
+       __u8                    request_id;
+       __u8                    status;
+       __le16                  major;
+       __le16                  minor;
+} __packed;
+/* firmware management loaded firmware response has no payload */
+
+/* firmware management backend firmware version request/response */
+struct gb_fw_mgmt_backend_fw_version_request {
+       __u8                    firmware_tag[GB_FIRMWARE_TAG_MAX_LEN];
+} __packed;
+
+struct gb_fw_mgmt_backend_fw_version_response {
+       __le16                  major;
+       __le16                  minor;
+} __packed;
+
+/* firmware management backend firmware update request */
+struct gb_fw_mgmt_backend_fw_update_request {
+       __u8                    request_id;
+       __u8                    firmware_tag[GB_FIRMWARE_TAG_MAX_LEN];
+} __packed;
+/* firmware management backend firmware update response has no payload */
+
+/* firmware management backend firmware updated request */
+struct gb_fw_mgmt_backend_fw_updated_request {
+       __u8                    request_id;
+       __u8                    status;
+} __packed;
+/* firmware management backend firmware updated response has no payload */
+
+
 /* Bootrom Protocol */
 
 /* Version of the Greybus bootrom protocol we support */