diff options
Diffstat (limited to 'greybus_firmware.patch')
| -rw-r--r-- | greybus_firmware.patch | 2429 |
1 files changed, 2429 insertions, 0 deletions
diff --git a/greybus_firmware.patch b/greybus_firmware.patch new file mode 100644 index 00000000000000..aa4c4f041c4464 --- /dev/null +++ b/greybus_firmware.patch @@ -0,0 +1,2429 @@ +--- + 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.kumar@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 */ ++ |
