/src/fwupd/libfwupdplugin/fu-ifwi-cpd-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2022 Richard Hughes <richard@hughsie.com> |
3 | | * Copyright 2022 Intel |
4 | | * |
5 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
6 | | */ |
7 | | |
8 | | #define G_LOG_DOMAIN "FuFirmware" |
9 | | |
10 | | #include "config.h" |
11 | | |
12 | | #include "fu-byte-array.h" |
13 | | #include "fu-common.h" |
14 | | #include "fu-ifwi-cpd-firmware.h" |
15 | | #include "fu-ifwi-cpd-image.h" |
16 | | #include "fu-ifwi-struct.h" |
17 | | #include "fu-input-stream.h" |
18 | | #include "fu-partial-input-stream.h" |
19 | | #include "fu-string.h" |
20 | | #include "fu-version-common.h" |
21 | | |
22 | | /** |
23 | | * FuIfwiCpdFirmware: |
24 | | * |
25 | | * An Intel Code Partition Directory (aka CPD) can be found in IFWI (Integrated Firmware Image) |
26 | | * firmware blobs which are used in various Intel products using an IPU (Infrastructure Processing |
27 | | * Unit). |
28 | | * |
29 | | * This could include hardware like SmartNICs, GPUs, camera and audio devices. |
30 | | * |
31 | | * See also: [class@FuFirmware] |
32 | | */ |
33 | | |
34 | | typedef struct { |
35 | | guint8 header_version; |
36 | | guint8 entry_version; |
37 | | } FuIfwiCpdFirmwarePrivate; |
38 | | |
39 | 1.49k | G_DEFINE_TYPE_WITH_PRIVATE(FuIfwiCpdFirmware, fu_ifwi_cpd_firmware, FU_TYPE_FIRMWARE) |
40 | 1.50k | #define GET_PRIVATE(o) (fu_ifwi_cpd_firmware_get_instance_private(o)) |
41 | | |
42 | 1.38k | #define FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX 1024 |
43 | | |
44 | | static void |
45 | | fu_ifwi_cpd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
46 | 0 | { |
47 | 0 | FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware); |
48 | 0 | FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self); |
49 | 0 | fu_xmlb_builder_insert_kx(bn, "header_version", priv->header_version); |
50 | 0 | fu_xmlb_builder_insert_kx(bn, "entry_version", priv->entry_version); |
51 | 0 | } |
52 | | |
53 | | static gboolean |
54 | | fu_ifwi_cpd_firmware_parse_manifest(FuIfwiCpdFirmware *self, |
55 | | FuFirmware *firmware, |
56 | | FuInputStream *stream, |
57 | | FuFirmwareParseFlags flags, |
58 | | GError **error) |
59 | 733 | { |
60 | 733 | gsize streamsz = 0; |
61 | 733 | guint32 size; |
62 | 733 | gsize header_length; |
63 | 733 | gsize offset = 0; |
64 | 733 | guint64 version_raw = 0; |
65 | 733 | g_autoptr(FuStructIfwiCpdManifest) st_mhd = NULL; |
66 | | |
67 | | /* raw version */ |
68 | 733 | st_mhd = fu_struct_ifwi_cpd_manifest_parse_stream(stream, offset, error); |
69 | 733 | if (st_mhd == NULL) |
70 | 0 | return FALSE; |
71 | | |
72 | | /* read as [u16le;4] and then build back into a native u64 */ |
73 | 733 | version_raw += (guint64)fu_struct_ifwi_cpd_manifest_get_version_major(st_mhd) << 48; |
74 | 733 | version_raw += (guint64)fu_struct_ifwi_cpd_manifest_get_version_minor(st_mhd) << 32; |
75 | 733 | version_raw += (guint64)fu_struct_ifwi_cpd_manifest_get_version_hotfix(st_mhd) << 16; |
76 | 733 | version_raw += ((guint64)fu_struct_ifwi_cpd_manifest_get_version_build(st_mhd)) << 0; |
77 | 733 | fu_firmware_set_version_raw(FU_FIRMWARE(self), version_raw); |
78 | | |
79 | | /* verify the size */ |
80 | 733 | if (!fu_input_stream_size(stream, &streamsz, error)) |
81 | 0 | return FALSE; |
82 | 733 | size = fu_struct_ifwi_cpd_manifest_get_size(st_mhd); |
83 | 733 | if ((guint64)size * 4 > G_MAXUINT32) { |
84 | 14 | g_set_error(error, |
85 | 14 | FWUPD_ERROR, |
86 | 14 | FWUPD_ERROR_INVALID_DATA, |
87 | 14 | "manifest size 0x%x dwords would overflow", |
88 | 14 | size); |
89 | 14 | return FALSE; |
90 | 14 | } |
91 | 719 | if (size * 4 != streamsz) { |
92 | 198 | g_set_error(error, |
93 | 198 | FWUPD_ERROR, |
94 | 198 | FWUPD_ERROR_INVALID_DATA, |
95 | 198 | "invalid manifest length, got 0x%x, expected 0x%x", |
96 | 198 | size * 4, |
97 | 198 | (guint)streamsz); |
98 | 198 | return FALSE; |
99 | 198 | } |
100 | | |
101 | | /* parse extensions */ |
102 | 521 | header_length = fu_struct_ifwi_cpd_manifest_get_header_length(st_mhd); |
103 | 521 | if (!fu_size_checked_inc_product(&offset, header_length, 4, error)) { |
104 | 0 | g_prefix_error_literal(error, "manifest offset overflow: "); |
105 | 0 | return FALSE; |
106 | 0 | } |
107 | 4.34k | while (offset < streamsz) { |
108 | 4.14k | guint32 extension_type = 0; |
109 | 4.14k | guint32 extension_length = 0; |
110 | 4.14k | g_autoptr(FuFirmware) img = fu_firmware_new(); |
111 | 4.14k | g_autoptr(FuStructIfwiCpdManifestExt) st_mex = NULL; |
112 | 4.14k | g_autoptr(FuInputStream) partial_stream = NULL; |
113 | | |
114 | | /* set the extension type as the index */ |
115 | 4.14k | st_mex = fu_struct_ifwi_cpd_manifest_ext_parse_stream(stream, offset, error); |
116 | 4.14k | if (st_mex == NULL) |
117 | 14 | return FALSE; |
118 | 4.13k | extension_type = fu_struct_ifwi_cpd_manifest_ext_get_extension_type(st_mex); |
119 | 4.13k | if (extension_type == 0x0) |
120 | 47 | break; |
121 | 4.08k | fu_firmware_set_idx(img, extension_type); |
122 | | |
123 | | /* add data section */ |
124 | 4.08k | extension_length = fu_struct_ifwi_cpd_manifest_ext_get_extension_length(st_mex); |
125 | 4.08k | if (extension_length == 0x0) |
126 | 95 | break; |
127 | 3.98k | if (extension_length < st_mex->buf->len) { |
128 | 7 | g_set_error(error, |
129 | 7 | FWUPD_ERROR, |
130 | 7 | FWUPD_ERROR_INVALID_DATA, |
131 | 7 | "invalid manifest extension header length 0x%x", |
132 | 7 | (guint)extension_length); |
133 | 7 | return FALSE; |
134 | 7 | } |
135 | 3.98k | partial_stream = fu_partial_input_stream_new(stream, |
136 | 3.98k | offset + st_mex->buf->len, |
137 | 3.98k | extension_length - st_mex->buf->len, |
138 | 3.98k | error); |
139 | 3.98k | if (partial_stream == NULL) { |
140 | 151 | g_prefix_error_literal(error, "failed to cut CPD extension: "); |
141 | 151 | return FALSE; |
142 | 151 | } |
143 | 3.83k | if (!fu_firmware_parse_stream(img, partial_stream, 0x0, flags, error)) |
144 | 3 | return FALSE; |
145 | | |
146 | | /* success */ |
147 | 3.82k | fu_firmware_set_offset(img, offset); |
148 | 3.82k | if (!fu_firmware_add_image(firmware, img, error)) |
149 | 0 | return FALSE; |
150 | 3.82k | if (!fu_size_checked_inc(&offset, extension_length, error)) |
151 | 0 | return FALSE; |
152 | 3.82k | } |
153 | | |
154 | | /* success */ |
155 | 346 | return TRUE; |
156 | 521 | } |
157 | | |
158 | | static gboolean |
159 | | fu_ifwi_cpd_firmware_validate(FuFirmware *firmware, |
160 | | FuInputStream *stream, |
161 | | gsize offset, |
162 | | GError **error) |
163 | 1.47k | { |
164 | 1.47k | return fu_struct_ifwi_cpd_validate_stream(stream, offset, error); |
165 | 1.47k | } |
166 | | |
167 | | static gboolean |
168 | | fu_ifwi_cpd_firmware_parse(FuFirmware *firmware, |
169 | | FuInputStream *stream, |
170 | | FuFirmwareParseFlags flags, |
171 | | GError **error) |
172 | 1.32k | { |
173 | 1.32k | FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware); |
174 | 1.32k | FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self); |
175 | 1.32k | g_autoptr(FuStructIfwiCpd) st_hdr = NULL; |
176 | 1.32k | gsize offset = 0; |
177 | 1.32k | guint32 num_of_entries; |
178 | | |
179 | | /* other header fields */ |
180 | 1.32k | st_hdr = fu_struct_ifwi_cpd_parse_stream(stream, offset, error); |
181 | 1.32k | if (st_hdr == NULL) |
182 | 0 | return FALSE; |
183 | 1.32k | priv->header_version = fu_struct_ifwi_cpd_get_header_version(st_hdr); |
184 | 1.32k | priv->entry_version = fu_struct_ifwi_cpd_get_entry_version(st_hdr); |
185 | 1.32k | fu_firmware_set_idx(firmware, fu_struct_ifwi_cpd_get_partition_name(st_hdr)); |
186 | | |
187 | | /* read out entries */ |
188 | 1.32k | num_of_entries = fu_struct_ifwi_cpd_get_num_of_entries(st_hdr); |
189 | 1.32k | if (num_of_entries > FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX) { |
190 | 57 | g_set_error(error, |
191 | 57 | FWUPD_ERROR, |
192 | 57 | FWUPD_ERROR_INVALID_DATA, |
193 | 57 | "too many entries 0x%x, expected <= 0x%x", |
194 | 57 | num_of_entries, |
195 | 57 | (guint)FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX); |
196 | 57 | return FALSE; |
197 | 57 | } |
198 | 1.26k | if (!fu_size_checked_inc(&offset, fu_struct_ifwi_cpd_get_header_length(st_hdr), error)) |
199 | 0 | return FALSE; |
200 | 36.2k | for (guint32 i = 0; i < num_of_entries; i++) { |
201 | 36.0k | guint32 img_offset = 0; |
202 | 36.0k | g_autofree gchar *id = NULL; |
203 | 36.0k | g_autoptr(FuFirmware) img = fu_ifwi_cpd_image_new(); |
204 | 36.0k | g_autoptr(FuStructIfwiCpdEntry) st_ent = NULL; |
205 | 36.0k | g_autoptr(FuInputStream) partial_stream = NULL; |
206 | | |
207 | | /* the IDX is the position in the file */ |
208 | 36.0k | fu_firmware_set_idx(img, i); |
209 | | |
210 | 36.0k | st_ent = fu_struct_ifwi_cpd_entry_parse_stream(stream, offset, error); |
211 | 36.0k | if (st_ent == NULL) |
212 | 262 | return FALSE; |
213 | | |
214 | | /* copy name as id */ |
215 | 35.7k | id = fu_struct_ifwi_cpd_entry_get_name(st_ent); |
216 | 35.7k | fu_firmware_set_id(img, id); |
217 | | |
218 | | /* copy offset, ignoring huffman and reserved bits */ |
219 | 35.7k | img_offset = fu_struct_ifwi_cpd_entry_get_offset(st_ent); |
220 | 35.7k | img_offset &= 0x1FFFFFF; |
221 | 35.7k | fu_firmware_set_offset(img, img_offset); |
222 | | |
223 | | /* copy data */ |
224 | 35.7k | partial_stream = |
225 | 35.7k | fu_partial_input_stream_new(stream, |
226 | 35.7k | img_offset, |
227 | 35.7k | fu_struct_ifwi_cpd_entry_get_length(st_ent), |
228 | 35.7k | error); |
229 | 35.7k | if (partial_stream == NULL) { |
230 | 401 | g_prefix_error_literal(error, "failed to cut IFD image: "); |
231 | 401 | return FALSE; |
232 | 401 | } |
233 | 35.3k | if (!fu_firmware_parse_stream(img, partial_stream, 0x0, flags, error)) |
234 | 39 | return FALSE; |
235 | | |
236 | | /* read the manifest */ |
237 | 35.3k | if (i == FU_IFWI_CPD_FIRMWARE_IDX_MANIFEST && |
238 | 1.03k | fu_struct_ifwi_cpd_entry_get_length(st_ent) > |
239 | 1.03k | FU_STRUCT_IFWI_CPD_MANIFEST_SIZE) { |
240 | 733 | if (!fu_ifwi_cpd_firmware_parse_manifest(self, |
241 | 733 | img, |
242 | 733 | partial_stream, |
243 | 733 | flags, |
244 | 733 | error)) |
245 | 387 | return FALSE; |
246 | 733 | } |
247 | | |
248 | | /* success */ |
249 | 34.9k | if (!fu_firmware_add_image(firmware, img, error)) |
250 | 0 | return FALSE; |
251 | 34.9k | if (!fu_size_checked_inc(&offset, st_ent->buf->len, error)) |
252 | 0 | return FALSE; |
253 | 34.9k | } |
254 | | |
255 | | /* success */ |
256 | 179 | return TRUE; |
257 | 1.26k | } |
258 | | |
259 | | static GByteArray * |
260 | | fu_ifwi_cpd_firmware_write(FuFirmware *firmware, GError **error) |
261 | 179 | { |
262 | 179 | FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware); |
263 | 179 | FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self); |
264 | 179 | gsize offset = 0; |
265 | 179 | g_autoptr(FuStructIfwiCpd) st = fu_struct_ifwi_cpd_new(); |
266 | 179 | g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); |
267 | | |
268 | | /* write the header */ |
269 | 179 | fu_struct_ifwi_cpd_set_num_of_entries(st, imgs->len); |
270 | 179 | fu_struct_ifwi_cpd_set_header_version(st, priv->header_version); |
271 | 179 | fu_struct_ifwi_cpd_set_entry_version(st, priv->entry_version); |
272 | 179 | fu_struct_ifwi_cpd_set_checksum(st, 0x0); |
273 | 179 | fu_struct_ifwi_cpd_set_partition_name(st, fu_firmware_get_idx(firmware)); |
274 | 179 | fu_struct_ifwi_cpd_set_crc32(st, 0x0); |
275 | | |
276 | | /* fixup the image offsets */ |
277 | 179 | if (!fu_size_checked_inc(&offset, st->buf->len, error)) |
278 | 0 | return NULL; |
279 | 179 | if (!fu_size_checked_inc_product(&offset, FU_STRUCT_IFWI_CPD_ENTRY_SIZE, imgs->len, error)) |
280 | 0 | return NULL; |
281 | 10.6k | for (guint i = 0; i < imgs->len; i++) { |
282 | 10.4k | FuFirmware *img = g_ptr_array_index(imgs, i); |
283 | 10.4k | g_autoptr(GBytes) blob = NULL; |
284 | | |
285 | 10.4k | blob = fu_firmware_get_bytes(img, error); |
286 | 10.4k | if (blob == NULL) { |
287 | 0 | g_prefix_error(error, "image 0x%x: ", i); |
288 | 0 | return NULL; |
289 | 0 | } |
290 | 10.4k | fu_firmware_set_offset(img, offset); |
291 | 10.4k | if (!fu_size_checked_inc(&offset, g_bytes_get_size(blob), error)) { |
292 | 0 | g_prefix_error(error, "offset overflow for entry %u: ", i); |
293 | 0 | return NULL; |
294 | 0 | } |
295 | 10.4k | } |
296 | | |
297 | | /* add entry headers */ |
298 | 5.08k | for (guint i = 0; i < imgs->len; i++) { |
299 | 4.96k | FuFirmware *img = g_ptr_array_index(imgs, i); |
300 | 4.96k | g_autoptr(FuStructIfwiCpdEntry) st_ent = fu_struct_ifwi_cpd_entry_new(); |
301 | | |
302 | | /* sanity check */ |
303 | 4.96k | if (fu_firmware_get_id(img) == NULL) { |
304 | 62 | g_set_error(error, |
305 | 62 | FWUPD_ERROR, |
306 | 62 | FWUPD_ERROR_INVALID_DATA, |
307 | 62 | "image 0x%x must have an ID", |
308 | 62 | (guint)fu_firmware_get_idx(img)); |
309 | 62 | return NULL; |
310 | 62 | } |
311 | 4.90k | if (!fu_struct_ifwi_cpd_entry_set_name(st_ent, fu_firmware_get_id(img), error)) |
312 | 0 | return NULL; |
313 | 4.90k | fu_struct_ifwi_cpd_entry_set_offset(st_ent, fu_firmware_get_offset(img)); |
314 | 4.90k | fu_struct_ifwi_cpd_entry_set_length(st_ent, fu_firmware_get_size(img)); |
315 | 4.90k | fu_byte_array_append_array(st->buf, st_ent->buf); |
316 | 4.90k | } |
317 | | |
318 | | /* add entry data */ |
319 | 3.70k | for (guint i = 0; i < imgs->len; i++) { |
320 | 3.59k | FuFirmware *img = g_ptr_array_index(imgs, i); |
321 | 3.59k | g_autoptr(GBytes) blob = NULL; |
322 | 3.59k | blob = fu_firmware_get_bytes(img, error); |
323 | 3.59k | if (blob == NULL) |
324 | 0 | return NULL; |
325 | 3.59k | fu_byte_array_append_bytes(st->buf, blob); |
326 | 3.59k | } |
327 | | |
328 | | /* success */ |
329 | 117 | return g_steal_pointer(&st->buf); |
330 | 117 | } |
331 | | |
332 | | static gboolean |
333 | | fu_ifwi_cpd_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
334 | 0 | { |
335 | 0 | FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware); |
336 | 0 | FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self); |
337 | 0 | const gchar *tmp; |
338 | | |
339 | | /* simple properties */ |
340 | 0 | tmp = xb_node_query_text(n, "header_version", NULL); |
341 | 0 | if (tmp != NULL) { |
342 | 0 | guint64 val = 0; |
343 | 0 | if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT8, FU_INTEGER_BASE_AUTO, error)) |
344 | 0 | return FALSE; |
345 | 0 | priv->header_version = val; |
346 | 0 | } |
347 | 0 | tmp = xb_node_query_text(n, "entry_version", NULL); |
348 | 0 | if (tmp != NULL) { |
349 | 0 | guint64 val = 0; |
350 | 0 | if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT8, FU_INTEGER_BASE_AUTO, error)) |
351 | 0 | return FALSE; |
352 | 0 | priv->entry_version = val; |
353 | 0 | } |
354 | | |
355 | | /* success */ |
356 | 0 | return TRUE; |
357 | 0 | } |
358 | | |
359 | | static gchar * |
360 | | fu_ifwi_cpd_firmware_convert_version(FuFirmware *firmware, guint64 version_raw) |
361 | 733 | { |
362 | 733 | return fu_version_from_uint64(version_raw, fu_firmware_get_version_format(firmware)); |
363 | 733 | } |
364 | | |
365 | | static void |
366 | | fu_ifwi_cpd_firmware_init(FuIfwiCpdFirmware *self) |
367 | 1.49k | { |
368 | 1.49k | fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_QUAD); |
369 | 1.49k | } |
370 | | |
371 | | static void |
372 | | fu_ifwi_cpd_firmware_class_init(FuIfwiCpdFirmwareClass *klass) |
373 | 2 | { |
374 | 2 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
375 | 2 | fu_firmware_add_image_gtype(firmware_class, FU_TYPE_IFWI_CPD_IMAGE); |
376 | 2 | fu_firmware_set_size_max(firmware_class, 128 * FU_MB); |
377 | 2 | firmware_class->validate = fu_ifwi_cpd_firmware_validate; |
378 | 2 | firmware_class->export = fu_ifwi_cpd_firmware_export; |
379 | 2 | firmware_class->parse = fu_ifwi_cpd_firmware_parse; |
380 | 2 | firmware_class->write = fu_ifwi_cpd_firmware_write; |
381 | 2 | firmware_class->build = fu_ifwi_cpd_firmware_build; |
382 | 2 | firmware_class->convert_version = fu_ifwi_cpd_firmware_convert_version; |
383 | 2 | fu_firmware_set_images_max(firmware_class, FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX); |
384 | 2 | } |
385 | | |
386 | | /** |
387 | | * fu_ifwi_cpd_firmware_new: |
388 | | * |
389 | | * Creates a new #FuFirmware of Intel Code Partition Directory format |
390 | | * |
391 | | * Since: 1.8.2 |
392 | | **/ |
393 | | FuFirmware * |
394 | | fu_ifwi_cpd_firmware_new(void) |
395 | 0 | { |
396 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_IFWI_CPD_FIRMWARE, NULL)); |
397 | 0 | } |