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