summaryrefslogtreecommitdiff
path: root/drivers/media/pci/intel/ipu6/ipu6-cpd.c
blob: 55ffd988ae4f1c1ab5b431ddb3593792971ce18d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
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"
#include "ipu6-dma.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_ULL(23, 0)
#define PKG_DIR_VERSION_MASK	GENMASK_ULL(47, 32)
#define PKG_DIR_TYPE_MASK	GENMASK_ULL(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 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 = ipu6_dma_alloc(adev, 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");
		ipu6_dma_free(adev, 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);

	ipu6_dma_sync_single(adev, adev->pkg_dir_dma_addr,
			     adev->pkg_dir_size);

	return 0;
}
EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, INTEL_IPU6);

void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev)
{
	ipu6_dma_free(adev, 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;
}