/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 | 767 | G_DEFINE_TYPE(FuElantpFirmware, fu_elantp_firmware, FU_TYPE_FIRMWARE) |
24 | 767 | |
25 | 823 | #define FU_ELANTP_FIRMWARE_OFFSET_IAP 0x100 /* bytes */ |
26 | | |
27 | | guint16 |
28 | | fu_elantp_firmware_get_module_id(FuElantpFirmware *self) |
29 | 0 | { |
30 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); |
31 | 0 | return self->module_id; |
32 | 0 | } |
33 | | |
34 | | guint16 |
35 | | fu_elantp_firmware_get_ic_type(FuElantpFirmware *self) |
36 | 0 | { |
37 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); |
38 | 0 | return self->ic_type; |
39 | 0 | } |
40 | | |
41 | | guint16 |
42 | | fu_elantp_firmware_get_iap_addr(FuElantpFirmware *self) |
43 | 0 | { |
44 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); |
45 | 0 | return self->iap_addr; |
46 | 0 | } |
47 | | |
48 | | gboolean |
49 | | fu_elantp_firmware_get_forcetable_support(FuElantpFirmware *self) |
50 | 0 | { |
51 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), FALSE); |
52 | 0 | return self->force_table_support; |
53 | 0 | } |
54 | | |
55 | | guint32 |
56 | | fu_elantp_firmware_get_forcetable_addr(FuElantpFirmware *self) |
57 | 0 | { |
58 | 0 | g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); |
59 | 0 | return self->force_table_addr; |
60 | 0 | } |
61 | | |
62 | | static void |
63 | | fu_elantp_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
64 | 0 | { |
65 | 0 | FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); |
66 | 0 | fu_xmlb_builder_insert_kx(bn, "ic_type", self->ic_type); |
67 | 0 | fu_xmlb_builder_insert_kx(bn, "iap_addr", self->iap_addr); |
68 | 0 | fu_xmlb_builder_insert_kx(bn, "module_id", self->module_id); |
69 | 0 | } |
70 | | |
71 | | static gboolean |
72 | | fu_elantp_firmware_validate(FuFirmware *firmware, |
73 | | GInputStream *stream, |
74 | | gsize offset, |
75 | | GError **error) |
76 | 337 | { |
77 | 337 | gsize streamsz = 0; |
78 | | |
79 | 337 | if (!fu_input_stream_size(stream, &streamsz, error)) |
80 | 0 | return FALSE; |
81 | 337 | if (streamsz < FU_STRUCT_ELANTP_FIRMWARE_FTR_SIZE) { |
82 | 6 | g_set_error_literal(error, |
83 | 6 | FWUPD_ERROR, |
84 | 6 | FWUPD_ERROR_INVALID_FILE, |
85 | 6 | "stream was too small"); |
86 | 6 | return FALSE; |
87 | 6 | } |
88 | 331 | if (!fu_struct_elantp_firmware_ftr_validate_stream(stream, |
89 | 331 | streamsz - |
90 | 331 | FU_STRUCT_ELANTP_FIRMWARE_FTR_SIZE, |
91 | 331 | error)) |
92 | 58 | return FALSE; |
93 | | |
94 | | /* success */ |
95 | 273 | return TRUE; |
96 | 331 | } |
97 | | |
98 | | static gboolean |
99 | | fu_elantp_firmware_parse(FuFirmware *firmware, |
100 | | GInputStream *stream, |
101 | | FuFirmwareParseFlags flags, |
102 | | GError **error) |
103 | 273 | { |
104 | 273 | FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); |
105 | 273 | guint16 force_table_addr_wrds; |
106 | 273 | guint16 module_id_wrds; |
107 | 273 | guint32 iap_addr_tmp; |
108 | 273 | g_autoptr(FuStructElantpFirmwareHdr) st = NULL; |
109 | | |
110 | 273 | st = fu_struct_elantp_firmware_hdr_parse_stream(stream, |
111 | 273 | FU_ELANTP_FIRMWARE_OFFSET_IAP, |
112 | 273 | error); |
113 | 273 | if (st == NULL) |
114 | 27 | return FALSE; |
115 | | |
116 | | /* convert from words */ |
117 | 246 | iap_addr_tmp = (guint32)fu_struct_elantp_firmware_hdr_get_iap_start(st) * 2; |
118 | 246 | if (iap_addr_tmp >= G_MAXUINT16 || |
119 | 236 | iap_addr_tmp < (FU_ELANTP_FIRMWARE_OFFSET_IAP + FU_STRUCT_ELANTP_FIRMWARE_HDR_SIZE)) { |
120 | 20 | g_set_error(error, |
121 | 20 | FWUPD_ERROR, |
122 | 20 | FWUPD_ERROR_INVALID_FILE, |
123 | 20 | "IAP address invalid: 0x%x", |
124 | 20 | iap_addr_tmp); |
125 | 20 | return FALSE; |
126 | 20 | } |
127 | 226 | self->iap_addr = (guint16)iap_addr_tmp; |
128 | 226 | self->ic_type = fu_struct_elantp_firmware_hdr_get_ic_type(st); |
129 | 226 | self->iap_ver = fu_struct_elantp_firmware_hdr_get_iap_ver(st); |
130 | | |
131 | | /* read module ID */ |
132 | 226 | if (!fu_input_stream_read_u16(stream, |
133 | 226 | self->iap_addr, |
134 | 226 | &module_id_wrds, |
135 | 226 | G_LITTLE_ENDIAN, |
136 | 226 | error)) |
137 | 20 | return FALSE; |
138 | 206 | if (module_id_wrds == 0xFFFF) { |
139 | 1 | g_set_error(error, |
140 | 1 | FWUPD_ERROR, |
141 | 1 | FWUPD_ERROR_INVALID_FILE, |
142 | 1 | "module ID address invalid: 0x%x", |
143 | 1 | module_id_wrds); |
144 | 1 | return FALSE; |
145 | 1 | } |
146 | 205 | if (!fu_input_stream_read_u16(stream, |
147 | 205 | module_id_wrds * 2, |
148 | 205 | &self->module_id, |
149 | 205 | G_LITTLE_ENDIAN, |
150 | 205 | error)) |
151 | 23 | return FALSE; |
152 | | |
153 | | /* get the forcetable address */ |
154 | 182 | if (self->ic_type == FU_ETP_IC_NUM12 || self->ic_type == FU_ETP_IC_NUM13 || |
155 | 151 | self->ic_type == FU_ETP_IC_NUM14 || self->ic_type == FU_ETP_IC_NUM15) { |
156 | 45 | if (self->iap_ver <= 4) { |
157 | 7 | if (!fu_input_stream_read_u16(stream, |
158 | 7 | self->iap_addr + 6, |
159 | 7 | &force_table_addr_wrds, |
160 | 7 | G_LITTLE_ENDIAN, |
161 | 7 | error)) |
162 | 1 | return FALSE; |
163 | 38 | } else { |
164 | 38 | force_table_addr_wrds = |
165 | 38 | fu_struct_elantp_firmware_hdr_get_iap_forcetable(st); |
166 | 38 | } |
167 | 44 | if (force_table_addr_wrds == 0xFFFF) { |
168 | 3 | self->force_table_support = FALSE; |
169 | 41 | } else { |
170 | 41 | if (force_table_addr_wrds % 32 != 0 || force_table_addr_wrds >= 0x7FFF) { |
171 | 24 | g_set_error(error, |
172 | 24 | FWUPD_ERROR, |
173 | 24 | FWUPD_ERROR_INVALID_FILE, |
174 | 24 | "forcetable address invalid: 0x%x", |
175 | 24 | force_table_addr_wrds); |
176 | 24 | return FALSE; |
177 | 24 | } |
178 | 17 | self->force_table_addr = force_table_addr_wrds * 2; |
179 | 17 | self->force_table_support = TRUE; |
180 | 17 | } |
181 | 44 | } |
182 | | |
183 | | /* success */ |
184 | 157 | return TRUE; |
185 | 182 | } |
186 | | |
187 | | static gboolean |
188 | | fu_elantp_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
189 | 0 | { |
190 | 0 | FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); |
191 | 0 | guint64 tmp; |
192 | | |
193 | | /* optional properties */ |
194 | 0 | tmp = xb_node_query_text_as_uint(n, "ic_type", NULL); |
195 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
196 | 0 | self->ic_type = tmp; |
197 | 0 | tmp = xb_node_query_text_as_uint(n, "module_id", NULL); |
198 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
199 | 0 | self->module_id = tmp; |
200 | 0 | tmp = xb_node_query_text_as_uint(n, "iap_addr", NULL); |
201 | 0 | if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) |
202 | 0 | self->iap_addr = tmp; |
203 | | |
204 | | /* success */ |
205 | 0 | return TRUE; |
206 | 0 | } |
207 | | |
208 | | static GByteArray * |
209 | | fu_elantp_firmware_write(FuFirmware *firmware, GError **error) |
210 | 157 | { |
211 | 157 | FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); |
212 | 157 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
213 | 157 | g_autoptr(GBytes) blob = NULL; |
214 | 157 | g_autoptr(FuStructElantpFirmwareHdr) st_hdr = fu_struct_elantp_firmware_hdr_new(); |
215 | 157 | g_autoptr(FuStructElantpFirmwareFtr) st_ftr = fu_struct_elantp_firmware_ftr_new(); |
216 | | |
217 | | /* sanity check */ |
218 | 157 | if (self->iap_addr < FU_ELANTP_FIRMWARE_OFFSET_IAP + FU_STRUCT_ELANTP_FIRMWARE_HDR_SIZE) { |
219 | 0 | g_set_error(error, |
220 | 0 | FWUPD_ERROR, |
221 | 0 | FWUPD_ERROR_INVALID_DATA, |
222 | 0 | "IAP address 0x%x would truncate header", |
223 | 0 | self->iap_addr); |
224 | 0 | return NULL; |
225 | 0 | } |
226 | | |
227 | | /* header */ |
228 | 157 | fu_byte_array_set_size(buf, FU_ELANTP_FIRMWARE_OFFSET_IAP, 0x00); |
229 | 157 | fu_struct_elantp_firmware_hdr_set_ic_type(st_hdr, self->ic_type); |
230 | 157 | fu_struct_elantp_firmware_hdr_set_iap_ver(st_hdr, self->iap_ver); |
231 | 157 | fu_struct_elantp_firmware_hdr_set_iap_start(st_hdr, self->iap_addr / 2); |
232 | 157 | fu_struct_elantp_firmware_hdr_set_iap_forcetable( |
233 | 157 | st_hdr, |
234 | 157 | self->force_table_support ? self->force_table_addr / 2 : 0xFFFF); |
235 | 157 | g_byte_array_append(buf, st_hdr->buf->data, st_hdr->buf->len); |
236 | | |
237 | | /* IAP */ |
238 | 157 | fu_byte_array_set_size(buf, self->iap_addr + 0x4, 0x00); |
239 | 157 | if (!fu_memwrite_uint16_safe(buf->data, |
240 | 157 | buf->len, |
241 | 157 | self->iap_addr, |
242 | 157 | (self->iap_addr + 2) / 2, |
243 | 157 | G_LITTLE_ENDIAN, |
244 | 157 | error)) |
245 | 0 | return NULL; |
246 | 157 | if (!fu_memwrite_uint16_safe(buf->data, |
247 | 157 | buf->len, |
248 | 157 | self->iap_addr + 0x2, |
249 | 157 | self->module_id, |
250 | 157 | G_LITTLE_ENDIAN, |
251 | 157 | error)) |
252 | 0 | return NULL; |
253 | | |
254 | | /* data */ |
255 | 157 | blob = fu_firmware_get_bytes_with_patches(firmware, error); |
256 | 157 | if (blob == NULL) |
257 | 157 | return NULL; |
258 | 0 | fu_byte_array_append_bytes(buf, blob); |
259 | | |
260 | | /* footer */ |
261 | 0 | g_byte_array_append(buf, st_ftr->buf->data, st_ftr->buf->len); |
262 | 0 | return g_steal_pointer(&buf); |
263 | 157 | } |
264 | | |
265 | | static void |
266 | | fu_elantp_firmware_init(FuElantpFirmware *self) |
267 | 337 | { |
268 | 337 | } |
269 | | |
270 | | static void |
271 | | fu_elantp_firmware_class_init(FuElantpFirmwareClass *klass) |
272 | 1 | { |
273 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
274 | 1 | firmware_class->validate = fu_elantp_firmware_validate; |
275 | 1 | firmware_class->parse = fu_elantp_firmware_parse; |
276 | 1 | firmware_class->build = fu_elantp_firmware_build; |
277 | 1 | firmware_class->write = fu_elantp_firmware_write; |
278 | 1 | firmware_class->export = fu_elantp_firmware_export; |
279 | 1 | } |