Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cca22207 authored by Viresh Kumar's avatar Viresh Kumar Committed by Greg Kroah-Hartman
Browse files

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: default avatarViresh Kumar <viresh.kumar@linaro.org>
Reviewed-by: default avatarJun Li <li_jun@projectara.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@google.com>
parent 7adb32b4
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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
+20 −0
Original line number Diff line number Diff line
/*
 * 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 */
+35 −2
Original line number Diff line number Diff line
@@ -9,9 +9,11 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/firmware.h>
#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);
+299 −0
Original line number Diff line number Diff line
/*
 * Greybus Firmware Download Protocol Driver.
 *
 * Copyright 2016 Google Inc.
 * Copyright 2016 Linaro Ltd.
 *
 * Released under the GPLv2 only.
 */

#include <linux/firmware.h>
#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);
}
+37 −0
Original line number Diff line number Diff line
@@ -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 */