xref: /linux/drivers/media/pci/intel/ipu6/ipu6-cpd.c (revision cdd30ebb1b9f36159d66f088b61aee264e649d7a)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2013--2024 Intel Corporation
4  */
5 
6 #include <linux/bitfield.h>
7 #include <linux/bits.h>
8 #include <linux/err.h>
9 #include <linux/dma-mapping.h>
10 #include <linux/gfp_types.h>
11 #include <linux/math64.h>
12 #include <linux/sizes.h>
13 #include <linux/types.h>
14 
15 #include "ipu6.h"
16 #include "ipu6-bus.h"
17 #include "ipu6-cpd.h"
18 #include "ipu6-dma.h"
19 
20 /* 15 entries + header*/
21 #define MAX_PKG_DIR_ENT_CNT		16
22 /* 2 qword per entry/header */
23 #define PKG_DIR_ENT_LEN			2
24 /* PKG_DIR size in bytes */
25 #define PKG_DIR_SIZE			((MAX_PKG_DIR_ENT_CNT) *	\
26 					 (PKG_DIR_ENT_LEN) * sizeof(u64))
27 /* _IUPKDR_ */
28 #define PKG_DIR_HDR_MARK		0x5f4955504b44525fULL
29 
30 /* $CPD */
31 #define CPD_HDR_MARK			0x44504324
32 
33 #define MAX_MANIFEST_SIZE		(SZ_2K * sizeof(u32))
34 #define MAX_METADATA_SIZE		SZ_64K
35 
36 #define MAX_COMPONENT_ID		127
37 #define MAX_COMPONENT_VERSION		0xffff
38 
39 #define MANIFEST_IDX	0
40 #define METADATA_IDX	1
41 #define MODULEDATA_IDX	2
42 /*
43  * PKG_DIR Entry (type == id)
44  * 63:56        55      54:48   47:32   31:24   23:0
45  * Rsvd         Rsvd    Type    Version Rsvd    Size
46  */
47 #define PKG_DIR_SIZE_MASK	GENMASK_ULL(23, 0)
48 #define PKG_DIR_VERSION_MASK	GENMASK_ULL(47, 32)
49 #define PKG_DIR_TYPE_MASK	GENMASK_ULL(54, 48)
50 
ipu6_cpd_get_entry(const void * cpd,u8 idx)51 static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd,
52 							    u8 idx)
53 {
54 	const struct ipu6_cpd_hdr *cpd_hdr = cpd;
55 	const struct ipu6_cpd_ent *ent;
56 
57 	ent = (const struct ipu6_cpd_ent *)((const u8 *)cpd + cpd_hdr->hdr_len);
58 	return ent + idx;
59 }
60 
61 #define ipu6_cpd_get_manifest(cpd) ipu6_cpd_get_entry(cpd, MANIFEST_IDX)
62 #define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX)
63 #define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX)
64 
65 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)66 ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata,
67 			    unsigned int metadata_size, u8 idx)
68 {
69 	size_t extn_size = sizeof(struct ipu6_cpd_metadata_extn);
70 	size_t cmpnt_count = metadata_size - extn_size;
71 
72 	cmpnt_count = div_u64(cmpnt_count, isp->cpd_metadata_cmpnt_size);
73 
74 	if (idx > MAX_COMPONENT_ID || idx >= cmpnt_count) {
75 		dev_err(&isp->pdev->dev, "Component index out of range (%d)\n",
76 			idx);
77 		return ERR_PTR(-EINVAL);
78 	}
79 
80 	return metadata + extn_size + idx * isp->cpd_metadata_cmpnt_size;
81 }
82 
ipu6_cpd_metadata_cmpnt_version(struct ipu6_device * isp,const void * metadata,unsigned int metadata_size,u8 idx)83 static u32 ipu6_cpd_metadata_cmpnt_version(struct ipu6_device *isp,
84 					   const void *metadata,
85 					   unsigned int metadata_size, u8 idx)
86 {
87 	const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt;
88 
89 	cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
90 	if (IS_ERR(cmpnt))
91 		return PTR_ERR(cmpnt);
92 
93 	return cmpnt->ver;
94 }
95 
ipu6_cpd_metadata_get_cmpnt_id(struct ipu6_device * isp,const void * metadata,unsigned int metadata_size,u8 idx)96 static int ipu6_cpd_metadata_get_cmpnt_id(struct ipu6_device *isp,
97 					  const void *metadata,
98 					  unsigned int metadata_size, u8 idx)
99 {
100 	const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt;
101 
102 	cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
103 	if (IS_ERR(cmpnt))
104 		return PTR_ERR(cmpnt);
105 
106 	return cmpnt->id;
107 }
108 
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)109 static int ipu6_cpd_parse_module_data(struct ipu6_device *isp,
110 				      const void *module_data,
111 				      unsigned int module_data_size,
112 				      dma_addr_t dma_addr_module_data,
113 				      u64 *pkg_dir, const void *metadata,
114 				      unsigned int metadata_size)
115 {
116 	const struct ipu6_cpd_module_data_hdr *module_data_hdr;
117 	const struct ipu6_cpd_hdr *dir_hdr;
118 	const struct ipu6_cpd_ent *dir_ent;
119 	unsigned int i;
120 	u8 len;
121 
122 	if (!module_data)
123 		return -EINVAL;
124 
125 	module_data_hdr = module_data;
126 	dir_hdr = module_data + module_data_hdr->hdr_len;
127 	len = dir_hdr->hdr_len;
128 	dir_ent = (const struct ipu6_cpd_ent *)(((u8 *)dir_hdr) + len);
129 
130 	pkg_dir[0] = PKG_DIR_HDR_MARK;
131 	/* pkg_dir entry count = component count + pkg_dir header */
132 	pkg_dir[1] = dir_hdr->ent_cnt + 1;
133 
134 	for (i = 0; i < dir_hdr->ent_cnt; i++, dir_ent++) {
135 		u64 *p = &pkg_dir[PKG_DIR_ENT_LEN *  (1 + i)];
136 		int ver, id;
137 
138 		*p++ = dma_addr_module_data + dir_ent->offset;
139 		id = ipu6_cpd_metadata_get_cmpnt_id(isp, metadata,
140 						    metadata_size, i);
141 		if (id < 0 || id > MAX_COMPONENT_ID) {
142 			dev_err(&isp->pdev->dev, "Invalid CPD component id\n");
143 			return -EINVAL;
144 		}
145 
146 		ver = ipu6_cpd_metadata_cmpnt_version(isp, metadata,
147 						      metadata_size, i);
148 		if (ver < 0 || ver > MAX_COMPONENT_VERSION) {
149 			dev_err(&isp->pdev->dev,
150 				"Invalid CPD component version\n");
151 			return -EINVAL;
152 		}
153 
154 		*p = FIELD_PREP(PKG_DIR_SIZE_MASK, dir_ent->len) |
155 			FIELD_PREP(PKG_DIR_TYPE_MASK, id) |
156 			FIELD_PREP(PKG_DIR_VERSION_MASK, ver);
157 	}
158 
159 	return 0;
160 }
161 
ipu6_cpd_create_pkg_dir(struct ipu6_bus_device * adev,const void * src)162 int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src)
163 {
164 	dma_addr_t dma_addr_src = sg_dma_address(adev->fw_sgt.sgl);
165 	const struct ipu6_cpd_ent *ent, *man_ent, *met_ent;
166 	struct ipu6_device *isp = adev->isp;
167 	unsigned int man_sz, met_sz;
168 	void *pkg_dir_pos;
169 	int ret;
170 
171 	man_ent = ipu6_cpd_get_manifest(src);
172 	man_sz = man_ent->len;
173 
174 	met_ent = ipu6_cpd_get_metadata(src);
175 	met_sz = met_ent->len;
176 
177 	adev->pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz;
178 	adev->pkg_dir = ipu6_dma_alloc(adev, adev->pkg_dir_size,
179 				       &adev->pkg_dir_dma_addr, GFP_KERNEL, 0);
180 	if (!adev->pkg_dir)
181 		return -ENOMEM;
182 
183 	/*
184 	 * pkg_dir entry/header:
185 	 * qword | 63:56 | 55   | 54:48 | 47:32 | 31:24 | 23:0
186 	 * N         Address/Offset/"_IUPKDR_"
187 	 * N + 1 | rsvd  | rsvd | type  | ver   | rsvd  | size
188 	 *
189 	 * We can ignore other fields that size in N + 1 qword as they
190 	 * are 0 anyway. Just setting size for now.
191 	 */
192 
193 	ent = ipu6_cpd_get_moduledata(src);
194 
195 	ret = ipu6_cpd_parse_module_data(isp, src + ent->offset,
196 					 ent->len, dma_addr_src + ent->offset,
197 					 adev->pkg_dir, src + met_ent->offset,
198 					 met_ent->len);
199 	if (ret) {
200 		dev_err(&isp->pdev->dev, "Failed to parse module data\n");
201 		ipu6_dma_free(adev, adev->pkg_dir_size,
202 			      adev->pkg_dir, adev->pkg_dir_dma_addr, 0);
203 		return ret;
204 	}
205 
206 	/* Copy manifest after pkg_dir */
207 	pkg_dir_pos = adev->pkg_dir + PKG_DIR_ENT_LEN * MAX_PKG_DIR_ENT_CNT;
208 	memcpy(pkg_dir_pos, src + man_ent->offset, man_sz);
209 
210 	/* Copy metadata after manifest */
211 	pkg_dir_pos += man_sz;
212 	memcpy(pkg_dir_pos, src + met_ent->offset, met_sz);
213 
214 	ipu6_dma_sync_single(adev, adev->pkg_dir_dma_addr,
215 			     adev->pkg_dir_size);
216 
217 	return 0;
218 }
219 EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, "INTEL_IPU6");
220 
ipu6_cpd_free_pkg_dir(struct ipu6_bus_device * adev)221 void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev)
222 {
223 	ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir,
224 		      adev->pkg_dir_dma_addr, 0);
225 }
226 EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, "INTEL_IPU6");
227 
ipu6_cpd_validate_cpd(struct ipu6_device * isp,const void * cpd,unsigned long cpd_size,unsigned long data_size)228 static int ipu6_cpd_validate_cpd(struct ipu6_device *isp, const void *cpd,
229 				 unsigned long cpd_size,
230 				 unsigned long data_size)
231 {
232 	const struct ipu6_cpd_hdr *cpd_hdr = cpd;
233 	const struct ipu6_cpd_ent *ent;
234 	unsigned int i;
235 	u8 len;
236 
237 	len = cpd_hdr->hdr_len;
238 
239 	/* Ensure cpd hdr is within moduledata */
240 	if (cpd_size < len) {
241 		dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
242 		return -EINVAL;
243 	}
244 
245 	/* Sanity check for CPD header */
246 	if ((cpd_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) {
247 		dev_err(&isp->pdev->dev, "Invalid CPD header\n");
248 		return -EINVAL;
249 	}
250 
251 	/* Ensure that all entries are within moduledata */
252 	ent = (const struct ipu6_cpd_ent *)(((const u8 *)cpd_hdr) + len);
253 	for (i = 0; i < cpd_hdr->ent_cnt; i++, ent++) {
254 		if (data_size < ent->offset ||
255 		    data_size - ent->offset < ent->len) {
256 			dev_err(&isp->pdev->dev, "Invalid CPD entry (%d)\n", i);
257 			return -EINVAL;
258 		}
259 	}
260 
261 	return 0;
262 }
263 
ipu6_cpd_validate_moduledata(struct ipu6_device * isp,const void * moduledata,u32 moduledata_size)264 static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp,
265 					const void *moduledata,
266 					u32 moduledata_size)
267 {
268 	const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata;
269 	int ret;
270 
271 	/* Ensure moduledata hdr is within moduledata */
272 	if (moduledata_size < sizeof(*mod_hdr) ||
273 	    moduledata_size < mod_hdr->hdr_len) {
274 		dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
275 		return -EINVAL;
276 	}
277 
278 	dev_info(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date);
279 	ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len,
280 				    moduledata_size - mod_hdr->hdr_len,
281 				    moduledata_size);
282 	if (ret) {
283 		dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n");
284 		return ret;
285 	}
286 
287 	return 0;
288 }
289 
ipu6_cpd_validate_metadata(struct ipu6_device * isp,const void * metadata,u32 meta_size)290 static int ipu6_cpd_validate_metadata(struct ipu6_device *isp,
291 				      const void *metadata, u32 meta_size)
292 {
293 	const struct ipu6_cpd_metadata_extn *extn = metadata;
294 
295 	/* Sanity check for metadata size */
296 	if (meta_size < sizeof(*extn) || meta_size > MAX_METADATA_SIZE) {
297 		dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
298 		return -EINVAL;
299 	}
300 
301 	/* Validate extension and image types */
302 	if (extn->extn_type != IPU6_CPD_METADATA_EXTN_TYPE_IUNIT ||
303 	    extn->img_type != IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE) {
304 		dev_err(&isp->pdev->dev,
305 			"Invalid CPD metadata descriptor img_type (%d)\n",
306 			extn->img_type);
307 		return -EINVAL;
308 	}
309 
310 	/* Validate metadata size multiple of metadata components */
311 	if ((meta_size - sizeof(*extn)) % isp->cpd_metadata_cmpnt_size) {
312 		dev_err(&isp->pdev->dev, "Invalid CPD metadata size\n");
313 		return -EINVAL;
314 	}
315 
316 	return 0;
317 }
318 
ipu6_cpd_validate_cpd_file(struct ipu6_device * isp,const void * cpd_file,unsigned long cpd_file_size)319 int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file,
320 			       unsigned long cpd_file_size)
321 {
322 	const struct ipu6_cpd_hdr *hdr = cpd_file;
323 	const struct ipu6_cpd_ent *ent;
324 	int ret;
325 
326 	ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size,
327 				    cpd_file_size);
328 	if (ret) {
329 		dev_err(&isp->pdev->dev, "Invalid CPD in file\n");
330 		return ret;
331 	}
332 
333 	/* Check for CPD file marker */
334 	if (hdr->hdr_mark != CPD_HDR_MARK) {
335 		dev_err(&isp->pdev->dev, "Invalid CPD header\n");
336 		return -EINVAL;
337 	}
338 
339 	/* Sanity check for manifest size */
340 	ent = ipu6_cpd_get_manifest(cpd_file);
341 	if (ent->len > MAX_MANIFEST_SIZE) {
342 		dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n");
343 		return -EINVAL;
344 	}
345 
346 	/* Validate metadata */
347 	ent = ipu6_cpd_get_metadata(cpd_file);
348 	ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len);
349 	if (ret) {
350 		dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
351 		return ret;
352 	}
353 
354 	/* Validate moduledata */
355 	ent = ipu6_cpd_get_moduledata(cpd_file);
356 	ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset,
357 					   ent->len);
358 	if (ret)
359 		dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n");
360 
361 	return ret;
362 }
363