File Index
Files and directories
Sign in
FILE

ipu6-cpd.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/pci/intel/ipu6/ipu6-cpd.c C
  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
 51static 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
 65static const struct ipu6_cpd_metadata_cmpnt_hdr *
 66ipu6_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
 83static 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
 96static 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
109static 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
162int 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}
219EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, "INTEL_IPU6");
220
221void 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}
226EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, "INTEL_IPU6");
227
228static 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
264static 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_dbg(&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
290static 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
319int 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}
~/ Read-only file preview.