Contributors: 2
Author Tokens Token Proportion Commits Commit Proportion
Viresh Kumar 1865 99.68% 8 66.67%
Greg Kroah-Hartman 6 0.32% 4 33.33%
Total 1871 12


// SPDX-License-Identifier: GPL-2.0
/*
 * Greybus Firmware Download Protocol Driver.
 *
 * Copyright 2016 Google Inc.
 * Copyright 2016 Linaro Ltd.
 */

#include <linux/firmware.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/greybus.h>
#include "firmware.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);
}