/src/fwupd/plugins/elanfp/fu-elanfp-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2021 Michael Cheng <michael.cheng@emc.com.tw> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | | #include "config.h" |
8 | | |
9 | | #include "fu-elanfp-firmware.h" |
10 | | #include "fu-elanfp-struct.h" |
11 | | |
12 | | struct _FuElanfpFirmware { |
13 | | FuFirmware parent_instance; |
14 | | guint32 format_version; |
15 | | }; |
16 | | |
17 | 1.01k | G_DEFINE_TYPE(FuElanfpFirmware, fu_elanfp_firmware, FU_TYPE_FIRMWARE) |
18 | 1.01k | |
19 | 1.01k | static void |
20 | 1.01k | fu_elanfp_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
21 | 1.01k | { |
22 | 0 | FuElanfpFirmware *self = FU_ELANFP_FIRMWARE(firmware); |
23 | 0 | fu_xmlb_builder_insert_kx(bn, "format_version", self->format_version); |
24 | 0 | } |
25 | | |
26 | | static gboolean |
27 | | fu_elanfp_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
28 | 0 | { |
29 | 0 | FuElanfpFirmware *self = FU_ELANFP_FIRMWARE(firmware); |
30 | 0 | guint64 tmp; |
31 | | |
32 | | /* optional properties */ |
33 | 0 | tmp = xb_node_query_text_as_uint(n, "format_version", NULL); |
34 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) |
35 | 0 | self->format_version = tmp; |
36 | | |
37 | | /* success */ |
38 | 0 | return TRUE; |
39 | 0 | } |
40 | | |
41 | | static gboolean |
42 | | fu_elanfp_firmware_validate(FuFirmware *firmware, |
43 | | FuInputStream *stream, |
44 | | gsize offset, |
45 | | GError **error) |
46 | 486 | { |
47 | 486 | return fu_struct_elanfp_firmware_hdr_validate_stream(stream, offset, error); |
48 | 486 | } |
49 | | |
50 | | static gboolean |
51 | | fu_elanfp_firmware_parse(FuFirmware *firmware, |
52 | | FuInputStream *stream, |
53 | | FuFirmwareParseFlags flags, |
54 | | GError **error) |
55 | 427 | { |
56 | 427 | FuElanfpFirmware *self = FU_ELANFP_FIRMWARE(firmware); |
57 | 427 | gsize offset = 0; |
58 | | |
59 | | /* file format version */ |
60 | 427 | if (!fu_input_stream_read_u32(stream, |
61 | 427 | offset + 0x4, |
62 | 427 | &self->format_version, |
63 | 427 | G_LITTLE_ENDIAN, |
64 | 427 | error)) |
65 | 3 | return FALSE; |
66 | | |
67 | | /* read indexes */ |
68 | 424 | if (!fu_size_checked_inc(&offset, 0x10, error)) |
69 | 0 | return FALSE; |
70 | 907 | while (1) { |
71 | 907 | guint32 start_addr = 0; |
72 | 907 | guint32 length = 0; |
73 | 907 | guint32 fwtype = 0; |
74 | 907 | g_autoptr(FuFirmware) img = NULL; |
75 | 907 | g_autoptr(FuInputStream) stream_tmp = NULL; |
76 | | |
77 | | /* type, reserved, start-addr, len */ |
78 | 907 | if (!fu_input_stream_read_u32(stream, |
79 | 907 | offset + 0x0, |
80 | 907 | &fwtype, |
81 | 907 | G_LITTLE_ENDIAN, |
82 | 907 | error)) |
83 | 34 | return FALSE; |
84 | | |
85 | | /* check not already added */ |
86 | 873 | img = fu_firmware_get_image_by_idx(firmware, fwtype, NULL); |
87 | 873 | if (img != NULL) { |
88 | 5 | g_set_error(error, |
89 | 5 | FWUPD_ERROR, |
90 | 5 | FWUPD_ERROR_NOT_SUPPORTED, |
91 | 5 | "already parsed image with fwtype 0x%x", |
92 | 5 | fwtype); |
93 | 5 | return FALSE; |
94 | 5 | } |
95 | | |
96 | | /* done */ |
97 | 868 | if (fwtype == FU_ELANTP_FIRMWARE_IDX_END) |
98 | 101 | break; |
99 | 767 | switch (fwtype) { |
100 | 52 | case FU_ELANTP_FIRMWARE_IDX_CFU_OFFER_A: |
101 | 101 | case FU_ELANTP_FIRMWARE_IDX_CFU_OFFER_B: |
102 | 101 | img = fu_cfu_offer_new(); |
103 | 101 | break; |
104 | 282 | case FU_ELANTP_FIRMWARE_IDX_CFU_PAYLOAD_A: |
105 | 502 | case FU_ELANTP_FIRMWARE_IDX_CFU_PAYLOAD_B: |
106 | 502 | img = fu_cfu_payload_new(); |
107 | 502 | break; |
108 | 164 | default: |
109 | 164 | img = fu_firmware_new(); |
110 | 164 | break; |
111 | 767 | } |
112 | 767 | fu_firmware_set_idx(img, fwtype); |
113 | 767 | if (!fu_input_stream_read_u32(stream, |
114 | 767 | offset + 0x8, |
115 | 767 | &start_addr, |
116 | 767 | G_LITTLE_ENDIAN, |
117 | 767 | error)) |
118 | 57 | return FALSE; |
119 | 710 | fu_firmware_set_addr(img, start_addr); |
120 | 710 | if (!fu_input_stream_read_u32(stream, |
121 | 710 | offset + 0xC, |
122 | 710 | &length, |
123 | 710 | G_LITTLE_ENDIAN, |
124 | 710 | error)) |
125 | 13 | return FALSE; |
126 | 697 | if (length == 0) { |
127 | 14 | g_set_error(error, |
128 | 14 | FWUPD_ERROR, |
129 | 14 | FWUPD_ERROR_NOT_SUPPORTED, |
130 | 14 | "zero size fwtype 0x%x not supported", |
131 | 14 | fwtype); |
132 | 14 | return FALSE; |
133 | 14 | } |
134 | | |
135 | 683 | stream_tmp = fu_partial_input_stream_new(stream, start_addr, length, error); |
136 | 683 | if (stream_tmp == NULL) |
137 | 117 | return FALSE; |
138 | 566 | if (!fu_firmware_parse_stream(img, |
139 | 566 | stream_tmp, |
140 | 566 | 0x0, |
141 | 566 | flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH, |
142 | 566 | error)) |
143 | 70 | return FALSE; |
144 | 496 | if (!fu_firmware_add_image(firmware, img, error)) |
145 | 13 | return FALSE; |
146 | | |
147 | 483 | if (!fu_size_checked_inc(&offset, 0x10, error)) { |
148 | 0 | g_prefix_error_literal(error, "index offset overflow: "); |
149 | 0 | return FALSE; |
150 | 0 | } |
151 | 483 | } |
152 | | |
153 | | /* success */ |
154 | 101 | return TRUE; |
155 | 424 | } |
156 | | |
157 | | static GByteArray * |
158 | | fu_elanfp_firmware_write(FuFirmware *firmware, GError **error) |
159 | 101 | { |
160 | 101 | FuElanfpFirmware *self = FU_ELANFP_FIRMWARE(firmware); |
161 | 101 | gsize offset = 0; |
162 | 101 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
163 | 101 | g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); |
164 | | |
165 | | /* S2F_HEADER */ |
166 | 101 | fu_byte_array_append_uint32(buf, 0x46325354, G_LITTLE_ENDIAN); |
167 | 101 | fu_byte_array_append_uint32(buf, self->format_version, G_LITTLE_ENDIAN); |
168 | 101 | fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* ICID, assumed */ |
169 | 101 | fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* reserved */ |
170 | | |
171 | | /* S2F_INDEX */ |
172 | 101 | if (!fu_size_checked_inc_product(&offset, (imgs->len + 1), 0x10, error)) { |
173 | 0 | g_prefix_error_literal(error, "index size overflow: "); |
174 | 0 | return NULL; |
175 | 0 | } |
176 | 101 | if (!fu_size_checked_inc(&offset, 0x10, error)) { |
177 | 0 | g_prefix_error_literal(error, "header offset overflow: "); |
178 | 0 | return NULL; |
179 | 0 | } |
180 | | |
181 | 288 | for (guint i = 0; i < imgs->len; i++) { |
182 | 187 | FuFirmware *img = g_ptr_array_index(imgs, i); |
183 | 187 | g_autoptr(GBytes) blob = fu_firmware_write(img, error); |
184 | 187 | if (blob == NULL) |
185 | 0 | return NULL; |
186 | 187 | fu_byte_array_append_uint32(buf, fu_firmware_get_idx(img), G_LITTLE_ENDIAN); |
187 | 187 | fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* reserved */ |
188 | 187 | fu_byte_array_append_uint32(buf, offset, G_LITTLE_ENDIAN); |
189 | 187 | fu_byte_array_append_uint32(buf, g_bytes_get_size(blob), G_LITTLE_ENDIAN); |
190 | 187 | if (!fu_size_checked_inc(&offset, g_bytes_get_size(blob), error)) { |
191 | 0 | g_prefix_error(error, "image %u offset overflow: ", i); |
192 | 0 | return NULL; |
193 | 0 | } |
194 | 187 | } |
195 | | |
196 | | /* end of index */ |
197 | 101 | fu_byte_array_append_uint32(buf, FU_ELANTP_FIRMWARE_IDX_END, G_LITTLE_ENDIAN); |
198 | 101 | fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* reserved */ |
199 | 101 | fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* assumed */ |
200 | 101 | fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* assumed */ |
201 | | |
202 | | /* data */ |
203 | 288 | for (guint i = 0; i < imgs->len; i++) { |
204 | 187 | FuFirmware *img = g_ptr_array_index(imgs, i); |
205 | 187 | g_autoptr(GBytes) blob = fu_firmware_write(img, error); |
206 | 187 | if (blob == NULL) |
207 | 0 | return NULL; |
208 | 187 | fu_byte_array_append_bytes(buf, blob); |
209 | 187 | } |
210 | | |
211 | | /* success */ |
212 | 101 | return g_steal_pointer(&buf); |
213 | 101 | } |
214 | | |
215 | | static void |
216 | | fu_elanfp_firmware_init(FuElanfpFirmware *self) |
217 | 486 | { |
218 | 486 | } |
219 | | |
220 | | static void |
221 | | fu_elanfp_firmware_class_init(FuElanfpFirmwareClass *klass) |
222 | 1 | { |
223 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
224 | 1 | fu_firmware_add_image_gtype(firmware_class, FU_TYPE_CFU_OFFER); |
225 | 1 | fu_firmware_add_image_gtype(firmware_class, FU_TYPE_CFU_PAYLOAD); |
226 | 1 | firmware_class->validate = fu_elanfp_firmware_validate; |
227 | 1 | firmware_class->parse = fu_elanfp_firmware_parse; |
228 | 1 | firmware_class->write = fu_elanfp_firmware_write; |
229 | 1 | firmware_class->export = fu_elanfp_firmware_export; |
230 | 1 | firmware_class->build = fu_elanfp_firmware_build; |
231 | 1 | fu_firmware_set_images_max(firmware_class, 256); |
232 | 1 | fu_firmware_set_size_max(firmware_class, 16 * FU_MB); |
233 | 1 | } |