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
ipu7_cpd_get_entry(const void * cpd,int idx)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
ipu7_cpd_get_metadata(const void * cpd,int idx)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
ipu7_cpd_validate_cpd(struct ipu7_device * isp,const void * cpd,unsigned long data_size)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
ipu7_cpd_validate_metadata(struct ipu7_device * isp,const void * cpd,int idx)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
ipu7_cpd_validate_cpd_file(struct ipu7_device * isp,const void * cpd_file,unsigned long cpd_file_size)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
ipu7_cpd_copy_binary(const void * cpd,const char * name,void * code_region,u32 * entry)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