From cca22207673896262443c72f19b049552d65f88e Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 28 Apr 2016 10:06:38 +0530 Subject: [PATCH] greybus: firmware: Add firmware-download protocol driver This patch adds Firmware Download Protocol support to firmware core, which allows an Interface to download a firmware package over Unipro. Signed-off-by: Viresh Kumar Reviewed-by: Jun Li Signed-off-by: Greg Kroah-Hartman --- drivers/staging/greybus/Makefile | 2 +- drivers/staging/greybus/firmware.h | 20 ++ drivers/staging/greybus/fw-core.c | 37 ++- drivers/staging/greybus/fw-download.c | 299 ++++++++++++++++++++ drivers/staging/greybus/greybus_protocols.h | 37 +++ 5 files changed, 392 insertions(+), 3 deletions(-) create mode 100644 drivers/staging/greybus/firmware.h create mode 100644 drivers/staging/greybus/fw-download.c diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index 1a120d928485..43c0c9910de1 100644 --- a/drivers/staging/greybus/Makefile +++ b/drivers/staging/greybus/Makefile @@ -39,7 +39,7 @@ gb-audio-apbridgea-y := audio_apbridgea.o gb-audio-manager-y += audio_manager.o gb-audio-manager-y += audio_manager_module.o gb-camera-y := camera.o -gb-firmware-y := fw-core.o +gb-firmware-y := fw-core.o fw-download.o obj-m += greybus.o obj-m += gb-phy.o diff --git a/drivers/staging/greybus/firmware.h b/drivers/staging/greybus/firmware.h new file mode 100644 index 000000000000..c5736d5f96a2 --- /dev/null +++ b/drivers/staging/greybus/firmware.h @@ -0,0 +1,20 @@ +/* + * 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" + +/* 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); + +#endif /* __FIRMWARE_H */ diff --git a/drivers/staging/greybus/fw-core.c b/drivers/staging/greybus/fw-core.c index 4720d595cc2b..a6865276855a 100644 --- a/drivers/staging/greybus/fw-core.c +++ b/drivers/staging/greybus/fw-core.c @@ -9,9 +9,11 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include "firmware.h" #include "greybus.h" struct gb_fw_core { + struct gb_connection *download_connection; struct gb_connection *mgmt_connection; }; @@ -52,10 +54,29 @@ static int gb_fw_core_probe(struct gb_bundle *bundle, dev_err(&bundle->dev, "failed to create management connection (%d)\n", ret); - goto err_free_fw_core; + 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; default: dev_err(&bundle->dev, "invalid protocol id (0x%02x)\n", @@ -69,7 +90,16 @@ static int gb_fw_core_probe(struct gb_bundle *bundle, if (!fw_core->mgmt_connection) { dev_err(&bundle->dev, "missing management connection\n"); ret = -ENODEV; - goto err_free_fw_core; + 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; } greybus_set_drvdata(bundle, fw_core); @@ -77,6 +107,7 @@ static int gb_fw_core_probe(struct gb_bundle *bundle, return 0; err_destroy_connections: + gb_connection_destroy(fw_core->download_connection); gb_connection_destroy(fw_core->mgmt_connection); err_free_fw_core: kfree(fw_core); @@ -88,6 +119,8 @@ static void gb_fw_core_disconnect(struct gb_bundle *bundle) { struct gb_fw_core *fw_core = greybus_get_drvdata(bundle); + gb_fw_download_connection_exit(fw_core->download_connection); + gb_connection_destroy(fw_core->download_connection); gb_connection_destroy(fw_core->mgmt_connection); kfree(fw_core); diff --git a/drivers/staging/greybus/fw-download.c b/drivers/staging/greybus/fw-download.c new file mode 100644 index 000000000000..836b02b9d7c6 --- /dev/null +++ b/drivers/staging/greybus/fw-download.c @@ -0,0 +1,299 @@ +/* + * Greybus Firmware Download Protocol Driver. + * + * Copyright 2016 Google Inc. + * Copyright 2016 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + +#include +#include "firmware.h" +#include "greybus.h" + +struct fw_request { + u8 firmware_id; + char name[56]; /* ara_%08x_%08x_%08x_%08x_%s.tftf */ + const struct firmware *fw; + struct list_head node; +}; + +struct fw_download { + struct device *parent; + struct gb_connection *connection; + struct list_head fw_requests; + struct ida id_map; +}; + +static struct fw_request *match_firmware(struct fw_download *fw_download, + u8 firmware_id) +{ + struct fw_request *fw_req; + + list_for_each_entry(fw_req, &fw_download->fw_requests, node) { + if (fw_req->firmware_id == firmware_id) + return fw_req; + } + + return NULL; +} + +static void free_firmware(struct fw_download *fw_download, + struct fw_request *fw_req) +{ + list_del(&fw_req->node); + release_firmware(fw_req->fw); + ida_simple_remove(&fw_download->id_map, fw_req->firmware_id); + kfree(fw_req); +} + +/* 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; + + 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), + "ara_%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; + } + + list_add(&fw_req->node, &fw_download->fw_requests); + + 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 should be null-terminated */ + if (strnlen(tag, GB_FIRMWARE_TAG_MAX_LEN) == GB_FIRMWARE_TAG_MAX_LEN) { + dev_err(fw_download->parent, + "firmware-tag is not null-terminated\n"); + return -EINVAL; + } + + fw_req = find_firmware(fw_download, request->firmware_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; + + 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 = match_firmware(fw_download, firmware_id); + if (!fw_req) { + dev_err(fw_download->parent, + "firmware not available for id: %02u\n", firmware_id); + return -EINVAL; + } + + 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); + return -EINVAL; + } + + if (!gb_operation_response_alloc(op, sizeof(*response) + size, + GFP_KERNEL)) { + dev_err(fw_download->parent, + "error allocating fetch firmware response\n"); + return -ENOMEM; + } + + 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); + + return 0; +} + +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 = match_firmware(fw_download, firmware_id); + if (!fw_req) { + dev_err(fw_download->parent, + "firmware not available for id: %02u\n", firmware_id); + return -EINVAL; + } + + free_firmware(fw_download, 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; + + 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); + + /* Release pending firmware packages */ + list_for_each_entry_safe(fw_req, tmp, &fw_download->fw_requests, node) + free_firmware(fw_download, fw_req); + + ida_destroy(&fw_download->id_map); + kfree(fw_download); +} diff --git a/drivers/staging/greybus/greybus_protocols.h b/drivers/staging/greybus/greybus_protocols.h index f1e8b345a761..b73acc0c3ee5 100644 --- a/drivers/staging/greybus/greybus_protocols.h +++ b/drivers/staging/greybus/greybus_protocols.h @@ -213,6 +213,43 @@ struct gb_control_timesync_authoritative_request { #define GB_APB_REQUEST_CPORT_FEAT_EN 0x0b #define GB_APB_REQUEST_CPORT_FEAT_DIS 0x0c +/* Firmware Download Protocol */ + +/* Request Types */ +#define GB_FW_DOWNLOAD_TYPE_FIND_FIRMWARE 0x01 +#define GB_FW_DOWNLOAD_TYPE_FETCH_FIRMWARE 0x02 +#define GB_FW_DOWNLOAD_TYPE_RELEASE_FIRMWARE 0x03 + +#define GB_FIRMWARE_TAG_MAX_LEN 10 + +/* firmware download find firmware request/response */ +struct gb_fw_download_find_firmware_request { + __u8 firmware_tag[GB_FIRMWARE_TAG_MAX_LEN]; +} __packed; + +struct gb_fw_download_find_firmware_response { + __u8 firmware_id; + __le32 size; +} __packed; + +/* firmware download fetch firmware request/response */ +struct gb_fw_download_fetch_firmware_request { + __u8 firmware_id; + __le32 offset; + __le32 size; +} __packed; + +struct gb_fw_download_fetch_firmware_response { + __u8 data[0]; +} __packed; + +/* firmware download release firmware request */ +struct gb_fw_download_release_firmware_request { + __u8 firmware_id; +} __packed; +/* firmware download release firmware response has no payload */ + + /* Bootrom Protocol */ /* Version of the Greybus bootrom protocol we support */ -- 2.20.1