]>
Commit | Line | Data |
---|---|---|
9dd0abd2 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
c04c674f RB |
2 | /* |
3 | * NCI based driver for Samsung S3FWRN5 NFC chip | |
4 | * | |
5 | * Copyright (C) 2015 Samsung Electrnoics | |
6 | * Robert Baldyga <r.baldyga@samsung.com> | |
c04c674f RB |
7 | */ |
8 | ||
9 | #include <linux/completion.h> | |
10 | #include <linux/firmware.h> | |
4a31340b | 11 | #include <crypto/hash.h> |
c04c674f RB |
12 | #include <crypto/sha.h> |
13 | ||
14 | #include "s3fwrn5.h" | |
15 | #include "firmware.h" | |
16 | ||
17 | struct s3fwrn5_fw_version { | |
18 | __u8 major; | |
19 | __u8 build1; | |
20 | __u8 build2; | |
21 | __u8 target; | |
22 | }; | |
23 | ||
24 | static int s3fwrn5_fw_send_msg(struct s3fwrn5_fw_info *fw_info, | |
25 | struct sk_buff *msg, struct sk_buff **rsp) | |
26 | { | |
27 | struct s3fwrn5_info *info = | |
28 | container_of(fw_info, struct s3fwrn5_info, fw_info); | |
29 | long ret; | |
30 | ||
31 | reinit_completion(&fw_info->completion); | |
32 | ||
33 | ret = s3fwrn5_write(info, msg); | |
34 | if (ret < 0) | |
35 | return ret; | |
36 | ||
37 | ret = wait_for_completion_interruptible_timeout( | |
38 | &fw_info->completion, msecs_to_jiffies(1000)); | |
39 | if (ret < 0) | |
40 | return ret; | |
41 | else if (ret == 0) | |
42 | return -ENXIO; | |
43 | ||
44 | if (!fw_info->rsp) | |
45 | return -EINVAL; | |
46 | ||
47 | *rsp = fw_info->rsp; | |
48 | fw_info->rsp = NULL; | |
49 | ||
50 | return 0; | |
51 | } | |
52 | ||
53 | static int s3fwrn5_fw_prep_msg(struct s3fwrn5_fw_info *fw_info, | |
54 | struct sk_buff **msg, u8 type, u8 code, const void *data, u16 len) | |
55 | { | |
56 | struct s3fwrn5_fw_header hdr; | |
57 | struct sk_buff *skb; | |
58 | ||
59 | hdr.type = type | fw_info->parity; | |
60 | fw_info->parity ^= 0x80; | |
61 | hdr.code = code; | |
62 | hdr.len = len; | |
63 | ||
64 | skb = alloc_skb(S3FWRN5_FW_HDR_SIZE + len, GFP_KERNEL); | |
65 | if (!skb) | |
66 | return -ENOMEM; | |
67 | ||
59ae1d12 | 68 | skb_put_data(skb, &hdr, S3FWRN5_FW_HDR_SIZE); |
c04c674f | 69 | if (len) |
59ae1d12 | 70 | skb_put_data(skb, data, len); |
c04c674f RB |
71 | |
72 | *msg = skb; | |
73 | ||
74 | return 0; | |
75 | } | |
76 | ||
77 | static int s3fwrn5_fw_get_bootinfo(struct s3fwrn5_fw_info *fw_info, | |
78 | struct s3fwrn5_fw_cmd_get_bootinfo_rsp *bootinfo) | |
79 | { | |
80 | struct sk_buff *msg, *rsp = NULL; | |
81 | struct s3fwrn5_fw_header *hdr; | |
82 | int ret; | |
83 | ||
84 | /* Send GET_BOOTINFO command */ | |
85 | ||
86 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD, | |
87 | S3FWRN5_FW_CMD_GET_BOOTINFO, NULL, 0); | |
88 | if (ret < 0) | |
89 | return ret; | |
90 | ||
91 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | |
92 | kfree_skb(msg); | |
93 | if (ret < 0) | |
94 | return ret; | |
95 | ||
96 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | |
97 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | |
98 | ret = -EINVAL; | |
99 | goto out; | |
100 | } | |
101 | ||
102 | memcpy(bootinfo, rsp->data + S3FWRN5_FW_HDR_SIZE, 10); | |
103 | ||
104 | out: | |
105 | kfree_skb(rsp); | |
106 | return ret; | |
107 | } | |
108 | ||
109 | static int s3fwrn5_fw_enter_update_mode(struct s3fwrn5_fw_info *fw_info, | |
110 | const void *hash_data, u16 hash_size, | |
111 | const void *sig_data, u16 sig_size) | |
112 | { | |
113 | struct s3fwrn5_fw_cmd_enter_updatemode args; | |
114 | struct sk_buff *msg, *rsp = NULL; | |
115 | struct s3fwrn5_fw_header *hdr; | |
116 | int ret; | |
117 | ||
118 | /* Send ENTER_UPDATE_MODE command */ | |
119 | ||
120 | args.hashcode_size = hash_size; | |
121 | args.signature_size = sig_size; | |
122 | ||
123 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD, | |
124 | S3FWRN5_FW_CMD_ENTER_UPDATE_MODE, &args, sizeof(args)); | |
125 | if (ret < 0) | |
126 | return ret; | |
127 | ||
128 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | |
129 | kfree_skb(msg); | |
130 | if (ret < 0) | |
131 | return ret; | |
132 | ||
133 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | |
134 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | |
135 | ret = -EPROTO; | |
136 | goto out; | |
137 | } | |
138 | ||
139 | kfree_skb(rsp); | |
140 | ||
141 | /* Send hashcode data */ | |
142 | ||
143 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_DATA, 0, | |
144 | hash_data, hash_size); | |
145 | if (ret < 0) | |
146 | return ret; | |
147 | ||
148 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | |
149 | kfree_skb(msg); | |
150 | if (ret < 0) | |
151 | return ret; | |
152 | ||
153 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | |
154 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | |
155 | ret = -EPROTO; | |
156 | goto out; | |
157 | } | |
158 | ||
159 | kfree_skb(rsp); | |
160 | ||
161 | /* Send signature data */ | |
162 | ||
163 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_DATA, 0, | |
164 | sig_data, sig_size); | |
165 | if (ret < 0) | |
166 | return ret; | |
167 | ||
168 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | |
169 | kfree_skb(msg); | |
170 | if (ret < 0) | |
171 | return ret; | |
172 | ||
173 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | |
174 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) | |
175 | ret = -EPROTO; | |
176 | ||
177 | out: | |
178 | kfree_skb(rsp); | |
179 | return ret; | |
180 | } | |
181 | ||
182 | static int s3fwrn5_fw_update_sector(struct s3fwrn5_fw_info *fw_info, | |
183 | u32 base_addr, const void *data) | |
184 | { | |
185 | struct s3fwrn5_fw_cmd_update_sector args; | |
186 | struct sk_buff *msg, *rsp = NULL; | |
187 | struct s3fwrn5_fw_header *hdr; | |
188 | int ret, i; | |
189 | ||
190 | /* Send UPDATE_SECTOR command */ | |
191 | ||
192 | args.base_address = base_addr; | |
193 | ||
194 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD, | |
195 | S3FWRN5_FW_CMD_UPDATE_SECTOR, &args, sizeof(args)); | |
196 | if (ret < 0) | |
197 | return ret; | |
198 | ||
199 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | |
200 | kfree_skb(msg); | |
201 | if (ret < 0) | |
202 | return ret; | |
203 | ||
204 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | |
205 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | |
206 | ret = -EPROTO; | |
207 | goto err; | |
208 | } | |
209 | ||
210 | kfree_skb(rsp); | |
211 | ||
212 | /* Send data split into 256-byte packets */ | |
213 | ||
214 | for (i = 0; i < 16; ++i) { | |
215 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, | |
216 | S3FWRN5_FW_MSG_DATA, 0, data+256*i, 256); | |
217 | if (ret < 0) | |
218 | break; | |
219 | ||
220 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | |
221 | kfree_skb(msg); | |
222 | if (ret < 0) | |
223 | break; | |
224 | ||
225 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | |
226 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | |
227 | ret = -EPROTO; | |
228 | goto err; | |
229 | } | |
230 | ||
231 | kfree_skb(rsp); | |
232 | } | |
233 | ||
234 | return ret; | |
235 | ||
236 | err: | |
237 | kfree_skb(rsp); | |
238 | return ret; | |
239 | } | |
240 | ||
241 | static int s3fwrn5_fw_complete_update_mode(struct s3fwrn5_fw_info *fw_info) | |
242 | { | |
243 | struct sk_buff *msg, *rsp = NULL; | |
244 | struct s3fwrn5_fw_header *hdr; | |
245 | int ret; | |
246 | ||
247 | /* Send COMPLETE_UPDATE_MODE command */ | |
248 | ||
249 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD, | |
250 | S3FWRN5_FW_CMD_COMPLETE_UPDATE_MODE, NULL, 0); | |
251 | if (ret < 0) | |
252 | return ret; | |
253 | ||
254 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | |
255 | kfree_skb(msg); | |
256 | if (ret < 0) | |
257 | return ret; | |
258 | ||
259 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | |
260 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) | |
261 | ret = -EPROTO; | |
262 | ||
263 | kfree_skb(rsp); | |
264 | ||
265 | return ret; | |
266 | } | |
267 | ||
268 | /* | |
269 | * Firmware header stucture: | |
270 | * | |
271 | * 0x00 - 0x0B : Date and time string (w/o NUL termination) | |
272 | * 0x10 - 0x13 : Firmware version | |
273 | * 0x14 - 0x17 : Signature address | |
274 | * 0x18 - 0x1B : Signature size | |
275 | * 0x1C - 0x1F : Firmware image address | |
276 | * 0x20 - 0x23 : Firmware sectors count | |
277 | * 0x24 - 0x27 : Custom signature address | |
278 | * 0x28 - 0x2B : Custom signature size | |
279 | */ | |
280 | ||
281 | #define S3FWRN5_FW_IMAGE_HEADER_SIZE 44 | |
282 | ||
283 | static int s3fwrn5_fw_request_firmware(struct s3fwrn5_fw_info *fw_info) | |
284 | { | |
285 | struct s3fwrn5_fw_image *fw = &fw_info->fw; | |
286 | u32 sig_off; | |
287 | u32 image_off; | |
288 | u32 custom_sig_off; | |
289 | int ret; | |
290 | ||
291 | ret = request_firmware(&fw->fw, fw_info->fw_name, | |
292 | &fw_info->ndev->nfc_dev->dev); | |
293 | if (ret < 0) | |
294 | return ret; | |
295 | ||
296 | if (fw->fw->size < S3FWRN5_FW_IMAGE_HEADER_SIZE) | |
297 | return -EINVAL; | |
298 | ||
299 | memcpy(fw->date, fw->fw->data + 0x00, 12); | |
300 | fw->date[12] = '\0'; | |
301 | ||
302 | memcpy(&fw->version, fw->fw->data + 0x10, 4); | |
303 | ||
304 | memcpy(&sig_off, fw->fw->data + 0x14, 4); | |
305 | fw->sig = fw->fw->data + sig_off; | |
306 | memcpy(&fw->sig_size, fw->fw->data + 0x18, 4); | |
307 | ||
308 | memcpy(&image_off, fw->fw->data + 0x1C, 4); | |
309 | fw->image = fw->fw->data + image_off; | |
310 | memcpy(&fw->image_sectors, fw->fw->data + 0x20, 4); | |
311 | ||
312 | memcpy(&custom_sig_off, fw->fw->data + 0x24, 4); | |
313 | fw->custom_sig = fw->fw->data + custom_sig_off; | |
314 | memcpy(&fw->custom_sig_size, fw->fw->data + 0x28, 4); | |
315 | ||
316 | return 0; | |
317 | } | |
318 | ||
319 | static void s3fwrn5_fw_release_firmware(struct s3fwrn5_fw_info *fw_info) | |
320 | { | |
321 | release_firmware(fw_info->fw.fw); | |
322 | } | |
323 | ||
324 | static int s3fwrn5_fw_get_base_addr( | |
325 | struct s3fwrn5_fw_cmd_get_bootinfo_rsp *bootinfo, u32 *base_addr) | |
326 | { | |
327 | int i; | |
5057f664 | 328 | static const struct { |
c04c674f RB |
329 | u8 version[4]; |
330 | u32 base_addr; | |
331 | } match[] = { | |
332 | {{0x05, 0x00, 0x00, 0x00}, 0x00005000}, | |
333 | {{0x05, 0x00, 0x00, 0x01}, 0x00003000}, | |
334 | {{0x05, 0x00, 0x00, 0x02}, 0x00003000}, | |
335 | {{0x05, 0x00, 0x00, 0x03}, 0x00003000}, | |
336 | {{0x05, 0x00, 0x00, 0x05}, 0x00003000} | |
337 | }; | |
338 | ||
339 | for (i = 0; i < ARRAY_SIZE(match); ++i) | |
340 | if (bootinfo->hw_version[0] == match[i].version[0] && | |
341 | bootinfo->hw_version[1] == match[i].version[1] && | |
342 | bootinfo->hw_version[3] == match[i].version[3]) { | |
343 | *base_addr = match[i].base_addr; | |
344 | return 0; | |
345 | } | |
346 | ||
347 | return -EINVAL; | |
348 | } | |
349 | ||
350 | static inline bool | |
351 | s3fwrn5_fw_is_custom(struct s3fwrn5_fw_cmd_get_bootinfo_rsp *bootinfo) | |
352 | { | |
353 | return !!bootinfo->hw_version[2]; | |
354 | } | |
355 | ||
356 | int s3fwrn5_fw_setup(struct s3fwrn5_fw_info *fw_info) | |
357 | { | |
358 | struct s3fwrn5_fw_cmd_get_bootinfo_rsp bootinfo; | |
359 | int ret; | |
360 | ||
361 | /* Get firmware data */ | |
362 | ||
363 | ret = s3fwrn5_fw_request_firmware(fw_info); | |
364 | if (ret < 0) { | |
365 | dev_err(&fw_info->ndev->nfc_dev->dev, | |
366 | "Failed to get fw file, ret=%02x\n", ret); | |
367 | return ret; | |
368 | } | |
369 | ||
370 | /* Get bootloader info */ | |
371 | ||
372 | ret = s3fwrn5_fw_get_bootinfo(fw_info, &bootinfo); | |
373 | if (ret < 0) { | |
374 | dev_err(&fw_info->ndev->nfc_dev->dev, | |
375 | "Failed to get bootinfo, ret=%02x\n", ret); | |
376 | goto err; | |
377 | } | |
378 | ||
379 | /* Match hardware version to obtain firmware base address */ | |
380 | ||
381 | ret = s3fwrn5_fw_get_base_addr(&bootinfo, &fw_info->base_addr); | |
382 | if (ret < 0) { | |
383 | dev_err(&fw_info->ndev->nfc_dev->dev, | |
384 | "Unknown hardware version\n"); | |
385 | goto err; | |
386 | } | |
387 | ||
388 | fw_info->sector_size = bootinfo.sector_size; | |
389 | ||
390 | fw_info->sig_size = s3fwrn5_fw_is_custom(&bootinfo) ? | |
391 | fw_info->fw.custom_sig_size : fw_info->fw.sig_size; | |
392 | fw_info->sig = s3fwrn5_fw_is_custom(&bootinfo) ? | |
393 | fw_info->fw.custom_sig : fw_info->fw.sig; | |
394 | ||
395 | return 0; | |
396 | ||
397 | err: | |
398 | s3fwrn5_fw_release_firmware(fw_info); | |
399 | return ret; | |
400 | } | |
401 | ||
402 | bool s3fwrn5_fw_check_version(struct s3fwrn5_fw_info *fw_info, u32 version) | |
403 | { | |
404 | struct s3fwrn5_fw_version *new = (void *) &fw_info->fw.version; | |
405 | struct s3fwrn5_fw_version *old = (void *) &version; | |
406 | ||
407 | if (new->major > old->major) | |
408 | return true; | |
409 | if (new->build1 > old->build1) | |
410 | return true; | |
411 | if (new->build2 > old->build2) | |
412 | return true; | |
413 | ||
414 | return false; | |
415 | } | |
416 | ||
417 | int s3fwrn5_fw_download(struct s3fwrn5_fw_info *fw_info) | |
418 | { | |
419 | struct s3fwrn5_fw_image *fw = &fw_info->fw; | |
420 | u8 hash_data[SHA1_DIGEST_SIZE]; | |
4a31340b | 421 | struct crypto_shash *tfm; |
c04c674f RB |
422 | u32 image_size, off; |
423 | int ret; | |
424 | ||
425 | image_size = fw_info->sector_size * fw->image_sectors; | |
426 | ||
427 | /* Compute SHA of firmware data */ | |
428 | ||
4a31340b HX |
429 | tfm = crypto_alloc_shash("sha1", 0, 0); |
430 | if (IS_ERR(tfm)) { | |
431 | ret = PTR_ERR(tfm); | |
432 | dev_err(&fw_info->ndev->nfc_dev->dev, | |
433 | "Cannot allocate shash (code=%d)\n", ret); | |
434 | goto out; | |
435 | } | |
436 | ||
96a5aa72 | 437 | ret = crypto_shash_tfm_digest(tfm, fw->image, image_size, hash_data); |
4a31340b HX |
438 | |
439 | crypto_free_shash(tfm); | |
440 | if (ret) { | |
441 | dev_err(&fw_info->ndev->nfc_dev->dev, | |
442 | "Cannot compute hash (code=%d)\n", ret); | |
443 | goto out; | |
444 | } | |
c04c674f RB |
445 | |
446 | /* Firmware update process */ | |
447 | ||
448 | dev_info(&fw_info->ndev->nfc_dev->dev, | |
449 | "Firmware update: %s\n", fw_info->fw_name); | |
450 | ||
451 | ret = s3fwrn5_fw_enter_update_mode(fw_info, hash_data, | |
452 | SHA1_DIGEST_SIZE, fw_info->sig, fw_info->sig_size); | |
453 | if (ret < 0) { | |
454 | dev_err(&fw_info->ndev->nfc_dev->dev, | |
455 | "Unable to enter update mode\n"); | |
456 | goto out; | |
457 | } | |
458 | ||
459 | for (off = 0; off < image_size; off += fw_info->sector_size) { | |
460 | ret = s3fwrn5_fw_update_sector(fw_info, | |
461 | fw_info->base_addr + off, fw->image + off); | |
462 | if (ret < 0) { | |
463 | dev_err(&fw_info->ndev->nfc_dev->dev, | |
464 | "Firmware update error (code=%d)\n", ret); | |
465 | goto out; | |
466 | } | |
467 | } | |
468 | ||
469 | ret = s3fwrn5_fw_complete_update_mode(fw_info); | |
470 | if (ret < 0) { | |
471 | dev_err(&fw_info->ndev->nfc_dev->dev, | |
472 | "Unable to complete update mode\n"); | |
473 | goto out; | |
474 | } | |
475 | ||
476 | dev_info(&fw_info->ndev->nfc_dev->dev, | |
477 | "Firmware update: success\n"); | |
478 | ||
479 | out: | |
480 | return ret; | |
481 | } | |
482 | ||
483 | void s3fwrn5_fw_init(struct s3fwrn5_fw_info *fw_info, const char *fw_name) | |
484 | { | |
485 | fw_info->parity = 0x00; | |
486 | fw_info->rsp = NULL; | |
487 | fw_info->fw.fw = NULL; | |
488 | strcpy(fw_info->fw_name, fw_name); | |
489 | init_completion(&fw_info->completion); | |
490 | } | |
491 | ||
492 | void s3fwrn5_fw_cleanup(struct s3fwrn5_fw_info *fw_info) | |
493 | { | |
494 | s3fwrn5_fw_release_firmware(fw_info); | |
495 | } | |
496 | ||
497 | int s3fwrn5_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb) | |
498 | { | |
499 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); | |
500 | struct s3fwrn5_fw_info *fw_info = &info->fw_info; | |
501 | ||
615f22f5 AP |
502 | if (WARN_ON(fw_info->rsp)) { |
503 | kfree_skb(skb); | |
504 | return -EINVAL; | |
505 | } | |
c04c674f RB |
506 | |
507 | fw_info->rsp = skb; | |
508 | ||
509 | complete(&fw_info->completion); | |
510 | ||
511 | return 0; | |
512 | } |