/src/fwupd/plugins/wacom-usb/fu-wac-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2018 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | | #include "config.h" |
8 | | |
9 | | #include <string.h> |
10 | | |
11 | | #include "fu-wac-firmware.h" |
12 | | #include "fu-wac-struct.h" |
13 | | |
14 | | struct _FuWacFirmware { |
15 | | FuFirmware parent_instance; |
16 | | }; |
17 | | |
18 | 2.01k | G_DEFINE_TYPE(FuWacFirmware, fu_wac_firmware, FU_TYPE_FIRMWARE) |
19 | 2.01k | |
20 | 867k | #define FU_WAC_FIRMWARE_TOKENS_MAX 100000 /* lines */ |
21 | 1.57k | #define FU_WAC_FIRMWARE_SECTIONS_MAX 10 |
22 | | |
23 | | typedef struct { |
24 | | guint32 addr; |
25 | | guint32 sz; |
26 | | guint32 prog_start_addr; |
27 | | } FuFirmwareWacHeaderRecord; |
28 | | |
29 | | typedef struct { |
30 | | FuFirmware *firmware; |
31 | | FuFirmwareParseFlags flags; |
32 | | GPtrArray *header_infos; |
33 | | GString *image_buffer; |
34 | | guint8 images_cnt; |
35 | | } FuWacFirmwareTokenHelper; |
36 | | |
37 | | static gboolean |
38 | | fu_wac_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) |
39 | 867k | { |
40 | 867k | FuWacFirmwareTokenHelper *helper = (FuWacFirmwareTokenHelper *)user_data; |
41 | 867k | g_autofree gchar *cmd = NULL; |
42 | | |
43 | | /* sanity check */ |
44 | 867k | if (token_idx > FU_WAC_FIRMWARE_TOKENS_MAX) { |
45 | 1 | g_set_error_literal(error, |
46 | 1 | FWUPD_ERROR, |
47 | 1 | FWUPD_ERROR_INVALID_DATA, |
48 | 1 | "file has too many lines"); |
49 | 1 | return FALSE; |
50 | 1 | } |
51 | | |
52 | | /* remove WIN32 line endings */ |
53 | 867k | g_strdelimit(token->str, "\r\x1a", '\0'); |
54 | 867k | token->len = strlen(token->str); |
55 | | |
56 | | /* ignore blank lines */ |
57 | 867k | cmd = g_strndup(token->str, 2); |
58 | 867k | if (g_strcmp0(cmd, "") == 0) |
59 | 355k | return TRUE; |
60 | | |
61 | | /* custom metadata */ |
62 | 512k | if (g_strcmp0(cmd, "WA") == 0) { |
63 | | /* header info record */ |
64 | 4.29k | if (token->len > 3 && memcmp(token->str + 2, "COM", 3) == 0) { |
65 | 1.71k | guint8 header_image_cnt = 0; |
66 | 1.71k | if (token->len != 40) { |
67 | 134 | g_set_error(error, |
68 | 134 | FWUPD_ERROR, |
69 | 134 | FWUPD_ERROR_INTERNAL, |
70 | 134 | "invalid header, got %" G_GSIZE_FORMAT " bytes", |
71 | 134 | token->len); |
72 | 134 | return FALSE; |
73 | 134 | } |
74 | | |
75 | | /* sanity check */ |
76 | 1.57k | if (helper->header_infos->len > FU_WAC_FIRMWARE_SECTIONS_MAX) { |
77 | 2 | g_set_error(error, |
78 | 2 | FWUPD_ERROR, |
79 | 2 | FWUPD_ERROR_INTERNAL, |
80 | 2 | "too many metadata sections: %u", |
81 | 2 | helper->header_infos->len); |
82 | 2 | return FALSE; |
83 | 2 | } |
84 | 1.57k | if (!fu_firmware_strparse_uint4_safe(token->str, |
85 | 1.57k | token->len, |
86 | 1.57k | 5, |
87 | 1.57k | &header_image_cnt, |
88 | 1.57k | error)) |
89 | 1 | return FALSE; |
90 | 3.63k | for (guint j = 0; j < header_image_cnt; j++) { |
91 | 2.11k | g_autofree FuFirmwareWacHeaderRecord *hdr = NULL; |
92 | 2.11k | hdr = g_new0(FuFirmwareWacHeaderRecord, 1); |
93 | 2.11k | if (!fu_firmware_strparse_uint32_safe(token->str, |
94 | 2.11k | token->len, |
95 | 2.11k | (j * 16) + 6, |
96 | 2.11k | &hdr->addr, |
97 | 2.11k | error)) |
98 | 43 | return FALSE; |
99 | 2.07k | if (!fu_firmware_strparse_uint32_safe(token->str, |
100 | 2.07k | token->len, |
101 | 2.07k | (j * 16) + 14, |
102 | 2.07k | &hdr->sz, |
103 | 2.07k | error)) |
104 | 20 | return FALSE; |
105 | 2.05k | g_debug("header_fw%u_addr: 0x%x", j, hdr->addr); |
106 | 2.05k | g_debug("header_fw%u_sz: 0x%x", j, hdr->sz); |
107 | 2.05k | g_ptr_array_add(helper->header_infos, g_steal_pointer(&hdr)); |
108 | 2.05k | } |
109 | 1.51k | return TRUE; |
110 | 1.57k | } |
111 | | |
112 | | /* firmware headline record */ |
113 | 2.57k | if (token->len == 13) { |
114 | 566 | FuFirmwareWacHeaderRecord *hdr; |
115 | 566 | guint8 idx = 0; |
116 | 566 | if (!fu_firmware_strparse_uint4_safe(token->str, |
117 | 566 | token->len, |
118 | 566 | 2, |
119 | 566 | &idx, |
120 | 566 | error)) |
121 | 2 | return FALSE; |
122 | 564 | if (idx == 0) { |
123 | 1 | g_set_error(error, |
124 | 1 | FWUPD_ERROR, |
125 | 1 | FWUPD_ERROR_INTERNAL, |
126 | 1 | "headline %u invalid", |
127 | 1 | idx); |
128 | 1 | return FALSE; |
129 | 1 | } |
130 | 563 | if (idx > helper->header_infos->len) { |
131 | 6 | g_set_error(error, |
132 | 6 | FWUPD_ERROR, |
133 | 6 | FWUPD_ERROR_INTERNAL, |
134 | 6 | "headline %u exceeds header count %u", |
135 | 6 | idx, |
136 | 6 | helper->header_infos->len); |
137 | 6 | return FALSE; |
138 | 6 | } |
139 | 557 | if (idx - 1 != helper->images_cnt) { |
140 | 4 | g_set_error(error, |
141 | 4 | FWUPD_ERROR, |
142 | 4 | FWUPD_ERROR_INTERNAL, |
143 | 4 | "headline %u is not in sorted order", |
144 | 4 | idx); |
145 | 4 | return FALSE; |
146 | 4 | } |
147 | 553 | hdr = g_ptr_array_index(helper->header_infos, idx - 1); |
148 | 553 | if (!fu_firmware_strparse_uint32_safe(token->str, |
149 | 553 | token->len, |
150 | 553 | 3, |
151 | 553 | &hdr->prog_start_addr, |
152 | 553 | error)) |
153 | 1 | return FALSE; |
154 | 552 | if (hdr->prog_start_addr != hdr->addr) { |
155 | 46 | g_set_error(error, |
156 | 46 | FWUPD_ERROR, |
157 | 46 | FWUPD_ERROR_INTERNAL, |
158 | 46 | "programming address 0x%x != " |
159 | 46 | "base address 0x%0x for idx %u", |
160 | 46 | hdr->prog_start_addr, |
161 | 46 | hdr->addr, |
162 | 46 | idx); |
163 | 46 | return FALSE; |
164 | 46 | } |
165 | 506 | g_debug("programing-start-address: 0x%x", hdr->prog_start_addr); |
166 | 506 | return TRUE; |
167 | 552 | } |
168 | | |
169 | 2.01k | g_debug("unknown Wacom-specific metadata"); |
170 | 2.01k | return TRUE; |
171 | 2.57k | } |
172 | | |
173 | | /* start */ |
174 | 507k | if (g_strcmp0(cmd, "S0") == 0) { |
175 | 1.90k | if (helper->image_buffer->len > 0) { |
176 | 19 | g_set_error_literal(error, |
177 | 19 | FWUPD_ERROR, |
178 | 19 | FWUPD_ERROR_INTERNAL, |
179 | 19 | "duplicate S0 without S7"); |
180 | 19 | return FALSE; |
181 | 19 | } |
182 | 1.88k | g_string_append_printf(helper->image_buffer, "%s\n", token->str); |
183 | 1.88k | return TRUE; |
184 | 1.90k | } |
185 | | |
186 | | /* these are things we want to include in the image */ |
187 | 505k | if (g_strcmp0(cmd, "S1") == 0 || g_strcmp0(cmd, "S2") == 0 || g_strcmp0(cmd, "S3") == 0 || |
188 | 7.18k | g_strcmp0(cmd, "S5") == 0 || g_strcmp0(cmd, "S7") == 0 || g_strcmp0(cmd, "S8") == 0 || |
189 | 505k | g_strcmp0(cmd, "S9") == 0) { |
190 | 505k | if (helper->image_buffer->len == 0) { |
191 | 22 | g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s without S0", cmd); |
192 | 22 | return FALSE; |
193 | 22 | } |
194 | 505k | g_string_append_printf(helper->image_buffer, "%s\n", token->str); |
195 | 505k | } else { |
196 | 68 | g_set_error(error, |
197 | 68 | FWUPD_ERROR, |
198 | 68 | FWUPD_ERROR_INTERNAL, |
199 | 68 | "invalid SREC command on line %u: %s", |
200 | 68 | token_idx + 1, |
201 | 68 | cmd); |
202 | 68 | return FALSE; |
203 | 68 | } |
204 | | |
205 | | /* end */ |
206 | 505k | if (g_strcmp0(cmd, "S7") == 0) { |
207 | 1.60k | g_autoptr(GBytes) blob = NULL; |
208 | 1.60k | g_autoptr(GBytes) fw_srec = NULL; |
209 | 1.60k | g_autoptr(FuFirmware) firmware_srec = fu_srec_firmware_new(); |
210 | 1.60k | g_autoptr(FuFirmware) img = fu_firmware_new(); |
211 | 1.60k | FuFirmwareWacHeaderRecord *hdr; |
212 | | |
213 | | /* get the correct relocated start address */ |
214 | 1.60k | if (helper->images_cnt >= helper->header_infos->len) { |
215 | 24 | g_set_error(error, |
216 | 24 | FWUPD_ERROR, |
217 | 24 | FWUPD_ERROR_INTERNAL, |
218 | 24 | "%s without header", |
219 | 24 | cmd); |
220 | 24 | return FALSE; |
221 | 24 | } |
222 | 1.58k | hdr = g_ptr_array_index(helper->header_infos, helper->images_cnt); |
223 | | |
224 | 1.58k | if (helper->image_buffer->len == 0) { |
225 | 0 | g_set_error(error, |
226 | 0 | FWUPD_ERROR, |
227 | 0 | FWUPD_ERROR_INTERNAL, |
228 | 0 | "%s with missing image buffer", |
229 | 0 | cmd); |
230 | 0 | return FALSE; |
231 | 0 | } |
232 | | |
233 | | /* parse SREC file and add as image */ |
234 | 1.58k | blob = g_bytes_new(helper->image_buffer->str, helper->image_buffer->len); |
235 | 1.58k | fu_srec_firmware_set_addr_min(FU_SREC_FIRMWARE(firmware_srec), hdr->addr); |
236 | 1.58k | if (!fu_firmware_parse_bytes(firmware_srec, |
237 | 1.58k | blob, |
238 | 1.58k | 0x0, |
239 | 1.58k | helper->flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH, |
240 | 1.58k | error)) |
241 | 362 | return FALSE; |
242 | 1.22k | fw_srec = fu_firmware_get_bytes(firmware_srec, error); |
243 | 1.22k | if (fw_srec == NULL) |
244 | 0 | return FALSE; |
245 | 1.22k | fu_firmware_set_bytes(img, fw_srec); |
246 | 1.22k | fu_firmware_set_addr(img, fu_firmware_get_addr(firmware_srec)); |
247 | 1.22k | fu_firmware_set_idx(img, helper->images_cnt); |
248 | 1.22k | if (!fu_firmware_add_image(helper->firmware, img, error)) |
249 | 0 | return FALSE; |
250 | 1.22k | helper->images_cnt++; |
251 | | |
252 | | /* clear the image buffer */ |
253 | 1.22k | g_string_set_size(helper->image_buffer, 0); |
254 | 1.22k | } |
255 | | |
256 | | /* success */ |
257 | 505k | return TRUE; |
258 | 505k | } |
259 | | |
260 | | static gboolean |
261 | | fu_wac_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error) |
262 | 224k | { |
263 | 224k | return fu_struct_wac_firmware_hdr_validate_stream(stream, offset, error); |
264 | 224k | } |
265 | | |
266 | | static gboolean |
267 | | fu_wac_firmware_parse(FuFirmware *firmware, |
268 | | GInputStream *stream, |
269 | | FuFirmwareParseFlags flags, |
270 | | GError **error) |
271 | 1.85k | { |
272 | 1.85k | g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func(g_free); |
273 | 1.85k | g_autoptr(GString) image_buffer = g_string_new(NULL); |
274 | 1.85k | FuWacFirmwareTokenHelper helper = {.firmware = firmware, |
275 | 1.85k | .flags = flags, |
276 | 1.85k | .header_infos = header_infos, |
277 | 1.85k | .image_buffer = image_buffer, |
278 | 1.85k | .images_cnt = 0}; |
279 | | |
280 | | /* tokenize */ |
281 | 1.85k | if (!fu_strsplit_stream(stream, 0x0, "\n", fu_wac_firmware_tokenize_cb, &helper, error)) |
282 | 756 | return FALSE; |
283 | | |
284 | | /* verify data is complete */ |
285 | 1.10k | if (helper.image_buffer->len > 0) { |
286 | 241 | g_set_error_literal(error, |
287 | 241 | FWUPD_ERROR, |
288 | 241 | FWUPD_ERROR_INTERNAL, |
289 | 241 | "truncated data: no S7"); |
290 | 241 | return FALSE; |
291 | 241 | } |
292 | | |
293 | | /* ensure this matched the header */ |
294 | 861 | if (helper.header_infos->len != helper.images_cnt) { |
295 | 63 | g_set_error(error, |
296 | 63 | FWUPD_ERROR, |
297 | 63 | FWUPD_ERROR_INTERNAL, |
298 | 63 | "not enough images %u for header count %u", |
299 | 63 | helper.images_cnt, |
300 | 63 | header_infos->len); |
301 | 63 | return FALSE; |
302 | 63 | } |
303 | | |
304 | | /* success */ |
305 | 798 | return TRUE; |
306 | 861 | } |
307 | | |
308 | | static guint8 |
309 | | fu_wac_firmware_calc_checksum(GByteArray *buf) |
310 | 906 | { |
311 | 906 | return fu_sum8(buf->data, buf->len) ^ 0xFF; |
312 | 906 | } |
313 | | |
314 | | static GByteArray * |
315 | | fu_wac_firmware_write(FuFirmware *firmware, GError **error) |
316 | 798 | { |
317 | 798 | g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); |
318 | 798 | g_autoptr(GString) str = g_string_new(NULL); |
319 | 798 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
320 | 798 | g_autoptr(GByteArray) buf_hdr = g_byte_array_new(); |
321 | | |
322 | | /* fw header */ |
323 | 798 | if (images->len == 0) { |
324 | 520 | g_set_error_literal(error, |
325 | 520 | FWUPD_ERROR, |
326 | 520 | FWUPD_ERROR_NOT_SUPPORTED, |
327 | 520 | "no firmware images found"); |
328 | 520 | return NULL; |
329 | 520 | } |
330 | 906 | for (guint i = 0; i < images->len; i++) { |
331 | 628 | FuFirmware *img = g_ptr_array_index(images, i); |
332 | 628 | fu_byte_array_append_uint32(buf_hdr, fu_firmware_get_addr(img), G_BIG_ENDIAN); |
333 | 628 | fu_byte_array_append_uint32(buf_hdr, fu_firmware_get_size(img), G_BIG_ENDIAN); |
334 | 628 | } |
335 | 278 | g_string_append_printf(str, "WACOM%u", images->len); |
336 | 5.30k | for (guint i = 0; i < buf_hdr->len; i++) |
337 | 5.02k | g_string_append_printf(str, "%02X", buf_hdr->data[i]); |
338 | 278 | g_string_append_printf(str, "%02X\n", fu_wac_firmware_calc_checksum(buf_hdr)); |
339 | | |
340 | | /* payload */ |
341 | 906 | for (guint i = 0; i < images->len; i++) { |
342 | 628 | FuFirmware *img = g_ptr_array_index(images, i); |
343 | 628 | g_autoptr(GBytes) img_blob = NULL; |
344 | 628 | g_autoptr(GByteArray) buf_img = g_byte_array_new(); |
345 | | |
346 | | /* img header */ |
347 | 628 | g_string_append_printf(str, "WA%u", (guint)fu_firmware_get_idx(img) + 1); |
348 | 628 | fu_byte_array_append_uint32(buf_img, fu_firmware_get_addr(img), G_BIG_ENDIAN); |
349 | 3.14k | for (guint j = 0; j < buf_img->len; j++) |
350 | 2.51k | g_string_append_printf(str, "%02X", buf_img->data[j]); |
351 | 628 | g_string_append_printf(str, "%02X\n", fu_wac_firmware_calc_checksum(buf_img)); |
352 | | |
353 | | /* srec */ |
354 | 628 | img_blob = fu_firmware_write(img, error); |
355 | 628 | if (img_blob == NULL) |
356 | 0 | return NULL; |
357 | 628 | g_string_append_len(str, |
358 | 628 | (const gchar *)g_bytes_get_data(img_blob, NULL), |
359 | 628 | g_bytes_get_size(img_blob)); |
360 | 628 | } |
361 | | |
362 | | /* success */ |
363 | 278 | g_byte_array_append(buf, (const guint8 *)str->str, str->len); |
364 | 278 | return g_steal_pointer(&buf); |
365 | 278 | } |
366 | | |
367 | | static void |
368 | | fu_wac_firmware_init(FuWacFirmware *self) |
369 | 2.01k | { |
370 | 2.01k | fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); |
371 | 2.01k | g_type_ensure(FU_TYPE_SREC_FIRMWARE); |
372 | 2.01k | } |
373 | | |
374 | | static void |
375 | | fu_wac_firmware_class_init(FuWacFirmwareClass *klass) |
376 | 1 | { |
377 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
378 | 1 | firmware_class->validate = fu_wac_firmware_validate; |
379 | 1 | firmware_class->parse = fu_wac_firmware_parse; |
380 | 1 | firmware_class->write = fu_wac_firmware_write; |
381 | 1 | } |
382 | | |
383 | | FuFirmware * |
384 | | fu_wac_firmware_new(void) |
385 | 0 | { |
386 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_WAC_FIRMWARE, NULL)); |
387 | 0 | } |