#include <linux/device.h>
#include <linux/export.h>
#include <linux/gfp_types.h>
#include <linux/pci.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "ipu7.h"
#include "ipu7-cpd.h"
#define CPD_HDR_MARK 0x44504324
#define MAX_MANIFEST_SIZE (SZ_4K * sizeof(u32))
#define CPD_MANIFEST_IDX 0
#define CPD_BINARY_START_IDX 1U
#define CPD_METADATA_START_IDX 2U
#define CPD_BINARY_NUM 2U
#define CPD_ENTRY_NUM (CPD_BINARY_NUM * 2U + 1U)
#define CPD_METADATA_ATTR 0xa
#define CPD_METADATA_IPL 0x1c
#define ONLINE_METADATA_SIZE 128U
#define ONLINE_METADATA_LINES 6U
struct ipu7_cpd_hdr {
u32 hdr_mark;
u32 ent_cnt;
u8 hdr_ver;
u8 ent_ver;
u8 hdr_len;
u8 rsvd;
u8 partition_name[4];
u32 crc32;
} __packed;
struct ipu7_cpd_ent {
u8 name[12];
u32 offset;
u32 len;
u8 rsvd[4];
} __packed;
struct ipu7_cpd_metadata_hdr {
u32 type;
u32 len;
} __packed;
struct ipu7_cpd_metadata_attr {
struct ipu7_cpd_metadata_hdr hdr;
u8 compression_type;
u8 encryption_type;
u8 rsvd[2];
u32 uncompressed_size;
u32 compressed_size;
u32 module_id;
u8 hash[48];
} __packed;
struct ipu7_cpd_metadata_ipl {
struct ipu7_cpd_metadata_hdr hdr;
u32 param[4];
u8 rsvd[8];
} __packed;
struct ipu7_cpd_metadata {
struct ipu7_cpd_metadata_attr attr;
struct ipu7_cpd_metadata_ipl ipl;
} __packed;
static inline struct ipu7_cpd_ent *ipu7_cpd_get_entry(const void *cpd, int idx)
{
const struct ipu7_cpd_hdr *cpd_hdr = cpd;
return ((struct ipu7_cpd_ent *)((u8 *)cpd + cpd_hdr->hdr_len)) + idx;
}
#define ipu7_cpd_get_manifest(cpd) ipu7_cpd_get_entry(cpd, 0)
static struct ipu7_cpd_metadata *ipu7_cpd_get_metadata(const void *cpd, int idx)
{
struct ipu7_cpd_ent *cpd_ent =
ipu7_cpd_get_entry(cpd, CPD_METADATA_START_IDX + idx * 2);
return (struct ipu7_cpd_metadata *)((u8 *)cpd + cpd_ent->offset);
}
static int ipu7_cpd_validate_cpd(struct ipu7_device *isp,
const void *cpd, unsigned long data_size)
{
const struct ipu7_cpd_hdr *cpd_hdr = cpd;
struct device *dev = &isp->pdev->dev;
struct ipu7_cpd_ent *ent;
unsigned int i;
u8 len;
len = cpd_hdr->hdr_len;
if (data_size < len) {
dev_err(dev, "Invalid CPD moduledata size\n");
return -EINVAL;
}
if (cpd_hdr->hdr_mark != CPD_HDR_MARK) {
dev_err(dev, "Invalid CPD header marker\n");
return -EINVAL;
}
if (cpd_hdr->ent_cnt != CPD_ENTRY_NUM) {
dev_err(dev, "Invalid CPD entry number %d\n",
cpd_hdr->ent_cnt);
return -EINVAL;
}
if ((data_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) {
dev_err(dev, "Invalid CPD entry headers\n");
return -EINVAL;
}
ent = (struct ipu7_cpd_ent *)(((u8 *)cpd_hdr) + len);
for (i = 0; i < cpd_hdr->ent_cnt; i++) {
if (data_size < ent->offset ||
data_size - ent->offset < ent->len) {
dev_err(dev, "Invalid CPD entry %d\n", i);
return -EINVAL;
}
ent++;
}
return 0;
}
static int ipu7_cpd_validate_metadata(struct ipu7_device *isp,
const void *cpd, int idx)
{
const struct ipu7_cpd_ent *cpd_ent =
ipu7_cpd_get_entry(cpd, CPD_METADATA_START_IDX + idx * 2);
const struct ipu7_cpd_metadata *metadata =
ipu7_cpd_get_metadata(cpd, idx);
struct device *dev = &isp->pdev->dev;
if (cpd_ent->len != sizeof(struct ipu7_cpd_metadata)) {
dev_err(dev, "Invalid metadata size\n");
return -EINVAL;
}
if (metadata->attr.hdr.type != CPD_METADATA_ATTR) {
dev_err(dev, "Invalid metadata attr type (%d)\n",
metadata->attr.hdr.type);
return -EINVAL;
}
if (metadata->attr.hdr.len != sizeof(struct ipu7_cpd_metadata_attr)) {
dev_err(dev, "Invalid metadata attr size (%d)\n",
metadata->attr.hdr.len);
return -EINVAL;
}
if (metadata->ipl.hdr.type != CPD_METADATA_IPL) {
dev_err(dev, "Invalid metadata ipl type (%d)\n",
metadata->ipl.hdr.type);
return -EINVAL;
}
if (metadata->ipl.hdr.len != sizeof(struct ipu7_cpd_metadata_ipl)) {
dev_err(dev, "Invalid metadata ipl size (%d)\n",
metadata->ipl.hdr.len);
return -EINVAL;
}
return 0;
}
int ipu7_cpd_validate_cpd_file(struct ipu7_device *isp, const void *cpd_file,
unsigned long cpd_file_size)
{
struct device *dev = &isp->pdev->dev;
struct ipu7_cpd_ent *ent;
unsigned int i;
int ret;
char *buf;
ret = ipu7_cpd_validate_cpd(isp, cpd_file, cpd_file_size);
if (ret) {
dev_err(dev, "Invalid CPD in file\n");
return -EINVAL;
}
ent = ipu7_cpd_get_manifest(cpd_file);
if (ent->len > MAX_MANIFEST_SIZE) {
dev_err(dev, "Invalid manifest size\n");
return -EINVAL;
}
for (i = 0; i < CPD_BINARY_NUM; i++) {
ret = ipu7_cpd_validate_metadata(isp, cpd_file, i);
if (ret) {
dev_err(dev, "Invalid metadata%d\n", i);
return ret;
}
}
buf = kmalloc(ONLINE_METADATA_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
for (i = 0; i < CPD_BINARY_NUM; i++) {
char *lines[ONLINE_METADATA_LINES];
char *info = buf;
unsigned int l;
ent = ipu7_cpd_get_entry(cpd_file,
CPD_BINARY_START_IDX + i * 2U);
memcpy(info, (u8 *)cpd_file + ent->offset + ent->len -
ONLINE_METADATA_SIZE, ONLINE_METADATA_SIZE);
for (l = 0; l < ONLINE_METADATA_LINES; l++) {
lines[l] = strsep((char **)&info, "\n");
if (!lines[l])
break;
}
if (l < ONLINE_METADATA_LINES) {
dev_err(dev, "Failed to parse fw binary%d info.\n", i);
continue;
}
dev_info(dev, "FW binary%d info:\n", i);
dev_info(dev, "Name: %s\n", lines[1]);
dev_info(dev, "Version: %s\n", lines[2]);
dev_info(dev, "Timestamp: %s\n", lines[3]);
dev_info(dev, "Commit: %s\n", lines[4]);
}
kfree(buf);
return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu7_cpd_validate_cpd_file, "INTEL_IPU7");
int ipu7_cpd_copy_binary(const void *cpd, const char *name,
void *code_region, u32 *entry)
{
unsigned int i;
for (i = 0; i < CPD_BINARY_NUM; i++) {
const struct ipu7_cpd_ent *binary =
ipu7_cpd_get_entry(cpd, CPD_BINARY_START_IDX + i * 2U);
const struct ipu7_cpd_metadata *metadata =
ipu7_cpd_get_metadata(cpd, i);
if (!strncmp(binary->name, name, sizeof(binary->name))) {
memcpy(code_region + metadata->ipl.param[0],
cpd + binary->offset, binary->len);
*entry = metadata->ipl.param[2];
return 0;
}
}
return -ENOENT;
}
EXPORT_SYMBOL_NS_GPL(ipu7_cpd_copy_binary, "INTEL_IPU7");