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