xref: /linux/drivers/staging/media/ipu7/ipu7-cpd.c (revision 8d2b0853add1d7534dc0794e3c8e0b9e8c4ec640)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2015 - 2025 Intel Corporation
4  */
5 
6 #include <linux/device.h>
7 #include <linux/export.h>
8 #include <linux/gfp_types.h>
9 #include <linux/pci.h>
10 #include <linux/sizes.h>
11 #include <linux/slab.h>
12 #include <linux/types.h>
13 
14 #include "ipu7.h"
15 #include "ipu7-cpd.h"
16 
17 /* $CPD */
18 #define CPD_HDR_MARK		0x44504324
19 
20 /* Maximum size is 4K DWORDs or 16KB */
21 #define MAX_MANIFEST_SIZE	(SZ_4K * sizeof(u32))
22 
23 #define CPD_MANIFEST_IDX	0
24 #define CPD_BINARY_START_IDX	1U
25 #define CPD_METADATA_START_IDX	2U
26 #define CPD_BINARY_NUM		2U /* ISYS + PSYS */
27 /*
28  * Entries include:
29  * 1 manifest entry.
30  * 1 metadata entry for each sub system(ISYS and PSYS).
31  * 1 binary entry for each sub system(ISYS and PSYS).
32  */
33 #define CPD_ENTRY_NUM		(CPD_BINARY_NUM * 2U + 1U)
34 
35 #define CPD_METADATA_ATTR	0xa
36 #define CPD_METADATA_IPL	0x1c
37 #define ONLINE_METADATA_SIZE	128U
38 #define ONLINE_METADATA_LINES	6U
39 
40 struct ipu7_cpd_hdr {
41 	u32 hdr_mark;
42 	u32 ent_cnt;
43 	u8 hdr_ver;
44 	u8 ent_ver;
45 	u8 hdr_len;
46 	u8 rsvd;
47 	u8 partition_name[4];
48 	u32 crc32;
49 } __packed;
50 
51 struct ipu7_cpd_ent {
52 	u8 name[12];
53 	u32 offset;
54 	u32 len;
55 	u8 rsvd[4];
56 } __packed;
57 
58 struct ipu7_cpd_metadata_hdr {
59 	u32 type;
60 	u32 len;
61 } __packed;
62 
63 struct ipu7_cpd_metadata_attr {
64 	struct ipu7_cpd_metadata_hdr hdr;
65 	u8 compression_type;
66 	u8 encryption_type;
67 	u8 rsvd[2];
68 	u32 uncompressed_size;
69 	u32 compressed_size;
70 	u32 module_id;
71 	u8 hash[48];
72 } __packed;
73 
74 struct ipu7_cpd_metadata_ipl {
75 	struct ipu7_cpd_metadata_hdr hdr;
76 	u32 param[4];
77 	u8 rsvd[8];
78 } __packed;
79 
80 struct ipu7_cpd_metadata {
81 	struct ipu7_cpd_metadata_attr attr;
82 	struct ipu7_cpd_metadata_ipl ipl;
83 } __packed;
84 
85 static inline struct ipu7_cpd_ent *ipu7_cpd_get_entry(const void *cpd, int idx)
86 {
87 	const struct ipu7_cpd_hdr *cpd_hdr = cpd;
88 
89 	return ((struct ipu7_cpd_ent *)((u8 *)cpd + cpd_hdr->hdr_len)) + idx;
90 }
91 
92 #define ipu7_cpd_get_manifest(cpd) ipu7_cpd_get_entry(cpd, 0)
93 
94 static struct ipu7_cpd_metadata *ipu7_cpd_get_metadata(const void *cpd, int idx)
95 {
96 	struct ipu7_cpd_ent *cpd_ent =
97 		ipu7_cpd_get_entry(cpd, CPD_METADATA_START_IDX + idx * 2);
98 
99 	return (struct ipu7_cpd_metadata *)((u8 *)cpd + cpd_ent->offset);
100 }
101 
102 static int ipu7_cpd_validate_cpd(struct ipu7_device *isp,
103 				 const void *cpd, unsigned long data_size)
104 {
105 	const struct ipu7_cpd_hdr *cpd_hdr = cpd;
106 	struct device *dev = &isp->pdev->dev;
107 	struct ipu7_cpd_ent *ent;
108 	unsigned int i;
109 	u8 len;
110 
111 	len = cpd_hdr->hdr_len;
112 
113 	/* Ensure cpd hdr is within moduledata */
114 	if (data_size < len) {
115 		dev_err(dev, "Invalid CPD moduledata size\n");
116 		return -EINVAL;
117 	}
118 
119 	/* Check for CPD file marker */
120 	if (cpd_hdr->hdr_mark != CPD_HDR_MARK) {
121 		dev_err(dev, "Invalid CPD header marker\n");
122 		return -EINVAL;
123 	}
124 
125 	/* Sanity check for CPD entry header */
126 	if (cpd_hdr->ent_cnt != CPD_ENTRY_NUM) {
127 		dev_err(dev, "Invalid CPD entry number %d\n",
128 			cpd_hdr->ent_cnt);
129 		return -EINVAL;
130 	}
131 	if ((data_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) {
132 		dev_err(dev, "Invalid CPD entry headers\n");
133 		return -EINVAL;
134 	}
135 
136 	/* Ensure that all entries are within moduledata */
137 	ent = (struct ipu7_cpd_ent *)(((u8 *)cpd_hdr) + len);
138 	for (i = 0; i < cpd_hdr->ent_cnt; i++) {
139 		if (data_size < ent->offset ||
140 		    data_size - ent->offset < ent->len) {
141 			dev_err(dev, "Invalid CPD entry %d\n", i);
142 			return -EINVAL;
143 		}
144 		ent++;
145 	}
146 
147 	return 0;
148 }
149 
150 static int ipu7_cpd_validate_metadata(struct ipu7_device *isp,
151 				      const void *cpd, int idx)
152 {
153 	const struct ipu7_cpd_ent *cpd_ent =
154 		ipu7_cpd_get_entry(cpd, CPD_METADATA_START_IDX + idx * 2);
155 	const struct ipu7_cpd_metadata *metadata =
156 		ipu7_cpd_get_metadata(cpd, idx);
157 	struct device *dev = &isp->pdev->dev;
158 
159 	/* Sanity check for metadata size */
160 	if (cpd_ent->len != sizeof(struct ipu7_cpd_metadata)) {
161 		dev_err(dev, "Invalid metadata size\n");
162 		return -EINVAL;
163 	}
164 
165 	/* Validate type and length of metadata sections */
166 	if (metadata->attr.hdr.type != CPD_METADATA_ATTR) {
167 		dev_err(dev, "Invalid metadata attr type (%d)\n",
168 			metadata->attr.hdr.type);
169 		return -EINVAL;
170 	}
171 	if (metadata->attr.hdr.len != sizeof(struct ipu7_cpd_metadata_attr)) {
172 		dev_err(dev, "Invalid metadata attr size (%d)\n",
173 			metadata->attr.hdr.len);
174 		return -EINVAL;
175 	}
176 	if (metadata->ipl.hdr.type != CPD_METADATA_IPL) {
177 		dev_err(dev, "Invalid metadata ipl type (%d)\n",
178 			metadata->ipl.hdr.type);
179 		return -EINVAL;
180 	}
181 	if (metadata->ipl.hdr.len != sizeof(struct ipu7_cpd_metadata_ipl)) {
182 		dev_err(dev, "Invalid metadata ipl size (%d)\n",
183 			metadata->ipl.hdr.len);
184 		return -EINVAL;
185 	}
186 
187 	return 0;
188 }
189 
190 int ipu7_cpd_validate_cpd_file(struct ipu7_device *isp, const void *cpd_file,
191 			       unsigned long cpd_file_size)
192 {
193 	struct device *dev = &isp->pdev->dev;
194 	struct ipu7_cpd_ent *ent;
195 	unsigned int i;
196 	int ret;
197 	char *buf;
198 
199 	ret = ipu7_cpd_validate_cpd(isp, cpd_file, cpd_file_size);
200 	if (ret) {
201 		dev_err(dev, "Invalid CPD in file\n");
202 		return -EINVAL;
203 	}
204 
205 	/* Sanity check for manifest size */
206 	ent = ipu7_cpd_get_manifest(cpd_file);
207 	if (ent->len > MAX_MANIFEST_SIZE) {
208 		dev_err(dev, "Invalid manifest size\n");
209 		return -EINVAL;
210 	}
211 
212 	/* Validate metadata */
213 	for (i = 0; i < CPD_BINARY_NUM; i++) {
214 		ret = ipu7_cpd_validate_metadata(isp, cpd_file, i);
215 		if (ret) {
216 			dev_err(dev, "Invalid metadata%d\n", i);
217 			return ret;
218 		}
219 	}
220 
221 	/* Get fw binary version. */
222 	buf = kmalloc(ONLINE_METADATA_SIZE, GFP_KERNEL);
223 	if (!buf)
224 		return -ENOMEM;
225 	for (i = 0; i < CPD_BINARY_NUM; i++) {
226 		char *lines[ONLINE_METADATA_LINES];
227 		char *info = buf;
228 		unsigned int l;
229 
230 		ent = ipu7_cpd_get_entry(cpd_file,
231 					 CPD_BINARY_START_IDX + i * 2U);
232 		memcpy(info, (u8 *)cpd_file + ent->offset + ent->len -
233 		       ONLINE_METADATA_SIZE, ONLINE_METADATA_SIZE);
234 		for (l = 0; l < ONLINE_METADATA_LINES; l++) {
235 			lines[l] = strsep((char **)&info, "\n");
236 			if (!lines[l])
237 				break;
238 		}
239 		if (l < ONLINE_METADATA_LINES) {
240 			dev_err(dev, "Failed to parse fw binary%d info.\n", i);
241 			continue;
242 		}
243 		dev_info(dev, "FW binary%d info:\n", i);
244 		dev_info(dev, "Name: %s\n", lines[1]);
245 		dev_info(dev, "Version: %s\n", lines[2]);
246 		dev_info(dev, "Timestamp: %s\n", lines[3]);
247 		dev_info(dev, "Commit: %s\n", lines[4]);
248 	}
249 	kfree(buf);
250 
251 	return 0;
252 }
253 EXPORT_SYMBOL_NS_GPL(ipu7_cpd_validate_cpd_file, "INTEL_IPU7");
254 
255 int ipu7_cpd_copy_binary(const void *cpd, const char *name,
256 			 void *code_region, u32 *entry)
257 {
258 	unsigned int i;
259 
260 	for (i = 0; i < CPD_BINARY_NUM; i++) {
261 		const struct ipu7_cpd_ent *binary =
262 			ipu7_cpd_get_entry(cpd, CPD_BINARY_START_IDX + i * 2U);
263 		const struct ipu7_cpd_metadata *metadata =
264 			ipu7_cpd_get_metadata(cpd, i);
265 
266 		if (!strncmp(binary->name, name, sizeof(binary->name))) {
267 			memcpy(code_region + metadata->ipl.param[0],
268 			       cpd + binary->offset, binary->len);
269 			*entry = metadata->ipl.param[2];
270 			return 0;
271 		}
272 	}
273 
274 	return -ENOENT;
275 }
276 EXPORT_SYMBOL_NS_GPL(ipu7_cpd_copy_binary, "INTEL_IPU7");
277