Line data Source code
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 :
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 4 : ent = (const struct ipu6_cpd_ent *)((const u8 *)cpd + cpd_hdr->hdr_len);
58 5 : 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 *
66 0 : 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 0 : size_t cmpnt_count = metadata_size - extn_size;
71 :
72 0 : cmpnt_count = div_u64(cmpnt_count, isp->cpd_metadata_cmpnt_size);
73 :
74 0 : if (idx > MAX_COMPONENT_ID || idx >= cmpnt_count) {
75 0 : dev_err(&isp->pdev->dev, "Component index out of range (%d)\n",
76 : idx);
77 0 : return ERR_PTR(-EINVAL);
78 : }
79 :
80 0 : return metadata + extn_size + idx * isp->cpd_metadata_cmpnt_size;
81 : }
82 :
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 0 : cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
90 0 : if (IS_ERR(cmpnt))
91 0 : return PTR_ERR(cmpnt);
92 :
93 0 : return cmpnt->ver;
94 : }
95 :
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 0 : cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
103 0 : if (IS_ERR(cmpnt))
104 0 : return PTR_ERR(cmpnt);
105 :
106 0 : return cmpnt->id;
107 : }
108 :
109 1 : 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 1 : if (!module_data)
123 : return -EINVAL;
124 :
125 : module_data_hdr = module_data;
126 1 : dir_hdr = module_data + module_data_hdr->hdr_len;
127 1 : len = dir_hdr->hdr_len;
128 1 : dir_ent = (const struct ipu6_cpd_ent *)(((u8 *)dir_hdr) + len);
129 :
130 1 : pkg_dir[0] = PKG_DIR_HDR_MARK;
131 : /* pkg_dir entry count = component count + pkg_dir header */
132 1 : pkg_dir[1] = dir_hdr->ent_cnt + 1;
133 :
134 1 : for (i = 0; i < dir_hdr->ent_cnt; i++, dir_ent++) {
135 0 : u64 *p = &pkg_dir[PKG_DIR_ENT_LEN * (1 + i)];
136 : int ver, id;
137 :
138 0 : *p++ = dma_addr_module_data + dir_ent->offset;
139 : id = ipu6_cpd_metadata_get_cmpnt_id(isp, metadata,
140 : metadata_size, i);
141 0 : if (id < 0 || id > MAX_COMPONENT_ID) {
142 0 : dev_err(&isp->pdev->dev, "Invalid CPD component id\n");
143 0 : return -EINVAL;
144 : }
145 :
146 : ver = ipu6_cpd_metadata_cmpnt_version(isp, metadata,
147 : metadata_size, i);
148 0 : if (ver < 0 || ver > MAX_COMPONENT_VERSION) {
149 0 : dev_err(&isp->pdev->dev,
150 : "Invalid CPD component version\n");
151 0 : return -EINVAL;
152 : }
153 :
154 0 : *p = FIELD_PREP(PKG_DIR_SIZE_MASK, dir_ent->len) |
155 0 : FIELD_PREP(PKG_DIR_TYPE_MASK, id) |
156 0 : FIELD_PREP(PKG_DIR_VERSION_MASK, ver);
157 : }
158 :
159 : return 0;
160 : }
161 :
162 1 : int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src)
163 : {
164 1 : 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 1 : 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 1 : man_sz = man_ent->len;
173 :
174 : met_ent = ipu6_cpd_get_metadata(src);
175 1 : met_sz = met_ent->len;
176 :
177 1 : adev->pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz;
178 1 : adev->pkg_dir = ipu6_dma_alloc(adev, adev->pkg_dir_size,
179 : &adev->pkg_dir_dma_addr, GFP_KERNEL, 0);
180 1 : 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 1 : ret = ipu6_cpd_parse_module_data(isp, src + ent->offset,
196 1 : ent->len, dma_addr_src + ent->offset,
197 1 : adev->pkg_dir, src + met_ent->offset,
198 1 : met_ent->len);
199 1 : if (ret) {
200 0 : dev_err(&isp->pdev->dev, "Failed to parse module data\n");
201 0 : ipu6_dma_free(adev, adev->pkg_dir_size,
202 0 : adev->pkg_dir, adev->pkg_dir_dma_addr, 0);
203 0 : return ret;
204 : }
205 :
206 : /* Copy manifest after pkg_dir */
207 1 : pkg_dir_pos = adev->pkg_dir + PKG_DIR_ENT_LEN * MAX_PKG_DIR_ENT_CNT;
208 1 : memcpy(pkg_dir_pos, src + man_ent->offset, man_sz);
209 :
210 : /* Copy metadata after manifest */
211 1 : pkg_dir_pos += man_sz;
212 1 : memcpy(pkg_dir_pos, src + met_ent->offset, met_sz);
213 :
214 1 : ipu6_dma_sync_single(adev, adev->pkg_dir_dma_addr,
215 1 : adev->pkg_dir_size);
216 :
217 1 : return 0;
218 : }
219 : EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, INTEL_IPU6);
220 :
221 0 : void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev)
222 : {
223 0 : ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir,
224 : adev->pkg_dir_dma_addr, 0);
225 0 : }
226 : EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, INTEL_IPU6);
227 :
228 2 : 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 2 : len = cpd_hdr->hdr_len;
238 :
239 : /* Ensure cpd hdr is within moduledata */
240 2 : if (cpd_size < len) {
241 0 : dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
242 0 : return -EINVAL;
243 : }
244 :
245 : /* Sanity check for CPD header */
246 2 : if ((cpd_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) {
247 0 : dev_err(&isp->pdev->dev, "Invalid CPD header\n");
248 0 : return -EINVAL;
249 : }
250 :
251 : /* Ensure that all entries are within moduledata */
252 2 : ent = (const struct ipu6_cpd_ent *)(((const u8 *)cpd_hdr) + len);
253 5 : for (i = 0; i < cpd_hdr->ent_cnt; i++, ent++) {
254 3 : if (data_size < ent->offset ||
255 3 : data_size - ent->offset < ent->len) {
256 0 : dev_err(&isp->pdev->dev, "Invalid CPD entry (%d)\n", i);
257 0 : return -EINVAL;
258 : }
259 : }
260 :
261 : return 0;
262 : }
263 :
264 1 : 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 1 : if (moduledata_size < sizeof(*mod_hdr) ||
273 1 : moduledata_size < mod_hdr->hdr_len) {
274 0 : dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
275 0 : return -EINVAL;
276 : }
277 :
278 1 : dev_dbg(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date);
279 1 : ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len,
280 1 : moduledata_size - mod_hdr->hdr_len,
281 : moduledata_size);
282 1 : if (ret) {
283 0 : dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n");
284 0 : return ret;
285 : }
286 :
287 : return 0;
288 : }
289 :
290 1 : 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 1 : if (meta_size < sizeof(*extn) || meta_size > MAX_METADATA_SIZE) {
297 0 : dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
298 0 : return -EINVAL;
299 : }
300 :
301 : /* Validate extension and image types */
302 1 : if (extn->extn_type != IPU6_CPD_METADATA_EXTN_TYPE_IUNIT ||
303 1 : extn->img_type != IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE) {
304 0 : dev_err(&isp->pdev->dev,
305 : "Invalid CPD metadata descriptor img_type (%d)\n",
306 : extn->img_type);
307 0 : return -EINVAL;
308 : }
309 :
310 : /* Validate metadata size multiple of metadata components */
311 1 : if ((meta_size - sizeof(*extn)) % isp->cpd_metadata_cmpnt_size) {
312 0 : dev_err(&isp->pdev->dev, "Invalid CPD metadata size\n");
313 0 : return -EINVAL;
314 : }
315 :
316 : return 0;
317 : }
318 :
319 1 : 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 1 : ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size,
327 : cpd_file_size);
328 1 : if (ret) {
329 0 : dev_err(&isp->pdev->dev, "Invalid CPD in file\n");
330 0 : return ret;
331 : }
332 :
333 : /* Check for CPD file marker */
334 1 : if (hdr->hdr_mark != CPD_HDR_MARK) {
335 0 : dev_err(&isp->pdev->dev, "Invalid CPD header\n");
336 0 : return -EINVAL;
337 : }
338 :
339 : /* Sanity check for manifest size */
340 : ent = ipu6_cpd_get_manifest(cpd_file);
341 1 : if (ent->len > MAX_MANIFEST_SIZE) {
342 0 : dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n");
343 0 : return -EINVAL;
344 : }
345 :
346 : /* Validate metadata */
347 : ent = ipu6_cpd_get_metadata(cpd_file);
348 1 : ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len);
349 1 : if (ret) {
350 0 : dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
351 0 : return ret;
352 : }
353 :
354 : /* Validate moduledata */
355 : ent = ipu6_cpd_get_moduledata(cpd_file);
356 1 : ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset,
357 1 : ent->len);
358 1 : if (ret)
359 0 : dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n");
360 :
361 : return ret;
362 : }
|