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