/src/fwupd/libfwupdplugin/fu-fdt-firmware.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2022 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 578k | #define G_LOG_DOMAIN "FuFirmware" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-byte-array.h" |
12 | | #include "fu-bytes.h" |
13 | | #include "fu-common.h" |
14 | | #include "fu-dump.h" |
15 | | #include "fu-fdt-firmware.h" |
16 | | #include "fu-fdt-image.h" |
17 | | #include "fu-fdt-struct.h" |
18 | | #include "fu-input-stream.h" |
19 | | #include "fu-mem.h" |
20 | | |
21 | | /** |
22 | | * FuFdtFirmware: |
23 | | * |
24 | | * A Flattened DeviceTree firmware image. |
25 | | * |
26 | | * Documented: |
27 | | * https://devicetree-specification.readthedocs.io/en/latest/chapter5-flattened-format.html |
28 | | * |
29 | | * See also: [class@FuFirmware] |
30 | | */ |
31 | | |
32 | | typedef struct { |
33 | | guint32 cpuid; |
34 | | } FuFdtFirmwarePrivate; |
35 | | |
36 | | G_DEFINE_TYPE_WITH_PRIVATE(FuFdtFirmware, fu_fdt_firmware, FU_TYPE_FIRMWARE) |
37 | 5.37k | #define GET_PRIVATE(o) (fu_fdt_firmware_get_instance_private(o)) |
38 | | |
39 | 3.65k | #define FDT_LAST_COMP_VERSION 2 |
40 | 130k | #define FDT_DEPTH_MAX 128 |
41 | | |
42 | | static GString * |
43 | | fu_fdt_firmware_string_new_safe(const guint8 *buf, gsize bufsz, gsize offset, GError **error) |
44 | 171k | { |
45 | 171k | g_autoptr(GString) str = g_string_new(NULL); |
46 | 1.76G | for (gsize i = offset; i < bufsz; i++) { |
47 | 1.76G | if (buf[i] == '\0') |
48 | 170k | return g_steal_pointer(&str); |
49 | 1.76G | g_string_append_c(str, (gchar)buf[i]); |
50 | 1.76G | } |
51 | 127 | g_set_error_literal(error, |
52 | 127 | FWUPD_ERROR, |
53 | 127 | FWUPD_ERROR_INVALID_DATA, |
54 | 127 | "buffer not NULL terminated"); |
55 | 127 | return NULL; |
56 | 171k | } |
57 | | |
58 | | static void |
59 | | fu_fdt_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
60 | 0 | { |
61 | 0 | FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); |
62 | 0 | FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); |
63 | 0 | fu_xmlb_builder_insert_kx(bn, "cpuid", priv->cpuid); |
64 | 0 | } |
65 | | |
66 | | /** |
67 | | * fu_fdt_firmware_get_cpuid: |
68 | | * @self: a #FuFdtFirmware |
69 | | * |
70 | | * Gets the CPUID. |
71 | | * |
72 | | * Returns: integer |
73 | | * |
74 | | * Since: 1.8.2 |
75 | | **/ |
76 | | guint32 |
77 | | fu_fdt_firmware_get_cpuid(FuFdtFirmware *self) |
78 | 0 | { |
79 | 0 | FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); |
80 | 0 | g_return_val_if_fail(FU_IS_FDT_FIRMWARE(self), 0x0); |
81 | 0 | return priv->cpuid; |
82 | 0 | } |
83 | | |
84 | | /** |
85 | | * fu_fdt_firmware_set_cpuid: |
86 | | * @self: a #FuFdtFirmware |
87 | | * @cpuid: integer value |
88 | | * |
89 | | * Sets the CPUID. |
90 | | * |
91 | | * Since: 1.8.2 |
92 | | **/ |
93 | | void |
94 | | fu_fdt_firmware_set_cpuid(FuFdtFirmware *self, guint32 cpuid) |
95 | 0 | { |
96 | 0 | FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); |
97 | 0 | g_return_if_fail(FU_IS_FDT_FIRMWARE(self)); |
98 | 0 | priv->cpuid = cpuid; |
99 | 0 | } |
100 | | |
101 | | /** |
102 | | * fu_fdt_firmware_get_image_by_path: |
103 | | * @self: a #FuFdtFirmware |
104 | | * @path: ID path, e.g. `/images/firmware-1` |
105 | | * @error: (nullable): optional return location for an error |
106 | | * |
107 | | * Gets the FDT image for a specific path. |
108 | | * |
109 | | * Returns: (transfer full): a #FuFirmware, or %NULL |
110 | | * |
111 | | * Since: 1.8.2 |
112 | | **/ |
113 | | FuFdtImage * |
114 | | fu_fdt_firmware_get_image_by_path(FuFdtFirmware *self, const gchar *path, GError **error) |
115 | 0 | { |
116 | 0 | g_auto(GStrv) paths = NULL; |
117 | 0 | g_autoptr(FuFirmware) img_current = g_object_ref(FU_FIRMWARE(self)); |
118 | |
|
119 | 0 | g_return_val_if_fail(FU_IS_FDT_FIRMWARE(self), NULL); |
120 | 0 | g_return_val_if_fail(path != NULL, NULL); |
121 | 0 | g_return_val_if_fail(path[0] != '\0', NULL); |
122 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
123 | | |
124 | 0 | paths = g_strsplit(path, "/", -1); |
125 | 0 | for (guint i = 0; paths[i] != NULL; i++) { |
126 | 0 | const gchar *id = paths[i]; |
127 | 0 | g_autoptr(FuFirmware) img_tmp = NULL; |
128 | | |
129 | | /* special case for empty */ |
130 | 0 | if (id[0] == '\0') |
131 | 0 | id = NULL; |
132 | 0 | img_tmp = fu_firmware_get_image_by_id(img_current, id, error); |
133 | 0 | if (img_tmp == NULL) |
134 | 0 | return NULL; |
135 | 0 | g_set_object(&img_current, img_tmp); |
136 | 0 | } |
137 | | |
138 | | /* success */ |
139 | 0 | return FU_FDT_IMAGE(g_steal_pointer(&img_current)); |
140 | 0 | } |
141 | | |
142 | | static gboolean |
143 | | fu_fdt_firmware_parse_dt_struct(FuFdtFirmware *self, GBytes *fw, GByteArray *strtab, GError **error) |
144 | 3.17k | { |
145 | 3.17k | gsize bufsz = 0; |
146 | 3.17k | gsize offset = 0; |
147 | 3.17k | guint depth = 0; |
148 | 3.17k | gboolean has_end = FALSE; |
149 | 3.17k | const guint8 *buf = g_bytes_get_data(fw, &bufsz); |
150 | 3.17k | g_autoptr(FuFirmware) firmware_current = g_object_ref(FU_FIRMWARE(self)); |
151 | | |
152 | | /* debug */ |
153 | 3.17k | fu_dump_bytes(G_LOG_DOMAIN, "dt_struct", fw); |
154 | | |
155 | | /* parse */ |
156 | 294k | while (offset < bufsz) { |
157 | 292k | guint32 token = 0; |
158 | | |
159 | | /* read tag from aligned offset */ |
160 | 292k | offset = fu_common_align_up(offset, FU_FIRMWARE_ALIGNMENT_4); |
161 | 292k | if (!fu_memread_uint32_safe(buf, bufsz, offset, &token, G_BIG_ENDIAN, error)) |
162 | 153 | return FALSE; |
163 | 292k | offset += sizeof(guint32); |
164 | | |
165 | | /* nothing to do */ |
166 | 292k | if (token == FU_FDT_TOKEN_NOP) |
167 | 1.99k | continue; |
168 | | |
169 | | /* END */ |
170 | 290k | if (token == FU_FDT_TOKEN_END) { |
171 | 531 | if (firmware_current != FU_FIRMWARE(self)) { |
172 | 3 | g_set_error_literal(error, |
173 | 3 | FWUPD_ERROR, |
174 | 3 | FWUPD_ERROR_INVALID_DATA, |
175 | 3 | "got END with unclosed node"); |
176 | 3 | return FALSE; |
177 | 3 | } |
178 | 528 | has_end = TRUE; |
179 | 528 | break; |
180 | 531 | } |
181 | | |
182 | | /* BEGIN NODE */ |
183 | 290k | if (token == FU_FDT_TOKEN_BEGIN_NODE) { |
184 | 130k | g_autoptr(GString) str = NULL; |
185 | 130k | g_autoptr(FuFirmware) image = NULL; |
186 | | |
187 | | /* sanity check */ |
188 | 130k | if (depth++ > FDT_DEPTH_MAX) { |
189 | 0 | g_set_error(error, |
190 | 0 | FWUPD_ERROR, |
191 | 0 | FWUPD_ERROR_INVALID_DATA, |
192 | 0 | "node depth exceeded maximum: 0x%x", |
193 | 0 | (guint)FDT_DEPTH_MAX); |
194 | 0 | return FALSE; |
195 | 0 | } |
196 | | |
197 | 130k | str = fu_fdt_firmware_string_new_safe(buf, bufsz, offset, error); |
198 | 130k | if (str == NULL) |
199 | 13 | return FALSE; |
200 | 130k | offset += str->len + 1; |
201 | 130k | image = fu_fdt_image_new(); |
202 | 130k | if (str->len > 0) |
203 | 122k | fu_firmware_set_id(image, str->str); |
204 | 130k | fu_firmware_set_offset(image, offset); |
205 | 130k | if (!fu_firmware_add_image_full(firmware_current, image, error)) |
206 | 28 | return FALSE; |
207 | 130k | g_set_object(&firmware_current, image); |
208 | 130k | continue; |
209 | 130k | } |
210 | | |
211 | | /* END NODE */ |
212 | 159k | if (token == FU_FDT_TOKEN_END_NODE) { |
213 | 118k | if (firmware_current == FU_FIRMWARE(self)) { |
214 | 2 | g_set_error_literal(error, |
215 | 2 | FWUPD_ERROR, |
216 | 2 | FWUPD_ERROR_INVALID_DATA, |
217 | 2 | "got END NODE with no node to end"); |
218 | 2 | return FALSE; |
219 | 2 | } |
220 | 118k | g_set_object(&firmware_current, fu_firmware_get_parent(firmware_current)); |
221 | 118k | if (depth > 0) |
222 | 118k | depth--; |
223 | 118k | continue; |
224 | 118k | } |
225 | | |
226 | | /* PROP */ |
227 | 40.8k | if (token == FU_FDT_TOKEN_PROP) { |
228 | 40.3k | guint32 prop_len; |
229 | 40.3k | guint32 prop_nameoff; |
230 | 40.3k | g_autoptr(GBytes) blob = NULL; |
231 | 40.3k | g_autoptr(GString) str = NULL; |
232 | 40.3k | g_autoptr(GByteArray) st_prp = NULL; |
233 | | |
234 | | /* sanity check */ |
235 | 40.3k | if (firmware_current == FU_FIRMWARE(self)) { |
236 | 4 | g_set_error_literal(error, |
237 | 4 | FWUPD_ERROR, |
238 | 4 | FWUPD_ERROR_INVALID_DATA, |
239 | 4 | "got PROP with unopen node"); |
240 | 4 | return FALSE; |
241 | 4 | } |
242 | | |
243 | | /* parse */ |
244 | 40.3k | st_prp = fu_struct_fdt_prop_parse(buf, bufsz, offset, error); |
245 | 40.3k | if (st_prp == NULL) |
246 | 13 | return FALSE; |
247 | 40.2k | prop_len = fu_struct_fdt_prop_get_len(st_prp); |
248 | 40.2k | prop_nameoff = fu_struct_fdt_prop_get_nameoff(st_prp); |
249 | 40.2k | offset += st_prp->len; |
250 | | |
251 | | /* add property */ |
252 | 40.2k | str = fu_fdt_firmware_string_new_safe(strtab->data, |
253 | 40.2k | strtab->len, |
254 | 40.2k | prop_nameoff, |
255 | 40.2k | error); |
256 | 40.2k | if (str == NULL) { |
257 | 114 | g_prefix_error(error, "invalid strtab offset 0x%x: ", prop_nameoff); |
258 | 114 | return FALSE; |
259 | 114 | } |
260 | 40.1k | blob = fu_bytes_new_offset(fw, offset, prop_len, error); |
261 | 40.1k | if (blob == NULL) |
262 | 130 | return FALSE; |
263 | 40.0k | fu_fdt_image_set_attr(FU_FDT_IMAGE(firmware_current), str->str, blob); |
264 | 40.0k | offset += prop_len; |
265 | 40.0k | continue; |
266 | 40.1k | } |
267 | | |
268 | | /* unknown token */ |
269 | 553 | g_set_error(error, |
270 | 553 | FWUPD_ERROR, |
271 | 553 | FWUPD_ERROR_INVALID_DATA, |
272 | 553 | "invalid token 0x%x @0%x", |
273 | 553 | token, |
274 | 553 | (guint)offset); |
275 | 553 | return FALSE; |
276 | 40.8k | } |
277 | | |
278 | | /* did not see FDT_END */ |
279 | 2.15k | if (!has_end) |
280 | 1.63k | g_warning("did not see FDT_END, perhaps size_dt_struct is invalid?"); |
281 | | |
282 | | /* success */ |
283 | 2.15k | return TRUE; |
284 | 3.17k | } |
285 | | |
286 | | static gboolean |
287 | | fu_fdt_firmware_parse_mem_rsvmap(FuFdtFirmware *self, |
288 | | GInputStream *stream, |
289 | | gsize offset, |
290 | | GError **error) |
291 | 3.59k | { |
292 | 3.59k | gsize streamsz = 0; |
293 | | |
294 | | /* parse */ |
295 | 3.59k | if (!fu_input_stream_size(stream, &streamsz, error)) |
296 | 0 | return FALSE; |
297 | 567k | for (; offset < streamsz; offset += FU_STRUCT_FDT_RESERVE_ENTRY_SIZE) { |
298 | 564k | guint64 address = 0; |
299 | 564k | guint64 size = 0; |
300 | 564k | g_autoptr(GByteArray) st_res = NULL; |
301 | 564k | st_res = fu_struct_fdt_reserve_entry_parse_stream(stream, offset, error); |
302 | 564k | if (st_res == NULL) |
303 | 229 | return FALSE; |
304 | 564k | address = fu_struct_fdt_reserve_entry_get_address(st_res); |
305 | 564k | size = fu_struct_fdt_reserve_entry_get_size(st_res); |
306 | 564k | g_debug("mem_rsvmap: 0x%x, 0x%x", (guint)address, (guint)size); |
307 | 564k | if (address == 0x0 && size == 0x0) |
308 | 84 | break; |
309 | 564k | } |
310 | | |
311 | | /* success */ |
312 | 3.36k | return TRUE; |
313 | 3.59k | } |
314 | | |
315 | | static gboolean |
316 | | fu_fdt_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error) |
317 | 12.2M | { |
318 | 12.2M | return fu_struct_fdt_validate_stream(stream, offset, error); |
319 | 12.2M | } |
320 | | |
321 | | static gboolean |
322 | | fu_fdt_firmware_parse(FuFirmware *firmware, |
323 | | GInputStream *stream, |
324 | | FuFirmwareParseFlags flags, |
325 | | GError **error) |
326 | 3.92k | { |
327 | 3.92k | FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); |
328 | 3.92k | FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); |
329 | 3.92k | guint32 totalsize; |
330 | 3.92k | gsize streamsz = 0; |
331 | 3.92k | guint32 off_mem_rsvmap = 0; |
332 | 3.92k | g_autoptr(GByteArray) st_hdr = NULL; |
333 | | |
334 | | /* sanity check */ |
335 | 3.92k | st_hdr = fu_struct_fdt_parse_stream(stream, 0x0, error); |
336 | 3.92k | if (st_hdr == NULL) |
337 | 0 | return FALSE; |
338 | 3.92k | if (!fu_input_stream_size(stream, &streamsz, error)) |
339 | 0 | return FALSE; |
340 | 3.92k | totalsize = fu_struct_fdt_get_totalsize(st_hdr); |
341 | 3.92k | if (totalsize > streamsz) { |
342 | 75 | g_set_error(error, |
343 | 75 | FWUPD_ERROR, |
344 | 75 | FWUPD_ERROR_INVALID_DATA, |
345 | 75 | "truncated image, got 0x%x, expected >= 0x%x", |
346 | 75 | (guint)streamsz, |
347 | 75 | (guint)totalsize); |
348 | 75 | return FALSE; |
349 | 75 | } |
350 | 3.85k | fu_firmware_set_size(firmware, totalsize); |
351 | | |
352 | | /* read header */ |
353 | 3.85k | priv->cpuid = fu_struct_fdt_get_boot_cpuid_phys(st_hdr); |
354 | 3.85k | off_mem_rsvmap = fu_struct_fdt_get_off_mem_rsvmap(st_hdr); |
355 | 3.85k | if (off_mem_rsvmap != 0x0) { |
356 | 3.59k | if (!fu_fdt_firmware_parse_mem_rsvmap(self, stream, off_mem_rsvmap, error)) |
357 | 229 | return FALSE; |
358 | 3.59k | } |
359 | 3.62k | if (fu_struct_fdt_get_last_comp_version(st_hdr) < FDT_LAST_COMP_VERSION) { |
360 | 31 | g_set_error(error, |
361 | 31 | FWUPD_ERROR, |
362 | 31 | FWUPD_ERROR_INVALID_DATA, |
363 | 31 | "invalid header version, got 0x%x, expected >= 0x%x", |
364 | 31 | (guint)fu_struct_fdt_get_last_comp_version(st_hdr), |
365 | 31 | (guint)FDT_LAST_COMP_VERSION); |
366 | 31 | return FALSE; |
367 | 31 | } |
368 | 3.59k | fu_firmware_set_version_raw(firmware, fu_struct_fdt_get_version(st_hdr)); |
369 | | |
370 | | /* parse device tree struct */ |
371 | 3.59k | if (fu_struct_fdt_get_size_dt_struct(st_hdr) != 0x0 && |
372 | 3.59k | fu_struct_fdt_get_size_dt_strings(st_hdr) != 0x0) { |
373 | 3.57k | g_autoptr(GByteArray) dt_strings = NULL; |
374 | 3.57k | g_autoptr(GByteArray) dt_struct = NULL; |
375 | 3.57k | g_autoptr(GBytes) dt_struct_buf = NULL; |
376 | 3.57k | dt_strings = |
377 | 3.57k | fu_input_stream_read_byte_array(stream, |
378 | 3.57k | fu_struct_fdt_get_off_dt_strings(st_hdr), |
379 | 3.57k | fu_struct_fdt_get_size_dt_strings(st_hdr), |
380 | 3.57k | NULL, |
381 | 3.57k | error); |
382 | 3.57k | if (dt_strings == NULL) |
383 | 147 | return FALSE; |
384 | 3.42k | dt_struct = |
385 | 3.42k | fu_input_stream_read_byte_array(stream, |
386 | 3.42k | fu_struct_fdt_get_off_dt_struct(st_hdr), |
387 | 3.42k | fu_struct_fdt_get_size_dt_struct(st_hdr), |
388 | 3.42k | NULL, |
389 | 3.42k | error); |
390 | 3.42k | if (dt_struct == NULL) |
391 | 56 | return FALSE; |
392 | 3.37k | if (dt_struct->len != fu_struct_fdt_get_size_dt_struct(st_hdr)) { |
393 | 201 | g_set_error(error, |
394 | 201 | FWUPD_ERROR, |
395 | 201 | FWUPD_ERROR_INVALID_DATA, |
396 | 201 | "invalid firmware -- dt_struct invalid"); |
397 | 201 | return FALSE; |
398 | 201 | } |
399 | 3.17k | dt_struct_buf = |
400 | 3.17k | g_byte_array_free_to_bytes(g_steal_pointer(&dt_struct)); /* nocheck:blocked */ |
401 | 3.17k | if (!fu_fdt_firmware_parse_dt_struct(self, dt_struct_buf, dt_strings, error)) |
402 | 1.01k | return FALSE; |
403 | 3.17k | } |
404 | | |
405 | | /* success */ |
406 | 2.17k | return TRUE; |
407 | 3.59k | } |
408 | | |
409 | | typedef struct { |
410 | | GByteArray *dt_strings; |
411 | | GByteArray *dt_struct; |
412 | | GHashTable *strtab; |
413 | | } FuFdtFirmwareBuildHelper; |
414 | | |
415 | | static guint32 |
416 | | fu_fdt_firmware_append_to_strtab(FuFdtFirmwareBuildHelper *helper, const gchar *key) |
417 | 15.5k | { |
418 | 15.5k | gpointer tmp = NULL; |
419 | 15.5k | guint32 offset; |
420 | | |
421 | | /* already exists */ |
422 | 15.5k | if (g_hash_table_lookup_extended(helper->strtab, key, NULL, &tmp)) |
423 | 5.78k | return GPOINTER_TO_UINT(tmp); |
424 | | |
425 | 9.78k | g_debug("adding strtab: %s", key); |
426 | 9.78k | offset = helper->dt_strings->len; |
427 | 9.78k | g_byte_array_append(helper->dt_strings, (const guint8 *)key, strlen(key)); |
428 | 9.78k | fu_byte_array_append_uint8(helper->dt_strings, 0x0); |
429 | 9.78k | g_hash_table_insert(helper->strtab, g_strdup(key), GUINT_TO_POINTER(offset)); |
430 | 9.78k | return offset; |
431 | 15.5k | } |
432 | | |
433 | | static gboolean |
434 | | fu_fdt_firmware_write_image(FuFdtFirmware *self, |
435 | | FuFdtImage *img, |
436 | | FuFdtFirmwareBuildHelper *helper, |
437 | | guint depth, |
438 | | GError **error) |
439 | 9.46k | { |
440 | 9.46k | const gchar *id = fu_firmware_get_id(FU_FIRMWARE(img)); |
441 | 9.46k | g_autoptr(GPtrArray) images = fu_firmware_get_images(FU_FIRMWARE(img)); |
442 | 9.46k | g_autoptr(GPtrArray) attrs = fu_fdt_image_get_attrs(img); |
443 | | |
444 | | /* sanity check */ |
445 | 9.46k | if (depth > 0 && id == NULL) { |
446 | 127 | g_set_error_literal(error, |
447 | 127 | FWUPD_ERROR, |
448 | 127 | FWUPD_ERROR_INVALID_DATA, |
449 | 127 | "child FuFdtImage requires ID"); |
450 | 127 | return FALSE; |
451 | 127 | } |
452 | | |
453 | | /* BEGIN_NODE, ID, NUL */ |
454 | 9.33k | fu_byte_array_append_uint32(helper->dt_struct, FU_FDT_TOKEN_BEGIN_NODE, G_BIG_ENDIAN); |
455 | 9.33k | if (id != NULL) { |
456 | 8.19k | g_byte_array_append(helper->dt_struct, (const guint8 *)id, strlen(id) + 1); |
457 | 8.19k | } else { |
458 | 1.14k | fu_byte_array_append_uint8(helper->dt_struct, 0x0); |
459 | 1.14k | } |
460 | 9.33k | fu_byte_array_align_up(helper->dt_struct, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
461 | | |
462 | | /* write properties */ |
463 | 24.9k | for (guint i = 0; i < attrs->len; i++) { |
464 | 15.5k | const gchar *key = g_ptr_array_index(attrs, i); |
465 | 15.5k | g_autoptr(GBytes) blob = NULL; |
466 | 15.5k | g_autoptr(GByteArray) st_prp = fu_struct_fdt_prop_new(); |
467 | | |
468 | 15.5k | blob = fu_fdt_image_get_attr(img, key, error); |
469 | 15.5k | if (blob == NULL) |
470 | 0 | return FALSE; |
471 | 15.5k | fu_byte_array_append_uint32(helper->dt_struct, FU_FDT_TOKEN_PROP, G_BIG_ENDIAN); |
472 | 15.5k | fu_struct_fdt_prop_set_len(st_prp, g_bytes_get_size(blob)); |
473 | 15.5k | fu_struct_fdt_prop_set_nameoff(st_prp, |
474 | 15.5k | fu_fdt_firmware_append_to_strtab(helper, key)); |
475 | 15.5k | g_byte_array_append(helper->dt_struct, st_prp->data, st_prp->len); |
476 | 15.5k | fu_byte_array_append_bytes(helper->dt_struct, blob); |
477 | 15.5k | fu_byte_array_align_up(helper->dt_struct, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
478 | 15.5k | } |
479 | | |
480 | | /* write children, recursively */ |
481 | 16.6k | for (guint i = 0; i < images->len; i++) { |
482 | 8.04k | FuFdtImage *img_child = g_ptr_array_index(images, i); |
483 | 8.04k | if (!fu_fdt_firmware_write_image(self, img_child, helper, depth + 1, error)) |
484 | 704 | return FALSE; |
485 | 8.04k | } |
486 | | |
487 | | /* END_NODE */ |
488 | 8.63k | fu_byte_array_append_uint32(helper->dt_struct, FU_FDT_TOKEN_END_NODE, G_BIG_ENDIAN); |
489 | 8.63k | return TRUE; |
490 | 9.33k | } |
491 | | |
492 | | static GByteArray * |
493 | | fu_fdt_firmware_write(FuFirmware *firmware, GError **error) |
494 | 1.44k | { |
495 | 1.44k | FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); |
496 | 1.44k | FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); |
497 | 1.44k | guint32 off_dt_struct; |
498 | 1.44k | guint32 off_dt_strings; |
499 | 1.44k | guint32 off_mem_rsvmap; |
500 | 1.44k | g_autoptr(GByteArray) dt_strings = g_byte_array_new(); |
501 | 1.44k | g_autoptr(GByteArray) dt_struct = g_byte_array_new(); |
502 | 1.44k | g_autoptr(GHashTable) strtab = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
503 | 1.44k | g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); |
504 | 1.44k | g_autoptr(GByteArray) st_hdr = fu_struct_fdt_new(); |
505 | 1.44k | g_autoptr(GByteArray) mem_rsvmap = fu_struct_fdt_reserve_entry_new(); |
506 | 1.44k | FuFdtFirmwareBuildHelper helper = { |
507 | 1.44k | .dt_strings = dt_strings, |
508 | 1.44k | .dt_struct = dt_struct, |
509 | 1.44k | .strtab = strtab, |
510 | 1.44k | }; |
511 | | |
512 | | /* empty mem_rsvmap */ |
513 | 1.44k | off_mem_rsvmap = fu_common_align_up(st_hdr->len, FU_FIRMWARE_ALIGNMENT_4); |
514 | | |
515 | | /* dt_struct */ |
516 | 1.44k | off_dt_struct = |
517 | 1.44k | fu_common_align_up(off_mem_rsvmap + mem_rsvmap->len, FU_FIRMWARE_ALIGNMENT_4); |
518 | | |
519 | | /* only one root node supported */ |
520 | 1.44k | if (images->len != 1) { |
521 | 23 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "no root node"); |
522 | 23 | return NULL; |
523 | 23 | } |
524 | 1.42k | if (!fu_fdt_firmware_write_image(self, |
525 | 1.42k | FU_FDT_IMAGE(g_ptr_array_index(images, 0)), |
526 | 1.42k | &helper, |
527 | 1.42k | 0, |
528 | 1.42k | error)) |
529 | 127 | return NULL; |
530 | 1.29k | fu_byte_array_append_uint32(dt_struct, FU_FDT_TOKEN_END, G_BIG_ENDIAN); |
531 | | |
532 | | /* dt_strings */ |
533 | 1.29k | off_dt_strings = |
534 | 1.29k | fu_common_align_up(off_dt_struct + dt_struct->len, FU_FIRMWARE_ALIGNMENT_4); |
535 | | |
536 | | /* write header */ |
537 | 1.29k | fu_struct_fdt_set_totalsize(st_hdr, off_dt_strings + dt_strings->len); |
538 | 1.29k | fu_struct_fdt_set_off_dt_struct(st_hdr, off_dt_struct); |
539 | 1.29k | fu_struct_fdt_set_off_dt_strings(st_hdr, off_dt_strings); |
540 | 1.29k | fu_struct_fdt_set_off_mem_rsvmap(st_hdr, off_mem_rsvmap); |
541 | 1.29k | fu_struct_fdt_set_version(st_hdr, fu_firmware_get_version_raw(firmware)); |
542 | 1.29k | fu_struct_fdt_set_boot_cpuid_phys(st_hdr, priv->cpuid); |
543 | 1.29k | fu_struct_fdt_set_size_dt_strings(st_hdr, dt_strings->len); |
544 | 1.29k | fu_struct_fdt_set_size_dt_struct(st_hdr, dt_struct->len); |
545 | 1.29k | fu_byte_array_align_up(st_hdr, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
546 | | |
547 | | /* write mem_rsvmap, dt_struct, dt_strings */ |
548 | 1.29k | g_byte_array_append(st_hdr, mem_rsvmap->data, mem_rsvmap->len); |
549 | 1.29k | fu_byte_array_align_up(st_hdr, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
550 | 1.29k | g_byte_array_append(st_hdr, dt_struct->data, dt_struct->len); |
551 | 1.29k | fu_byte_array_align_up(st_hdr, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
552 | 1.29k | g_byte_array_append(st_hdr, dt_strings->data, dt_strings->len); |
553 | 1.29k | fu_byte_array_align_up(st_hdr, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
554 | | |
555 | | /* success */ |
556 | 1.29k | return g_steal_pointer(&st_hdr); |
557 | 1.42k | } |
558 | | |
559 | | static gboolean |
560 | | fu_fdt_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
561 | 0 | { |
562 | 0 | FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); |
563 | 0 | FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); |
564 | 0 | guint64 tmp; |
565 | | |
566 | | /* optional properties */ |
567 | 0 | tmp = xb_node_query_text_as_uint(n, "cpuid", NULL); |
568 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) |
569 | 0 | priv->cpuid = tmp; |
570 | | |
571 | | /* success */ |
572 | 0 | return TRUE; |
573 | 0 | } |
574 | | |
575 | | static void |
576 | | fu_fdt_firmware_init(FuFdtFirmware *self) |
577 | 4.18k | { |
578 | 4.18k | g_type_ensure(FU_TYPE_FDT_IMAGE); |
579 | 4.18k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); |
580 | 4.18k | } |
581 | | |
582 | | static void |
583 | | fu_fdt_firmware_class_init(FuFdtFirmwareClass *klass) |
584 | 2 | { |
585 | 2 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
586 | 2 | firmware_class->validate = fu_fdt_firmware_validate; |
587 | 2 | firmware_class->export = fu_fdt_firmware_export; |
588 | 2 | firmware_class->parse = fu_fdt_firmware_parse; |
589 | 2 | firmware_class->write = fu_fdt_firmware_write; |
590 | 2 | firmware_class->build = fu_fdt_firmware_build; |
591 | 2 | } |
592 | | |
593 | | /** |
594 | | * fu_fdt_firmware_new: |
595 | | * |
596 | | * Creates a new #FuFirmware of sub type FDT |
597 | | * |
598 | | * Since: 1.8.2 |
599 | | **/ |
600 | | FuFirmware * |
601 | | fu_fdt_firmware_new(void) |
602 | 0 | { |
603 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_FDT_FIRMWARE, NULL)); |
604 | 0 | } |