123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- /*
- * 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 "/*(DEBLOBBED)*/",
- 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 = reject_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);
- }
|