| // 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_obj(*comp); | |
| 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_obj(*fw); | |
| 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_fw_component_flash - Flash all components | |
| * @zldev: zl3073x device structure | |
| * @comp: pointer to components array | |
| * @extack: netlink extack pointer to report errors | |
| * | |
| * Return: 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; | |
| } |