/src/fwupd/plugins/bcm57xx/fu-bcm57xx-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2018 Evan Lojewski |
3 | | * Copyright 2020 Richard Hughes <richard@hughsie.com> |
4 | | * |
5 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
6 | | */ |
7 | | |
8 | | #include "config.h" |
9 | | |
10 | | #include "fu-bcm57xx-common.h" |
11 | | #include "fu-bcm57xx-dict-image.h" |
12 | | #include "fu-bcm57xx-firmware.h" |
13 | | #include "fu-bcm57xx-stage1-image.h" |
14 | | #include "fu-bcm57xx-stage2-image.h" |
15 | | #include "fu-bcm57xx-struct.h" |
16 | | |
17 | | struct _FuBcm57xxFirmware { |
18 | | FuFirmware parent_instance; |
19 | | guint16 vendor; |
20 | | guint16 model; |
21 | | gboolean is_backup; |
22 | | guint32 phys_addr; |
23 | | gsize source_size; |
24 | | guint8 source_padchar; |
25 | | }; |
26 | | |
27 | 2.06k | G_DEFINE_TYPE(FuBcm57xxFirmware, fu_bcm57xx_firmware, FU_TYPE_FIRMWARE) |
28 | 2.06k | |
29 | 3.63k | #define BCM_STAGE1_HEADER_MAGIC_BROADCOM 0x0E000E03 |
30 | 2.70k | #define BCM_STAGE1_HEADER_MAGIC_MEKLORT 0x3C1D0800 |
31 | | |
32 | 2.80k | #define BCM_APE_HEADER_MAGIC 0x1A4D4342 |
33 | | |
34 | 2 | #define BCM_CODE_DIRECTORY_ADDR_APE 0x07 |
35 | | |
36 | | static void |
37 | | fu_bcm57xx_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
38 | 0 | { |
39 | 0 | FuBcm57xxFirmware *self = FU_BCM57XX_FIRMWARE(firmware); |
40 | 0 | fu_xmlb_builder_insert_kx(bn, "vendor", self->vendor); |
41 | 0 | fu_xmlb_builder_insert_kx(bn, "model", self->model); |
42 | 0 | if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { |
43 | 0 | fu_xmlb_builder_insert_kb(bn, "is_backup", self->is_backup); |
44 | 0 | fu_xmlb_builder_insert_kx(bn, "phys_addr", self->phys_addr); |
45 | 0 | } |
46 | 0 | } |
47 | | |
48 | | static gboolean |
49 | | fu_bcm57xx_firmware_parse_header(FuBcm57xxFirmware *self, GInputStream *stream, GError **error) |
50 | 741 | { |
51 | | /* verify magic and CRC */ |
52 | 741 | if (!fu_bcm57xx_verify_magic(stream, 0x0, error)) |
53 | 0 | return FALSE; |
54 | 741 | if (!fu_bcm57xx_verify_crc(stream, error)) |
55 | 50 | return FALSE; |
56 | | |
57 | | /* get address */ |
58 | 691 | return fu_input_stream_read_u32(stream, |
59 | 691 | FU_STRUCT_BCM57XX_NVRAM_HEADER_OFFSET_PHYS_ADDR, |
60 | 691 | &self->phys_addr, |
61 | 691 | G_BIG_ENDIAN, |
62 | 691 | error); |
63 | 741 | } |
64 | | |
65 | | static FuFirmware * |
66 | | fu_bcm57xx_firmware_parse_info(FuBcm57xxFirmware *self, |
67 | | GInputStream *stream, |
68 | | FuFirmwareParseFlags flags, |
69 | | GError **error) |
70 | 684 | { |
71 | 684 | guint32 mac_addr0; |
72 | 684 | g_autoptr(FuFirmware) img = fu_firmware_new(); |
73 | 684 | g_autoptr(FuStructBcm57xxNvramInfo) st = NULL; |
74 | | |
75 | 684 | st = fu_struct_bcm57xx_nvram_info_parse_stream(stream, 0x0, error); |
76 | 684 | if (st == NULL) |
77 | 0 | return NULL; |
78 | | |
79 | | /* if the MAC is set non-zero this is an actual backup rather than a container */ |
80 | 684 | mac_addr0 = fu_struct_bcm57xx_nvram_info_get_mac_addr(st, 0); |
81 | 684 | self->is_backup = mac_addr0 != 0x0 && mac_addr0 != 0xffffffff; |
82 | | |
83 | | /* read vendor + model */ |
84 | 684 | self->vendor = fu_struct_bcm57xx_nvram_info_get_vendor(st); |
85 | 684 | self->model = fu_struct_bcm57xx_nvram_info_get_device(st); |
86 | | |
87 | | /* success */ |
88 | 684 | if (!fu_firmware_parse_stream(img, stream, 0x0, flags, error)) |
89 | 0 | return NULL; |
90 | 684 | fu_firmware_set_id(img, "info"); |
91 | 684 | return g_steal_pointer(&img); |
92 | 684 | } |
93 | | |
94 | | static FuFirmware * |
95 | | fu_bcm57xx_firmware_parse_stage1(FuBcm57xxFirmware *self, |
96 | | GInputStream *stream, |
97 | | guint32 *out_stage1_sz, |
98 | | FuFirmwareParseFlags flags, |
99 | | GError **error) |
100 | 631 | { |
101 | 631 | gsize streamsz = 0; |
102 | 631 | guint32 stage1_wrds = 0; |
103 | 631 | guint32 stage1_sz; |
104 | 631 | guint32 stage1_off = 0; |
105 | 631 | g_autoptr(FuStructBcm57xxNvramHeader) st = NULL; |
106 | 631 | g_autoptr(FuFirmware) img = fu_bcm57xx_stage1_image_new(); |
107 | 631 | g_autoptr(GInputStream) stream_tmp = NULL; |
108 | | |
109 | 631 | if (!fu_input_stream_size(stream, &streamsz, error)) |
110 | 0 | return NULL; |
111 | 631 | st = fu_struct_bcm57xx_nvram_header_parse_stream(stream, BCM_NVRAM_HEADER_BASE, error); |
112 | 631 | if (st == NULL) |
113 | 0 | return NULL; |
114 | 631 | stage1_wrds = fu_struct_bcm57xx_nvram_header_get_size_wrds(st); |
115 | 631 | stage1_off = fu_struct_bcm57xx_nvram_header_get_offset(st); |
116 | | |
117 | 631 | if (stage1_wrds > G_MAXUINT32 / sizeof(guint32)) { |
118 | 10 | g_set_error(error, |
119 | 10 | FWUPD_ERROR, |
120 | 10 | FWUPD_ERROR_NOT_SUPPORTED, |
121 | 10 | "stage1 word count too large: 0x%x", |
122 | 10 | stage1_wrds); |
123 | 10 | return NULL; |
124 | 10 | } |
125 | 621 | stage1_sz = (stage1_wrds * sizeof(guint32)); |
126 | 621 | if (stage1_off != BCM_NVRAM_STAGE1_BASE) { |
127 | 29 | g_set_error(error, |
128 | 29 | FWUPD_ERROR, |
129 | 29 | FWUPD_ERROR_NOT_SUPPORTED, |
130 | 29 | "stage1 offset invalid, got: 0x%x, expected 0x%x", |
131 | 29 | (guint)stage1_sz, |
132 | 29 | (guint)BCM_NVRAM_STAGE1_BASE); |
133 | 29 | return NULL; |
134 | 29 | } |
135 | 592 | if ((gsize)stage1_off + stage1_sz > streamsz) { |
136 | 39 | g_set_error(error, |
137 | 39 | FWUPD_ERROR, |
138 | 39 | FWUPD_ERROR_NOT_SUPPORTED, |
139 | 39 | "bigger than firmware, got: 0x%x @ 0x%x", |
140 | 39 | (guint)stage1_sz, |
141 | 39 | (guint)stage1_off); |
142 | 39 | return NULL; |
143 | 39 | } |
144 | | |
145 | | /* verify CRC */ |
146 | 553 | stream_tmp = fu_partial_input_stream_new(stream, stage1_off, stage1_sz, error); |
147 | 553 | if (stream_tmp == NULL) |
148 | 0 | return NULL; |
149 | 553 | if (!fu_firmware_parse_stream(img, |
150 | 553 | stream_tmp, |
151 | 553 | 0x0, |
152 | 553 | flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH, |
153 | 553 | error)) |
154 | 102 | return NULL; |
155 | | |
156 | | /* needed for stage2 */ |
157 | 451 | if (out_stage1_sz != NULL) |
158 | 451 | *out_stage1_sz = stage1_sz; |
159 | | |
160 | | /* success */ |
161 | 451 | fu_firmware_set_id(img, "stage1"); |
162 | 451 | fu_firmware_set_offset(img, stage1_off); |
163 | 451 | return g_steal_pointer(&img); |
164 | 553 | } |
165 | | |
166 | | static FuFirmware * |
167 | | fu_bcm57xx_firmware_parse_stage2(FuBcm57xxFirmware *self, |
168 | | GInputStream *stream, |
169 | | guint32 stage1_sz, |
170 | | FuFirmwareParseFlags flags, |
171 | | GError **error) |
172 | 451 | { |
173 | 451 | gsize streamsz = 0; |
174 | 451 | guint32 stage2_off = 0; |
175 | 451 | guint32 stage2_sz = 0; |
176 | 451 | g_autoptr(FuFirmware) img = fu_bcm57xx_stage2_image_new(); |
177 | 451 | g_autoptr(GInputStream) stream_tmp = NULL; |
178 | | |
179 | 451 | stage2_off = BCM_NVRAM_STAGE1_BASE + stage1_sz; |
180 | 451 | if (!fu_bcm57xx_verify_magic(stream, stage2_off, error)) |
181 | 135 | return NULL; |
182 | 316 | if (!fu_input_stream_read_u32(stream, |
183 | 316 | stage2_off + sizeof(guint32), |
184 | 316 | &stage2_sz, |
185 | 316 | G_BIG_ENDIAN, |
186 | 316 | error)) |
187 | 1 | return NULL; |
188 | 315 | if (!fu_input_stream_size(stream, &streamsz, error)) |
189 | 0 | return NULL; |
190 | 315 | if ((gsize)stage2_off + stage2_sz > streamsz) { |
191 | 51 | g_set_error(error, |
192 | 51 | FWUPD_ERROR, |
193 | 51 | FWUPD_ERROR_NOT_SUPPORTED, |
194 | 51 | "bigger than firmware, got: 0x%x @ 0x%x", |
195 | 51 | (guint)stage2_sz, |
196 | 51 | (guint)stage2_off); |
197 | 51 | return NULL; |
198 | 51 | } |
199 | | |
200 | | /* verify CRC */ |
201 | 264 | stream_tmp = fu_partial_input_stream_new(stream, stage2_off + 0x8, stage2_sz, error); |
202 | 264 | if (stream_tmp == NULL) |
203 | 8 | return NULL; |
204 | 256 | if (!fu_firmware_parse_stream(img, |
205 | 256 | stream_tmp, |
206 | 256 | 0x0, |
207 | 256 | flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH, |
208 | 256 | error)) |
209 | 3 | return NULL; |
210 | | |
211 | | /* success */ |
212 | 253 | fu_firmware_set_id(img, "stage2"); |
213 | 253 | fu_firmware_set_offset(img, stage2_off); |
214 | 253 | return g_steal_pointer(&img); |
215 | 256 | } |
216 | | |
217 | | static gboolean |
218 | | fu_bcm57xx_firmware_parse_dict(FuBcm57xxFirmware *self, |
219 | | GInputStream *stream, |
220 | | guint idx, |
221 | | FuFirmwareParseFlags flags, |
222 | | GError **error) |
223 | 1.72k | { |
224 | 1.72k | gsize streamsz = 0; |
225 | 1.72k | guint32 dict_addr = 0x0; |
226 | 1.72k | guint32 dict_info = 0x0; |
227 | 1.72k | guint32 dict_off = 0x0; |
228 | 1.72k | guint32 dict_sz; |
229 | 1.72k | guint32 base = BCM_NVRAM_DIRECTORY_BASE + (idx * FU_STRUCT_BCM57XX_NVRAM_DIRECTORY_SIZE); |
230 | 1.72k | g_autoptr(FuFirmware) img = fu_bcm57xx_dict_image_new(); |
231 | 1.72k | g_autoptr(FuStructBcm57xxNvramDirectory) st = NULL; |
232 | 1.72k | g_autoptr(GInputStream) stream_tmp = NULL; |
233 | | |
234 | | /* header */ |
235 | 1.72k | st = fu_struct_bcm57xx_nvram_directory_parse_stream(stream, base, error); |
236 | 1.72k | if (st == NULL) |
237 | 0 | return FALSE; |
238 | 1.72k | dict_addr = fu_struct_bcm57xx_nvram_directory_get_addr(st); |
239 | 1.72k | dict_info = fu_struct_bcm57xx_nvram_directory_get_size_wrds(st); |
240 | 1.72k | dict_off = fu_struct_bcm57xx_nvram_directory_get_offset(st); |
241 | | |
242 | | /* no dict stored */ |
243 | 1.72k | if (dict_addr == 0 && dict_info == 0 && dict_off == 0) |
244 | 151 | return TRUE; |
245 | | |
246 | 1.57k | dict_sz = |
247 | 1.57k | (dict_info & 0x00FFFFFF) * sizeof(guint32); /* implies that maximum size is 16 MB */ |
248 | 1.57k | fu_bcm57xx_dict_image_set_target(FU_BCM57XX_DICT_IMAGE(img), |
249 | 1.57k | (dict_info & 0x0F000000) >> 24); |
250 | 1.57k | fu_bcm57xx_dict_image_set_kind(FU_BCM57XX_DICT_IMAGE(img), (dict_info & 0xF0000000) >> 28); |
251 | 1.57k | fu_firmware_set_addr(img, dict_addr); |
252 | 1.57k | fu_firmware_set_offset(img, dict_off); |
253 | 1.57k | fu_firmware_set_idx(img, 0x80 + idx); |
254 | | |
255 | | /* empty */ |
256 | 1.57k | if (dict_sz == 0) { |
257 | 720 | g_autoptr(GBytes) blob = g_bytes_new(NULL, 0); |
258 | 720 | fu_firmware_set_bytes(img, blob); |
259 | 720 | return fu_firmware_add_image(FU_FIRMWARE(self), img, error); |
260 | 720 | } |
261 | | |
262 | | /* check against image size */ |
263 | 850 | if (!fu_input_stream_size(stream, &streamsz, error)) |
264 | 0 | return FALSE; |
265 | 850 | if ((gsize)dict_off + (gsize)dict_sz > streamsz) { |
266 | 103 | g_set_error(error, |
267 | 103 | FWUPD_ERROR, |
268 | 103 | FWUPD_ERROR_NOT_SUPPORTED, |
269 | 103 | "bigger than firmware, got: 0x%x @ 0x%x", |
270 | 103 | (guint)dict_sz, |
271 | 103 | (guint)dict_off); |
272 | 103 | return FALSE; |
273 | 103 | } |
274 | 747 | stream_tmp = fu_partial_input_stream_new(stream, dict_off, dict_sz, error); |
275 | 747 | if (stream_tmp == NULL) |
276 | 0 | return FALSE; |
277 | 747 | if (!fu_firmware_parse_stream(img, |
278 | 747 | stream_tmp, |
279 | 747 | 0x0, |
280 | 747 | flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH, |
281 | 747 | error)) |
282 | 0 | return FALSE; |
283 | | |
284 | | /* success */ |
285 | 747 | return fu_firmware_add_image(FU_FIRMWARE(self), img, error); |
286 | 747 | } |
287 | | |
288 | | static gboolean |
289 | | fu_bcm57xx_firmware_validate(FuFirmware *firmware, |
290 | | GInputStream *stream, |
291 | | gsize offset, |
292 | | GError **error) |
293 | 989 | { |
294 | 989 | guint32 magic = 0; |
295 | | |
296 | 989 | if (!fu_input_stream_read_u32(stream, 0x0, &magic, G_BIG_ENDIAN, error)) { |
297 | 6 | g_prefix_error_literal(error, "failed to read magic: "); |
298 | 6 | return FALSE; |
299 | 6 | } |
300 | 983 | if (magic != BCM_APE_HEADER_MAGIC && magic != BCM_STAGE1_HEADER_MAGIC_BROADCOM && |
301 | 932 | magic != BCM_STAGE1_HEADER_MAGIC_MEKLORT && magic != BCM_NVRAM_MAGIC) { |
302 | 147 | g_set_error(error, |
303 | 147 | FWUPD_ERROR, |
304 | 147 | FWUPD_ERROR_INVALID_FILE, |
305 | 147 | "file not supported, got: 0x%08X", |
306 | 147 | magic); |
307 | 147 | return FALSE; |
308 | 147 | } |
309 | | |
310 | | /* success */ |
311 | 836 | return TRUE; |
312 | 983 | } |
313 | | |
314 | | static gboolean |
315 | | fu_bcm57xx_firmware_parse(FuFirmware *firmware, |
316 | | GInputStream *stream, |
317 | | FuFirmwareParseFlags flags, |
318 | | GError **error) |
319 | 836 | { |
320 | 836 | FuBcm57xxFirmware *self = FU_BCM57XX_FIRMWARE(firmware); |
321 | 836 | gsize streamsz = 0; |
322 | 836 | guint32 magic = 0; |
323 | 836 | guint32 stage1_sz = 0; |
324 | 836 | g_autoptr(FuFirmware) img_info2 = fu_firmware_new(); |
325 | 836 | g_autoptr(FuFirmware) img_info = NULL; |
326 | 836 | g_autoptr(FuFirmware) img_stage1 = NULL; |
327 | 836 | g_autoptr(FuFirmware) img_stage2 = NULL; |
328 | 836 | g_autoptr(FuFirmware) img_vpd = fu_firmware_new(); |
329 | 836 | g_autoptr(GInputStream) stream_header = NULL; |
330 | 836 | g_autoptr(GInputStream) stream_info2 = NULL; |
331 | 836 | g_autoptr(GInputStream) stream_info = NULL; |
332 | 836 | g_autoptr(GInputStream) stream_vpd = NULL; |
333 | | |
334 | | /* try to autodetect the file type */ |
335 | 836 | if (!fu_input_stream_read_u32(stream, 0x0, &magic, G_BIG_ENDIAN, error)) |
336 | 0 | return FALSE; |
337 | | |
338 | | /* standalone APE */ |
339 | 836 | if (magic == BCM_APE_HEADER_MAGIC) { |
340 | 2 | g_autoptr(FuFirmware) img = fu_bcm57xx_dict_image_new(); |
341 | 2 | fu_bcm57xx_dict_image_set_target(FU_BCM57XX_DICT_IMAGE(img), 0xD); |
342 | 2 | fu_bcm57xx_dict_image_set_kind(FU_BCM57XX_DICT_IMAGE(img), 0x0); |
343 | 2 | fu_firmware_set_addr(img, BCM_CODE_DIRECTORY_ADDR_APE); |
344 | 2 | fu_firmware_set_id(img, "ape"); |
345 | 2 | return fu_firmware_add_image(firmware, img, error); |
346 | 2 | } |
347 | | |
348 | | /* standalone stage1 */ |
349 | 834 | if (magic == BCM_STAGE1_HEADER_MAGIC_BROADCOM || magic == BCM_STAGE1_HEADER_MAGIC_MEKLORT) { |
350 | 84 | g_autoptr(FuFirmware) img_stage1_standalone = fu_firmware_new(); |
351 | 84 | if (!fu_firmware_set_stream(img_stage1_standalone, stream, error)) |
352 | 0 | return FALSE; |
353 | 84 | fu_firmware_set_id(img_stage1_standalone, "stage1"); |
354 | 84 | return fu_firmware_add_image(firmware, img_stage1_standalone, error); |
355 | 84 | } |
356 | | |
357 | | /* not full NVRAM image */ |
358 | 750 | if (magic != BCM_NVRAM_MAGIC) { |
359 | 0 | g_set_error(error, |
360 | 0 | FWUPD_ERROR, |
361 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
362 | 0 | "file not supported, got: 0x%08X", |
363 | 0 | magic); |
364 | 0 | return FALSE; |
365 | 0 | } |
366 | | |
367 | | /* save the size so we can export the padding for a perfect roundtrip */ |
368 | 750 | if (!fu_input_stream_size(stream, &streamsz, error)) |
369 | 0 | return FALSE; |
370 | 750 | self->source_size = streamsz; |
371 | 750 | if (!fu_input_stream_read_u8(stream, streamsz - 1, &self->source_padchar, error)) |
372 | 0 | return FALSE; |
373 | | |
374 | | /* NVRAM header */ |
375 | 750 | stream_header = fu_partial_input_stream_new(stream, |
376 | 750 | BCM_NVRAM_HEADER_BASE, |
377 | 750 | FU_STRUCT_BCM57XX_NVRAM_HEADER_SIZE, |
378 | 750 | error); |
379 | 750 | if (stream_header == NULL) |
380 | 9 | return FALSE; |
381 | 741 | if (!fu_bcm57xx_firmware_parse_header(self, stream_header, error)) { |
382 | 50 | g_prefix_error_literal(error, "failed to parse header: "); |
383 | 50 | return FALSE; |
384 | 50 | } |
385 | | |
386 | | /* info */ |
387 | 691 | stream_info = fu_partial_input_stream_new(stream, |
388 | 691 | BCM_NVRAM_INFO_BASE, |
389 | 691 | FU_STRUCT_BCM57XX_NVRAM_INFO_SIZE, |
390 | 691 | error); |
391 | 691 | if (stream_info == NULL) |
392 | 7 | return FALSE; |
393 | 684 | img_info = fu_bcm57xx_firmware_parse_info(self, stream_info, flags, error); |
394 | 684 | if (img_info == NULL) { |
395 | 0 | g_prefix_error_literal(error, "failed to parse info: "); |
396 | 0 | return FALSE; |
397 | 0 | } |
398 | 684 | fu_firmware_set_offset(img_info, BCM_NVRAM_INFO_BASE); |
399 | 684 | if (!fu_firmware_add_image(firmware, img_info, error)) |
400 | 0 | return FALSE; |
401 | | |
402 | | /* VPD */ |
403 | 684 | stream_vpd = |
404 | 684 | fu_partial_input_stream_new(stream, BCM_NVRAM_VPD_BASE, BCM_NVRAM_VPD_SZ, error); |
405 | 684 | if (stream_vpd == NULL) |
406 | 48 | return FALSE; |
407 | 636 | if (!fu_firmware_parse_stream(img_vpd, stream_vpd, 0x0, flags, error)) { |
408 | 0 | g_prefix_error_literal(error, "failed to parse VPD: "); |
409 | 0 | return FALSE; |
410 | 0 | } |
411 | 636 | fu_firmware_set_id(img_vpd, "vpd"); |
412 | 636 | fu_firmware_set_offset(img_vpd, BCM_NVRAM_VPD_BASE); |
413 | 636 | if (!fu_firmware_add_image(firmware, img_vpd, error)) |
414 | 0 | return FALSE; |
415 | | |
416 | | /* info2 */ |
417 | 636 | stream_info2 = |
418 | 636 | fu_partial_input_stream_new(stream, BCM_NVRAM_INFO2_BASE, BCM_NVRAM_INFO2_SZ, error); |
419 | 636 | if (stream_info2 == NULL) |
420 | 5 | return FALSE; |
421 | 631 | if (!fu_firmware_parse_stream(img_info2, stream_info2, 0x0, flags, error)) { |
422 | 0 | g_prefix_error_literal(error, "failed to parse info2: "); |
423 | 0 | return FALSE; |
424 | 0 | } |
425 | 631 | fu_firmware_set_id(img_info2, "info2"); |
426 | 631 | fu_firmware_set_offset(img_info2, BCM_NVRAM_INFO2_BASE); |
427 | 631 | if (!fu_firmware_add_image(firmware, img_info2, error)) |
428 | 0 | return FALSE; |
429 | | |
430 | | /* stage1 */ |
431 | 631 | img_stage1 = fu_bcm57xx_firmware_parse_stage1(self, stream, &stage1_sz, flags, error); |
432 | 631 | if (img_stage1 == NULL) { |
433 | 180 | g_prefix_error_literal(error, "failed to parse stage1: "); |
434 | 180 | return FALSE; |
435 | 180 | } |
436 | 451 | if (!fu_firmware_add_image(firmware, img_stage1, error)) |
437 | 0 | return FALSE; |
438 | | |
439 | | /* stage2 */ |
440 | 451 | img_stage2 = fu_bcm57xx_firmware_parse_stage2(self, stream, stage1_sz, flags, error); |
441 | 451 | if (img_stage2 == NULL) { |
442 | 198 | g_prefix_error_literal(error, "failed to parse stage2: "); |
443 | 198 | return FALSE; |
444 | 198 | } |
445 | 253 | if (!fu_firmware_add_image(firmware, img_stage2, error)) |
446 | 0 | return FALSE; |
447 | | |
448 | | /* dictionaries, e.g. APE */ |
449 | 1.87k | for (guint i = 0; i < 8; i++) { |
450 | 1.72k | if (!fu_bcm57xx_firmware_parse_dict(self, stream, i, flags, error)) { |
451 | 103 | g_prefix_error(error, "failed to parse dict 0x%x: ", i); |
452 | 103 | return FALSE; |
453 | 103 | } |
454 | 1.72k | } |
455 | | |
456 | | /* success */ |
457 | 150 | return TRUE; |
458 | 253 | } |
459 | | |
460 | | static GBytes * |
461 | | _g_bytes_new_sized(gsize sz) |
462 | 0 | { |
463 | 0 | g_autoptr(GByteArray) tmp = g_byte_array_sized_new(sz); |
464 | 0 | for (gsize i = 0; i < sz; i++) |
465 | 0 | fu_byte_array_append_uint8(tmp, 0x0); |
466 | 0 | return g_bytes_new(tmp->data, tmp->len); |
467 | 0 | } |
468 | | |
469 | | static gboolean |
470 | | fu_bcm57xx_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
471 | 0 | { |
472 | 0 | FuBcm57xxFirmware *self = FU_BCM57XX_FIRMWARE(firmware); |
473 | 0 | guint64 tmp; |
474 | | |
475 | | /* two simple properties */ |
476 | 0 | tmp = xb_node_query_text_as_uint(n, "vendor", NULL); |
477 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
478 | 0 | self->vendor = tmp; |
479 | 0 | tmp = xb_node_query_text_as_uint(n, "model", NULL); |
480 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
481 | 0 | self->model = tmp; |
482 | | |
483 | | /* success */ |
484 | 0 | return TRUE; |
485 | 0 | } |
486 | | |
487 | | static GByteArray * |
488 | | fu_bcm57xx_firmware_write(FuFirmware *firmware, GError **error) |
489 | 236 | { |
490 | 236 | gsize off = BCM_NVRAM_STAGE1_BASE; |
491 | 236 | FuBcm57xxFirmware *self = FU_BCM57XX_FIRMWARE(firmware); |
492 | 236 | g_autoptr(GByteArray) buf = g_byte_array_sized_new(self->source_size); |
493 | 236 | g_autoptr(FuFirmware) img_info2 = NULL; |
494 | 236 | g_autoptr(FuFirmware) img_info = NULL; |
495 | 236 | g_autoptr(FuFirmware) img_stage1 = NULL; |
496 | 236 | g_autoptr(FuFirmware) img_stage2 = NULL; |
497 | 236 | g_autoptr(FuFirmware) img_vpd = NULL; |
498 | 236 | g_autoptr(GBytes) blob_info2 = NULL; |
499 | 236 | g_autoptr(GBytes) blob_info = NULL; |
500 | 236 | g_autoptr(GBytes) blob_stage1 = NULL; |
501 | 236 | g_autoptr(GBytes) blob_stage2 = NULL; |
502 | 236 | g_autoptr(GBytes) blob_vpd = NULL; |
503 | 236 | g_autoptr(GPtrArray) blob_dicts = NULL; |
504 | | |
505 | | /* write out the things we need to pre-compute */ |
506 | 236 | img_stage1 = fu_firmware_get_image_by_id(firmware, "stage1", error); |
507 | 236 | if (img_stage1 == NULL) |
508 | 2 | return NULL; |
509 | 234 | blob_stage1 = fu_firmware_write(img_stage1, error); |
510 | 234 | if (blob_stage1 == NULL) |
511 | 26 | return NULL; |
512 | 208 | off += g_bytes_get_size(blob_stage1); |
513 | 208 | img_stage2 = fu_firmware_get_image_by_id(firmware, "stage2", error); |
514 | 208 | if (img_stage2 == NULL) |
515 | 84 | return NULL; |
516 | 124 | blob_stage2 = fu_firmware_write(img_stage2, error); |
517 | 124 | if (blob_stage2 == NULL) |
518 | 5 | return NULL; |
519 | 119 | off += g_bytes_get_size(blob_stage2); |
520 | | |
521 | | /* add header */ |
522 | 119 | fu_byte_array_append_uint32(buf, BCM_NVRAM_MAGIC, G_BIG_ENDIAN); |
523 | 119 | fu_byte_array_append_uint32(buf, self->phys_addr, G_BIG_ENDIAN); |
524 | 119 | fu_byte_array_append_uint32(buf, |
525 | 119 | g_bytes_get_size(blob_stage1) / sizeof(guint32), |
526 | 119 | G_BIG_ENDIAN); |
527 | 119 | fu_byte_array_append_uint32(buf, BCM_NVRAM_STAGE1_BASE, G_BIG_ENDIAN); |
528 | 119 | fu_byte_array_append_uint32(buf, |
529 | 119 | fu_crc32(FU_CRC_KIND_B32_STANDARD, buf->data, buf->len), |
530 | 119 | G_LITTLE_ENDIAN); |
531 | | |
532 | | /* add directory entries */ |
533 | 119 | blob_dicts = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); |
534 | 971 | for (guint i = 0; i < 8; i++) { |
535 | 874 | g_autoptr(FuFirmware) img = NULL; |
536 | 874 | g_autoptr(GBytes) blob = NULL; |
537 | | |
538 | 874 | img = fu_firmware_get_image_by_idx(firmware, 0x80 + i, NULL); |
539 | 874 | if (img != NULL) { |
540 | 601 | blob = fu_firmware_write(img, error); |
541 | 601 | if (blob == NULL) |
542 | 22 | return NULL; |
543 | 601 | } |
544 | 852 | if (blob != NULL) { |
545 | 579 | fu_byte_array_append_uint32(buf, fu_firmware_get_addr(img), G_BIG_ENDIAN); |
546 | 579 | fu_byte_array_append_uint32( |
547 | 579 | buf, |
548 | 579 | (g_bytes_get_size(blob) / sizeof(guint32)) | |
549 | 579 | (guint32)fu_bcm57xx_dict_image_get_target( |
550 | 579 | FU_BCM57XX_DICT_IMAGE(img)) |
551 | 579 | << 24 | |
552 | 579 | (guint32)fu_bcm57xx_dict_image_get_kind(FU_BCM57XX_DICT_IMAGE(img)) |
553 | 579 | << 28, |
554 | 579 | G_BIG_ENDIAN); |
555 | 579 | if (g_bytes_get_size(blob) > 0) { |
556 | 579 | fu_byte_array_append_uint32(buf, off, G_BIG_ENDIAN); |
557 | 579 | off += g_bytes_get_size(blob); |
558 | 579 | } else { |
559 | 0 | fu_byte_array_append_uint32(buf, 0x0, G_BIG_ENDIAN); |
560 | 0 | } |
561 | 579 | } else { |
562 | 273 | blob = g_bytes_new(NULL, 0); |
563 | 3.54k | for (guint32 j = 0; j < sizeof(guint32) * 3; j++) |
564 | 3.27k | fu_byte_array_append_uint8(buf, 0x0); |
565 | 273 | } |
566 | 852 | g_ptr_array_add(blob_dicts, g_steal_pointer(&blob)); |
567 | 852 | } |
568 | | |
569 | | /* add info */ |
570 | 97 | img_info = fu_firmware_get_image_by_id(firmware, "info", NULL); |
571 | 97 | if (img_info != NULL) { |
572 | 97 | blob_info = fu_firmware_write(img_info, error); |
573 | 97 | if (blob_info == NULL) |
574 | 97 | return NULL; |
575 | 97 | } else { |
576 | 0 | g_autoptr(FuStructBcm57xxNvramInfo) st = fu_struct_bcm57xx_nvram_info_new(); |
577 | 0 | fu_struct_bcm57xx_nvram_info_set_device(st, self->model); |
578 | 0 | fu_struct_bcm57xx_nvram_info_set_vendor(st, self->vendor); |
579 | 0 | blob_info = fu_struct_bcm57xx_nvram_info_to_bytes(st); |
580 | 0 | } |
581 | 0 | fu_byte_array_append_bytes(buf, blob_info); |
582 | | |
583 | | /* add vpd */ |
584 | 0 | img_vpd = fu_firmware_get_image_by_id(firmware, "vpd", NULL); |
585 | 0 | if (img_vpd != NULL) { |
586 | 0 | blob_vpd = fu_firmware_write(img_vpd, error); |
587 | 0 | if (blob_vpd == NULL) |
588 | 0 | return NULL; |
589 | 0 | } else { |
590 | 0 | blob_vpd = _g_bytes_new_sized(BCM_NVRAM_VPD_SZ); |
591 | 0 | } |
592 | 0 | fu_byte_array_append_bytes(buf, blob_vpd); |
593 | | |
594 | | /* add info2 */ |
595 | 0 | img_info2 = fu_firmware_get_image_by_id(firmware, "info2", NULL); |
596 | 0 | if (img_info2 != NULL) { |
597 | 0 | blob_info2 = fu_firmware_write(img_info2, error); |
598 | 0 | if (blob_info2 == NULL) |
599 | 0 | return NULL; |
600 | 0 | } else { |
601 | 0 | blob_info2 = _g_bytes_new_sized(BCM_NVRAM_INFO2_SZ); |
602 | 0 | } |
603 | 0 | fu_byte_array_append_bytes(buf, blob_info2); |
604 | | |
605 | | /* add stage1+2 */ |
606 | 0 | fu_byte_array_append_bytes(buf, blob_stage1); |
607 | 0 | fu_byte_array_append_bytes(buf, blob_stage2); |
608 | | |
609 | | /* add dictionaries, e.g. APE */ |
610 | 0 | for (guint i = 0; i < blob_dicts->len; i++) { |
611 | 0 | GBytes *blob = g_ptr_array_index(blob_dicts, i); |
612 | 0 | fu_byte_array_append_bytes(buf, blob); |
613 | 0 | } |
614 | | |
615 | | /* pad until full */ |
616 | 0 | for (guint32 i = buf->len; i < self->source_size; i++) |
617 | 0 | fu_byte_array_append_uint8(buf, self->source_padchar); |
618 | | |
619 | | /* add EOF */ |
620 | 0 | return g_steal_pointer(&buf); |
621 | 0 | } |
622 | | |
623 | | guint16 |
624 | | fu_bcm57xx_firmware_get_vendor(FuBcm57xxFirmware *self) |
625 | 0 | { |
626 | 0 | return self->vendor; |
627 | 0 | } |
628 | | |
629 | | guint16 |
630 | | fu_bcm57xx_firmware_get_model(FuBcm57xxFirmware *self) |
631 | 0 | { |
632 | 0 | return self->model; |
633 | 0 | } |
634 | | |
635 | | static void |
636 | | fu_bcm57xx_firmware_init(FuBcm57xxFirmware *self) |
637 | 989 | { |
638 | 989 | self->phys_addr = BCM_PHYS_ADDR_DEFAULT; |
639 | 989 | self->source_size = BCM_FIRMWARE_SIZE; |
640 | 989 | self->source_padchar = 0xff; |
641 | 989 | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_DEDUPE_ID); |
642 | 989 | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); |
643 | 989 | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); |
644 | 989 | fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_FIRMWARE); |
645 | 989 | fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_BCM57XX_STAGE1_IMAGE); |
646 | 989 | fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_BCM57XX_STAGE2_IMAGE); |
647 | 989 | fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_BCM57XX_DICT_IMAGE); |
648 | 989 | fu_firmware_set_size_max(FU_FIRMWARE(self), 16 * FU_MB); |
649 | 989 | } |
650 | | |
651 | | static void |
652 | | fu_bcm57xx_firmware_class_init(FuBcm57xxFirmwareClass *klass) |
653 | 1 | { |
654 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
655 | 1 | firmware_class->validate = fu_bcm57xx_firmware_validate; |
656 | 1 | firmware_class->parse = fu_bcm57xx_firmware_parse; |
657 | 1 | firmware_class->export = fu_bcm57xx_firmware_export; |
658 | 1 | firmware_class->write = fu_bcm57xx_firmware_write; |
659 | 1 | firmware_class->build = fu_bcm57xx_firmware_build; |
660 | 1 | } |
661 | | |
662 | | FuFirmware * |
663 | | fu_bcm57xx_firmware_new(void) |
664 | 0 | { |
665 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_BCM57XX_FIRMWARE, NULL)); |
666 | 0 | } |