Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Ivan Vecera 1508 100.00% 2 100.00%
Total 1508 2


// SPDX-License-Identifier: GPL-2.0-or-later

#include <linux/array_size.h>
#include <linux/build_bug.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/netlink.h>
#include <linux/slab.h>
#include <linux/string.h>

#include "core.h"
#include "flash.h"
#include "fw.h"

#define ZL3073X_FW_ERR_PFX "FW load failed: "
#define ZL3073X_FW_ERR_MSG(_extack, _msg, ...)				\
	NL_SET_ERR_MSG_FMT_MOD((_extack), ZL3073X_FW_ERR_PFX _msg,	\
			       ## __VA_ARGS__)

enum zl3073x_flash_type {
	ZL3073X_FLASH_TYPE_NONE = 0,
	ZL3073X_FLASH_TYPE_SECTORS,
	ZL3073X_FLASH_TYPE_PAGE,
	ZL3073X_FLASH_TYPE_PAGE_AND_COPY,
};

struct zl3073x_fw_component_info {
	const char		*name;
	size_t			max_size;
	enum zl3073x_flash_type	flash_type;
	u32			load_addr;
	u32			dest_page;
	u32			copy_page;
};

static const struct zl3073x_fw_component_info component_info[] = {
	[ZL_FW_COMPONENT_UTIL] = {
		.name		= "utility",
		.max_size	= 0x4000,
		.load_addr	= 0x20000000,
		.flash_type	= ZL3073X_FLASH_TYPE_NONE,
	},
	[ZL_FW_COMPONENT_FW1] = {
		.name		= "firmware1",
		.max_size	= 0x35000,
		.load_addr	= 0x20002000,
		.flash_type	= ZL3073X_FLASH_TYPE_SECTORS,
		.dest_page	= 0x020,
	},
	[ZL_FW_COMPONENT_FW2] = {
		.name		= "firmware2",
		.max_size	= 0x0040,
		.load_addr	= 0x20000000,
		.flash_type	= ZL3073X_FLASH_TYPE_PAGE_AND_COPY,
		.dest_page	= 0x3e0,
		.copy_page	= 0x000,
	},
	[ZL_FW_COMPONENT_FW3] = {
		.name		= "firmware3",
		.max_size	= 0x0248,
		.load_addr	= 0x20000400,
		.flash_type	= ZL3073X_FLASH_TYPE_PAGE_AND_COPY,
		.dest_page	= 0x3e4,
		.copy_page	= 0x004,
	},
	[ZL_FW_COMPONENT_CFG0] = {
		.name		= "config0",
		.max_size	= 0x1000,
		.load_addr	= 0x20000000,
		.flash_type	= ZL3073X_FLASH_TYPE_PAGE,
		.dest_page	= 0x3d0,
	},
	[ZL_FW_COMPONENT_CFG1] = {
		.name		= "config1",
		.max_size	= 0x1000,
		.load_addr	= 0x20000000,
		.flash_type	= ZL3073X_FLASH_TYPE_PAGE,
		.dest_page	= 0x3c0,
	},
	[ZL_FW_COMPONENT_CFG2] = {
		.name		= "config2",
		.max_size	= 0x1000,
		.load_addr	= 0x20000000,
		.flash_type	= ZL3073X_FLASH_TYPE_PAGE,
		.dest_page	= 0x3b0,
	},
	[ZL_FW_COMPONENT_CFG3] = {
		.name		= "config3",
		.max_size	= 0x1000,
		.load_addr	= 0x20000000,
		.flash_type	= ZL3073X_FLASH_TYPE_PAGE,
		.dest_page	= 0x3a0,
	},
	[ZL_FW_COMPONENT_CFG4] = {
		.name		= "config4",
		.max_size	= 0x1000,
		.load_addr	= 0x20000000,
		.flash_type	= ZL3073X_FLASH_TYPE_PAGE,
		.dest_page	= 0x390,
	},
	[ZL_FW_COMPONENT_CFG5] = {
		.name		= "config5",
		.max_size	= 0x1000,
		.load_addr	= 0x20000000,
		.flash_type	= ZL3073X_FLASH_TYPE_PAGE,
		.dest_page	= 0x380,
	},
	[ZL_FW_COMPONENT_CFG6] = {
		.name		= "config6",
		.max_size	= 0x1000,
		.load_addr	= 0x20000000,
		.flash_type	= ZL3073X_FLASH_TYPE_PAGE,
		.dest_page	= 0x370,
	},
};

/* Sanity check */
static_assert(ARRAY_SIZE(component_info) == ZL_FW_NUM_COMPONENTS);

/**
 * zl3073x_fw_component_alloc - Alloc structure to hold firmware component
 * @size: size of buffer to store data
 *
 * Return: pointer to allocated component structure or NULL on error.
 */
static struct zl3073x_fw_component *
zl3073x_fw_component_alloc(size_t size)
{
	struct zl3073x_fw_component *comp;

	comp = kzalloc(sizeof(*comp), GFP_KERNEL);
	if (!comp)
		return NULL;

	comp->size = size;
	comp->data = kzalloc(size, GFP_KERNEL);
	if (!comp->data) {
		kfree(comp);
		return NULL;
	}

	return comp;
}

/**
 * zl3073x_fw_component_free - Free allocated component structure
 * @comp: pointer to allocated component
 */
static void
zl3073x_fw_component_free(struct zl3073x_fw_component *comp)
{
	if (comp)
		kfree(comp->data);

	kfree(comp);
}

/**
 * zl3073x_fw_component_id_get - Get ID for firmware component name
 * @name: input firmware component name
 *
 * Return:
 * - ZL3073X_FW_COMPONENT_* ID for known component name
 * - ZL3073X_FW_COMPONENT_INVALID if the given name is unknown
 */
static enum zl3073x_fw_component_id
zl3073x_fw_component_id_get(const char *name)
{
	enum zl3073x_fw_component_id id;

	for (id = 0; id < ZL_FW_NUM_COMPONENTS; id++)
		if (!strcasecmp(name, component_info[id].name))
			return id;

	return ZL_FW_COMPONENT_INVALID;
}

/**
 * zl3073x_fw_component_load - Load component from firmware source
 * @zldev: zl3073x device structure
 * @pcomp: pointer to loaded component
 * @psrc: data pointer to load component from
 * @psize: remaining bytes in buffer
 * @extack: netlink extack pointer to report errors
 *
 * The function allocates single firmware component and loads the data from
 * the buffer specified by @psrc and @psize. Pointer to allocated component
 * is stored in output @pcomp. Source data pointer @psrc and remaining bytes
 * @psize are updated accordingly.
 *
 * Return:
 * * 1 when component was allocated and loaded
 * * 0 when there is no component to load
 * * <0 on error
 */
static ssize_t
zl3073x_fw_component_load(struct zl3073x_dev *zldev,
			  struct zl3073x_fw_component **pcomp,
			  const char **psrc, size_t *psize,
			  struct netlink_ext_ack *extack)
{
	const struct zl3073x_fw_component_info *info;
	struct zl3073x_fw_component *comp = NULL;
	struct device *dev = zldev->dev;
	enum zl3073x_fw_component_id id;
	char buf[32], name[16];
	u32 count, size, *dest;
	int pos, rc;

	/* Fetch image name and size from input */
	strscpy(buf, *psrc, min(sizeof(buf), *psize));
	rc = sscanf(buf, "%15s %u %n", name, &count, &pos);
	if (!rc) {
		/* No more data */
		return 0;
	} else if (rc == 1 || count > U32_MAX / sizeof(u32)) {
		ZL3073X_FW_ERR_MSG(extack, "invalid component size");
		return -EINVAL;
	}
	*psrc += pos;
	*psize -= pos;

	dev_dbg(dev, "Firmware component '%s' found\n", name);

	id = zl3073x_fw_component_id_get(name);
	if (id == ZL_FW_COMPONENT_INVALID) {
		ZL3073X_FW_ERR_MSG(extack, "unknown component type '%s'", name);
		return -EINVAL;
	}

	info = &component_info[id];
	size = count * sizeof(u32); /* get size in bytes */

	/* Check image size validity */
	if (size > component_info[id].max_size) {
		ZL3073X_FW_ERR_MSG(extack,
				   "[%s] component is too big (%u bytes)\n",
				   info->name, size);
		return -EINVAL;
	}

	dev_dbg(dev, "Indicated component image size: %u bytes\n", size);

	/* Alloc component */
	comp = zl3073x_fw_component_alloc(size);
	if (!comp) {
		ZL3073X_FW_ERR_MSG(extack, "failed to alloc memory");
		return -ENOMEM;
	}
	comp->id = id;

	/* Load component data from firmware source */
	for (dest = comp->data; count; count--, dest++) {
		strscpy(buf, *psrc, min(sizeof(buf), *psize));
		rc = sscanf(buf, "%x %n", dest, &pos);
		if (!rc)
			goto err_data;

		*psrc += pos;
		*psize -= pos;
	}

	*pcomp = comp;

	return 1;

err_data:
	ZL3073X_FW_ERR_MSG(extack, "[%s] invalid or missing data", info->name);

	zl3073x_fw_component_free(comp);

	return -ENODATA;
}

/**
 * zl3073x_fw_free - Free allocated firmware
 * @fw: firmware pointer
 *
 * The function frees existing firmware allocated by @zl3073x_fw_load.
 */
void zl3073x_fw_free(struct zl3073x_fw *fw)
{
	size_t i;

	if (!fw)
		return;

	for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++)
		zl3073x_fw_component_free(fw->component[i]);

	kfree(fw);
}

/**
 * zl3073x_fw_load - Load all components from source
 * @zldev: zl3073x device structure
 * @data: source buffer pointer
 * @size: size of source buffer
 * @extack: netlink extack pointer to report errors
 *
 * The functions allocate firmware structure and loads all components from
 * the given buffer specified by @data and @size.
 *
 * Return: pointer to firmware on success, error pointer on error
 */
struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data,
				   size_t size, struct netlink_ext_ack *extack)
{
	struct zl3073x_fw_component *comp;
	enum zl3073x_fw_component_id id;
	struct zl3073x_fw *fw;
	ssize_t rc;

	/* Allocate firmware structure */
	fw = kzalloc(sizeof(*fw), GFP_KERNEL);
	if (!fw)
		return ERR_PTR(-ENOMEM);

	do {
		/* Load single component */
		rc = zl3073x_fw_component_load(zldev, &comp, &data, &size,
					       extack);
		if (rc <= 0)
			/* Everything was read or error occurred */
			break;

		id = comp->id;

		/* Report error if the given component is present twice
		 * or more.
		 */
		if (fw->component[id]) {
			ZL3073X_FW_ERR_MSG(extack,
					   "duplicate component '%s' detected",
					   component_info[id].name);
			zl3073x_fw_component_free(comp);
			rc = -EINVAL;
			break;
		}

		fw->component[id] = comp;
	} while (true);

	if (rc) {
		/* Free allocated firmware in case of error */
		zl3073x_fw_free(fw);
		return ERR_PTR(rc);
	}

	return fw;
}

/**
 * zl3073x_flash_bundle_flash - Flash all components
 * @zldev: zl3073x device structure
 * @components: pointer to components array
 * @extack: netlink extack pointer to report errors
 *
 * Returns 0 in case of success or negative number otherwise.
 */
static int
zl3073x_fw_component_flash(struct zl3073x_dev *zldev,
			   struct zl3073x_fw_component *comp,
			   struct netlink_ext_ack *extack)
{
	const struct zl3073x_fw_component_info *info;
	int rc;

	info = &component_info[comp->id];

	switch (info->flash_type) {
	case ZL3073X_FLASH_TYPE_NONE:
		/* Non-flashable component - used for utility */
		return 0;
	case ZL3073X_FLASH_TYPE_SECTORS:
		rc = zl3073x_flash_sectors(zldev, info->name, info->dest_page,
					   info->load_addr, comp->data,
					   comp->size, extack);
		break;
	case ZL3073X_FLASH_TYPE_PAGE:
		rc = zl3073x_flash_page(zldev, info->name, info->dest_page,
					info->load_addr, comp->data, comp->size,
					extack);
		break;
	case ZL3073X_FLASH_TYPE_PAGE_AND_COPY:
		rc = zl3073x_flash_page(zldev, info->name, info->dest_page,
					info->load_addr, comp->data, comp->size,
					extack);
		if (!rc)
			rc = zl3073x_flash_page_copy(zldev, info->name,
						     info->dest_page,
						     info->copy_page, extack);
		break;
	}
	if (rc)
		ZL3073X_FW_ERR_MSG(extack, "Failed to flash component '%s'",
				   info->name);

	return rc;
}

int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw,
		     struct netlink_ext_ack *extack)
{
	int i, rc = 0;

	for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) {
		if (!zlfw->component[i])
			continue; /* Component is not present */

		rc = zl3073x_fw_component_flash(zldev, zlfw->component[i],
						extack);
		if (rc)
			break;
	}

	return rc;
}