/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.09k | G_DEFINE_TYPE(FuWacFirmware, fu_wac_firmware, FU_TYPE_FIRMWARE) |
19 | 2.09k | |
20 | 1.21M | #define FU_WAC_FIRMWARE_TOKENS_MAX 100000 /* lines */ |
21 | 1.45k | #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 | 1.21M | { |
40 | 1.21M | FuWacFirmwareTokenHelper *helper = (FuWacFirmwareTokenHelper *)user_data; |
41 | 1.21M | g_autofree gchar *cmd = NULL; |
42 | | |
43 | | /* sanity check */ |
44 | 1.21M | 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 | 1.21M | g_strdelimit(token->str, "\r\x1a", '\0'); |
54 | 1.21M | token->len = strlen(token->str); |
55 | | |
56 | | /* ignore blank lines */ |
57 | 1.21M | cmd = g_strndup(token->str, 2); |
58 | 1.21M | if (g_strcmp0(cmd, "") == 0) |
59 | 1.08M | return TRUE; |
60 | | |
61 | | /* custom metadata */ |
62 | 127k | if (g_strcmp0(cmd, "WA") == 0) { |
63 | | /* header info record */ |
64 | 3.43k | if (token->len > 3 && memcmp(token->str + 2, "COM", 3) == 0) { |
65 | 1.58k | guint8 header_image_cnt = 0; |
66 | 1.58k | if (token->len != 40) { |
67 | 139 | g_set_error(error, |
68 | 139 | FWUPD_ERROR, |
69 | 139 | FWUPD_ERROR_INTERNAL, |
70 | 139 | "invalid header, got %" G_GSIZE_FORMAT " bytes", |
71 | 139 | token->len); |
72 | 139 | return FALSE; |
73 | 139 | } |
74 | | |
75 | | /* sanity check */ |
76 | 1.45k | 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.44k | if (!fu_firmware_strparse_uint4_safe(token->str, |
85 | 1.44k | token->len, |
86 | 1.44k | 5, |
87 | 1.44k | &header_image_cnt, |
88 | 1.44k | error)) |
89 | 1 | return FALSE; |
90 | 3.27k | for (guint j = 0; j < header_image_cnt; j++) { |
91 | 1.88k | g_autofree FuFirmwareWacHeaderRecord *hdr = NULL; |
92 | 1.88k | hdr = g_new0(FuFirmwareWacHeaderRecord, 1); |
93 | 1.88k | if (!fu_firmware_strparse_uint32_safe(token->str, |
94 | 1.88k | token->len, |
95 | 1.88k | (j * 16) + 6, |
96 | 1.88k | &hdr->addr, |
97 | 1.88k | error)) |
98 | 47 | return FALSE; |
99 | 1.84k | if (!fu_firmware_strparse_uint32_safe(token->str, |
100 | 1.84k | token->len, |
101 | 1.84k | (j * 16) + 14, |
102 | 1.84k | &hdr->sz, |
103 | 1.84k | error)) |
104 | 15 | return FALSE; |
105 | 1.82k | g_debug("header_fw%u_addr: 0x%x", j, hdr->addr); |
106 | 1.82k | g_debug("header_fw%u_sz: 0x%x", j, hdr->sz); |
107 | 1.82k | g_ptr_array_add(helper->header_infos, g_steal_pointer(&hdr)); |
108 | 1.82k | } |
109 | 1.38k | return TRUE; |
110 | 1.44k | } |
111 | | |
112 | | /* firmware headline record */ |
113 | 1.84k | if (token->len == 13) { |
114 | 571 | FuFirmwareWacHeaderRecord *hdr; |
115 | 571 | guint8 idx = 0; |
116 | 571 | if (!fu_firmware_strparse_uint4_safe(token->str, |
117 | 571 | token->len, |
118 | 571 | 2, |
119 | 571 | &idx, |
120 | 571 | error)) |
121 | 6 | return FALSE; |
122 | 565 | 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 | 564 | 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 | 558 | if (idx - 1 != helper->images_cnt) { |
140 | 3 | g_set_error(error, |
141 | 3 | FWUPD_ERROR, |
142 | 3 | FWUPD_ERROR_INTERNAL, |
143 | 3 | "headline %u is not in sorted order", |
144 | 3 | idx); |
145 | 3 | return FALSE; |
146 | 3 | } |
147 | 555 | hdr = g_ptr_array_index(helper->header_infos, idx - 1); |
148 | 555 | if (!fu_firmware_strparse_uint32_safe(token->str, |
149 | 555 | token->len, |
150 | 555 | 3, |
151 | 555 | &hdr->prog_start_addr, |
152 | 555 | error)) |
153 | 1 | return FALSE; |
154 | 554 | if (hdr->prog_start_addr != hdr->addr) { |
155 | 42 | g_set_error(error, |
156 | 42 | FWUPD_ERROR, |
157 | 42 | FWUPD_ERROR_INTERNAL, |
158 | 42 | "programming address 0x%x != " |
159 | 42 | "base address 0x%0x for idx %u", |
160 | 42 | hdr->prog_start_addr, |
161 | 42 | hdr->addr, |
162 | 42 | idx); |
163 | 42 | return FALSE; |
164 | 42 | } |
165 | 512 | g_debug("programing-start-address: 0x%x", hdr->prog_start_addr); |
166 | 512 | return TRUE; |
167 | 554 | } |
168 | | |
169 | 1.27k | g_debug("unknown Wacom-specific metadata"); |
170 | 1.27k | return TRUE; |
171 | 1.84k | } |
172 | | |
173 | | /* start */ |
174 | 124k | if (g_strcmp0(cmd, "S0") == 0) { |
175 | 1.64k | if (helper->image_buffer->len > 0) { |
176 | 23 | g_set_error_literal(error, |
177 | 23 | FWUPD_ERROR, |
178 | 23 | FWUPD_ERROR_INTERNAL, |
179 | 23 | "duplicate S0 without S7"); |
180 | 23 | return FALSE; |
181 | 23 | } |
182 | 1.62k | g_string_append_printf(helper->image_buffer, "%s\n", token->str); |
183 | 1.62k | return TRUE; |
184 | 1.64k | } |
185 | | |
186 | | /* these are things we want to include in the image */ |
187 | 122k | if (g_strcmp0(cmd, "S1") == 0 || g_strcmp0(cmd, "S2") == 0 || g_strcmp0(cmd, "S3") == 0 || |
188 | 5.22k | g_strcmp0(cmd, "S5") == 0 || g_strcmp0(cmd, "S7") == 0 || g_strcmp0(cmd, "S8") == 0 || |
189 | 122k | g_strcmp0(cmd, "S9") == 0) { |
190 | 122k | 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 | 122k | g_string_append_printf(helper->image_buffer, "%s\n", token->str); |
195 | 122k | } else { |
196 | 71 | g_set_error(error, |
197 | 71 | FWUPD_ERROR, |
198 | 71 | FWUPD_ERROR_INTERNAL, |
199 | 71 | "invalid SREC command on line %u: %s", |
200 | 71 | token_idx + 1, |
201 | 71 | cmd); |
202 | 71 | return FALSE; |
203 | 71 | } |
204 | | |
205 | | /* end */ |
206 | 122k | if (g_strcmp0(cmd, "S7") == 0) { |
207 | 1.36k | g_autoptr(GBytes) blob = NULL; |
208 | 1.36k | g_autoptr(GBytes) fw_srec = NULL; |
209 | 1.36k | g_autoptr(FuFirmware) firmware_srec = fu_srec_firmware_new(); |
210 | 1.36k | g_autoptr(FuFirmware) img = fu_firmware_new(); |
211 | 1.36k | FuFirmwareWacHeaderRecord *hdr; |
212 | | |
213 | | /* get the correct relocated start address */ |
214 | 1.36k | if (helper->images_cnt >= helper->header_infos->len) { |
215 | 23 | g_set_error(error, |
216 | 23 | FWUPD_ERROR, |
217 | 23 | FWUPD_ERROR_INTERNAL, |
218 | 23 | "%s without header", |
219 | 23 | cmd); |
220 | 23 | return FALSE; |
221 | 23 | } |
222 | 1.34k | hdr = g_ptr_array_index(helper->header_infos, helper->images_cnt); |
223 | | |
224 | 1.34k | 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.34k | blob = g_bytes_new(helper->image_buffer->str, helper->image_buffer->len); |
235 | 1.34k | fu_srec_firmware_set_addr_min(FU_SREC_FIRMWARE(firmware_srec), hdr->addr); |
236 | 1.34k | if (!fu_firmware_parse_bytes(firmware_srec, |
237 | 1.34k | blob, |
238 | 1.34k | 0x0, |
239 | 1.34k | helper->flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH, |
240 | 1.34k | error)) |
241 | 345 | return FALSE; |
242 | 999 | fw_srec = fu_firmware_get_bytes(firmware_srec, error); |
243 | 999 | if (fw_srec == NULL) |
244 | 0 | return FALSE; |
245 | 999 | fu_firmware_set_bytes(img, fw_srec); |
246 | 999 | fu_firmware_set_addr(img, fu_firmware_get_addr(firmware_srec)); |
247 | 999 | fu_firmware_set_idx(img, helper->images_cnt); |
248 | 999 | if (!fu_firmware_add_image(helper->firmware, img, error)) |
249 | 0 | return FALSE; |
250 | 999 | helper->images_cnt++; |
251 | | |
252 | | /* clear the image buffer */ |
253 | 999 | g_string_set_size(helper->image_buffer, 0); |
254 | 999 | } |
255 | | |
256 | | /* success */ |
257 | 122k | return TRUE; |
258 | 122k | } |
259 | | |
260 | | static gboolean |
261 | | fu_wac_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error) |
262 | 100k | { |
263 | 100k | return fu_struct_wac_firmware_hdr_validate_stream(stream, offset, error); |
264 | 100k | } |
265 | | |
266 | | static gboolean |
267 | | fu_wac_firmware_parse(FuFirmware *firmware, |
268 | | GInputStream *stream, |
269 | | FuFirmwareParseFlags flags, |
270 | | GError **error) |
271 | 1.84k | { |
272 | 1.84k | g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func(g_free); |
273 | 1.84k | g_autoptr(GString) image_buffer = g_string_new(NULL); |
274 | 1.84k | FuWacFirmwareTokenHelper helper = {.firmware = firmware, |
275 | 1.84k | .flags = flags, |
276 | 1.84k | .header_infos = header_infos, |
277 | 1.84k | .image_buffer = image_buffer, |
278 | 1.84k | .images_cnt = 0}; |
279 | | |
280 | | /* tokenize */ |
281 | 1.84k | if (!fu_strsplit_stream(stream, 0x0, "\n", fu_wac_firmware_tokenize_cb, &helper, error)) |
282 | 748 | return FALSE; |
283 | | |
284 | | /* verify data is complete */ |
285 | 1.10k | if (helper.image_buffer->len > 0) { |
286 | 219 | g_set_error_literal(error, |
287 | 219 | FWUPD_ERROR, |
288 | 219 | FWUPD_ERROR_INTERNAL, |
289 | 219 | "truncated data: no S7"); |
290 | 219 | return FALSE; |
291 | 219 | } |
292 | | |
293 | | /* ensure this matched the header */ |
294 | 882 | if (helper.header_infos->len != helper.images_cnt) { |
295 | 68 | g_set_error(error, |
296 | 68 | FWUPD_ERROR, |
297 | 68 | FWUPD_ERROR_INTERNAL, |
298 | 68 | "not enough images %u for header count %u", |
299 | 68 | helper.images_cnt, |
300 | 68 | header_infos->len); |
301 | 68 | return FALSE; |
302 | 68 | } |
303 | | |
304 | | /* success */ |
305 | 814 | return TRUE; |
306 | 882 | } |
307 | | |
308 | | static guint8 |
309 | | fu_wac_firmware_calc_checksum(GByteArray *buf) |
310 | 886 | { |
311 | 886 | return fu_sum8(buf->data, buf->len) ^ 0xFF; |
312 | 886 | } |
313 | | |
314 | | static GByteArray * |
315 | | fu_wac_firmware_write(FuFirmware *firmware, GError **error) |
316 | 814 | { |
317 | 814 | g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); |
318 | 814 | g_autoptr(GString) str = g_string_new(NULL); |
319 | 814 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
320 | 814 | g_autoptr(GByteArray) buf_hdr = g_byte_array_new(); |
321 | | |
322 | | /* fw header */ |
323 | 814 | if (images->len == 0) { |
324 | 541 | g_set_error_literal(error, |
325 | 541 | FWUPD_ERROR, |
326 | 541 | FWUPD_ERROR_NOT_SUPPORTED, |
327 | 541 | "no firmware images found"); |
328 | 541 | return NULL; |
329 | 541 | } |
330 | 886 | for (guint i = 0; i < images->len; i++) { |
331 | 613 | FuFirmware *img = g_ptr_array_index(images, i); |
332 | 613 | fu_byte_array_append_uint32(buf_hdr, fu_firmware_get_addr(img), G_BIG_ENDIAN); |
333 | 613 | fu_byte_array_append_uint32(buf_hdr, fu_firmware_get_size(img), G_BIG_ENDIAN); |
334 | 613 | } |
335 | 273 | g_string_append_printf(str, "WACOM%u", images->len); |
336 | 5.17k | for (guint i = 0; i < buf_hdr->len; i++) |
337 | 4.90k | g_string_append_printf(str, "%02X", buf_hdr->data[i]); |
338 | 273 | g_string_append_printf(str, "%02X\n", fu_wac_firmware_calc_checksum(buf_hdr)); |
339 | | |
340 | | /* payload */ |
341 | 886 | for (guint i = 0; i < images->len; i++) { |
342 | 613 | FuFirmware *img = g_ptr_array_index(images, i); |
343 | 613 | g_autoptr(GBytes) img_blob = NULL; |
344 | 613 | g_autoptr(GByteArray) buf_img = g_byte_array_new(); |
345 | | |
346 | | /* img header */ |
347 | 613 | g_string_append_printf(str, "WA%u", (guint)fu_firmware_get_idx(img) + 1); |
348 | 613 | fu_byte_array_append_uint32(buf_img, fu_firmware_get_addr(img), G_BIG_ENDIAN); |
349 | 3.06k | for (guint j = 0; j < buf_img->len; j++) |
350 | 2.45k | g_string_append_printf(str, "%02X", buf_img->data[j]); |
351 | 613 | g_string_append_printf(str, "%02X\n", fu_wac_firmware_calc_checksum(buf_img)); |
352 | | |
353 | | /* srec */ |
354 | 613 | img_blob = fu_firmware_write(img, error); |
355 | 613 | if (img_blob == NULL) |
356 | 0 | return NULL; |
357 | 613 | g_string_append_len(str, |
358 | 613 | (const gchar *)g_bytes_get_data(img_blob, NULL), |
359 | 613 | g_bytes_get_size(img_blob)); |
360 | 613 | } |
361 | | |
362 | | /* success */ |
363 | 273 | g_byte_array_append(buf, (const guint8 *)str->str, str->len); |
364 | 273 | return g_steal_pointer(&buf); |
365 | 273 | } |
366 | | |
367 | | static void |
368 | | fu_wac_firmware_init(FuWacFirmware *self) |
369 | 2.09k | { |
370 | 2.09k | fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); |
371 | 2.09k | g_type_ensure(FU_TYPE_SREC_FIRMWARE); |
372 | 2.09k | } |
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 | } |