#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 = 0x2300,
.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,
},
};
static_assert(ARRAY_SIZE(component_info) == ZL_FW_NUM_COMPONENTS);
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;
}
static void
zl3073x_fw_component_free(struct zl3073x_fw_component *comp)
{
if (comp)
kfree(comp->data);
kfree(comp);
}
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;
}
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;
strscpy(buf, *psrc, min(sizeof(buf), *psize));
rc = sscanf(buf, "%15s %u %n", name, &count, &pos);
if (!rc) {
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);
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);
comp = zl3073x_fw_component_alloc(size);
if (!comp) {
ZL3073X_FW_ERR_MSG(extack, "failed to alloc memory");
return -ENOMEM;
}
comp->id = id;
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;
}
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);
}
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;
fw = kzalloc(sizeof(*fw), GFP_KERNEL);
if (!fw)
return ERR_PTR(-ENOMEM);
do {
rc = zl3073x_fw_component_load(zldev, &comp, &data, &size,
extack);
if (rc <= 0)
break;
id = comp->id;
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) {
zl3073x_fw_free(fw);
return ERR_PTR(rc);
}
return fw;
}
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:
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;
rc = zl3073x_fw_component_flash(zldev, zlfw->component[i],
extack);
if (rc)
break;
}
return rc;
}