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

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

greybus: Add firmware protocol driver



This adds firmware protocol driver based on the latest specs available
on mailing lists. This uses the firmware framework present in kernel.

Refer Documentation/firmware_class/README on how it works.

Signed-off-by: default avatarViresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@google.com>
parent 738599c0
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ greybus-y := core.o \
		protocol.o	\
		control.o	\
		svc.o		\
		firmware.o	\
		operation.o

gb-phy-y :=	gpbridge.o	\
+9 −0
Original line number Diff line number Diff line
@@ -300,8 +300,16 @@ static int __init gb_init(void)
		goto error_svc;
	}

	retval = gb_firmware_protocol_init();
	if (retval) {
		pr_err("gb_firmware_protocol_init failed\n");
		goto error_firmware;
	}

	return 0;	/* Success */

error_firmware:
	gb_svc_protocol_exit();
error_svc:
	gb_control_protocol_exit();
error_control:
@@ -321,6 +329,7 @@ module_init(gb_init);

static void __exit gb_exit(void)
{
	gb_firmware_protocol_exit();
	gb_svc_protocol_exit();
	gb_control_protocol_exit();
	gb_endo_exit();
+199 −0
Original line number Diff line number Diff line
/*
 * FIRMWARE Greybus driver.
 *
 * Copyright 2015 Google Inc.
 * Copyright 2015 Linaro Ltd.
 *
 * Released under the GPLv2 only.
 */

#include <linux/firmware.h>

#include "greybus.h"

struct gb_firmware {
	struct gb_connection	*connection;
	const struct firmware	*fw;
};

static void free_firmware(struct gb_firmware *firmware)
{
	release_firmware(firmware->fw);
	firmware->fw = NULL;
}

/* This returns path of the firmware blob on the disk */
static int download_firmware(struct gb_firmware *firmware, u8 stage)
{
	struct gb_connection *connection = firmware->connection;
	struct gb_interface *intf = connection->bundle->intf;
	char firmware_name[28];

	/* Already have a firmware, free it */
	if (firmware->fw)
		free_firmware(firmware);

	/*
	 * Create firmware name
	 *
	 * XXX Name it properly..
	 */
	sprintf(firmware_name, "ara:%04x:%04x:%04x:%04x:%04x.fw", intf->unipro_mfg_id,
		intf->unipro_prod_id, intf->ara_vend_id, intf->ara_prod_id,
		stage);

	return request_firmware(&firmware->fw, firmware_name, &connection->dev);
}

static int gb_firmware_size_request(struct gb_operation *op)
{
	struct gb_connection *connection = op->connection;
	struct gb_firmware *firmware = connection->private;
	struct gb_firmware_size_request *size_request = op->request->payload;
	struct gb_firmware_size_response *size_response;
	struct device *dev = &connection->dev;
	int ret;

	if (op->request->payload_size != sizeof(*size_request)) {
		dev_err(dev, "%s: illegal size of firmware size request (%zu != %zu)\n",
			__func__, op->request->payload_size,
			sizeof(*size_request));
		return -EINVAL;
	}

	ret = download_firmware(firmware, size_request->stage);
	if (ret) {
		dev_err(dev, "%s: failed to download firmware (%d)\n", __func__,
			ret);
		return ret;
	}

	if (!gb_operation_response_alloc(op, sizeof(*size_response),
					 GFP_KERNEL)) {
		dev_err(dev, "%s: error allocating response\n", __func__);
		free_firmware(firmware);
		return -ENOMEM;
	}

	size_response = op->response->payload;
	size_response->size = cpu_to_le32(firmware->fw->size);

	return 0;
}

static int gb_firmware_get_firmware(struct gb_operation *op)
{
	struct gb_connection *connection = op->connection;
	struct gb_firmware *firmware = connection->private;
	struct gb_firmware_get_firmware_request *firmware_request = op->request->payload;
	struct gb_firmware_get_firmware_response *firmware_response;
	struct device *dev = &connection->dev;
	unsigned int offset, size;

	if (op->request->payload_size != sizeof(*firmware_request)) {
		dev_err(dev, "%s: Illegal size of get firmware request (%zu %zu)\n",
			__func__, op->request->payload_size,
			sizeof(*firmware_request));
		return -EINVAL;
	}

	if (!firmware->fw) {
		dev_err(dev, "%s: firmware not available\n", __func__);
		return -EINVAL;
	}

	offset = le32_to_cpu(firmware_request->offset);
	size = le32_to_cpu(firmware_request->size);

	if (!gb_operation_response_alloc(op, sizeof(*firmware_response) + size,
					 GFP_KERNEL)) {
		dev_err(dev, "%s: error allocating response\n", __func__);
		return -ENOMEM;
	}

	firmware_response = op->response->payload;
	memcpy(firmware_response->data, firmware->fw->data + offset, size);

	return 0;
}

static int gb_firmware_ready_to_boot(struct gb_operation *op)
{
	struct gb_connection *connection = op->connection;
	struct gb_firmware_ready_to_boot_request *rtb_request = op->request->payload;
	struct device *dev = &connection->dev;
	u8 stage, status;

	if (op->request->payload_size != sizeof(*rtb_request)) {
		dev_err(dev, "%s: Illegal size of ready to boot request (%zu %zu)\n",
			__func__, op->request->payload_size,
			sizeof(*rtb_request));
		return -EINVAL;
	}

	stage = rtb_request->stage;
	status = rtb_request->status;

	/* Return error if the blob was invalid */
	if (status == GB_FIRMWARE_BOOT_STATUS_INVALID)
		return -EINVAL;

	/*
	 * XXX Should we return error for insecure firmware?
	 */

	return 0;
}

static int gb_firmware_request_recv(u8 type, struct gb_operation *op)
{
	switch (type) {
	case GB_FIRMWARE_TYPE_FIRMWARE_SIZE:
		return gb_firmware_size_request(op);
	case GB_FIRMWARE_TYPE_GET_FIRMWARE:
		return gb_firmware_get_firmware(op);
	case GB_FIRMWARE_TYPE_READY_TO_BOOT:
		return gb_firmware_ready_to_boot(op);
	default:
		dev_err(&op->connection->dev,
			"unsupported request: %hhu\n", type);
		return -EINVAL;
	}
}

static int gb_firmware_connection_init(struct gb_connection *connection)
{
	struct gb_firmware *firmware;

	firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
	if (!firmware)
		return -ENOMEM;

	firmware->connection = connection;
	connection->private = firmware;

	return 0;
}

static void gb_firmware_connection_exit(struct gb_connection *connection)
{
	struct gb_firmware *firmware = connection->private;

	/* Release firmware */
	if (firmware->fw)
		free_firmware(firmware);

	connection->private = NULL;
	kfree(firmware);
}

static struct gb_protocol firmware_protocol = {
	.name			= "firmware",
	.id			= GREYBUS_PROTOCOL_FIRMWARE,
	.major			= GB_FIRMWARE_VERSION_MAJOR,
	.minor			= GB_FIRMWARE_VERSION_MINOR,
	.connection_init	= gb_firmware_connection_init,
	.connection_exit	= gb_firmware_connection_exit,
	.request_recv		= gb_firmware_request_recv,
};
gb_builtin_protocol_driver(firmware_protocol);
+16 −0
Original line number Diff line number Diff line
/*
 * Greybus firmware code
 *
 * Copyright 2015 Google Inc.
 * Copyright 2015 Linaro Ltd.
 *
 * Released under the GPLv2 only.
 */

#ifndef __FIRMWARE_H
#define __FIRMWARE_H

int gb_firmware_protocol_init(void);
void gb_firmware_protocol_exit(void);

#endif /* __FIRMWARE_H */
+1 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@
#include "manifest.h"
#include "endo.h"
#include "svc.h"
#include "firmware.h"
#include "module.h"
#include "control.h"
#include "interface.h"
Loading