This driver implements the Greybus firmware download protocol.

Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>
---
 drivers/greybus/Documentation/firmware/authenticate.c      |  139 ++
 drivers/greybus/Documentation/firmware/firmware-management |  333 ++++++
 drivers/greybus/Documentation/firmware/firmware.c          |  262 ++++
 drivers/greybus/firmware.h                                 |   42 
 drivers/greybus/fw-core.c                                  |  312 +++++
 drivers/greybus/fw-download.c                              |  465 ++++++++
 drivers/greybus/fw-management.c                            |  721 +++++++++++++
 drivers/greybus/greybus_firmware.h                         |  120 ++
 8 files changed, 2394 insertions(+)

--- /dev/null
+++ b/drivers/greybus/Documentation/firmware/authenticate.c
@@ -0,0 +1,139 @@
+/*
+ * Sample code to test CAP protocol
+ *
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either 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.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of Google Inc. or Linaro Ltd. nor the names of
+ *    its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written
+ *    permission.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "../../greybus_authentication.h"
+
+struct cap_ioc_get_endpoint_uid uid;
+struct cap_ioc_get_ims_certificate cert = {
+       .certificate_class = 0,
+       .certificate_id = 0,
+};
+
+struct cap_ioc_authenticate authenticate = {
+       .auth_type = 0,
+       .challenge = {0},
+};
+
+int main(int argc, char *argv[])
+{
+       unsigned int timeout = 10000;
+       char *capdev;
+       int fd, ret;
+
+       /* Make sure arguments are correct */
+       if (argc != 2) {
+               printf("\nUsage: ./firmware <Path of the gb-cap-X dev>\n");
+               return 0;
+       }
+
+       capdev = argv[1];
+
+       printf("Opening %s authentication device\n", capdev);
+
+       fd = open(capdev, O_RDWR);
+       if (fd < 0) {
+               printf("Failed to open: %s\n", capdev);
+               return -1;
+       }
+
+       /* Get UID */
+       printf("Get UID\n");
+
+       ret = ioctl(fd, CAP_IOC_GET_ENDPOINT_UID, &uid);
+       if (ret < 0) {
+               printf("Failed to get UID: %s (%d)\n", capdev, ret);
+               ret = -1;
+               goto close_fd;
+       }
+
+       printf("UID received: 0x%llx\n", *(long long unsigned int *)(uid.uid));
+
+       /* Get certificate */
+       printf("Get IMS certificate\n");
+
+       ret = ioctl(fd, CAP_IOC_GET_IMS_CERTIFICATE, &cert);
+       if (ret < 0) {
+               printf("Failed to get IMS certificate: %s (%d)\n", capdev, ret);
+               ret = -1;
+               goto close_fd;
+       }
+
+       printf("IMS Certificate size: %d\n", cert.cert_size);
+
+       /* Authenticate */
+       printf("Authenticate module\n");
+
+       memcpy(authenticate.uid, uid.uid, 8);
+
+       ret = ioctl(fd, CAP_IOC_AUTHENTICATE, &authenticate);
+       if (ret < 0) {
+               printf("Failed to authenticate module: %s (%d)\n", capdev, ret);
+               ret = -1;
+               goto close_fd;
+       }
+
+       printf("Authenticated, result (%02x), sig-size (%02x)\n",
+               authenticate.result_code, authenticate.signature_size);
+
+close_fd:
+       close(fd);
+
+       return ret;
+}
--- /dev/null
+++ b/drivers/greybus/Documentation/firmware/firmware-management
@@ -0,0 +1,333 @@
+
+Firmware Management
+-------------------
+ Copyright 2016 Google Inc.
+ Copyright 2016 Linaro Ltd.
+
+Interface-Manifest
+------------------
+
+All firmware packages on the Modules or Interfaces are managed by a special
+Firmware Management Protocol. To support Firmware Management by the AP, the
+Interface Manifest shall at least contain the Firmware Management Bundle and a
+Firmware Management Protocol CPort within it.
+
+The bundle may contain additional CPorts based on the extra functionality
+required to manage firmware packages.
+
+For example, this is how the Firmware Management part of the Interface Manifest
+may look like:
+
+       ; Firmware Management Bundle (Bundle 1):
+       [bundle-descriptor 1]
+       class = 0x16
+
+       ; (Mandatory) Firmware Management Protocol on CPort 1
+       [cport-descriptor 2]
+       bundle = 1
+       protocol = 0x18
+
+       ; (Optional) Firmware Download Protocol on CPort 2
+       [cport-descriptor 1]
+       bundle = 1
+       protocol = 0x17
+
+       ; (Optional) SPI protocol on CPort 3
+       [cport-descriptor 3]
+       bundle = 1
+       protocol = 0x0b
+
+       ; (Optional) Component Authentication Protocol (CAP) on CPort 4
+       [cport-descriptor 4]
+       bundle = 1
+       protocol = 0x19
+
+
+Sysfs Interfaces - Firmware Management
+--------------------------------------
+
+The Firmware Management Protocol interacts with Userspace using the character
+device interface. The character device will be present in /dev/ directory
+and will be named gb-fw-mgmt-<N>. The number <N> is assigned at runtime.
+
+Identifying the Character Device
+================================
+
+There can be multiple devices present in /dev/ directory with name gb-fw-mgmt-N
+and user first needs to identify the character device used for
+firmware-management for a particular interface.
+
+The Firmware Management core creates a device of class 'gb_fw_mgmt', which 
shall
+be used by the user to identify the right character device for it. The class
+device is created within the Bundle directory for a particular Interface.
+
+For example this is how the class-device can be present:
+
+/sys/bus/greybus/devices/1-1/1-1.1/1-1.1.1/gb_fw_mgmt/gb-fw-mgmt-0
+
+The last name in this path: gb-fw-mgmt-0 is precisely the name of the char
+device and so the device in this case will be:
+
+/dev/gb-fw-mgmt-0.
+
+Operations on the Char device
+=============================
+
+The Character device (gb-fw-mgmt-0 in example) can be opened by the userspace
+application and it can perform various 'ioctl' operations on the device. The
+device doesn't support any read/write operations.
+
+Following are the IOCTLs and their data structures available to the user:
+
+/* IOCTL support */
+#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
+#define GB_FW_BACKEND_FW_STATUS_RETRY          0x06
+#define GB_FW_BACKEND_FW_STATUS_NOT_SUPPORTED  0x07
+
+#define GB_FW_BACKEND_VERSION_STATUS_SUCCESS           0x01
+#define GB_FW_BACKEND_VERSION_STATUS_NOT_AVAILABLE     0x02
+#define GB_FW_BACKEND_VERSION_STATUS_NOT_SUPPORTED     0x03
+#define GB_FW_BACKEND_VERSION_STATUS_RETRY             0x04
+#define GB_FW_BACKEND_VERSION_STATUS_FAIL_INT          0x05
+
+
+struct fw_mgmt_ioc_get_intf_version {
+       __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+       __u16 major;
+       __u16 minor;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_get_backend_version {
+       __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+       __u16 major;
+       __u16 minor;
+       __u8 status;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_intf_load_and_validate {
+       __u8                    firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE];
+       __u8                    load_method;
+       __u8                    status;
+       __u16                   major;
+       __u16                   minor;
+} __packed;
+
+struct fw_mgmt_ioc_backend_fw_update {
+       __u8                    firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE];
+       __u8                    status;
+} __packed;
+
+#define FW_MGMT_IOCTL_BASE                     'S'
+#define FW_MGMT_IOC_GET_INTF_FW                        
_IOR(FW_MGMT_IOCTL_BASE, 0, struct fw_mgmt_ioc_get_intf_version)
+#define FW_MGMT_IOC_GET_BACKEND_FW             _IOWR(FW_MGMT_IOCTL_BASE, 1, 
struct fw_mgmt_ioc_get_backend_version)
+#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)
+#define FW_MGMT_IOC_MODE_SWITCH                        _IO(FW_MGMT_IOCTL_BASE, 
5)
+
+1. FW_MGMT_IOC_GET_INTF_FW:
+
+   This ioctl shall be used by the user to get the version and firmware-tag of
+   the currently running Interface Firmware. All the fields of the 'struct
+   fw_mgmt_ioc_get_fw' are filled by the kernel.
+
+2. FW_MGMT_IOC_GET_BACKEND_FW:
+
+   This ioctl shall be used by the user to get the version of a currently
+   running Backend Interface Firmware identified by a firmware-tag. The user is
+   required to fill the 'firmware_tag' field of the 'struct fw_mgmt_ioc_get_fw'
+   in this case. The 'major' and 'minor' fields are set by the kernel in
+   response.
+
+3. FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE:
+
+   This ioctl shall be used by the user to load an Interface Firmware package 
on
+   an Interface. The user needs to fill the 'firmware_tag' and 'load_method'
+   fields of the 'struct fw_mgmt_ioc_intf_load_and_validate'. The 'status',
+   'major' and 'minor' fields are set by the kernel in response.
+
+4. FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE:
+
+   This ioctl shall be used by the user to request an Interface to update a
+   Backend Interface Firmware.  The user is required to fill the 'firmware_tag'
+   field of the 'struct fw_mgmt_ioc_get_fw' in this case. The 'status' field is
+   set by the kernel in response.
+
+5. FW_MGMT_IOC_SET_TIMEOUT_MS:
+
+   This ioctl shall be used by the user to increase the timeout interval within
+   which the firmware must get loaded by the Module. The default timeout is 1
+   second. The user needs to pass the timeout in milliseconds.
+
+6. FW_MGMT_IOC_MODE_SWITCH:
+
+   This ioctl shall be used by the user to mode-switch the module to the
+   previously loaded interface firmware. If the interface firmware isn't loaded
+   previously, or if another unsuccessful FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE
+   operation is started after loading interface firmware, then the firmware 
core
+   wouldn't allow mode-switch.
+
+
+Sysfs Interfaces - Authentication
+---------------------------------
+
+The Component Authentication Protocol interacts with Userspace using the
+character device interface. The character device will be present in /dev/
+directory and will be named gb-authenticate-<N>. The number <N> is assigned at
+runtime.
+
+Identifying the Character Device
+================================
+
+There can be multiple devices present in /dev/ directory with name
+gb-authenticate-N and user first needs to identify the character device used 
for
+authentication a of particular interface.
+
+The Authentication core creates a device of class 'gb_authenticate', which 
shall
+be used by the user to identify the right character device for it. The class
+device is created within the Bundle directory for a particular Interface.
+
+For example this is how the class-device can be present:
+
+/sys/bus/greybus/devices/1-1/1-1.1/1-1.1.1/gb_authenticate/gb-authenticate-0
+
+The last name in this path: gb-authenticate-0 is precisely the name of the char
+device and so the device in this case will be:
+
+/dev/gb-authenticate-0.
+
+Operations on the Char device
+=============================
+
+The Character device (/dev/gb-authenticate-0 in above example) can be opened by
+the userspace application and it can perform various 'ioctl' operations on the
+device. The device doesn't support any read/write operations.
+
+Following are the IOCTLs and their data structures available to the user:
+
+#define CAP_CERTIFICATE_MAX_SIZE       1600
+#define CAP_SIGNATURE_MAX_SIZE         320
+
+/* Certificate class types */
+#define CAP_CERT_IMS_EAPC              0x00000001
+#define CAP_CERT_IMS_EASC              0x00000002
+#define CAP_CERT_IMS_EARC              0x00000003
+#define CAP_CERT_IMS_IAPC              0x00000004
+#define CAP_CERT_IMS_IASC              0x00000005
+#define CAP_CERT_IMS_IARC              0x00000006
+
+/* IMS Certificate response result codes */
+#define CAP_IMS_RESULT_CERT_FOUND      0x00
+#define CAP_IMS_RESULT_CERT_CLASS_INVAL        0x01
+#define CAP_IMS_RESULT_CERT_CORRUPT    0x02
+#define CAP_IMS_RESULT_CERT_NOT_FOUND  0x03
+
+/* Authentication types */
+#define CAP_AUTH_IMS_PRI               0x00000001
+#define CAP_AUTH_IMS_SEC               0x00000002
+#define CAP_AUTH_IMS_RSA               0x00000003
+
+/* Authenticate response result codes */
+#define CAP_AUTH_RESULT_CR_SUCCESS     0x00
+#define CAP_AUTH_RESULT_CR_BAD_TYPE    0x01
+#define CAP_AUTH_RESULT_CR_WRONG_EP    0x02
+#define CAP_AUTH_RESULT_CR_NO_KEY      0x03
+#define CAP_AUTH_RESULT_CR_SIG_FAIL    0x04
+
+
+/* IOCTL support */
+struct cap_ioc_get_endpoint_uid {
+       __u8                    uid[8];
+} __attribute__ ((__packed__));
+
+struct cap_ioc_get_ims_certificate {
+       __u32                   certificate_class;
+       __u32                   certificate_id;
+
+       __u8                    result_code;
+       __u32                   cert_size;
+       __u8                    certificate[CAP_CERTIFICATE_MAX_SIZE];
+} __attribute__ ((__packed__));
+
+struct cap_ioc_authenticate {
+       __u32                   auth_type;
+       __u8                    uid[8];
+       __u8                    challenge[32];
+
+       __u8                    result_code;
+       __u8                    response[64];
+       __u32                   signature_size;
+       __u8                    signature[CAP_SIGNATURE_MAX_SIZE];
+} __attribute__ ((__packed__));
+
+#define CAP_IOCTL_BASE                 'C'
+#define CAP_IOC_GET_ENDPOINT_UID       _IOR(CAP_IOCTL_BASE, 0, struct 
cap_ioc_get_endpoint_uid)
+#define CAP_IOC_GET_IMS_CERTIFICATE    _IOWR(CAP_IOCTL_BASE, 1, struct 
cap_ioc_get_ims_certificate)
+#define CAP_IOC_AUTHENTICATE           _IOWR(CAP_IOCTL_BASE, 2, struct 
cap_ioc_authenticate)
+
+
+1. CAP_IOC_GET_ENDPOINT_UID:
+
+   This ioctl shall be used by the user to get the endpoint UID associated with
+   the Interface.  All the fields of the 'struct cap_ioc_get_endpoint_uid' are
+   filled by the kernel.
+
+2. CAP_IOC_GET_IMS_CERTIFICATE:
+
+   This ioctl shall be used by the user to retrieve one of the available
+   cryptographic certificates held by the Interface for use in Component
+   Authentication. The user is required to fill the 'certificate_class' and
+   'certificate_id' field of the 'struct cap_ioc_get_ims_certificate' in this
+   case. The other fields will be set by the kernel in response. The first
+   'cert_size' bytes of the 'certificate' shall be read by the user and others
+   must be discarded.
+
+3. CAP_IOC_AUTHENTICATE:
+
+   This ioctl shall be used by the user to authenticate the Module attached to
+   an Interface.  The user needs to fill the 'auth_type', 'uid', and 
'challenge'
+   fields of the 'struct cap_ioc_authenticate'. The other fields will be set by
+   the kernel in response.  The first 'signature_size' bytes of the 'signature'
+   shall be read by the user and others must be discarded.
+
+
+Sysfs Interfaces - Firmware Download
+------------------------------------
+
+The Firmware Download Protocol uses the existing Linux Kernel's Firmware class
+and the interface provided to userspace are described in:
+Documentation/firmware_class/.
+
+
+Sysfs Interfaces - SPI Flash
+----------------------------
+
+The SPI flash is exposed in userspace as a MTD device and is created
+within the Bundle directory. For example, this is how the path may look like:
+
+$ ls 
/sys/bus/greybus/devices/1-1/1-1.1/1-1.1.1/spi_master/spi32766/spi32766.0/mtd
+mtd0    mtd0ro
+
+
+Sample Applications
+-------------------
+
+The current directory also provides a firmware.c test application, which can be
+referenced while developing userspace application to talk to 
firmware-management
+protocol.
+
+The current directory also provides a authenticate.c test application, which 
can
+be referenced while developing userspace application to talk to
+component authentication protocol.
--- /dev/null
+++ b/drivers/greybus/Documentation/firmware/firmware.c
@@ -0,0 +1,262 @@
+/*
+ * Sample code to test firmware-management protocol
+ *
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either 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.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of Google Inc. or Linaro Ltd. nor the names of
+ *    its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written
+ *    permission.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "../../greybus_firmware.h"
+
+#define FW_DEV_DEFAULT         "/dev/gb-fw-mgmt-0"
+#define FW_TAG_INT_DEFAULT     "s3f"
+#define FW_TAG_BCND_DEFAULT    "bf_01"
+#define FW_UPDATE_TYPE_DEFAULT 0
+#define FW_TIMEOUT_DEFAULT     10000;
+
+static const char *firmware_tag;
+static const char *fwdev = FW_DEV_DEFAULT;
+static int fw_update_type = FW_UPDATE_TYPE_DEFAULT;
+static int fw_timeout = FW_TIMEOUT_DEFAULT;
+
+static struct fw_mgmt_ioc_get_intf_version intf_fw_info;
+static struct fw_mgmt_ioc_get_backend_version backend_fw_info;
+static struct fw_mgmt_ioc_intf_load_and_validate intf_load;
+static struct fw_mgmt_ioc_backend_fw_update backend_update;
+
+static void usage(void)
+{
+       printf("\nUsage: ./firmware <gb-fw-mgmt-X (default: gb-fw-mgmt-0)> 
<interface: 0, backend: 1 (default: 0)> <firmware-tag> (default: 
\"s3f\"/\"bf_01\") <timeout (default: 10000 ms)>\n");
+}
+
+static int update_intf_firmware(int fd)
+{
+       int ret;
+
+       /* Get Interface Firmware Version */
+       printf("Get Interface Firmware Version\n");
+
+       ret = ioctl(fd, FW_MGMT_IOC_GET_INTF_FW, &intf_fw_info);
+       if (ret < 0) {
+               printf("Failed to get interface firmware version: %s (%d)\n",
+                       fwdev, ret);
+               return -1;
+       }
+
+       printf("Interface Firmware tag (%s), major (%d), minor (%d)\n",
+               intf_fw_info.firmware_tag, intf_fw_info.major,
+               intf_fw_info.minor);
+
+       /* Try Interface Firmware load over Unipro */
+       printf("Loading Interface Firmware\n");
+
+       intf_load.load_method = GB_FW_U_LOAD_METHOD_UNIPRO;
+       intf_load.status = 0;
+       intf_load.major = 0;
+       intf_load.minor = 0;
+
+       strncpy((char *)&intf_load.firmware_tag, firmware_tag,
+               GB_FIRMWARE_U_TAG_MAX_SIZE);
+
+       ret = ioctl(fd, FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE, &intf_load);
+       if (ret < 0) {
+               printf("Failed to load interface firmware: %s (%d)\n", fwdev,
+                       ret);
+               return -1;
+       }
+
+       if (intf_load.status != GB_FW_U_LOAD_STATUS_VALIDATED &&
+           intf_load.status != GB_FW_U_LOAD_STATUS_UNVALIDATED) {
+               printf("Load status says loading failed: %d\n",
+                       intf_load.status);
+               return -1;
+       }
+
+       printf("Interface Firmware (%s) Load done: major: %d, minor: %d, 
status: %d\n",
+               firmware_tag, intf_load.major, intf_load.minor,
+               intf_load.status);
+
+       /* Initiate Mode-switch to the newly loaded firmware */
+       printf("Initiate Mode switch\n");
+
+       ret = ioctl(fd, FW_MGMT_IOC_MODE_SWITCH);
+       if (ret < 0)
+               printf("Failed to initiate mode-switch (%d)\n", ret);
+
+       return ret;
+}
+
+static int update_backend_firmware(int fd)
+{
+       int ret;
+
+       /* Get Backend Firmware Version */
+       printf("Getting Backend Firmware Version\n");
+
+       strncpy((char *)&backend_fw_info.firmware_tag, firmware_tag,
+               GB_FIRMWARE_U_TAG_MAX_SIZE);
+
+retry_fw_version:
+       ret = ioctl(fd, FW_MGMT_IOC_GET_BACKEND_FW, &backend_fw_info);
+       if (ret < 0) {
+               printf("Failed to get backend firmware version: %s (%d)\n",
+                       fwdev, ret);
+               return -1;
+       }
+
+       printf("Backend Firmware tag (%s), major (%d), minor (%d), status 
(%d)\n",
+               backend_fw_info.firmware_tag, backend_fw_info.major,
+               backend_fw_info.minor, backend_fw_info.status);
+
+       if (backend_fw_info.status == GB_FW_U_BACKEND_VERSION_STATUS_RETRY)
+               goto retry_fw_version;
+
+       if ((backend_fw_info.status != GB_FW_U_BACKEND_VERSION_STATUS_SUCCESS)
+           && (backend_fw_info.status != 
GB_FW_U_BACKEND_VERSION_STATUS_NOT_AVAILABLE)) {
+               printf("Failed to get backend firmware version: %s (%d)\n",
+                       fwdev, backend_fw_info.status);
+               return -1;
+       }
+
+       /* Try Backend Firmware Update over Unipro */
+       printf("Updating Backend Firmware\n");
+
+       strncpy((char *)&backend_update.firmware_tag, firmware_tag,
+               GB_FIRMWARE_U_TAG_MAX_SIZE);
+
+retry_fw_update:
+       backend_update.status = 0;
+
+       ret = ioctl(fd, FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE, &backend_update);
+       if (ret < 0) {
+               printf("Failed to load backend firmware: %s (%d)\n", fwdev, 
ret);
+               return -1;
+       }
+
+       if (backend_update.status == GB_FW_U_BACKEND_FW_STATUS_RETRY) {
+               printf("Retrying firmware update: %d\n", backend_update.status);
+               goto retry_fw_update;
+       }
+
+       if (backend_update.status != GB_FW_U_BACKEND_FW_STATUS_SUCCESS) {
+               printf("Load status says loading failed: %d\n",
+                       backend_update.status);
+       } else {
+               printf("Backend Firmware (%s) Load done: status: %d\n",
+                               firmware_tag, backend_update.status);
+       }
+
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       int fd, ret;
+
+       if (argc > 1 &&
+           (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
+               usage();
+               return -1;
+       }
+
+       if (argc > 1)
+               fwdev = argv[1];
+
+       if (argc > 2)
+               sscanf(argv[2], "%u", &fw_update_type);
+
+       if (argc > 3) {
+               firmware_tag = argv[3];
+       } else if (!fw_update_type) {
+               firmware_tag = FW_TAG_INT_DEFAULT;
+       } else {
+               firmware_tag = FW_TAG_BCND_DEFAULT;
+       }
+
+       if (argc > 4)
+               sscanf(argv[4], "%u", &fw_timeout);
+
+       printf("Trying Firmware update: fwdev: %s, type: %s, tag: %s, timeout: 
%d\n",
+               fwdev, fw_update_type == 0 ? "interface" : "backend",
+               firmware_tag, fw_timeout);
+
+       printf("Opening %s firmware management device\n", fwdev);
+
+       fd = open(fwdev, O_RDWR);
+       if (fd < 0) {
+               printf("Failed to open: %s\n", fwdev);
+               return -1;
+       }
+
+       /* Set Timeout */
+       printf("Setting timeout to %u ms\n", fw_timeout);
+
+       ret = ioctl(fd, FW_MGMT_IOC_SET_TIMEOUT_MS, &fw_timeout);
+       if (ret < 0) {
+               printf("Failed to set timeout: %s (%d)\n", fwdev, ret);
+               ret = -1;
+               goto close_fd;
+       }
+
+       if (!fw_update_type)
+               ret = update_intf_firmware(fd);
+       else
+               ret = update_backend_firmware(fd);
+
+close_fd:
+       close(fd);
+
+       return ret;
+}
--- /dev/null
+++ b/drivers/greybus/firmware.h
@@ -0,0 +1,42 @@
+/*
+ * 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"
+
+#define FW_NAME_PREFIX "gmp_"
+
+/*
+ * Length of the string in format: 
"FW_NAME_PREFIX""%08x_%08x_%08x_%08x_%s.tftf"
+ *                                  (3 + 1 + 4 * (8 + 1) + 10 + 1 + 4 + 1)
+ */
+#define FW_NAME_SIZE           56
+
+/* 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);
+void gb_fw_download_connection_exit(struct gb_connection *connection);
+
+/* CAP Protocol specific functions */
+int cap_init(void);
+void cap_exit(void);
+int gb_cap_connection_init(struct gb_connection *connection);
+void gb_cap_connection_exit(struct gb_connection *connection);
+
+#endif /* __FIRMWARE_H */
--- /dev/null
+++ b/drivers/greybus/fw-core.c
@@ -0,0 +1,312 @@
+/*
+ * Greybus Firmware Core Bundle Driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/firmware.h>
+#include "firmware.h"
+#include "greybus.h"
+#include "spilib.h"
+
+struct gb_fw_core {
+       struct gb_connection    *download_connection;
+       struct gb_connection    *mgmt_connection;
+       struct gb_connection    *spi_connection;
+       struct gb_connection    *cap_connection;
+};
+
+static struct spilib_ops *spilib_ops;
+
+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_spi_connection_init(struct gb_connection *connection)
+{
+       int ret;
+
+       if (!connection)
+               return 0;
+
+       ret = gb_connection_enable(connection);
+       if (ret)
+               return ret;
+
+       ret = gb_spilib_master_init(connection, &connection->bundle->dev,
+                                   spilib_ops);
+       if (ret) {
+               gb_connection_disable(connection);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void gb_fw_spi_connection_exit(struct gb_connection *connection)
+{
+       if (!connection)
+               return;
+
+       gb_spilib_master_exit(connection);
+       gb_connection_disable(connection);
+}
+
+static int gb_fw_core_probe(struct gb_bundle *bundle,
+                           const struct greybus_bundle_id *id)
+{
+       struct greybus_descriptor_cport *cport_desc;
+       struct gb_connection *connection;
+       struct gb_fw_core *fw_core;
+       int ret, i;
+       u16 cport_id;
+       u8 protocol_id;
+
+       fw_core = kzalloc(sizeof(*fw_core), GFP_KERNEL);
+       if (!fw_core)
+               return -ENOMEM;
+
+       /* Parse CPorts and create connections */
+       for (i = 0; i < bundle->num_cports; i++) {
+               cport_desc = &bundle->cport_desc[i];
+               cport_id = le16_to_cpu(cport_desc->id);
+               protocol_id = cport_desc->protocol_id;
+
+               switch (protocol_id) {
+               case GREYBUS_PROTOCOL_FW_MANAGEMENT:
+                       /* Disallow multiple Firmware Management CPorts */
+                       if (fw_core->mgmt_connection) {
+                               dev_err(&bundle->dev,
+                                       "multiple management CPorts found\n");
+                               ret = -EINVAL;
+                               goto err_destroy_connections;
+                       }
+
+                       connection = gb_connection_create(bundle, cport_id,
+                                               gb_fw_mgmt_request_handler);
+                       if (IS_ERR(connection)) {
+                               ret = PTR_ERR(connection);
+                               dev_err(&bundle->dev,
+                                       "failed to create management connection 
(%d)\n",
+                                       ret);
+                               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;
+               case GREYBUS_PROTOCOL_SPI:
+                       /* Disallow multiple SPI CPorts */
+                       if (fw_core->spi_connection) {
+                               dev_err(&bundle->dev,
+                                       "multiple SPI CPorts found\n");
+                               ret = -EINVAL;
+                               goto err_destroy_connections;
+                       }
+
+                       connection = gb_connection_create(bundle, cport_id,
+                                                         NULL);
+                       if (IS_ERR(connection)) {
+                               dev_err(&bundle->dev, "failed to create SPI 
connection (%ld)\n",
+                                       PTR_ERR(connection));
+                       } else {
+                               fw_core->spi_connection = connection;
+                       }
+
+                       break;
+               case GREYBUS_PROTOCOL_AUTHENTICATION:
+                       /* Disallow multiple CAP CPorts */
+                       if (fw_core->cap_connection) {
+                               dev_err(&bundle->dev, "multiple Authentication 
CPorts found\n");
+                               ret = -EINVAL;
+                               goto err_destroy_connections;
+                       }
+
+                       connection = gb_connection_create(bundle, cport_id,
+                                                         NULL);
+                       if (IS_ERR(connection)) {
+                               dev_err(&bundle->dev, "failed to create 
Authentication connection (%ld)\n",
+                                       PTR_ERR(connection));
+                       } else {
+                               fw_core->cap_connection = connection;
+                       }
+
+                       break;
+               default:
+                       dev_err(&bundle->dev, "invalid protocol id (0x%02x)\n",
+                               protocol_id);
+                       ret = -EINVAL;
+                       goto err_destroy_connections;
+               }
+       }
+
+       /* Firmware Management connection is mandatory */
+       if (!fw_core->mgmt_connection) {
+               dev_err(&bundle->dev, "missing management connection\n");
+               ret = -ENODEV;
+               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;
+       }
+
+       ret = gb_fw_spi_connection_init(fw_core->spi_connection);
+       if (ret) {
+               /* We may still be able to work with the Interface */
+               dev_err(&bundle->dev, "failed to initialize SPI connection, 
disable it (%d)\n",
+                       ret);
+               gb_connection_destroy(fw_core->spi_connection);
+               fw_core->spi_connection = NULL;
+       }
+
+       ret = gb_cap_connection_init(fw_core->cap_connection);
+       if (ret) {
+               /* We may still be able to work with the Interface */
+               dev_err(&bundle->dev, "failed to initialize CAP connection, 
disable it (%d)\n",
+                       ret);
+               gb_connection_destroy(fw_core->cap_connection);
+               fw_core->cap_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);
+
+       /* FIXME: Remove this after S2 Loader gets runtime PM support */
+       if (!(bundle->intf->quirks & GB_INTERFACE_QUIRK_NO_PM))
+               gb_pm_runtime_put_autosuspend(bundle);
+
+       return 0;
+
+err_exit_connections:
+       gb_cap_connection_exit(fw_core->cap_connection);
+       gb_fw_spi_connection_exit(fw_core->spi_connection);
+       gb_fw_download_connection_exit(fw_core->download_connection);
+err_destroy_connections:
+       gb_connection_destroy(fw_core->mgmt_connection);
+       gb_connection_destroy(fw_core->cap_connection);
+       gb_connection_destroy(fw_core->spi_connection);
+       gb_connection_destroy(fw_core->download_connection);
+       kfree(fw_core);
+
+       return ret;
+}
+
+static void gb_fw_core_disconnect(struct gb_bundle *bundle)
+{
+       struct gb_fw_core *fw_core = greybus_get_drvdata(bundle);
+       int ret;
+
+       /* FIXME: Remove this after S2 Loader gets runtime PM support */
+       if (!(bundle->intf->quirks & GB_INTERFACE_QUIRK_NO_PM)) {
+               ret = gb_pm_runtime_get_sync(bundle);
+               if (ret)
+                       gb_pm_runtime_get_noresume(bundle);
+       }
+
+       gb_fw_mgmt_connection_exit(fw_core->mgmt_connection);
+       gb_cap_connection_exit(fw_core->cap_connection);
+       gb_fw_spi_connection_exit(fw_core->spi_connection);
+       gb_fw_download_connection_exit(fw_core->download_connection);
+
+       gb_connection_destroy(fw_core->mgmt_connection);
+       gb_connection_destroy(fw_core->cap_connection);
+       gb_connection_destroy(fw_core->spi_connection);
+       gb_connection_destroy(fw_core->download_connection);
+
+       kfree(fw_core);
+}
+
+static const struct greybus_bundle_id gb_fw_core_id_table[] = {
+       { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_FW_MANAGEMENT) },
+       { }
+};
+
+static struct greybus_driver gb_fw_core_driver = {
+       .name           = "gb-firmware",
+       .probe          = gb_fw_core_probe,
+       .disconnect     = gb_fw_core_disconnect,
+       .id_table       = gb_fw_core_id_table,
+};
+
+static int fw_core_init(void)
+{
+       int ret;
+
+       ret = fw_mgmt_init();
+       if (ret) {
+               pr_err("Failed to initialize fw-mgmt core (%d)\n", ret);
+               return ret;
+       }
+
+       ret = cap_init();
+       if (ret) {
+               pr_err("Failed to initialize component authentication core 
(%d)\n",
+                      ret);
+               goto fw_mgmt_exit;
+       }
+
+       ret = greybus_register(&gb_fw_core_driver);
+       if (ret)
+               goto cap_exit;
+
+       return 0;
+
+cap_exit:
+       cap_exit();
+fw_mgmt_exit:
+       fw_mgmt_exit();
+
+       return ret;
+}
+module_init(fw_core_init);
+
+static void __exit fw_core_exit(void)
+{
+       greybus_deregister(&gb_fw_core_driver);
+       cap_exit();
+       fw_mgmt_exit();
+}
+module_exit(fw_core_exit);
+
+MODULE_ALIAS("greybus:firmware");
+MODULE_AUTHOR("Viresh Kumar <viresh.ku...@linaro.org>");
+MODULE_DESCRIPTION("Greybus Firmware Bundle Driver");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/drivers/greybus/fw-download.c
@@ -0,0 +1,465 @@
+/*
+ * Greybus Firmware Download Protocol Driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/firmware.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include "firmware.h"
+#include "greybus.h"
+
+/* Estimated minimum buffer size, actual size can be smaller than this */
+#define MIN_FETCH_SIZE         512
+/* Timeout, in jiffies, within which fetch or release firmware must be called 
*/
+#define NEXT_REQ_TIMEOUT_J     msecs_to_jiffies(1000)
+
+struct fw_request {
+       u8                      firmware_id;
+       bool                    disabled;
+       bool                    timedout;
+       char                    name[FW_NAME_SIZE];
+       const struct firmware   *fw;
+       struct list_head        node;
+
+       struct delayed_work     dwork;
+       /* Timeout, in jiffies, within which the firmware shall download */
+       unsigned long           release_timeout_j;
+       struct kref             kref;
+       struct fw_download      *fw_download;
+};
+
+struct fw_download {
+       struct device           *parent;
+       struct gb_connection    *connection;
+       struct list_head        fw_requests;
+       struct ida              id_map;
+       struct mutex            mutex;
+};
+
+static void fw_req_release(struct kref *kref)
+{
+       struct fw_request *fw_req = container_of(kref, struct fw_request, kref);
+
+       dev_dbg(fw_req->fw_download->parent, "firmware %s released\n",
+               fw_req->name);
+
+       release_firmware(fw_req->fw);
+
+       /*
+        * The request timed out and the module may send a fetch-fw or
+        * release-fw request later. Lets block the id we allocated for this
+        * request, so that the AP doesn't refer to a later fw-request (with
+        * same firmware_id) for the old timedout fw-request.
+        *
+        * NOTE:
+        *
+        * This also means that after 255 timeouts we will fail to service new
+        * firmware downloads. But what else can we do in that case anyway? Lets
+        * just hope that it never happens.
+        */
+       if (!fw_req->timedout)
+               ida_simple_remove(&fw_req->fw_download->id_map,
+                                 fw_req->firmware_id);
+
+       kfree(fw_req);
+}
+
+/*
+ * Incoming requests are serialized for a connection, and the only race 
possible
+ * is between the timeout handler freeing this and an incoming request.
+ *
+ * The operations on the fw-request list are protected by the mutex and
+ * get_fw_req() increments the reference count before returning a fw_req 
pointer
+ * to the users.
+ *
+ * free_firmware() also takes the mutex while removing an entry from the list,
+ * it guarantees that every user of fw_req has taken a kref-reference by now 
and
+ * we wouldn't have any new users.
+ *
+ * Once the last user drops the reference, the fw_req structure is freed.
+ */
+static void put_fw_req(struct fw_request *fw_req)
+{
+       kref_put(&fw_req->kref, fw_req_release);
+}
+
+/* Caller must call put_fw_req() after using struct fw_request */
+static struct fw_request *get_fw_req(struct fw_download *fw_download,
+                                    u8 firmware_id)
+{
+       struct fw_request *fw_req;
+
+       mutex_lock(&fw_download->mutex);
+
+       list_for_each_entry(fw_req, &fw_download->fw_requests, node) {
+               if (fw_req->firmware_id == firmware_id) {
+                       kref_get(&fw_req->kref);
+                       goto unlock;
+               }
+       }
+
+       fw_req = NULL;
+
+unlock:
+       mutex_unlock(&fw_download->mutex);
+
+       return fw_req;
+}
+
+static void free_firmware(struct fw_download *fw_download,
+                         struct fw_request *fw_req)
+{
+       /* Already disabled from timeout handlers */
+       if (fw_req->disabled)
+               return;
+
+       mutex_lock(&fw_download->mutex);
+       list_del(&fw_req->node);
+       mutex_unlock(&fw_download->mutex);
+
+       fw_req->disabled = true;
+       put_fw_req(fw_req);
+}
+
+static void fw_request_timedout(struct work_struct *work)
+{
+       struct delayed_work *dwork = to_delayed_work(work);
+       struct fw_request *fw_req = container_of(dwork, struct fw_request, 
dwork);
+       struct fw_download *fw_download = fw_req->fw_download;
+
+       dev_err(fw_download->parent,
+               "Timed out waiting for fetch / release firmware requests: %u\n",
+               fw_req->firmware_id);
+
+       fw_req->timedout = true;
+       free_firmware(fw_download, fw_req);
+}
+
+static int exceeds_release_timeout(struct fw_request *fw_req)
+{
+       struct fw_download *fw_download = fw_req->fw_download;
+
+       if (time_before(jiffies, fw_req->release_timeout_j))
+               return 0;
+
+       dev_err(fw_download->parent,
+               "Firmware download didn't finish in time, abort: %d\n",
+               fw_req->firmware_id);
+
+       fw_req->timedout = true;
+       free_firmware(fw_download, fw_req);
+
+       return -ETIMEDOUT;
+}
+
+/* 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, req_count;
+
+       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),
+                FW_NAME_PREFIX "%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;
+       }
+
+       fw_req->fw_download = fw_download;
+       kref_init(&fw_req->kref);
+
+       mutex_lock(&fw_download->mutex);
+       list_add(&fw_req->node, &fw_download->fw_requests);
+       mutex_unlock(&fw_download->mutex);
+
+       /* Timeout, in jiffies, within which firmware should get loaded */
+       req_count = DIV_ROUND_UP(fw_req->fw->size, MIN_FETCH_SIZE);
+       fw_req->release_timeout_j = jiffies + req_count * NEXT_REQ_TIMEOUT_J;
+
+       INIT_DELAYED_WORK(&fw_req->dwork, fw_request_timedout);
+       schedule_delayed_work(&fw_req->dwork, NEXT_REQ_TIMEOUT_J);
+
+       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 must be null-terminated */
+       if (strnlen(tag, GB_FIRMWARE_TAG_MAX_SIZE) == GB_FIRMWARE_TAG_MAX_SIZE) 
{
+               dev_err(fw_download->parent,
+                       "firmware-tag is not null-terminated\n");
+               return -EINVAL;
+       }
+
+       fw_req = find_firmware(fw_download, 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;
+       int ret = 0;
+
+       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 = get_fw_req(fw_download, firmware_id);
+       if (!fw_req) {
+               dev_err(fw_download->parent,
+                       "firmware not available for id: %02u\n", firmware_id);
+               return -EINVAL;
+       }
+
+       /* Make sure work handler isn't running in parallel */
+       cancel_delayed_work_sync(&fw_req->dwork);
+
+       /* We timed-out before reaching here ? */
+       if (fw_req->disabled) {
+               ret = -ETIMEDOUT;
+               goto put_fw;
+       }
+
+       /*
+        * Firmware download must finish within a limited time interval. If it
+        * doesn't, then we might have a buggy Module on the other side. Abort
+        * download.
+        */
+       ret = exceeds_release_timeout(fw_req);
+       if (ret)
+               goto put_fw;
+
+       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);
+               ret = -EINVAL;
+               goto put_fw;
+       }
+
+       if (!gb_operation_response_alloc(op, sizeof(*response) + size,
+                                        GFP_KERNEL)) {
+               dev_err(fw_download->parent,
+                       "error allocating fetch firmware response\n");
+               ret = -ENOMEM;
+               goto put_fw;
+       }
+
+       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);
+
+       /* Refresh timeout */
+       schedule_delayed_work(&fw_req->dwork, NEXT_REQ_TIMEOUT_J);
+
+put_fw:
+       put_fw_req(fw_req);
+
+       return ret;
+}
+
+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 = get_fw_req(fw_download, firmware_id);
+       if (!fw_req) {
+               dev_err(fw_download->parent,
+                       "firmware not available for id: %02u\n", firmware_id);
+               return -EINVAL;
+       }
+
+       cancel_delayed_work_sync(&fw_req->dwork);
+
+       free_firmware(fw_download, fw_req);
+       put_fw_req(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;
+       mutex_init(&fw_download->mutex);
+
+       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);
+
+       /*
+        * Make sure we have a reference to the pending requests, before they
+        * are freed from the timeout handler.
+        */
+       mutex_lock(&fw_download->mutex);
+       list_for_each_entry(fw_req, &fw_download->fw_requests, node)
+               kref_get(&fw_req->kref);
+       mutex_unlock(&fw_download->mutex);
+
+       /* Release pending firmware packages */
+       list_for_each_entry_safe(fw_req, tmp, &fw_download->fw_requests, node) {
+               cancel_delayed_work_sync(&fw_req->dwork);
+               free_firmware(fw_download, fw_req);
+               put_fw_req(fw_req);
+       }
+
+       ida_destroy(&fw_download->id_map);
+       kfree(fw_download);
+}
--- /dev/null
+++ b/drivers/greybus/fw-management.c
@@ -0,0 +1,721 @@
+/*
+ * 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;
+       struct kref             kref;
+       struct list_head        node;
+
+       /* 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;
+       bool                    disabled; /* connection getting disabled */
+
+       /* Interface Firmware specific fields */
+       bool                    mode_switch_started;
+       bool                    intf_fw_loaded;
+       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 LIST_HEAD(fw_mgmt_list);
+static DEFINE_MUTEX(list_mutex);
+
+static void fw_mgmt_kref_release(struct kref *kref)
+{
+       struct fw_mgmt *fw_mgmt = container_of(kref, struct fw_mgmt, kref);
+
+       ida_destroy(&fw_mgmt->id_map);
+       kfree(fw_mgmt);
+}
+
+/*
+ * All users of fw_mgmt take a reference (from within list_mutex lock), before
+ * they get a pointer to play with. And the structure will be freed only after
+ * the last user has put the reference to it.
+ */
+static void put_fw_mgmt(struct fw_mgmt *fw_mgmt)
+{
+       kref_put(&fw_mgmt->kref, fw_mgmt_kref_release);
+}
+
+/* Caller must call put_fw_mgmt() after using struct fw_mgmt */
+static struct fw_mgmt *get_fw_mgmt(struct cdev *cdev)
+{
+       struct fw_mgmt *fw_mgmt;
+
+       mutex_lock(&list_mutex);
+
+       list_for_each_entry(fw_mgmt, &fw_mgmt_list, node) {
+               if (&fw_mgmt->cdev == cdev) {
+                       kref_get(&fw_mgmt->kref);
+                       goto unlock;
+               }
+       }
+
+       fw_mgmt = NULL;
+
+unlock:
+       mutex_unlock(&list_mutex);
+
+       return fw_mgmt;
+}
+
+static int fw_mgmt_interface_fw_version_operation(struct fw_mgmt *fw_mgmt,
+               struct fw_mgmt_ioc_get_intf_version *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_SIZE);
+
+       /*
+        * The firmware-tag should be NULL terminated, otherwise throw error but
+        * don't fail.
+        */
+       if (fw_info->firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] != '\0') {
+               dev_err(fw_mgmt->parent,
+                       "fw-version: firmware-tag is not NULL terminated\n");
+               fw_info->firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 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_SIZE);
+
+       /*
+        * The firmware-tag should be NULL terminated, otherwise throw error and
+        * fail.
+        */
+       if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 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;
+       fw_mgmt->intf_fw_loaded = false;
+       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);
+       else
+               fw_mgmt->intf_fw_loaded = true;
+
+       complete(&fw_mgmt->completion);
+
+       return 0;
+}
+
+static int fw_mgmt_backend_fw_version_operation(struct fw_mgmt *fw_mgmt,
+               struct fw_mgmt_ioc_get_backend_version *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_SIZE);
+
+       /*
+        * The firmware-tag should be NULL terminated, otherwise throw error and
+        * fail.
+        */
+       if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 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->status = response.status;
+
+       /* Reset version as that should be non-zero only for success case */
+       fw_info->major = 0;
+       fw_info->minor = 0;
+
+       switch (fw_info->status) {
+       case GB_FW_BACKEND_VERSION_STATUS_SUCCESS:
+               fw_info->major = le16_to_cpu(response.major);
+               fw_info->minor = le16_to_cpu(response.minor);
+               break;
+       case GB_FW_BACKEND_VERSION_STATUS_NOT_AVAILABLE:
+       case GB_FW_BACKEND_VERSION_STATUS_RETRY:
+               break;
+       case GB_FW_BACKEND_VERSION_STATUS_NOT_SUPPORTED:
+               dev_err(fw_mgmt->parent,
+                       "Firmware with tag %s is not supported by Interface\n",
+                       fw_info->firmware_tag);
+               break;
+       default:
+               dev_err(fw_mgmt->parent, "Invalid status received: %u\n",
+                       fw_info->status);
+       }
+
+       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_SIZE);
+
+       /*
+        * The firmware-tag should be NULL terminated, otherwise throw error and
+        * fail.
+        */
+       if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 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) &&
+           (fw_mgmt->backend_fw_status != GB_FW_BACKEND_FW_STATUS_RETRY))
+               dev_err(fw_mgmt->parent,
+                       "failed to load backend firmware: %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 = get_fw_mgmt(inode->i_cdev);
+
+       /* fw_mgmt structure can't get freed until file descriptor is closed */
+       if (fw_mgmt) {
+               file->private_data = fw_mgmt;
+               return 0;
+       }
+
+       return -ENODEV;
+}
+
+static int fw_mgmt_release(struct inode *inode, struct file *file)
+{
+       struct fw_mgmt *fw_mgmt = file->private_data;
+
+       put_fw_mgmt(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_intf_version intf_fw_info;
+       struct fw_mgmt_ioc_get_backend_version backend_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;
+
+       /* Reject any operations after mode-switch has started */
+       if (fw_mgmt->mode_switch_started)
+               return -EBUSY;
+
+       switch (cmd) {
+       case FW_MGMT_IOC_GET_INTF_FW:
+               ret = fw_mgmt_interface_fw_version_operation(fw_mgmt,
+                                                            &intf_fw_info);
+               if (ret)
+                       return ret;
+
+               if (copy_to_user(buf, &intf_fw_info, sizeof(intf_fw_info)))
+                       return -EFAULT;
+
+               return 0;
+       case FW_MGMT_IOC_GET_BACKEND_FW:
+               if (copy_from_user(&backend_fw_info, buf,
+                                  sizeof(backend_fw_info)))
+                       return -EFAULT;
+
+               ret = fw_mgmt_backend_fw_version_operation(fw_mgmt,
+                                                          &backend_fw_info);
+               if (ret)
+                       return ret;
+
+               if (copy_to_user(buf, &backend_fw_info,
+                                sizeof(backend_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 = fw_mgmt->intf_fw_major;
+               intf_load.minor = 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;
+       case FW_MGMT_IOC_MODE_SWITCH:
+               if (!fw_mgmt->intf_fw_loaded) {
+                       dev_err(fw_mgmt->parent,
+                               "Firmware not loaded for mode-switch\n");
+                       return -EPERM;
+               }
+
+               /*
+                * Disallow new ioctls as the fw-core bundle driver is going to
+                * get disconnected soon and the character device will get
+                * removed.
+                */
+               fw_mgmt->mode_switch_started = true;
+
+               ret = 
gb_interface_request_mode_switch(fw_mgmt->connection->intf);
+               if (ret) {
+                       dev_err(fw_mgmt->parent, "Mode-switch failed: %d\n",
+                               ret);
+                       fw_mgmt->mode_switch_started = false;
+                       return ret;
+               }
+
+               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;
+       struct gb_bundle *bundle = fw_mgmt->connection->bundle;
+       int ret = -ENODEV;
+
+       /*
+        * 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 code for now.
+        *
+        * This is also used to protect ->disabled, which is used to check if
+        * the connection is getting disconnected, so that we don't start any
+        * new operations.
+        */
+       mutex_lock(&fw_mgmt->mutex);
+       if (!fw_mgmt->disabled) {
+               ret = gb_pm_runtime_get_sync(bundle);
+               if (!ret) {
+                       ret = fw_mgmt_ioctl(fw_mgmt, cmd, (void __user *)arg);
+                       gb_pm_runtime_put_autosuspend(bundle);
+               }
+       }
+       mutex_unlock(&fw_mgmt->mutex);
+
+       return ret;
+}
+
+static const struct file_operations fw_mgmt_fops = {
+       .owner          = THIS_MODULE,
+       .open           = fw_mgmt_open,
+       .release        = fw_mgmt_release,
+       .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);
+       kref_init(&fw_mgmt->kref);
+
+       mutex_lock(&list_mutex);
+       list_add(&fw_mgmt->node, &fw_mgmt_list);
+       mutex_unlock(&list_mutex);
+
+       ret = gb_connection_enable(connection);
+       if (ret)
+               goto err_list_del;
+
+       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,
+                                             "gb-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_list_del:
+       mutex_lock(&list_mutex);
+       list_del(&fw_mgmt->node);
+       mutex_unlock(&list_mutex);
+
+       put_fw_mgmt(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));
+
+       /*
+        * Disallow any new ioctl operations on the char device and wait for
+        * existing ones to finish.
+        */
+       mutex_lock(&fw_mgmt->mutex);
+       fw_mgmt->disabled = true;
+       mutex_unlock(&fw_mgmt->mutex);
+
+       /* All pending greybus operations should have finished by now */
+       gb_connection_disable(fw_mgmt->connection);
+
+       /* Disallow new users to get access to the fw_mgmt structure */
+       mutex_lock(&list_mutex);
+       list_del(&fw_mgmt->node);
+       mutex_unlock(&list_mutex);
+
+       /*
+        * All current users of fw_mgmt would have taken a reference to it by
+        * now, we can drop our reference and wait the last user will get
+        * fw_mgmt freed.
+        */
+       put_fw_mgmt(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);
+}
--- /dev/null
+++ b/drivers/greybus/greybus_firmware.h
@@ -0,0 +1,120 @@
+/*
+ * Greybus Firmware Management User Header
+ *
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either 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.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of Google Inc. or Linaro Ltd. nor the names of
+ *    its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written
+ *    permission.
+ *
+ * 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_SIZE             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
+#define GB_FW_U_BACKEND_FW_STATUS_RETRY                0x06
+#define GB_FW_U_BACKEND_FW_STATUS_NOT_SUPPORTED        0x07
+
+#define GB_FW_U_BACKEND_VERSION_STATUS_SUCCESS         0x01
+#define GB_FW_U_BACKEND_VERSION_STATUS_NOT_AVAILABLE   0x02
+#define GB_FW_U_BACKEND_VERSION_STATUS_NOT_SUPPORTED   0x03
+#define GB_FW_U_BACKEND_VERSION_STATUS_RETRY           0x04
+#define GB_FW_U_BACKEND_VERSION_STATUS_FAIL_INT                0x05
+
+/* IOCTL support */
+struct fw_mgmt_ioc_get_intf_version {
+       __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+       __u16 major;
+       __u16 minor;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_get_backend_version {
+       __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+       __u16 major;
+       __u16 minor;
+       __u8 status;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_intf_load_and_validate {
+       __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+       __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_SIZE];
+       __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_intf_version)
+#define FW_MGMT_IOC_GET_BACKEND_FW             _IOWR(FW_MGMT_IOCTL_BASE, 1, 
struct fw_mgmt_ioc_get_backend_version)
+#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)
+#define FW_MGMT_IOC_MODE_SWITCH                        _IO(FW_MGMT_IOCTL_BASE, 
5)
+
+#endif /* __GREYBUS_FIRMWARE_USER_H */
+


Reply via email to