420 lines
10 KiB
C
420 lines
10 KiB
C
// 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;
|
|
}
|