/src/fwupd/plugins/ccgx/fu-ccgx-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2020 Cypress Semiconductor Corporation. |
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 <string.h> |
11 | | |
12 | | #include "fu-ccgx-common.h" |
13 | | #include "fu-ccgx-firmware.h" |
14 | | |
15 | | struct _FuCcgxFirmware { |
16 | | FuFirmware parent_instance; |
17 | | GPtrArray *records; |
18 | | guint16 app_type; |
19 | | guint16 silicon_id; |
20 | | FuCcgxFwMode fw_mode; |
21 | | }; |
22 | | |
23 | 1.73M | G_DEFINE_TYPE(FuCcgxFirmware, fu_ccgx_firmware, FU_TYPE_FIRMWARE) |
24 | 1.73M | |
25 | 1.73M | /* offset stored application version for CCGx */ |
26 | 1.73M | #define CCGX_APP_VERSION_OFFSET 228 /* 128+64+32+4 */ |
27 | | |
28 | 1.72M | #define FU_CCGX_FIRMWARE_TOKENS_MAX 100000 /* lines */ |
29 | | |
30 | | GPtrArray * |
31 | | fu_ccgx_firmware_get_records(FuCcgxFirmware *self) |
32 | 0 | { |
33 | 0 | g_return_val_if_fail(FU_IS_CCGX_FIRMWARE(self), NULL); |
34 | 0 | return self->records; |
35 | 0 | } |
36 | | |
37 | | guint16 |
38 | | fu_ccgx_firmware_get_app_type(FuCcgxFirmware *self) |
39 | 0 | { |
40 | 0 | g_return_val_if_fail(FU_IS_CCGX_FIRMWARE(self), 0); |
41 | 0 | return self->app_type; |
42 | 0 | } |
43 | | |
44 | | guint16 |
45 | | fu_ccgx_firmware_get_silicon_id(FuCcgxFirmware *self) |
46 | 0 | { |
47 | 0 | g_return_val_if_fail(FU_IS_CCGX_FIRMWARE(self), 0); |
48 | 0 | return self->silicon_id; |
49 | 0 | } |
50 | | |
51 | | FuCcgxFwMode |
52 | | fu_ccgx_firmware_get_fw_mode(FuCcgxFirmware *self) |
53 | 0 | { |
54 | 0 | g_return_val_if_fail(FU_IS_CCGX_FIRMWARE(self), 0); |
55 | 0 | return self->fw_mode; |
56 | 0 | } |
57 | | |
58 | | static void |
59 | | fu_ccgx_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
60 | 0 | { |
61 | 0 | FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); |
62 | 0 | fu_xmlb_builder_insert_kx(bn, "silicon_id", self->silicon_id); |
63 | 0 | if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { |
64 | 0 | fu_xmlb_builder_insert_kx(bn, "app_type", self->app_type); |
65 | 0 | fu_xmlb_builder_insert_kx(bn, "records", self->records->len); |
66 | 0 | fu_xmlb_builder_insert_kv(bn, "fw_mode", fu_ccgx_fw_mode_to_string(self->fw_mode)); |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | | static void |
71 | | fu_ccgx_firmware_record_free(FuCcgxFirmwareRecord *rcd) |
72 | 809k | { |
73 | 809k | if (rcd->data != NULL) |
74 | 809k | g_bytes_unref(rcd->data); |
75 | 809k | g_free(rcd); |
76 | 809k | } |
77 | | |
78 | | G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCcgxFirmwareRecord, fu_ccgx_firmware_record_free) |
79 | | |
80 | | static gboolean |
81 | | fu_ccgx_firmware_add_record(FuCcgxFirmware *self, |
82 | | GString *token, |
83 | | FuFirmwareParseFlags flags, |
84 | | GError **error) |
85 | 809k | { |
86 | 809k | guint16 buflen; |
87 | 809k | guint8 checksum_calc = 0; |
88 | 809k | g_autoptr(FuCcgxFirmwareRecord) rcd = NULL; |
89 | 809k | g_autoptr(GByteArray) data = g_byte_array_new(); |
90 | | |
91 | | /* this is not in the specification, but exists in reality */ |
92 | 809k | if (token->str[0] == ':') |
93 | 330 | g_string_erase(token, 0, 1); |
94 | | |
95 | | /* parse according to https://community.cypress.com/docs/DOC-10562 */ |
96 | 809k | rcd = g_new0(FuCcgxFirmwareRecord, 1); |
97 | 809k | if (!fu_firmware_strparse_uint8_safe(token->str, token->len, 0, &rcd->array_id, error)) |
98 | 67 | return FALSE; |
99 | 809k | if (!fu_firmware_strparse_uint16_safe(token->str, token->len, 2, &rcd->row_number, error)) |
100 | 19 | return FALSE; |
101 | 809k | if (!fu_firmware_strparse_uint16_safe(token->str, token->len, 6, &buflen, error)) |
102 | 26 | return FALSE; |
103 | 809k | if (token->len != ((gsize)buflen * 2) + 12) { |
104 | 101 | g_set_error(error, |
105 | 101 | FWUPD_ERROR, |
106 | 101 | FWUPD_ERROR_NOT_SUPPORTED, |
107 | 101 | "invalid record, expected %u chars, got %u", |
108 | 101 | (guint)(buflen * 2) + 12, |
109 | 101 | (guint)token->len); |
110 | 101 | return FALSE; |
111 | 101 | } |
112 | | |
113 | | /* parse payload, adding checksum */ |
114 | 924k | for (guint i = 0; i < buflen; i++) { |
115 | 115k | guint8 tmp = 0; |
116 | 115k | if (!fu_firmware_strparse_uint8_safe(token->str, |
117 | 115k | token->len, |
118 | 115k | 10 + (i * 2), |
119 | 115k | &tmp, |
120 | 115k | error)) |
121 | 3 | return FALSE; |
122 | 115k | fu_byte_array_append_uint8(data, tmp); |
123 | 115k | checksum_calc += tmp; |
124 | 115k | } |
125 | 809k | rcd->data = g_bytes_new(data->data, data->len); |
126 | | |
127 | | /* verify 2s complement checksum */ |
128 | 809k | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
129 | 0 | guint8 checksum_file; |
130 | 0 | if (!fu_firmware_strparse_uint8_safe(token->str, |
131 | 0 | token->len, |
132 | 0 | (buflen * 2) + 10, |
133 | 0 | &checksum_file, |
134 | 0 | error)) |
135 | 0 | return FALSE; |
136 | 0 | for (guint i = 0; i < 5; i++) { |
137 | 0 | guint8 tmp = 0; |
138 | 0 | if (!fu_firmware_strparse_uint8_safe(token->str, |
139 | 0 | token->len, |
140 | 0 | i * 2, |
141 | 0 | &tmp, |
142 | 0 | error)) |
143 | 0 | return FALSE; |
144 | 0 | checksum_calc += tmp; |
145 | 0 | } |
146 | 0 | checksum_calc = 1 + ~checksum_calc; |
147 | 0 | if (checksum_file != checksum_calc) { |
148 | 0 | g_set_error(error, |
149 | 0 | FWUPD_ERROR, |
150 | 0 | FWUPD_ERROR_INVALID_FILE, |
151 | 0 | "checksum invalid, got %02x, expected %02x", |
152 | 0 | checksum_calc, |
153 | 0 | checksum_file); |
154 | 0 | return FALSE; |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | | /* success */ |
159 | 809k | g_ptr_array_add(self->records, g_steal_pointer(&rcd)); |
160 | 809k | return TRUE; |
161 | 809k | } |
162 | | |
163 | | static gboolean |
164 | | fu_ccgx_firmware_parse_md_block(FuCcgxFirmware *self, FuFirmwareParseFlags flags, GError **error) |
165 | 940 | { |
166 | 940 | FuCcgxFirmwareRecord *rcd; |
167 | 940 | gsize bufsz = 0; |
168 | 940 | gsize md_offset = 0; |
169 | 940 | guint32 fw_size = 0; |
170 | 940 | guint32 rcd_version_idx = 0; |
171 | 940 | guint32 version = 0; |
172 | 940 | guint8 checksum_calc = 0; |
173 | 940 | g_autoptr(FuStructCcgxMetadataHdr) st_metadata = NULL; |
174 | | |
175 | | /* sanity check */ |
176 | 940 | if (self->records->len == 0) { |
177 | 620 | g_set_error_literal(error, |
178 | 620 | FWUPD_ERROR, |
179 | 620 | FWUPD_ERROR_NOT_SUPPORTED, |
180 | 620 | "no records added to image"); |
181 | 620 | return FALSE; |
182 | 620 | } |
183 | | |
184 | | /* read metadata from correct offset */ |
185 | 320 | rcd = g_ptr_array_index(self->records, self->records->len - 1); |
186 | 320 | bufsz = g_bytes_get_size(rcd->data); |
187 | 320 | if (bufsz == 0) { |
188 | 45 | g_set_error_literal(error, |
189 | 45 | FWUPD_ERROR, |
190 | 45 | FWUPD_ERROR_NOT_SUPPORTED, |
191 | 45 | "invalid buffer size"); |
192 | 45 | return FALSE; |
193 | 45 | } |
194 | 275 | switch (bufsz) { |
195 | 2 | case 0x80: |
196 | 2 | md_offset = 0x40; |
197 | 2 | break; |
198 | 1 | case 0x100: |
199 | 1 | md_offset = 0xC0; |
200 | 1 | break; |
201 | 272 | default: |
202 | 272 | break; |
203 | 275 | } |
204 | | |
205 | | /* parse */ |
206 | 275 | st_metadata = fu_struct_ccgx_metadata_hdr_parse_bytes(rcd->data, md_offset, error); |
207 | 275 | if (st_metadata == NULL) |
208 | 35 | return FALSE; |
209 | 240 | if (fu_struct_ccgx_metadata_hdr_get_metadata_valid(st_metadata) != |
210 | 240 | FU_STRUCT_CCGX_METADATA_HDR_DEFAULT_METADATA_VALID) { |
211 | 44 | g_set_error(error, |
212 | 44 | FWUPD_ERROR, |
213 | 44 | FWUPD_ERROR_NOT_SUPPORTED, |
214 | 44 | "invalid metadata 0x@%x, expected 0x%04x, got 0x%04x", |
215 | 44 | (guint)md_offset, |
216 | 44 | (guint)FU_STRUCT_CCGX_METADATA_HDR_DEFAULT_METADATA_VALID, |
217 | 44 | (guint)fu_struct_ccgx_metadata_hdr_get_metadata_valid(st_metadata)); |
218 | 44 | return FALSE; |
219 | 44 | } |
220 | 563k | for (guint i = 0; i < self->records->len - 1; i++) { |
221 | 563k | gsize rcd_size; |
222 | 563k | rcd = g_ptr_array_index(self->records, i); |
223 | 563k | checksum_calc += fu_sum8_bytes(rcd->data); |
224 | | |
225 | | /* sanity check */ |
226 | 563k | rcd_size = g_bytes_get_size(rcd->data); |
227 | 563k | if (rcd_size > G_MAXUINT32 - fw_size) { |
228 | 0 | g_set_error(error, |
229 | 0 | FWUPD_ERROR, |
230 | 0 | FWUPD_ERROR_INVALID_FILE, |
231 | 0 | "firmware size overflow at record %u", |
232 | 0 | i); |
233 | 0 | return FALSE; |
234 | 0 | } |
235 | 563k | fw_size += rcd_size; |
236 | 563k | } |
237 | 196 | if (fw_size != fu_struct_ccgx_metadata_hdr_get_fw_size(st_metadata)) { |
238 | 98 | g_set_error(error, |
239 | 98 | FWUPD_ERROR, |
240 | 98 | FWUPD_ERROR_INVALID_FILE, |
241 | 98 | "firmware size invalid, got %02x, expected %02x", |
242 | 98 | fw_size, |
243 | 98 | fu_struct_ccgx_metadata_hdr_get_fw_size(st_metadata)); |
244 | 98 | return FALSE; |
245 | 98 | } |
246 | 98 | checksum_calc = 1 + ~checksum_calc; |
247 | 98 | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
248 | 0 | if (fu_struct_ccgx_metadata_hdr_get_fw_checksum(st_metadata) != checksum_calc) { |
249 | 0 | g_set_error(error, |
250 | 0 | FWUPD_ERROR, |
251 | 0 | FWUPD_ERROR_INVALID_FILE, |
252 | 0 | "checksum invalid, got %02x, expected %02x", |
253 | 0 | checksum_calc, |
254 | 0 | fu_struct_ccgx_metadata_hdr_get_fw_checksum(st_metadata)); |
255 | 0 | return FALSE; |
256 | 0 | } |
257 | 0 | } |
258 | | |
259 | | /* get version if enough data */ |
260 | 98 | rcd_version_idx = CCGX_APP_VERSION_OFFSET / bufsz; |
261 | 98 | if (rcd_version_idx < self->records->len) { |
262 | 68 | const guint8 *buf; |
263 | 68 | rcd = g_ptr_array_index(self->records, rcd_version_idx); |
264 | 68 | buf = g_bytes_get_data(rcd->data, &bufsz); |
265 | 68 | if (bufsz == 0) { |
266 | 51 | g_set_error_literal(error, |
267 | 51 | FWUPD_ERROR, |
268 | 51 | FWUPD_ERROR_INVALID_FILE, |
269 | 51 | "metadata record had zero size"); |
270 | 51 | return FALSE; |
271 | 51 | } |
272 | 17 | if (!fu_memread_uint32_safe(buf, |
273 | 17 | bufsz, |
274 | 17 | CCGX_APP_VERSION_OFFSET % bufsz, |
275 | 17 | &version, |
276 | 17 | G_LITTLE_ENDIAN, |
277 | 17 | error)) |
278 | 1 | return FALSE; |
279 | 16 | self->app_type = version & 0xffff; |
280 | 16 | fu_firmware_set_version_raw(FU_FIRMWARE(self), version); |
281 | 16 | } |
282 | | |
283 | | /* work out the FuCcgxFwMode */ |
284 | 46 | if (self->records->len > 0) { |
285 | 46 | rcd = g_ptr_array_index(self->records, self->records->len - 1); |
286 | 46 | if ((rcd->row_number & 0xFF) == 0xFF) /* last row */ |
287 | 1 | self->fw_mode = FU_CCGX_FW_MODE_FW1; |
288 | 46 | if ((rcd->row_number & 0xFF) == 0xFE) /* penultimate row */ |
289 | 1 | self->fw_mode = FU_CCGX_FW_MODE_FW2; |
290 | 46 | } |
291 | 46 | return TRUE; |
292 | 98 | } |
293 | | |
294 | | typedef struct { |
295 | | FuCcgxFirmware *self; |
296 | | FuFirmwareParseFlags flags; |
297 | | } FuCcgxFirmwareTokenHelper; |
298 | | |
299 | | static gboolean |
300 | | fu_ccgx_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) |
301 | 1.72M | { |
302 | 1.72M | FuCcgxFirmwareTokenHelper *helper = (FuCcgxFirmwareTokenHelper *)user_data; |
303 | 1.72M | FuCcgxFirmware *self = FU_CCGX_FIRMWARE(helper->self); |
304 | | |
305 | | /* sanity check */ |
306 | 1.72M | if (token_idx > FU_CCGX_FIRMWARE_TOKENS_MAX) { |
307 | 1 | g_set_error_literal(error, |
308 | 1 | FWUPD_ERROR, |
309 | 1 | FWUPD_ERROR_INVALID_DATA, |
310 | 1 | "file has too many lines"); |
311 | 1 | return FALSE; |
312 | 1 | } |
313 | | |
314 | | /* remove WIN32 line endings */ |
315 | 1.72M | g_strdelimit(token->str, "\r\x1a", '\0'); |
316 | 1.72M | token->len = strlen(token->str); |
317 | | |
318 | | /* header */ |
319 | 1.72M | if (token_idx == 0) { |
320 | 883 | guint32 device_id = 0; |
321 | 883 | if (token->len != 12) { |
322 | 165 | g_autofree gchar *strsafe = fu_strsafe(token->str, 12); |
323 | 165 | if (strsafe != NULL) { |
324 | 70 | g_set_error(error, |
325 | 70 | FWUPD_ERROR, |
326 | 70 | FWUPD_ERROR_NOT_SUPPORTED, |
327 | 70 | "invalid header, expected == 12 chars -- got %s", |
328 | 70 | strsafe); |
329 | 70 | return FALSE; |
330 | 70 | } |
331 | 95 | g_set_error_literal(error, |
332 | 95 | FWUPD_ERROR, |
333 | 95 | FWUPD_ERROR_NOT_SUPPORTED, |
334 | 95 | "invalid header, expected == 12 chars"); |
335 | 95 | return FALSE; |
336 | 165 | } |
337 | 718 | if (!fu_firmware_strparse_uint32_safe(token->str, token->len, 0, &device_id, error)) |
338 | 64 | return FALSE; |
339 | 654 | self->silicon_id = device_id >> 16; |
340 | 654 | return TRUE; |
341 | 718 | } |
342 | | |
343 | | /* ignore blank lines */ |
344 | 1.72M | if (token->len == 0) |
345 | 917k | return TRUE; |
346 | | |
347 | | /* parse record */ |
348 | 809k | if (!fu_ccgx_firmware_add_record(self, token, helper->flags, error)) { |
349 | 216 | g_prefix_error(error, "error on line %u: ", token_idx + 1); |
350 | 216 | return FALSE; |
351 | 216 | } |
352 | | |
353 | | /* success */ |
354 | 809k | return TRUE; |
355 | 809k | } |
356 | | |
357 | | static gboolean |
358 | | fu_ccgx_firmware_parse(FuFirmware *firmware, |
359 | | GInputStream *stream, |
360 | | FuFirmwareParseFlags flags, |
361 | | GError **error) |
362 | 1.38k | { |
363 | 1.38k | FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); |
364 | 1.38k | FuCcgxFirmwareTokenHelper helper = {.self = self, .flags = flags}; |
365 | | |
366 | | /* tokenize */ |
367 | 1.38k | if (!fu_strsplit_stream(stream, 0x0, "\n", fu_ccgx_firmware_tokenize_cb, &helper, error)) |
368 | 446 | return FALSE; |
369 | | |
370 | | /* address is first data entry */ |
371 | 940 | if (self->records->len > 0) { |
372 | 320 | FuCcgxFirmwareRecord *rcd = g_ptr_array_index(self->records, 0); |
373 | 320 | fu_firmware_set_addr(firmware, rcd->row_number); |
374 | 320 | } |
375 | | |
376 | | /* parse metadata block */ |
377 | 940 | if (!fu_ccgx_firmware_parse_md_block(self, flags, error)) { |
378 | 894 | g_prefix_error_literal(error, "failed to parse metadata: "); |
379 | 894 | return FALSE; |
380 | 894 | } |
381 | | |
382 | | /* success */ |
383 | 46 | return TRUE; |
384 | 940 | } |
385 | | |
386 | | static void |
387 | | fu_ccgx_firmware_write_record(GString *str, |
388 | | guint8 array_id, |
389 | | guint8 row_number, |
390 | | const guint8 *buf, |
391 | | guint16 bufsz) |
392 | 0 | { |
393 | 0 | guint8 checksum_calc = 0xff; |
394 | 0 | g_autoptr(GString) datastr = g_string_new(NULL); |
395 | | |
396 | | /* offset for bootloader perhaps? */ |
397 | 0 | row_number += 0xE; |
398 | |
|
399 | 0 | checksum_calc += array_id; |
400 | 0 | checksum_calc += row_number; |
401 | 0 | checksum_calc += bufsz & 0xff; |
402 | 0 | checksum_calc += (bufsz >> 8) & 0xff; |
403 | 0 | for (guint j = 0; j < bufsz; j++) { |
404 | 0 | g_string_append_printf(datastr, "%02X", buf[j]); |
405 | 0 | checksum_calc += buf[j]; |
406 | 0 | } |
407 | 0 | g_string_append_printf(str, |
408 | 0 | ":%02X%04X%04X%s%02X\n", |
409 | 0 | array_id, |
410 | 0 | row_number, |
411 | 0 | bufsz, |
412 | 0 | datastr->str, |
413 | 0 | (guint)((guint8)~checksum_calc)); |
414 | 0 | } |
415 | | |
416 | | static GByteArray * |
417 | | fu_ccgx_firmware_write(FuFirmware *firmware, GError **error) |
418 | 46 | { |
419 | 46 | FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); |
420 | 46 | gsize fwbufsz = 0; |
421 | 46 | guint8 checksum_img = 0xff; |
422 | 46 | const guint8 *fwbuf; |
423 | 46 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
424 | 46 | g_autoptr(GByteArray) mdbuf = g_byte_array_new(); |
425 | 46 | g_autoptr(FuStructCcgxMetadataHdr) st_metadata = fu_struct_ccgx_metadata_hdr_new(); |
426 | 46 | g_autoptr(GBytes) fw = NULL; |
427 | 46 | g_autoptr(FuChunkArray) chunks = NULL; |
428 | 46 | g_autoptr(GString) str = g_string_new(NULL); |
429 | | |
430 | | /* header record */ |
431 | 46 | g_string_append_printf(str, |
432 | 46 | "%04X%04X%02X%02X\n", |
433 | 46 | self->silicon_id, |
434 | 46 | (guint)0x11AF, /* SiliconID */ |
435 | 46 | (guint)0x0, /* SiliconRev */ |
436 | 46 | (guint)0x0); /* Checksum, or 0x0 */ |
437 | | |
438 | | /* add image in chunks */ |
439 | 46 | fw = fu_firmware_get_bytes_with_patches(firmware, error); |
440 | 46 | if (fw == NULL) |
441 | 46 | return NULL; |
442 | 0 | chunks = fu_chunk_array_new_from_bytes(fw, |
443 | 0 | FU_CHUNK_ADDR_OFFSET_NONE, |
444 | 0 | FU_CHUNK_PAGESZ_NONE, |
445 | 0 | 0x100); |
446 | 0 | for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { |
447 | 0 | g_autoptr(FuChunk) chk = NULL; |
448 | | |
449 | | /* prepare chunk */ |
450 | 0 | chk = fu_chunk_array_index(chunks, i, error); |
451 | 0 | if (chk == NULL) |
452 | 0 | return NULL; |
453 | 0 | fu_ccgx_firmware_write_record(str, |
454 | 0 | 0x0, |
455 | 0 | i, |
456 | 0 | fu_chunk_get_data(chk), |
457 | 0 | fu_chunk_get_data_sz(chk)); |
458 | 0 | } |
459 | | |
460 | | /* add metadata */ |
461 | 0 | fwbuf = g_bytes_get_data(fw, &fwbufsz); |
462 | 0 | for (guint j = 0; j < fwbufsz; j++) |
463 | 0 | checksum_img += fwbuf[j]; |
464 | | |
465 | | /* copy into place */ |
466 | 0 | fu_byte_array_set_size(mdbuf, 0x80, 0x00); |
467 | 0 | fu_struct_ccgx_metadata_hdr_set_fw_checksum(st_metadata, ~checksum_img); |
468 | 0 | fu_struct_ccgx_metadata_hdr_set_fw_entry(st_metadata, 0x0); /* unknown */ |
469 | 0 | fu_struct_ccgx_metadata_hdr_set_last_boot_row(st_metadata, 0x13); |
470 | 0 | fu_struct_ccgx_metadata_hdr_set_fw_size(st_metadata, fwbufsz); |
471 | 0 | fu_struct_ccgx_metadata_hdr_set_boot_seq(st_metadata, 0x0); /* unknown */ |
472 | 0 | if (!fu_memcpy_safe(mdbuf->data, |
473 | 0 | mdbuf->len, |
474 | 0 | 0x40, /* dst */ |
475 | 0 | st_metadata->buf->data, |
476 | 0 | st_metadata->buf->len, |
477 | 0 | 0x0, /* src */ |
478 | 0 | st_metadata->buf->len, |
479 | 0 | error)) |
480 | 0 | return NULL; |
481 | 0 | fu_ccgx_firmware_write_record(str, |
482 | 0 | 0x0, |
483 | 0 | 0xFE, /* FW2: penultimate row */ |
484 | 0 | mdbuf->data, |
485 | 0 | mdbuf->len); |
486 | | |
487 | | /* success */ |
488 | 0 | g_byte_array_append(buf, (const guint8 *)str->str, str->len); |
489 | 0 | return g_steal_pointer(&buf); |
490 | 0 | } |
491 | | |
492 | | static gboolean |
493 | | fu_ccgx_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
494 | 0 | { |
495 | 0 | FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); |
496 | 0 | guint64 tmp; |
497 | | |
498 | | /* optional properties */ |
499 | 0 | tmp = xb_node_query_text_as_uint(n, "silicon_id", NULL); |
500 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
501 | 0 | self->silicon_id = tmp; |
502 | | |
503 | | /* success */ |
504 | 0 | return TRUE; |
505 | 0 | } |
506 | | |
507 | | static gchar * |
508 | | fu_ccgx_firmware_convert_version(FuFirmware *firmware, guint64 version_raw) |
509 | 16 | { |
510 | 16 | return fu_ccgx_version_to_string(version_raw); |
511 | 16 | } |
512 | | |
513 | | static void |
514 | | fu_ccgx_firmware_init(FuCcgxFirmware *self) |
515 | 1.38k | { |
516 | 1.38k | self->records = g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_firmware_record_free); |
517 | 1.38k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); |
518 | 1.38k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); |
519 | 1.38k | fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_TRIPLET); |
520 | 1.38k | fu_firmware_set_size_max(FU_FIRMWARE(self), 128 * FU_MB); |
521 | 1.38k | } |
522 | | |
523 | | static void |
524 | | fu_ccgx_firmware_finalize(GObject *object) |
525 | 1.38k | { |
526 | 1.38k | FuCcgxFirmware *self = FU_CCGX_FIRMWARE(object); |
527 | 1.38k | g_ptr_array_unref(self->records); |
528 | 1.38k | G_OBJECT_CLASS(fu_ccgx_firmware_parent_class)->finalize(object); |
529 | 1.38k | } |
530 | | |
531 | | static void |
532 | | fu_ccgx_firmware_class_init(FuCcgxFirmwareClass *klass) |
533 | 1 | { |
534 | 1 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
535 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
536 | 1 | firmware_class->convert_version = fu_ccgx_firmware_convert_version; |
537 | 1 | object_class->finalize = fu_ccgx_firmware_finalize; |
538 | 1 | firmware_class->parse = fu_ccgx_firmware_parse; |
539 | 1 | firmware_class->write = fu_ccgx_firmware_write; |
540 | 1 | firmware_class->build = fu_ccgx_firmware_build; |
541 | 1 | firmware_class->export = fu_ccgx_firmware_export; |
542 | 1 | } |