/src/fwupd/plugins/pixart-rf/fu-pixart-rf-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2020 Jimmy Yu <Jimmy_yu@pixart.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | | #include "config.h" |
8 | | |
9 | | #include "fu-pixart-rf-common.h" |
10 | | #include "fu-pixart-rf-firmware.h" |
11 | | |
12 | 784 | #define PIXART_RF_FW_HEADER_SIZE 32 /* bytes */ |
13 | 653 | #define PIXART_RF_FW_HEADER_TAG_OFFSET 24 |
14 | | /* The hpac header is start from 821st byte from the end */ |
15 | 446 | #define PIXART_RF_FW_HEADER_HPAC_POS_FROM_END 821 |
16 | 87 | #define PIXART_RF_FW_HEADER_HPAC_VERSION_POS_FROM_END 823 |
17 | | |
18 | 627 | #define PIXART_RF_FW_HEADER_MAGIC 0x55AA55AA55AA55AA |
19 | | |
20 | | struct _FuPixartRfFirmware { |
21 | | FuFirmware parent_instance; |
22 | | gchar *model_name; |
23 | | gboolean is_hpac; |
24 | | }; |
25 | | |
26 | 401 | G_DEFINE_TYPE(FuPixartRfFirmware, fu_pixart_rf_firmware, FU_TYPE_FIRMWARE) |
27 | 401 | |
28 | 401 | const gchar * |
29 | 401 | fu_pixart_rf_firmware_get_model_name(FuPixartRfFirmware *self) |
30 | 401 | { |
31 | 0 | g_return_val_if_fail(FU_IS_PIXART_RF_FIRMWARE(self), NULL); |
32 | 0 | return self->model_name; |
33 | 0 | } |
34 | | |
35 | | static void |
36 | | fu_pixart_rf_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
37 | 0 | { |
38 | 0 | FuPixartRfFirmware *self = FU_PIXART_RF_FIRMWARE(firmware); |
39 | 0 | fu_xmlb_builder_insert_kv(bn, "model_name", self->model_name); |
40 | 0 | } |
41 | | |
42 | | static gboolean |
43 | | fu_pixart_rf_firmware_validate(FuFirmware *firmware, |
44 | | FuInputStream *stream, |
45 | | gsize offset, |
46 | | GError **error) |
47 | 399 | { |
48 | 399 | guint64 magic = 0; |
49 | 399 | FuPixartRfFirmware *self = FU_PIXART_RF_FIRMWARE(firmware); |
50 | 399 | gsize streamsz = 0; |
51 | | |
52 | | /* is a footer, in normal bin file, the header is starts from the 32nd byte from the end. */ |
53 | 399 | if (!fu_input_stream_size(stream, &streamsz, error)) |
54 | 0 | return FALSE; |
55 | 399 | if (streamsz < PIXART_RF_FW_HEADER_SIZE) { |
56 | 14 | g_set_error_literal(error, |
57 | 14 | FWUPD_ERROR, |
58 | 14 | FWUPD_ERROR_INVALID_FILE, |
59 | 14 | "stream was too small"); |
60 | 14 | return FALSE; |
61 | 14 | } |
62 | 385 | if (!fu_input_stream_read_u64(stream, |
63 | 385 | streamsz - PIXART_RF_FW_HEADER_SIZE + |
64 | 385 | PIXART_RF_FW_HEADER_TAG_OFFSET, |
65 | 385 | &magic, |
66 | 385 | G_BIG_ENDIAN, |
67 | 385 | error)) { |
68 | 0 | g_prefix_error_literal(error, "failed to read magic: "); |
69 | 0 | return FALSE; |
70 | 0 | } |
71 | 385 | if (magic != PIXART_RF_FW_HEADER_MAGIC) { |
72 | | /* if the magic number is not found, then start from the 821st byte from the end for |
73 | | * HPAC header */ |
74 | 260 | if (streamsz < PIXART_RF_FW_HEADER_HPAC_POS_FROM_END) { |
75 | 117 | g_set_error_literal(error, |
76 | 117 | FWUPD_ERROR, |
77 | 117 | FWUPD_ERROR_INVALID_FILE, |
78 | 117 | "stream too small for HPAC header"); |
79 | 117 | return FALSE; |
80 | 117 | } |
81 | 143 | if (!fu_input_stream_read_u64(stream, |
82 | 143 | streamsz - PIXART_RF_FW_HEADER_HPAC_POS_FROM_END + |
83 | 143 | PIXART_RF_FW_HEADER_TAG_OFFSET, |
84 | 143 | &magic, |
85 | 143 | G_BIG_ENDIAN, |
86 | 143 | error)) { |
87 | 0 | g_prefix_error_literal(error, "failed to read magic: "); |
88 | 0 | return FALSE; |
89 | 0 | } |
90 | 143 | if (magic != PIXART_RF_FW_HEADER_MAGIC) { |
91 | 99 | g_set_error(error, |
92 | 99 | FWUPD_ERROR, |
93 | 99 | FWUPD_ERROR_INVALID_FILE, |
94 | 99 | "invalid magic, expected 0x%08X got 0x%08X", |
95 | 99 | (guint32)PIXART_RF_FW_HEADER_MAGIC, |
96 | 99 | (guint32)magic); |
97 | 99 | return FALSE; |
98 | 99 | } |
99 | 44 | self->is_hpac = TRUE; |
100 | 44 | } |
101 | | |
102 | | /* success */ |
103 | 169 | return TRUE; |
104 | 385 | } |
105 | | |
106 | | static gboolean |
107 | | fu_pixart_rf_firmware_parse(FuFirmware *firmware, |
108 | | FuInputStream *stream, |
109 | | FuFirmwareParseFlags flags, |
110 | | GError **error) |
111 | 169 | { |
112 | 169 | FuPixartRfFirmware *self = FU_PIXART_RF_FIRMWARE(firmware); |
113 | 169 | gsize streamsz = 0; |
114 | 169 | guint32 version_raw = 0; |
115 | 169 | guint8 fw_header[PIXART_RF_FW_HEADER_SIZE] = {0}; |
116 | 169 | guint8 model_name[FU_PIXART_RF_DEVICE_MODEL_NAME_LEN] = {0x0}; |
117 | 169 | guint16 hpac_ver = 0; |
118 | 169 | g_autofree gchar *version = NULL; |
119 | | |
120 | | /* get fw header from buf */ |
121 | 169 | if (!fu_input_stream_size(stream, &streamsz, error)) |
122 | 0 | return FALSE; |
123 | 169 | if (fu_pixart_rf_firmware_is_hpac(self)) { |
124 | 44 | if (streamsz < PIXART_RF_FW_HEADER_HPAC_VERSION_POS_FROM_END) { |
125 | 1 | g_set_error_literal(error, |
126 | 1 | FWUPD_ERROR, |
127 | 1 | FWUPD_ERROR_INVALID_DATA, |
128 | 1 | "image is too small"); |
129 | 1 | return FALSE; |
130 | 1 | } |
131 | 43 | if (!fu_input_stream_read_safe(stream, |
132 | 43 | fw_header, |
133 | 43 | sizeof(fw_header), |
134 | 43 | 0x0, |
135 | 43 | streamsz - PIXART_RF_FW_HEADER_HPAC_POS_FROM_END, |
136 | 43 | sizeof(fw_header), |
137 | 43 | error)) { |
138 | 0 | g_prefix_error_literal(error, "failed to read fw header: "); |
139 | 0 | return FALSE; |
140 | 0 | } |
141 | 125 | } else { |
142 | 125 | if (streamsz < sizeof(fw_header)) { |
143 | 0 | g_set_error_literal(error, |
144 | 0 | FWUPD_ERROR, |
145 | 0 | FWUPD_ERROR_INVALID_DATA, |
146 | 0 | "image is too small"); |
147 | 0 | return FALSE; |
148 | 0 | } |
149 | 125 | if (!fu_input_stream_read_safe(stream, |
150 | 125 | fw_header, |
151 | 125 | sizeof(fw_header), |
152 | 125 | 0x0, |
153 | 125 | streamsz - sizeof(fw_header), |
154 | 125 | sizeof(fw_header), |
155 | 125 | error)) { |
156 | 0 | g_prefix_error_literal(error, "failed to read fw header: "); |
157 | 0 | return FALSE; |
158 | 0 | } |
159 | 125 | } |
160 | | |
161 | 168 | fu_dump_raw(G_LOG_DOMAIN, "fw header", fw_header, sizeof(fw_header)); |
162 | | |
163 | | /* set fw version */ |
164 | 168 | if (fu_pixart_rf_firmware_is_hpac(self)) { |
165 | 43 | if (!fu_input_stream_read_u16(stream, |
166 | 43 | streamsz - |
167 | 43 | PIXART_RF_FW_HEADER_HPAC_VERSION_POS_FROM_END, |
168 | 43 | &hpac_ver, |
169 | 43 | G_BIG_ENDIAN, |
170 | 43 | error)) |
171 | 0 | return FALSE; |
172 | | |
173 | 43 | version_raw = hpac_ver; |
174 | 43 | version = fu_pixart_rf_hpac_version_info_parse(hpac_ver); |
175 | 125 | } else { |
176 | 125 | version_raw = (((guint32)(fw_header[0] - '0')) << 16) + |
177 | 125 | (((guint32)(fw_header[2] - '0')) << 8) + |
178 | 125 | (guint32)(fw_header[4] - '0'); |
179 | 125 | version = fu_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_DELL_BIOS); |
180 | 125 | } |
181 | 168 | fu_firmware_set_version_raw(firmware, version_raw); |
182 | 168 | fu_firmware_set_version(firmware, version); /* nocheck:set-version */ |
183 | | |
184 | | /* set fw model name */ |
185 | 168 | if (!fu_memcpy_safe(model_name, |
186 | 168 | sizeof(model_name), |
187 | 168 | 0x0, |
188 | 168 | fw_header, |
189 | 168 | sizeof(fw_header), |
190 | 168 | 0x05, |
191 | 168 | sizeof(model_name), |
192 | 168 | error)) { |
193 | 0 | g_prefix_error_literal(error, "failed to get fw model name: "); |
194 | 0 | return FALSE; |
195 | 0 | } |
196 | 168 | self->model_name = fu_strsafe((const gchar *)model_name, sizeof(model_name)); |
197 | | |
198 | | /* success */ |
199 | 168 | return TRUE; |
200 | 168 | } |
201 | | |
202 | | static gboolean |
203 | | fu_pixart_rf_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) |
204 | 0 | { |
205 | 0 | FuPixartRfFirmware *self = FU_PIXART_RF_FIRMWARE(firmware); |
206 | 0 | const gchar *tmp; |
207 | | |
208 | | /* optional properties */ |
209 | 0 | tmp = xb_node_query_text(n, "model_name", NULL); |
210 | 0 | if (tmp != NULL) |
211 | 0 | self->model_name = g_strdup(tmp); |
212 | | |
213 | | /* success */ |
214 | 0 | return TRUE; |
215 | 0 | } |
216 | | |
217 | | static GByteArray * |
218 | | fu_pixart_rf_firmware_write(FuFirmware *firmware, GError **error) |
219 | 168 | { |
220 | 168 | FuPixartRfFirmware *self = FU_PIXART_RF_FIRMWARE(firmware); |
221 | 168 | guint8 fw_header[PIXART_RF_FW_HEADER_SIZE] = {0x0}; |
222 | 168 | guint64 version_raw = fu_firmware_get_version_raw(firmware); |
223 | 168 | g_autoptr(GByteArray) buf = NULL; |
224 | 168 | g_autoptr(GBytes) blob = NULL; |
225 | 168 | const guint8 tag[] = { |
226 | 168 | 0x55, |
227 | 168 | 0xAA, |
228 | 168 | 0x55, |
229 | 168 | 0xAA, |
230 | 168 | 0x55, |
231 | 168 | 0xAA, |
232 | 168 | 0x55, |
233 | 168 | 0xAA, |
234 | 168 | }; |
235 | | |
236 | | /* data first */ |
237 | 168 | blob = fu_firmware_get_bytes_with_patches(firmware, error); |
238 | 168 | if (blob == NULL) |
239 | 0 | return NULL; |
240 | | |
241 | 168 | if (fu_pixart_rf_firmware_is_hpac(self)) { |
242 | 43 | buf = g_byte_array_sized_new(g_bytes_get_size(blob)); |
243 | 43 | fu_byte_array_append_bytes(buf, blob); |
244 | 43 | return g_steal_pointer(&buf); |
245 | 43 | } |
246 | | |
247 | 125 | buf = g_byte_array_sized_new(g_bytes_get_size(blob) + sizeof(fw_header)); |
248 | 125 | fu_byte_array_append_bytes(buf, blob); |
249 | | |
250 | | /* footer */ |
251 | 125 | if (!fu_memcpy_safe(fw_header, |
252 | 125 | sizeof(fw_header), |
253 | 125 | PIXART_RF_FW_HEADER_TAG_OFFSET, /* dst */ |
254 | 125 | tag, |
255 | 125 | sizeof(tag), |
256 | 125 | 0x0, /* src */ |
257 | 125 | sizeof(tag), |
258 | 125 | error)) |
259 | 0 | return NULL; |
260 | 125 | fw_header[0] = ((version_raw >> 16) & 0xff) + '0'; |
261 | 125 | fw_header[1] = '.'; |
262 | 125 | fw_header[2] = ((version_raw >> 8) & 0xff) + '0'; |
263 | 125 | fw_header[3] = '.'; |
264 | 125 | fw_header[4] = ((version_raw >> 0) & 0xff) + '0'; |
265 | 125 | if (!g_ascii_isdigit(fw_header[0]) || !g_ascii_isdigit(fw_header[2]) || |
266 | 87 | !g_ascii_isdigit(fw_header[4])) { |
267 | 87 | g_set_error(error, |
268 | 87 | FWUPD_ERROR, |
269 | 87 | FWUPD_ERROR_NOT_SUPPORTED, |
270 | 87 | "cannot write invalid version number 0x%x", |
271 | 87 | (guint)version_raw); |
272 | 87 | return NULL; |
273 | 87 | } |
274 | 38 | if (self->model_name != NULL) { |
275 | 37 | gsize model_namesz = |
276 | 37 | MIN(strlen(self->model_name), FU_PIXART_RF_DEVICE_MODEL_NAME_LEN); |
277 | 37 | if (!fu_memcpy_safe(fw_header, |
278 | 37 | sizeof(fw_header), |
279 | 37 | 0x05, /* dst */ |
280 | 37 | (const guint8 *)self->model_name, |
281 | 37 | model_namesz, |
282 | 37 | 0x0, /* src */ |
283 | 37 | model_namesz, |
284 | 37 | error)) { |
285 | 0 | g_prefix_error_literal(error, "failed to get fw model name: "); |
286 | 0 | return NULL; |
287 | 0 | } |
288 | 37 | } |
289 | | |
290 | 38 | g_byte_array_append(buf, fw_header, sizeof(fw_header)); |
291 | 38 | return g_steal_pointer(&buf); |
292 | 38 | } |
293 | | |
294 | | static void |
295 | | fu_pixart_rf_firmware_init(FuPixartRfFirmware *self) |
296 | 399 | { |
297 | 399 | } |
298 | | |
299 | | static void |
300 | | fu_pixart_rf_firmware_finalize(GObject *object) |
301 | 399 | { |
302 | 399 | FuPixartRfFirmware *self = FU_PIXART_RF_FIRMWARE(object); |
303 | 399 | g_free(self->model_name); |
304 | 399 | G_OBJECT_CLASS(fu_pixart_rf_firmware_parent_class)->finalize(object); |
305 | 399 | } |
306 | | |
307 | | static void |
308 | | fu_pixart_rf_firmware_class_init(FuPixartRfFirmwareClass *klass) |
309 | 1 | { |
310 | 1 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
311 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
312 | 1 | object_class->finalize = fu_pixart_rf_firmware_finalize; |
313 | 1 | firmware_class->validate = fu_pixart_rf_firmware_validate; |
314 | 1 | firmware_class->parse = fu_pixart_rf_firmware_parse; |
315 | 1 | firmware_class->build = fu_pixart_rf_firmware_build; |
316 | 1 | firmware_class->write = fu_pixart_rf_firmware_write; |
317 | 1 | firmware_class->export = fu_pixart_rf_firmware_export; |
318 | 1 | fu_firmware_set_size_max(firmware_class, 16 * FU_MB); |
319 | 1 | } |
320 | | |
321 | | FuFirmware * |
322 | | fu_pixart_rf_firmware_new(void) |
323 | 0 | { |
324 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_PIXART_RF_FIRMWARE, NULL)); |
325 | 0 | } |
326 | | |
327 | | gboolean |
328 | | fu_pixart_rf_firmware_is_hpac(FuPixartRfFirmware *self) |
329 | 505 | { |
330 | 505 | return self->is_hpac; |
331 | 505 | } |