/src/fwupd/libfwupdplugin/fu-fdt-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2022 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 243k | #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 | 6.30k | G_DEFINE_TYPE_WITH_PRIVATE(FuFdtFirmware, fu_fdt_firmware, FU_TYPE_FIRMWARE) |
37 | 6.30k | #define GET_PRIVATE(o) (fu_fdt_firmware_get_instance_private(o)) |
38 | | |
39 | 3.22k | #define FDT_LAST_COMP_VERSION 2 |
40 | 91.7k | #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 | 109k | { |
45 | 109k | g_autoptr(GString) str = g_string_new(NULL); |
46 | 548M | for (gsize i = offset; i < bufsz; i++) { |
47 | 548M | if (buf[i] == '\0') |
48 | 109k | return g_steal_pointer(&str); |
49 | 548M | g_string_append_c(str, (gchar)buf[i]); |
50 | 548M | } |
51 | 130 | g_set_error_literal(error, |
52 | 130 | FWUPD_ERROR, |
53 | 130 | FWUPD_ERROR_INVALID_DATA, |
54 | 130 | "buffer not NULL terminated"); |
55 | 130 | return NULL; |
56 | 109k | } |
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 | 2.82k | { |
145 | 2.82k | gsize bufsz = 0; |
146 | 2.82k | gsize offset = 0; |
147 | 2.82k | guint depth = 0; |
148 | 2.82k | gboolean has_end = FALSE; |
149 | 2.82k | const guint8 *buf = g_bytes_get_data(fw, &bufsz); |
150 | 2.82k | g_autoptr(FuFirmware) firmware_current = g_object_ref(FU_FIRMWARE(self)); |
151 | | |
152 | | /* parse */ |
153 | 194k | while (offset < bufsz) { |
154 | 192k | guint32 token = 0; |
155 | | |
156 | | /* read tag from aligned offset */ |
157 | 192k | offset = fu_common_align_up(offset, FU_FIRMWARE_ALIGNMENT_4); |
158 | 192k | if (offset > G_MAXUINT32) { |
159 | 0 | g_set_error_literal(error, |
160 | 0 | FWUPD_ERROR, |
161 | 0 | FWUPD_ERROR_INVALID_DATA, |
162 | 0 | "offset bigger than 4GB"); |
163 | 0 | return FALSE; |
164 | 0 | } |
165 | 192k | if (!fu_memread_uint32_safe(buf, bufsz, offset, &token, G_BIG_ENDIAN, error)) |
166 | 210 | return FALSE; |
167 | 192k | offset += sizeof(guint32); |
168 | | |
169 | | /* nothing to do */ |
170 | 192k | if (token == FU_FDT_TOKEN_NOP) |
171 | 1.41k | continue; |
172 | | |
173 | | /* END */ |
174 | 191k | if (token == FU_FDT_TOKEN_END) { |
175 | 230 | if (firmware_current != FU_FIRMWARE(self)) { |
176 | 3 | g_set_error_literal(error, |
177 | 3 | FWUPD_ERROR, |
178 | 3 | FWUPD_ERROR_INVALID_DATA, |
179 | 3 | "got END with unclosed node"); |
180 | 3 | return FALSE; |
181 | 3 | } |
182 | 227 | has_end = TRUE; |
183 | 227 | break; |
184 | 230 | } |
185 | | |
186 | | /* BEGIN NODE */ |
187 | 191k | if (token == FU_FDT_TOKEN_BEGIN_NODE) { |
188 | 91.7k | g_autoptr(GString) str = NULL; |
189 | 91.7k | g_autoptr(FuFirmware) image = NULL; |
190 | | |
191 | | /* sanity check */ |
192 | 91.7k | if (depth++ > FDT_DEPTH_MAX) { |
193 | 0 | g_set_error(error, |
194 | 0 | FWUPD_ERROR, |
195 | 0 | FWUPD_ERROR_INVALID_DATA, |
196 | 0 | "node depth exceeded maximum: 0x%x", |
197 | 0 | (guint)FDT_DEPTH_MAX); |
198 | 0 | return FALSE; |
199 | 0 | } |
200 | | |
201 | 91.7k | str = fu_fdt_firmware_string_new_safe(buf, bufsz, offset, error); |
202 | 91.7k | if (str == NULL) |
203 | 16 | return FALSE; |
204 | 91.7k | offset += str->len + 1; |
205 | 91.7k | image = fu_fdt_image_new(); |
206 | 91.7k | if (str->len > 0) |
207 | 86.5k | fu_firmware_set_id(image, str->str); |
208 | 91.7k | fu_firmware_set_offset(image, offset); |
209 | 91.7k | if (!fu_firmware_add_image(firmware_current, image, error)) |
210 | 18 | return FALSE; |
211 | 91.7k | g_set_object(&firmware_current, image); |
212 | 91.7k | continue; |
213 | 91.7k | } |
214 | | |
215 | | /* END NODE */ |
216 | 99.2k | if (token == FU_FDT_TOKEN_END_NODE) { |
217 | 81.4k | if (firmware_current == FU_FIRMWARE(self)) { |
218 | 2 | g_set_error_literal(error, |
219 | 2 | FWUPD_ERROR, |
220 | 2 | FWUPD_ERROR_INVALID_DATA, |
221 | 2 | "got END NODE with no node to end"); |
222 | 2 | return FALSE; |
223 | 2 | } |
224 | 81.4k | g_set_object(&firmware_current, fu_firmware_get_parent(firmware_current)); |
225 | 81.4k | if (depth > 0) |
226 | 81.4k | depth--; |
227 | 81.4k | continue; |
228 | 81.4k | } |
229 | | |
230 | | /* PROP */ |
231 | 17.8k | if (token == FU_FDT_TOKEN_PROP) { |
232 | 17.4k | guint32 prop_len; |
233 | 17.4k | guint32 prop_nameoff; |
234 | 17.4k | g_autoptr(GBytes) blob = NULL; |
235 | 17.4k | g_autoptr(GString) str = NULL; |
236 | 17.4k | g_autoptr(FuStructFdtProp) st_prp = NULL; |
237 | | |
238 | | /* sanity check */ |
239 | 17.4k | if (firmware_current == FU_FIRMWARE(self)) { |
240 | 3 | g_set_error_literal(error, |
241 | 3 | FWUPD_ERROR, |
242 | 3 | FWUPD_ERROR_INVALID_DATA, |
243 | 3 | "got PROP with unopen node"); |
244 | 3 | return FALSE; |
245 | 3 | } |
246 | | |
247 | | /* parse */ |
248 | 17.4k | st_prp = fu_struct_fdt_prop_parse(buf, bufsz, offset, error); |
249 | 17.4k | if (st_prp == NULL) |
250 | 13 | return FALSE; |
251 | 17.4k | prop_len = fu_struct_fdt_prop_get_len(st_prp); |
252 | 17.4k | prop_nameoff = fu_struct_fdt_prop_get_nameoff(st_prp); |
253 | 17.4k | offset += st_prp->buf->len; |
254 | | |
255 | | /* add property */ |
256 | 17.4k | str = fu_fdt_firmware_string_new_safe(strtab->data, |
257 | 17.4k | strtab->len, |
258 | 17.4k | prop_nameoff, |
259 | 17.4k | error); |
260 | 17.4k | if (str == NULL) { |
261 | 114 | g_prefix_error(error, "invalid strtab offset 0x%x: ", prop_nameoff); |
262 | 114 | return FALSE; |
263 | 114 | } |
264 | 17.2k | blob = fu_bytes_new_offset(fw, offset, prop_len, error); |
265 | 17.2k | if (blob == NULL) |
266 | 140 | return FALSE; |
267 | 17.1k | fu_fdt_image_set_attr(FU_FDT_IMAGE(firmware_current), str->str, blob); |
268 | 17.1k | offset += prop_len; |
269 | 17.1k | continue; |
270 | 17.2k | } |
271 | | |
272 | | /* unknown token */ |
273 | 439 | g_set_error(error, |
274 | 439 | FWUPD_ERROR, |
275 | 439 | FWUPD_ERROR_INVALID_DATA, |
276 | 439 | "invalid token 0x%x @0%x", |
277 | 439 | token, |
278 | 439 | (guint)offset); |
279 | 439 | return FALSE; |
280 | 17.8k | } |
281 | | |
282 | | /* did not see FDT_END */ |
283 | 1.86k | if (!has_end) |
284 | 1.63k | g_warning("did not see FDT_END, perhaps size_dt_struct is invalid?"); |
285 | | |
286 | | /* success */ |
287 | 1.86k | return TRUE; |
288 | 2.82k | } |
289 | | |
290 | | static gboolean |
291 | | fu_fdt_firmware_parse_mem_rsvmap(FuFdtFirmware *self, |
292 | | GInputStream *stream, |
293 | | gsize offset, |
294 | | GError **error) |
295 | 2.99k | { |
296 | 2.99k | gsize streamsz = 0; |
297 | | |
298 | | /* parse */ |
299 | 2.99k | if (!fu_input_stream_size(stream, &streamsz, error)) |
300 | 0 | return FALSE; |
301 | 238k | for (; offset < streamsz; offset += FU_STRUCT_FDT_RESERVE_ENTRY_SIZE) { |
302 | 235k | guint64 address = 0; |
303 | 235k | guint64 size = 0; |
304 | 235k | g_autoptr(FuStructFdtReserveEntry) st_res = NULL; |
305 | 235k | st_res = fu_struct_fdt_reserve_entry_parse_stream(stream, offset, error); |
306 | 235k | if (st_res == NULL) |
307 | 212 | return FALSE; |
308 | 235k | address = fu_struct_fdt_reserve_entry_get_address(st_res); |
309 | 235k | size = fu_struct_fdt_reserve_entry_get_size(st_res); |
310 | 235k | g_debug("mem_rsvmap: 0x%x, 0x%x", (guint)address, (guint)size); |
311 | 235k | if (address == 0x0 && size == 0x0) |
312 | 47 | break; |
313 | 235k | } |
314 | | |
315 | | /* success */ |
316 | 2.78k | return TRUE; |
317 | 2.99k | } |
318 | | |
319 | | static gboolean |
320 | | fu_fdt_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error) |
321 | 3.58k | { |
322 | 3.58k | return fu_struct_fdt_validate_stream(stream, offset, error); |
323 | 3.58k | } |
324 | | |
325 | | static gboolean |
326 | | fu_fdt_firmware_parse(FuFirmware *firmware, |
327 | | GInputStream *stream, |
328 | | FuFirmwareParseFlags flags, |
329 | | GError **error) |
330 | 3.45k | { |
331 | 3.45k | FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); |
332 | 3.45k | FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); |
333 | 3.45k | guint32 totalsize; |
334 | 3.45k | gsize streamsz = 0; |
335 | 3.45k | guint32 off_mem_rsvmap = 0; |
336 | 3.45k | g_autoptr(FuStructFdt) st_hdr = NULL; |
337 | | |
338 | | /* sanity check */ |
339 | 3.45k | st_hdr = fu_struct_fdt_parse_stream(stream, 0x0, error); |
340 | 3.45k | if (st_hdr == NULL) |
341 | 0 | return FALSE; |
342 | 3.45k | if (!fu_input_stream_size(stream, &streamsz, error)) |
343 | 0 | return FALSE; |
344 | 3.45k | totalsize = fu_struct_fdt_get_totalsize(st_hdr); |
345 | 3.45k | if (totalsize > streamsz) { |
346 | 45 | g_set_error(error, |
347 | 45 | FWUPD_ERROR, |
348 | 45 | FWUPD_ERROR_INVALID_DATA, |
349 | 45 | "truncated image, got 0x%x, expected >= 0x%x", |
350 | 45 | (guint)streamsz, |
351 | 45 | (guint)totalsize); |
352 | 45 | return FALSE; |
353 | 45 | } |
354 | 3.40k | fu_firmware_set_size(firmware, totalsize); |
355 | | |
356 | | /* read header */ |
357 | 3.40k | priv->cpuid = fu_struct_fdt_get_boot_cpuid_phys(st_hdr); |
358 | 3.40k | off_mem_rsvmap = fu_struct_fdt_get_off_mem_rsvmap(st_hdr); |
359 | 3.40k | if (off_mem_rsvmap != 0x0) { |
360 | 2.99k | if (!fu_fdt_firmware_parse_mem_rsvmap(self, stream, off_mem_rsvmap, error)) |
361 | 212 | return FALSE; |
362 | 2.99k | } |
363 | 3.19k | if (fu_struct_fdt_get_last_comp_version(st_hdr) < FDT_LAST_COMP_VERSION) { |
364 | 26 | g_set_error(error, |
365 | 26 | FWUPD_ERROR, |
366 | 26 | FWUPD_ERROR_INVALID_DATA, |
367 | 26 | "invalid header version, got 0x%x, expected >= 0x%x", |
368 | 26 | (guint)fu_struct_fdt_get_last_comp_version(st_hdr), |
369 | 26 | (guint)FDT_LAST_COMP_VERSION); |
370 | 26 | return FALSE; |
371 | 26 | } |
372 | 3.16k | fu_firmware_set_version_raw(firmware, fu_struct_fdt_get_version(st_hdr)); |
373 | | |
374 | | /* parse device tree struct */ |
375 | 3.16k | if (fu_struct_fdt_get_size_dt_struct(st_hdr) != 0x0 && |
376 | 3.16k | fu_struct_fdt_get_size_dt_strings(st_hdr) != 0x0) { |
377 | 3.15k | g_autoptr(GByteArray) dt_strings = NULL; |
378 | 3.15k | g_autoptr(GByteArray) dt_struct = NULL; |
379 | 3.15k | g_autoptr(GBytes) dt_struct_buf = NULL; |
380 | 3.15k | dt_strings = |
381 | 3.15k | fu_input_stream_read_byte_array(stream, |
382 | 3.15k | fu_struct_fdt_get_off_dt_strings(st_hdr), |
383 | 3.15k | fu_struct_fdt_get_size_dt_strings(st_hdr), |
384 | 3.15k | NULL, |
385 | 3.15k | error); |
386 | 3.15k | if (dt_strings == NULL) |
387 | 134 | return FALSE; |
388 | 3.01k | dt_struct = |
389 | 3.01k | fu_input_stream_read_byte_array(stream, |
390 | 3.01k | fu_struct_fdt_get_off_dt_struct(st_hdr), |
391 | 3.01k | fu_struct_fdt_get_size_dt_struct(st_hdr), |
392 | 3.01k | NULL, |
393 | 3.01k | error); |
394 | 3.01k | if (dt_struct == NULL) |
395 | 50 | return FALSE; |
396 | 2.96k | if (dt_struct->len != fu_struct_fdt_get_size_dt_struct(st_hdr)) { |
397 | 148 | g_set_error_literal(error, |
398 | 148 | FWUPD_ERROR, |
399 | 148 | FWUPD_ERROR_INVALID_DATA, |
400 | 148 | "invalid firmware -- dt_struct invalid"); |
401 | 148 | return FALSE; |
402 | 148 | } |
403 | 2.82k | dt_struct_buf = |
404 | 2.82k | g_byte_array_free_to_bytes(g_steal_pointer(&dt_struct)); /* nocheck:blocked */ |
405 | 2.82k | if (!fu_fdt_firmware_parse_dt_struct(self, dt_struct_buf, dt_strings, error)) |
406 | 958 | return FALSE; |
407 | 2.82k | } |
408 | | |
409 | | /* success */ |
410 | 1.87k | return TRUE; |
411 | 3.16k | } |
412 | | |
413 | | typedef struct { |
414 | | GByteArray *dt_strings; |
415 | | GByteArray *dt_struct; |
416 | | GHashTable *strtab; |
417 | | } FuFdtFirmwareBuildHelper; |
418 | | |
419 | | static guint32 |
420 | | fu_fdt_firmware_append_to_strtab(FuFdtFirmwareBuildHelper *helper, const gchar *key) |
421 | 8.25k | { |
422 | 8.25k | gpointer tmp = NULL; |
423 | 8.25k | guint32 offset; |
424 | | |
425 | | /* already exists */ |
426 | 8.25k | if (g_hash_table_lookup_extended(helper->strtab, key, NULL, &tmp)) |
427 | 1.78k | return GPOINTER_TO_UINT(tmp); |
428 | | |
429 | 6.46k | g_debug("adding strtab: %s", key); |
430 | 6.46k | offset = helper->dt_strings->len; |
431 | 6.46k | g_byte_array_append(helper->dt_strings, (const guint8 *)key, strlen(key)); |
432 | 6.46k | fu_byte_array_append_uint8(helper->dt_strings, 0x0); |
433 | 6.46k | g_hash_table_insert(helper->strtab, g_strdup(key), GUINT_TO_POINTER(offset)); |
434 | 6.46k | return offset; |
435 | 8.25k | } |
436 | | |
437 | | static gboolean |
438 | | fu_fdt_firmware_write_image(FuFdtFirmware *self, |
439 | | FuFdtImage *img, |
440 | | FuFdtFirmwareBuildHelper *helper, |
441 | | guint depth, |
442 | | GError **error) |
443 | 6.88k | { |
444 | 6.88k | const gchar *id = fu_firmware_get_id(FU_FIRMWARE(img)); |
445 | 6.88k | g_autoptr(GPtrArray) images = fu_firmware_get_images(FU_FIRMWARE(img)); |
446 | 6.88k | g_autoptr(GPtrArray) attrs = fu_fdt_image_get_attrs(img); |
447 | | |
448 | | /* sanity check */ |
449 | 6.88k | if (depth > 0 && id == NULL) { |
450 | 92 | g_set_error_literal(error, |
451 | 92 | FWUPD_ERROR, |
452 | 92 | FWUPD_ERROR_INVALID_DATA, |
453 | 92 | "child FuFdtImage requires ID"); |
454 | 92 | return FALSE; |
455 | 92 | } |
456 | | |
457 | | /* BEGIN_NODE, ID, NUL */ |
458 | 6.79k | fu_byte_array_append_uint32(helper->dt_struct, FU_FDT_TOKEN_BEGIN_NODE, G_BIG_ENDIAN); |
459 | 6.79k | if (id != NULL) { |
460 | 5.78k | g_byte_array_append(helper->dt_struct, (const guint8 *)id, strlen(id) + 1); |
461 | 5.78k | } else { |
462 | 1.01k | fu_byte_array_append_uint8(helper->dt_struct, 0x0); |
463 | 1.01k | } |
464 | 6.79k | fu_byte_array_align_up(helper->dt_struct, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
465 | | |
466 | | /* write properties */ |
467 | 15.0k | for (guint i = 0; i < attrs->len; i++) { |
468 | 8.25k | const gchar *key = g_ptr_array_index(attrs, i); |
469 | 8.25k | g_autoptr(GBytes) blob = NULL; |
470 | 8.25k | g_autoptr(FuStructFdtProp) st_prp = fu_struct_fdt_prop_new(); |
471 | | |
472 | 8.25k | blob = fu_fdt_image_get_attr(img, key, error); |
473 | 8.25k | if (blob == NULL) |
474 | 0 | return FALSE; |
475 | 8.25k | fu_byte_array_append_uint32(helper->dt_struct, FU_FDT_TOKEN_PROP, G_BIG_ENDIAN); |
476 | 8.25k | fu_struct_fdt_prop_set_len(st_prp, g_bytes_get_size(blob)); |
477 | 8.25k | fu_struct_fdt_prop_set_nameoff(st_prp, |
478 | 8.25k | fu_fdt_firmware_append_to_strtab(helper, key)); |
479 | 8.25k | fu_byte_array_append_array(helper->dt_struct, st_prp->buf); |
480 | 8.25k | fu_byte_array_append_bytes(helper->dt_struct, blob); |
481 | 8.25k | fu_byte_array_align_up(helper->dt_struct, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
482 | 8.25k | } |
483 | | |
484 | | /* write children, recursively */ |
485 | 11.8k | for (guint i = 0; i < images->len; i++) { |
486 | 5.65k | FuFdtImage *img_child = g_ptr_array_index(images, i); |
487 | 5.65k | if (!fu_fdt_firmware_write_image(self, img_child, helper, depth + 1, error)) |
488 | 587 | return FALSE; |
489 | 5.65k | } |
490 | | |
491 | | /* END_NODE */ |
492 | 6.20k | fu_byte_array_append_uint32(helper->dt_struct, FU_FDT_TOKEN_END_NODE, G_BIG_ENDIAN); |
493 | 6.20k | return TRUE; |
494 | 6.79k | } |
495 | | |
496 | | static GByteArray * |
497 | | fu_fdt_firmware_write(FuFirmware *firmware, GError **error) |
498 | 1.25k | { |
499 | 1.25k | FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); |
500 | 1.25k | FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); |
501 | 1.25k | guint32 off_dt_struct; |
502 | 1.25k | guint32 off_dt_strings; |
503 | 1.25k | guint32 off_mem_rsvmap; |
504 | 1.25k | g_autoptr(GByteArray) dt_strings = g_byte_array_new(); |
505 | 1.25k | g_autoptr(GByteArray) dt_struct = g_byte_array_new(); |
506 | 1.25k | g_autoptr(GHashTable) strtab = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
507 | 1.25k | g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); |
508 | 1.25k | g_autoptr(FuStructFdt) st_hdr = fu_struct_fdt_new(); |
509 | 1.25k | g_autoptr(FuStructFdtReserveEntry) st_rsvmap = fu_struct_fdt_reserve_entry_new(); |
510 | 1.25k | FuFdtFirmwareBuildHelper helper = { |
511 | 1.25k | .dt_strings = dt_strings, |
512 | 1.25k | .dt_struct = dt_struct, |
513 | 1.25k | .strtab = strtab, |
514 | 1.25k | }; |
515 | | |
516 | | /* empty st_rsvmap */ |
517 | 1.25k | off_mem_rsvmap = fu_common_align_up(st_hdr->buf->len, FU_FIRMWARE_ALIGNMENT_4); |
518 | | |
519 | | /* dt_struct */ |
520 | 1.25k | off_dt_struct = |
521 | 1.25k | fu_common_align_up(off_mem_rsvmap + st_rsvmap->buf->len, FU_FIRMWARE_ALIGNMENT_4); |
522 | | |
523 | | /* only one root node supported */ |
524 | 1.25k | if (images->len != 1) { |
525 | 24 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "no root node"); |
526 | 24 | return NULL; |
527 | 24 | } |
528 | 1.23k | if (!fu_fdt_firmware_write_image(self, |
529 | 1.23k | FU_FDT_IMAGE(g_ptr_array_index(images, 0)), |
530 | 1.23k | &helper, |
531 | 1.23k | 0, |
532 | 1.23k | error)) |
533 | 92 | return NULL; |
534 | 1.14k | fu_byte_array_append_uint32(dt_struct, FU_FDT_TOKEN_END, G_BIG_ENDIAN); |
535 | | |
536 | | /* dt_strings */ |
537 | 1.14k | off_dt_strings = |
538 | 1.14k | fu_common_align_up(off_dt_struct + dt_struct->len, FU_FIRMWARE_ALIGNMENT_4); |
539 | | |
540 | | /* write header */ |
541 | 1.14k | fu_struct_fdt_set_totalsize(st_hdr, off_dt_strings + dt_strings->len); |
542 | 1.14k | fu_struct_fdt_set_off_dt_struct(st_hdr, off_dt_struct); |
543 | 1.14k | fu_struct_fdt_set_off_dt_strings(st_hdr, off_dt_strings); |
544 | 1.14k | fu_struct_fdt_set_off_mem_rsvmap(st_hdr, off_mem_rsvmap); |
545 | 1.14k | fu_struct_fdt_set_version(st_hdr, fu_firmware_get_version_raw(firmware)); |
546 | 1.14k | fu_struct_fdt_set_boot_cpuid_phys(st_hdr, priv->cpuid); |
547 | 1.14k | fu_struct_fdt_set_size_dt_strings(st_hdr, dt_strings->len); |
548 | 1.14k | fu_struct_fdt_set_size_dt_struct(st_hdr, dt_struct->len); |
549 | 1.14k | fu_byte_array_align_up(st_hdr->buf, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
550 | | |
551 | | /* write st_rsvmap, dt_struct, dt_strings */ |
552 | 1.14k | fu_byte_array_append_array(st_hdr->buf, st_rsvmap->buf); |
553 | 1.14k | fu_byte_array_align_up(st_hdr->buf, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
554 | 1.14k | fu_byte_array_append_array(st_hdr->buf, dt_struct); |
555 | 1.14k | fu_byte_array_align_up(st_hdr->buf, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
556 | 1.14k | fu_byte_array_append_array(st_hdr->buf, dt_strings); |
557 | 1.14k | fu_byte_array_align_up(st_hdr->buf, FU_FIRMWARE_ALIGNMENT_4, 0x0); |
558 | | |
559 | | /* success */ |
560 | 1.14k | return g_steal_pointer(&st_hdr->buf); |
561 | 1.23k | } |
562 | | |
563 | | static gboolean |
564 | | fu_fdt_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
565 | 0 | { |
566 | 0 | FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); |
567 | 0 | FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); |
568 | 0 | guint64 tmp; |
569 | | |
570 | | /* optional properties */ |
571 | 0 | tmp = xb_node_query_text_as_uint(n, "cpuid", NULL); |
572 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) |
573 | 0 | priv->cpuid = tmp; |
574 | | |
575 | | /* success */ |
576 | 0 | return TRUE; |
577 | 0 | } |
578 | | |
579 | | static void |
580 | | fu_fdt_firmware_init(FuFdtFirmware *self) |
581 | 3.58k | { |
582 | 3.58k | fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_FDT_IMAGE); |
583 | 3.58k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); |
584 | 3.58k | } |
585 | | |
586 | | static void |
587 | | fu_fdt_firmware_class_init(FuFdtFirmwareClass *klass) |
588 | 2 | { |
589 | 2 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
590 | 2 | firmware_class->validate = fu_fdt_firmware_validate; |
591 | 2 | firmware_class->export = fu_fdt_firmware_export; |
592 | 2 | firmware_class->parse = fu_fdt_firmware_parse; |
593 | 2 | firmware_class->write = fu_fdt_firmware_write; |
594 | 2 | firmware_class->build = fu_fdt_firmware_build; |
595 | 2 | } |
596 | | |
597 | | /** |
598 | | * fu_fdt_firmware_new: |
599 | | * |
600 | | * Creates a new #FuFirmware of sub type FDT |
601 | | * |
602 | | * Since: 1.8.2 |
603 | | **/ |
604 | | FuFirmware * |
605 | | fu_fdt_firmware_new(void) |
606 | 0 | { |
607 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_FDT_FIRMWARE, NULL)); |
608 | 0 | } |