diff options
Diffstat (limited to 'drivers/media/pci/intel/ipu6/ipu6-cpd.c')
-rw-r--r-- | drivers/media/pci/intel/ipu6/ipu6-cpd.c | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/drivers/media/pci/intel/ipu6/ipu6-cpd.c new file mode 100644 index 0000000000..715b21ab4b --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/err.h> +#include <linux/dma-mapping.h> +#include <linux/gfp_types.h> +#include <linux/math64.h> +#include <linux/sizes.h> +#include <linux/types.h> + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-cpd.h" + +/* 15 entries + header*/ +#define MAX_PKG_DIR_ENT_CNT 16 +/* 2 qword per entry/header */ +#define PKG_DIR_ENT_LEN 2 +/* PKG_DIR size in bytes */ +#define PKG_DIR_SIZE ((MAX_PKG_DIR_ENT_CNT) * \ + (PKG_DIR_ENT_LEN) * sizeof(u64)) +/* _IUPKDR_ */ +#define PKG_DIR_HDR_MARK 0x5f4955504b44525fULL + +/* $CPD */ +#define CPD_HDR_MARK 0x44504324 + +#define MAX_MANIFEST_SIZE (SZ_2K * sizeof(u32)) +#define MAX_METADATA_SIZE SZ_64K + +#define MAX_COMPONENT_ID 127 +#define MAX_COMPONENT_VERSION 0xffff + +#define MANIFEST_IDX 0 +#define METADATA_IDX 1 +#define MODULEDATA_IDX 2 +/* + * PKG_DIR Entry (type == id) + * 63:56 55 54:48 47:32 31:24 23:0 + * Rsvd Rsvd Type Version Rsvd Size + */ +#define PKG_DIR_SIZE_MASK GENMASK(23, 0) +#define PKG_DIR_VERSION_MASK GENMASK(47, 32) +#define PKG_DIR_TYPE_MASK GENMASK(54, 48) + +static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd, + u8 idx) +{ + const struct ipu6_cpd_hdr *cpd_hdr = cpd; + const struct ipu6_cpd_ent *ent; + + ent = (const struct ipu6_cpd_ent *)((const u8 *)cpd + cpd_hdr->hdr_len); + return ent + idx; +} + +#define ipu6_cpd_get_manifest(cpd) ipu6_cpd_get_entry(cpd, MANIFEST_IDX) +#define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX) +#define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX) + +static const struct ipu6_cpd_metadata_cmpnt_hdr * +ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata, + unsigned int metadata_size, u8 idx) +{ + size_t extn_size = sizeof(struct ipu6_cpd_metadata_extn); + size_t cmpnt_count = metadata_size - extn_size; + + cmpnt_count = div_u64(cmpnt_count, isp->cpd_metadata_cmpnt_size); + + if (idx > MAX_COMPONENT_ID || idx >= cmpnt_count) { + dev_err(&isp->pdev->dev, "Component index out of range (%d)\n", + idx); + return ERR_PTR(-EINVAL); + } + + return metadata + extn_size + idx * isp->cpd_metadata_cmpnt_size; +} + +static u32 ipu6_cpd_metadata_cmpnt_version(struct ipu6_device *isp, + const void *metadata, + unsigned int metadata_size, u8 idx) +{ + const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt; + + cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx); + if (IS_ERR(cmpnt)) + return PTR_ERR(cmpnt); + + return cmpnt->ver; +} + +static int ipu6_cpd_metadata_get_cmpnt_id(struct ipu6_device *isp, + const void *metadata, + unsigned int metadata_size, u8 idx) +{ + const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt; + + cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx); + if (IS_ERR(cmpnt)) + return PTR_ERR(cmpnt); + + return cmpnt->id; +} + +static int ipu6_cpd_parse_module_data(struct ipu6_device *isp, + const void *module_data, + unsigned int module_data_size, + dma_addr_t dma_addr_module_data, + u64 *pkg_dir, const void *metadata, + unsigned int metadata_size) +{ + const struct ipu6_cpd_module_data_hdr *module_data_hdr; + const struct ipu6_cpd_hdr *dir_hdr; + const struct ipu6_cpd_ent *dir_ent; + unsigned int i; + u8 len; + + if (!module_data) + return -EINVAL; + + module_data_hdr = module_data; + dir_hdr = module_data + module_data_hdr->hdr_len; + len = dir_hdr->hdr_len; + dir_ent = (const struct ipu6_cpd_ent *)(((u8 *)dir_hdr) + len); + + pkg_dir[0] = PKG_DIR_HDR_MARK; + /* pkg_dir entry count = component count + pkg_dir header */ + pkg_dir[1] = dir_hdr->ent_cnt + 1; + + for (i = 0; i < dir_hdr->ent_cnt; i++, dir_ent++) { + u64 *p = &pkg_dir[PKG_DIR_ENT_LEN * (1 + i)]; + int ver, id; + + *p++ = dma_addr_module_data + dir_ent->offset; + id = ipu6_cpd_metadata_get_cmpnt_id(isp, metadata, + metadata_size, i); + if (id < 0 || id > MAX_COMPONENT_ID) { + dev_err(&isp->pdev->dev, "Invalid CPD component id\n"); + return -EINVAL; + } + + ver = ipu6_cpd_metadata_cmpnt_version(isp, metadata, + metadata_size, i); + if (ver < 0 || ver > MAX_COMPONENT_VERSION) { + dev_err(&isp->pdev->dev, + "Invalid CPD component version\n"); + return -EINVAL; + } + + *p = FIELD_PREP(PKG_DIR_SIZE_MASK, dir_ent->len) | + FIELD_PREP(PKG_DIR_TYPE_MASK, id) | + FIELD_PREP(PKG_DIR_VERSION_MASK, ver); + } + + return 0; +} + +int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src) +{ + dma_addr_t dma_addr_src = sg_dma_address(adev->fw_sgt.sgl); + const struct ipu6_cpd_ent *ent, *man_ent, *met_ent; + struct device *dev = &adev->auxdev.dev; + struct ipu6_device *isp = adev->isp; + unsigned int man_sz, met_sz; + void *pkg_dir_pos; + int ret; + + man_ent = ipu6_cpd_get_manifest(src); + man_sz = man_ent->len; + + met_ent = ipu6_cpd_get_metadata(src); + met_sz = met_ent->len; + + adev->pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz; + adev->pkg_dir = dma_alloc_attrs(dev, adev->pkg_dir_size, + &adev->pkg_dir_dma_addr, GFP_KERNEL, 0); + if (!adev->pkg_dir) + return -ENOMEM; + + /* + * pkg_dir entry/header: + * qword | 63:56 | 55 | 54:48 | 47:32 | 31:24 | 23:0 + * N Address/Offset/"_IUPKDR_" + * N + 1 | rsvd | rsvd | type | ver | rsvd | size + * + * We can ignore other fields that size in N + 1 qword as they + * are 0 anyway. Just setting size for now. + */ + + ent = ipu6_cpd_get_moduledata(src); + + ret = ipu6_cpd_parse_module_data(isp, src + ent->offset, + ent->len, dma_addr_src + ent->offset, + adev->pkg_dir, src + met_ent->offset, + met_ent->len); + if (ret) { + dev_err(&isp->pdev->dev, "Failed to parse module data\n"); + dma_free_attrs(dev, adev->pkg_dir_size, + adev->pkg_dir, adev->pkg_dir_dma_addr, 0); + return ret; + } + + /* Copy manifest after pkg_dir */ + pkg_dir_pos = adev->pkg_dir + PKG_DIR_ENT_LEN * MAX_PKG_DIR_ENT_CNT; + memcpy(pkg_dir_pos, src + man_ent->offset, man_sz); + + /* Copy metadata after manifest */ + pkg_dir_pos += man_sz; + memcpy(pkg_dir_pos, src + met_ent->offset, met_sz); + + dma_sync_single_range_for_device(dev, adev->pkg_dir_dma_addr, + 0, adev->pkg_dir_size, DMA_TO_DEVICE); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, INTEL_IPU6); + +void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev) +{ + dma_free_attrs(&adev->auxdev.dev, adev->pkg_dir_size, adev->pkg_dir, + adev->pkg_dir_dma_addr, 0); +} +EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, INTEL_IPU6); + +static int ipu6_cpd_validate_cpd(struct ipu6_device *isp, const void *cpd, + unsigned long cpd_size, + unsigned long data_size) +{ + const struct ipu6_cpd_hdr *cpd_hdr = cpd; + const struct ipu6_cpd_ent *ent; + unsigned int i; + u8 len; + + len = cpd_hdr->hdr_len; + + /* Ensure cpd hdr is within moduledata */ + if (cpd_size < len) { + dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n"); + return -EINVAL; + } + + /* Sanity check for CPD header */ + if ((cpd_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) { + dev_err(&isp->pdev->dev, "Invalid CPD header\n"); + return -EINVAL; + } + + /* Ensure that all entries are within moduledata */ + ent = (const struct ipu6_cpd_ent *)(((const u8 *)cpd_hdr) + len); + for (i = 0; i < cpd_hdr->ent_cnt; i++, ent++) { + if (data_size < ent->offset || + data_size - ent->offset < ent->len) { + dev_err(&isp->pdev->dev, "Invalid CPD entry (%d)\n", i); + return -EINVAL; + } + } + + return 0; +} + +static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp, + const void *moduledata, + u32 moduledata_size) +{ + const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata; + int ret; + + /* Ensure moduledata hdr is within moduledata */ + if (moduledata_size < sizeof(*mod_hdr) || + moduledata_size < mod_hdr->hdr_len) { + dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n"); + return -EINVAL; + } + + dev_info(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date); + ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len, + moduledata_size - mod_hdr->hdr_len, + moduledata_size); + if (ret) { + dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n"); + return ret; + } + + return 0; +} + +static int ipu6_cpd_validate_metadata(struct ipu6_device *isp, + const void *metadata, u32 meta_size) +{ + const struct ipu6_cpd_metadata_extn *extn = metadata; + + /* Sanity check for metadata size */ + if (meta_size < sizeof(*extn) || meta_size > MAX_METADATA_SIZE) { + dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); + return -EINVAL; + } + + /* Validate extension and image types */ + if (extn->extn_type != IPU6_CPD_METADATA_EXTN_TYPE_IUNIT || + extn->img_type != IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE) { + dev_err(&isp->pdev->dev, + "Invalid CPD metadata descriptor img_type (%d)\n", + extn->img_type); + return -EINVAL; + } + + /* Validate metadata size multiple of metadata components */ + if ((meta_size - sizeof(*extn)) % isp->cpd_metadata_cmpnt_size) { + dev_err(&isp->pdev->dev, "Invalid CPD metadata size\n"); + return -EINVAL; + } + + return 0; +} + +int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, + unsigned long cpd_file_size) +{ + const struct ipu6_cpd_hdr *hdr = cpd_file; + const struct ipu6_cpd_ent *ent; + int ret; + + ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size, + cpd_file_size); + if (ret) { + dev_err(&isp->pdev->dev, "Invalid CPD in file\n"); + return ret; + } + + /* Check for CPD file marker */ + if (hdr->hdr_mark != CPD_HDR_MARK) { + dev_err(&isp->pdev->dev, "Invalid CPD header\n"); + return -EINVAL; + } + + /* Sanity check for manifest size */ + ent = ipu6_cpd_get_manifest(cpd_file); + if (ent->len > MAX_MANIFEST_SIZE) { + dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n"); + return -EINVAL; + } + + /* Validate metadata */ + ent = ipu6_cpd_get_metadata(cpd_file); + ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len); + if (ret) { + dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); + return ret; + } + + /* Validate moduledata */ + ent = ipu6_cpd_get_moduledata(cpd_file); + ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, + ent->len); + if (ret) + dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n"); + + return ret; +} |