/src/fwupd/plugins/elantp/fu-elantp-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2020 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | | #include "config.h" |
8 | | |
9 | | #include "fu-elantp-common.h" |
10 | | #include "fu-elantp-firmware.h" |
11 | | #include "fu-elantp-struct.h" |
12 | | |
13 | | struct _FuElantpFirmware { |
14 | | FuFirmware parent_instance; |
15 | | guint16 module_id; |
16 | | guint16 ic_type; |
17 | | guint16 iap_addr; |
18 | | guint16 iap_ver; |
19 | | gboolean force_table_support; |
20 | | guint32 force_table_addr; |
21 | | }; |
22 | | |
23 | 206k | G_DEFINE_TYPE(FuElantpFirmware, fu_elantp_firmware, FU_TYPE_FIRMWARE) |
24 | 206k | |
25 | 206k | /* firmware block update */ |
26 | 206k | #define ETP_IC_TYPE_ADDR_WRDS 0x0080 |
27 | 98 | #define ETP_IAP_VER_ADDR_WRDS 0x0082 |
28 | 481 | #define ETP_IAP_START_ADDR_WRDS 0x0083 |
29 | 14 | #define ETP_IAP_FORCETABLE_ADDR_V5 0x0085 |
30 | | |
31 | | const guint8 elantp_signature[] = {0xAA, 0x55, 0xCC, 0x33, 0xFF, 0xFF}; |
32 | | |
33 | | guint16 |
34 | | fu_elantp_firmware_get_module_id(FuElantpFirmware *self) |
35 | 0 | { |
36 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); |
37 | 0 | return self->module_id; |
38 | 0 | } |
39 | | |
40 | | guint16 |
41 | | fu_elantp_firmware_get_ic_type(FuElantpFirmware *self) |
42 | 0 | { |
43 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); |
44 | 0 | return self->ic_type; |
45 | 0 | } |
46 | | |
47 | | guint16 |
48 | | fu_elantp_firmware_get_iap_addr(FuElantpFirmware *self) |
49 | 0 | { |
50 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); |
51 | 0 | return self->iap_addr; |
52 | 0 | } |
53 | | |
54 | | gboolean |
55 | | fu_elantp_firmware_get_forcetable_support(FuElantpFirmware *self) |
56 | 0 | { |
57 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), FALSE); |
58 | 0 | return self->force_table_support; |
59 | 0 | } |
60 | | |
61 | | guint32 |
62 | | fu_elantp_firmware_get_forcetable_addr(FuElantpFirmware *self) |
63 | 0 | { |
64 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); |
65 | 0 | return self->force_table_addr; |
66 | 0 | } |
67 | | |
68 | | static void |
69 | | fu_elantp_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
70 | 0 | { |
71 | 0 | FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); |
72 | 0 | fu_xmlb_builder_insert_kx(bn, "iap_addr", self->iap_addr); |
73 | 0 | fu_xmlb_builder_insert_kx(bn, "module_id", self->module_id); |
74 | 0 | } |
75 | | |
76 | | static gboolean |
77 | | fu_elantp_firmware_validate(FuFirmware *firmware, |
78 | | GInputStream *stream, |
79 | | gsize offset, |
80 | | GError **error) |
81 | 205k | { |
82 | 205k | FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); |
83 | 205k | gsize streamsz = 0; |
84 | | |
85 | 205k | if (!fu_input_stream_size(stream, &streamsz, error)) |
86 | 0 | return FALSE; |
87 | 205k | if (streamsz < FU_STRUCT_ELANTP_FIRMWARE_HDR_SIZE) { |
88 | 32 | g_set_error_literal(error, |
89 | 32 | FWUPD_ERROR, |
90 | 32 | FWUPD_ERROR_INVALID_FILE, |
91 | 32 | "stream was too small"); |
92 | 32 | return FALSE; |
93 | 32 | } |
94 | 205k | if (!fu_struct_elantp_firmware_hdr_validate_stream(stream, |
95 | 205k | streamsz - |
96 | 205k | FU_STRUCT_ELANTP_FIRMWARE_HDR_SIZE, |
97 | 205k | error)) |
98 | 205k | return FALSE; |
99 | 177 | if (self->force_table_addr != 0) { |
100 | 0 | if (!fu_struct_elantp_firmware_hdr_validate_stream( |
101 | 0 | stream, |
102 | 0 | self->force_table_addr - 1 + FU_STRUCT_ELANTP_FIRMWARE_HDR_SIZE, |
103 | 0 | error)) |
104 | 0 | return FALSE; |
105 | 0 | } |
106 | | |
107 | | /* success */ |
108 | 177 | return TRUE; |
109 | 177 | } |
110 | | |
111 | | static gboolean |
112 | | fu_elantp_firmware_parse(FuFirmware *firmware, |
113 | | GInputStream *stream, |
114 | | FuFirmwareParseFlags flags, |
115 | | GError **error) |
116 | 177 | { |
117 | 177 | FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); |
118 | 177 | guint16 iap_addr_wrds; |
119 | 177 | guint16 force_table_addr_wrds; |
120 | 177 | guint16 module_id_wrds; |
121 | 177 | g_autoptr(GError) error_local = NULL; |
122 | | |
123 | | /* presumably in words */ |
124 | 177 | if (!fu_input_stream_read_u16(stream, |
125 | 177 | ETP_IAP_START_ADDR_WRDS * 2, |
126 | 177 | &iap_addr_wrds, |
127 | 177 | G_LITTLE_ENDIAN, |
128 | 177 | error)) |
129 | 25 | return FALSE; |
130 | 152 | if (iap_addr_wrds < ETP_IAP_START_ADDR_WRDS || iap_addr_wrds > 0x7FFF) { |
131 | 21 | g_set_error(error, |
132 | 21 | FWUPD_ERROR, |
133 | 21 | FWUPD_ERROR_INVALID_FILE, |
134 | 21 | "IAP address invalid: 0x%x", |
135 | 21 | iap_addr_wrds); |
136 | 21 | return FALSE; |
137 | 21 | } |
138 | 131 | self->iap_addr = iap_addr_wrds * 2; |
139 | | |
140 | | /* read module ID */ |
141 | 131 | if (!fu_input_stream_read_u16(stream, |
142 | 131 | self->iap_addr, |
143 | 131 | &module_id_wrds, |
144 | 131 | G_LITTLE_ENDIAN, |
145 | 131 | error)) |
146 | 16 | return FALSE; |
147 | 115 | if (module_id_wrds > 0x7FFF) { |
148 | 12 | g_set_error(error, |
149 | 12 | FWUPD_ERROR, |
150 | 12 | FWUPD_ERROR_INVALID_FILE, |
151 | 12 | "module ID address invalid: 0x%x", |
152 | 12 | module_id_wrds); |
153 | 12 | return FALSE; |
154 | 12 | } |
155 | 103 | if (!fu_input_stream_read_u16(stream, |
156 | 103 | module_id_wrds * 2, |
157 | 103 | &self->module_id, |
158 | 103 | G_LITTLE_ENDIAN, |
159 | 103 | error)) |
160 | 5 | return FALSE; |
161 | 98 | if (!fu_input_stream_read_u16(stream, |
162 | 98 | ETP_IC_TYPE_ADDR_WRDS * 2, |
163 | 98 | &self->ic_type, |
164 | 98 | G_LITTLE_ENDIAN, |
165 | 98 | error)) |
166 | 0 | return FALSE; |
167 | 98 | if (!fu_input_stream_read_u16(stream, |
168 | 98 | ETP_IAP_VER_ADDR_WRDS * 2, |
169 | 98 | &self->iap_ver, |
170 | 98 | G_LITTLE_ENDIAN, |
171 | 98 | error)) |
172 | 0 | return FALSE; |
173 | | |
174 | 98 | if (self->ic_type != 0x12 && self->ic_type != 0x13) |
175 | 78 | return TRUE; |
176 | | |
177 | 20 | if (self->iap_ver <= 4) { |
178 | 6 | if (!fu_input_stream_read_u16(stream, |
179 | 6 | self->iap_addr + 6, |
180 | 6 | &force_table_addr_wrds, |
181 | 6 | G_LITTLE_ENDIAN, |
182 | 6 | &error_local)) { |
183 | 2 | g_debug("forcetable address wrong: %s", error_local->message); |
184 | 2 | return TRUE; |
185 | 2 | } |
186 | 14 | } else { |
187 | 14 | if (!fu_input_stream_read_u16(stream, |
188 | 14 | ETP_IAP_FORCETABLE_ADDR_V5 * 2, |
189 | 14 | &force_table_addr_wrds, |
190 | 14 | G_LITTLE_ENDIAN, |
191 | 14 | &error_local)) { |
192 | 0 | g_debug("forcetable address wrong: %s", error_local->message); |
193 | 0 | return TRUE; |
194 | 0 | } |
195 | 14 | } |
196 | | |
197 | 18 | if (force_table_addr_wrds % 32 == 0) { |
198 | 1 | self->force_table_addr = force_table_addr_wrds * 2; |
199 | 1 | self->force_table_support = TRUE; |
200 | 1 | } |
201 | | |
202 | | /* success */ |
203 | 18 | return TRUE; |
204 | 20 | } |
205 | | |
206 | | static gboolean |
207 | | fu_elantp_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
208 | 0 | { |
209 | 0 | FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); |
210 | 0 | guint64 tmp; |
211 | | |
212 | | /* two simple properties */ |
213 | 0 | tmp = xb_node_query_text_as_uint(n, "module_id", NULL); |
214 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
215 | 0 | self->module_id = tmp; |
216 | 0 | tmp = xb_node_query_text_as_uint(n, "iap_addr", NULL); |
217 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
218 | 0 | self->iap_addr = tmp; |
219 | | |
220 | | /* success */ |
221 | 0 | return TRUE; |
222 | 0 | } |
223 | | |
224 | | static GByteArray * |
225 | | fu_elantp_firmware_write(FuFirmware *firmware, GError **error) |
226 | 98 | { |
227 | 98 | FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); |
228 | 98 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
229 | 98 | g_autoptr(GBytes) blob = NULL; |
230 | | |
231 | | /* only one image supported */ |
232 | 98 | blob = fu_firmware_get_bytes_with_patches(firmware, error); |
233 | 98 | if (blob == NULL) |
234 | 98 | return NULL; |
235 | | |
236 | | /* lets build a simple firmware like this: |
237 | | * ------ 0x0 |
238 | | * HEADER (containing IAP offset and module ID) |
239 | | * ------ ~0x10a |
240 | | * DATA |
241 | | * ------ |
242 | | * SIGNATURE |
243 | | * ------ |
244 | | */ |
245 | 0 | fu_byte_array_set_size(buf, self->iap_addr + 0x2 + 0x2, 0x00); |
246 | 0 | if (!fu_memwrite_uint16_safe(buf->data, |
247 | 0 | buf->len, |
248 | 0 | ETP_IAP_START_ADDR_WRDS * 2, |
249 | 0 | self->iap_addr / 2, |
250 | 0 | G_LITTLE_ENDIAN, |
251 | 0 | error)) |
252 | 0 | return NULL; |
253 | 0 | if (!fu_memwrite_uint16_safe(buf->data, |
254 | 0 | buf->len, |
255 | 0 | self->iap_addr, |
256 | 0 | (self->iap_addr + 2) / 2, |
257 | 0 | G_LITTLE_ENDIAN, |
258 | 0 | error)) |
259 | 0 | return NULL; |
260 | 0 | if (!fu_memwrite_uint16_safe(buf->data, |
261 | 0 | buf->len, |
262 | 0 | self->iap_addr + 0x2, |
263 | 0 | self->module_id, |
264 | 0 | G_LITTLE_ENDIAN, |
265 | 0 | error)) |
266 | 0 | return NULL; |
267 | 0 | fu_byte_array_append_bytes(buf, blob); |
268 | 0 | g_byte_array_append(buf, elantp_signature, sizeof(elantp_signature)); |
269 | 0 | return g_steal_pointer(&buf); |
270 | 0 | } |
271 | | |
272 | | static void |
273 | | fu_elantp_firmware_init(FuElantpFirmware *self) |
274 | 301 | { |
275 | 301 | } |
276 | | |
277 | | static void |
278 | | fu_elantp_firmware_class_init(FuElantpFirmwareClass *klass) |
279 | 1 | { |
280 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
281 | 1 | firmware_class->validate = fu_elantp_firmware_validate; |
282 | 1 | firmware_class->parse = fu_elantp_firmware_parse; |
283 | 1 | firmware_class->build = fu_elantp_firmware_build; |
284 | 1 | firmware_class->write = fu_elantp_firmware_write; |
285 | 1 | firmware_class->export = fu_elantp_firmware_export; |
286 | 1 | } |
287 | | |
288 | | FuFirmware * |
289 | | fu_elantp_firmware_new(void) |
290 | 0 | { |
291 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_ELANTP_FIRMWARE, NULL)); |
292 | 0 | } |