1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Generic driver for NXP NCI NFC chips |
4 | * |
5 | * Copyright (C) 2014 NXP Semiconductors All rights reserved. |
6 | * |
7 | * Author: Clément Perrochaud <clement.perrochaud@nxp.com> |
8 | * |
9 | * Derived from PN544 device driver: |
10 | * Copyright (C) 2012 Intel Corporation. All rights reserved. |
11 | */ |
12 | |
13 | #include <linux/completion.h> |
14 | #include <linux/firmware.h> |
15 | #include <linux/nfc.h> |
16 | #include <asm/unaligned.h> |
17 | |
18 | #include "nxp-nci.h" |
19 | |
20 | /* Crypto operations can take up to 30 seconds */ |
21 | #define NXP_NCI_FW_ANSWER_TIMEOUT msecs_to_jiffies(30000) |
22 | |
23 | #define NXP_NCI_FW_CMD_RESET 0xF0 |
24 | #define NXP_NCI_FW_CMD_GETVERSION 0xF1 |
25 | #define NXP_NCI_FW_CMD_CHECKINTEGRITY 0xE0 |
26 | #define NXP_NCI_FW_CMD_WRITE 0xC0 |
27 | #define NXP_NCI_FW_CMD_READ 0xA2 |
28 | #define NXP_NCI_FW_CMD_GETSESSIONSTATE 0xF2 |
29 | #define NXP_NCI_FW_CMD_LOG 0xA7 |
30 | #define NXP_NCI_FW_CMD_FORCE 0xD0 |
31 | #define NXP_NCI_FW_CMD_GET_DIE_ID 0xF4 |
32 | |
33 | #define NXP_NCI_FW_CHUNK_FLAG 0x0400 |
34 | |
35 | #define NXP_NCI_FW_RESULT_OK 0x00 |
36 | #define NXP_NCI_FW_RESULT_INVALID_ADDR 0x01 |
37 | #define NXP_NCI_FW_RESULT_GENERIC_ERROR 0x02 |
38 | #define NXP_NCI_FW_RESULT_UNKNOWN_CMD 0x0B |
39 | #define NXP_NCI_FW_RESULT_ABORTED_CMD 0x0C |
40 | #define NXP_NCI_FW_RESULT_PLL_ERROR 0x0D |
41 | #define NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR 0x1E |
42 | #define NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR 0x1F |
43 | #define NXP_NCI_FW_RESULT_MEM_BSY 0x20 |
44 | #define NXP_NCI_FW_RESULT_SIGNATURE_ERROR 0x21 |
45 | #define NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR 0x24 |
46 | #define NXP_NCI_FW_RESULT_PROTOCOL_ERROR 0x28 |
47 | #define NXP_NCI_FW_RESULT_SFWU_DEGRADED 0x2A |
48 | #define NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK 0x2D |
49 | #define NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK 0x2E |
50 | #define NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5 0xC5 |
51 | |
52 | void nxp_nci_fw_work_complete(struct nxp_nci_info *info, int result) |
53 | { |
54 | struct nxp_nci_fw_info *fw_info = &info->fw_info; |
55 | int r; |
56 | |
57 | if (info->phy_ops->set_mode) { |
58 | r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD); |
59 | if (r < 0 && result == 0) |
60 | result = -r; |
61 | } |
62 | |
63 | info->mode = NXP_NCI_MODE_COLD; |
64 | |
65 | if (fw_info->fw) { |
66 | release_firmware(fw: fw_info->fw); |
67 | fw_info->fw = NULL; |
68 | } |
69 | |
70 | nfc_fw_download_done(dev: info->ndev->nfc_dev, firmware_name: fw_info->name, result: (u32) -result); |
71 | } |
72 | |
73 | /* crc_ccitt cannot be used since it is computed MSB first and not LSB first */ |
74 | static u16 nxp_nci_fw_crc(u8 const *buffer, size_t len) |
75 | { |
76 | u16 crc = 0xffff; |
77 | |
78 | while (len--) { |
79 | crc = ((crc >> 8) | (crc << 8)) ^ *buffer++; |
80 | crc ^= (crc & 0xff) >> 4; |
81 | crc ^= (crc & 0xff) << 12; |
82 | crc ^= (crc & 0xff) << 5; |
83 | } |
84 | |
85 | return crc; |
86 | } |
87 | |
88 | static int nxp_nci_fw_send_chunk(struct nxp_nci_info *info) |
89 | { |
90 | struct nxp_nci_fw_info *fw_info = &info->fw_info; |
91 | u16 , crc; |
92 | struct sk_buff *skb; |
93 | size_t chunk_len; |
94 | size_t remaining_len; |
95 | int r; |
96 | |
97 | skb = nci_skb_alloc(ndev: info->ndev, len: info->max_payload, GFP_KERNEL); |
98 | if (!skb) |
99 | return -ENOMEM; |
100 | |
101 | chunk_len = info->max_payload - NXP_NCI_FW_HDR_LEN - NXP_NCI_FW_CRC_LEN; |
102 | remaining_len = fw_info->frame_size - fw_info->written; |
103 | |
104 | if (remaining_len > chunk_len) { |
105 | header = NXP_NCI_FW_CHUNK_FLAG; |
106 | } else { |
107 | chunk_len = remaining_len; |
108 | header = 0x0000; |
109 | } |
110 | |
111 | header |= chunk_len & NXP_NCI_FW_FRAME_LEN_MASK; |
112 | put_unaligned_be16(val: header, p: skb_put(skb, NXP_NCI_FW_HDR_LEN)); |
113 | |
114 | skb_put_data(skb, data: fw_info->data + fw_info->written, len: chunk_len); |
115 | |
116 | crc = nxp_nci_fw_crc(buffer: skb->data, len: chunk_len + NXP_NCI_FW_HDR_LEN); |
117 | put_unaligned_be16(val: crc, p: skb_put(skb, NXP_NCI_FW_CRC_LEN)); |
118 | |
119 | r = info->phy_ops->write(info->phy_id, skb); |
120 | if (r >= 0) |
121 | r = chunk_len; |
122 | |
123 | kfree_skb(skb); |
124 | |
125 | return r; |
126 | } |
127 | |
128 | static int nxp_nci_fw_send(struct nxp_nci_info *info) |
129 | { |
130 | struct nxp_nci_fw_info *fw_info = &info->fw_info; |
131 | long completion_rc; |
132 | int r; |
133 | |
134 | reinit_completion(x: &fw_info->cmd_completion); |
135 | |
136 | if (fw_info->written == 0) { |
137 | fw_info->frame_size = get_unaligned_be16(p: fw_info->data) & |
138 | NXP_NCI_FW_FRAME_LEN_MASK; |
139 | fw_info->data += NXP_NCI_FW_HDR_LEN; |
140 | fw_info->size -= NXP_NCI_FW_HDR_LEN; |
141 | } |
142 | |
143 | if (fw_info->frame_size > fw_info->size) |
144 | return -EMSGSIZE; |
145 | |
146 | r = nxp_nci_fw_send_chunk(info); |
147 | if (r < 0) |
148 | return r; |
149 | |
150 | fw_info->written += r; |
151 | |
152 | if (*fw_info->data == NXP_NCI_FW_CMD_RESET) { |
153 | fw_info->cmd_result = 0; |
154 | if (fw_info->fw) |
155 | schedule_work(work: &fw_info->work); |
156 | } else { |
157 | completion_rc = wait_for_completion_interruptible_timeout( |
158 | x: &fw_info->cmd_completion, NXP_NCI_FW_ANSWER_TIMEOUT); |
159 | if (completion_rc == 0) |
160 | return -ETIMEDOUT; |
161 | } |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | void nxp_nci_fw_work(struct work_struct *work) |
167 | { |
168 | struct nxp_nci_info *info; |
169 | struct nxp_nci_fw_info *fw_info; |
170 | int r; |
171 | |
172 | fw_info = container_of(work, struct nxp_nci_fw_info, work); |
173 | info = container_of(fw_info, struct nxp_nci_info, fw_info); |
174 | |
175 | mutex_lock(&info->info_lock); |
176 | |
177 | r = fw_info->cmd_result; |
178 | if (r < 0) |
179 | goto exit_work; |
180 | |
181 | if (fw_info->written == fw_info->frame_size) { |
182 | fw_info->data += fw_info->frame_size; |
183 | fw_info->size -= fw_info->frame_size; |
184 | fw_info->written = 0; |
185 | } |
186 | |
187 | if (fw_info->size > 0) |
188 | r = nxp_nci_fw_send(info); |
189 | |
190 | exit_work: |
191 | if (r < 0 || fw_info->size == 0) |
192 | nxp_nci_fw_work_complete(info, result: r); |
193 | mutex_unlock(lock: &info->info_lock); |
194 | } |
195 | |
196 | int nxp_nci_fw_download(struct nci_dev *ndev, const char *firmware_name) |
197 | { |
198 | struct nxp_nci_info *info = nci_get_drvdata(ndev); |
199 | struct nxp_nci_fw_info *fw_info = &info->fw_info; |
200 | int r; |
201 | |
202 | mutex_lock(&info->info_lock); |
203 | |
204 | if (!info->phy_ops->set_mode || !info->phy_ops->write) { |
205 | r = -ENOTSUPP; |
206 | goto fw_download_exit; |
207 | } |
208 | |
209 | if (!firmware_name || firmware_name[0] == '\0') { |
210 | r = -EINVAL; |
211 | goto fw_download_exit; |
212 | } |
213 | |
214 | strcpy(p: fw_info->name, q: firmware_name); |
215 | |
216 | r = request_firmware(fw: &fw_info->fw, name: firmware_name, |
217 | device: ndev->nfc_dev->dev.parent); |
218 | if (r < 0) |
219 | goto fw_download_exit; |
220 | |
221 | r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_FW); |
222 | if (r < 0) { |
223 | release_firmware(fw: fw_info->fw); |
224 | goto fw_download_exit; |
225 | } |
226 | |
227 | info->mode = NXP_NCI_MODE_FW; |
228 | |
229 | fw_info->data = fw_info->fw->data; |
230 | fw_info->size = fw_info->fw->size; |
231 | fw_info->written = 0; |
232 | fw_info->frame_size = 0; |
233 | fw_info->cmd_result = 0; |
234 | |
235 | schedule_work(work: &fw_info->work); |
236 | |
237 | fw_download_exit: |
238 | mutex_unlock(lock: &info->info_lock); |
239 | return r; |
240 | } |
241 | |
242 | static int nxp_nci_fw_read_status(u8 stat) |
243 | { |
244 | switch (stat) { |
245 | case NXP_NCI_FW_RESULT_OK: |
246 | return 0; |
247 | case NXP_NCI_FW_RESULT_INVALID_ADDR: |
248 | return -EINVAL; |
249 | case NXP_NCI_FW_RESULT_UNKNOWN_CMD: |
250 | return -EINVAL; |
251 | case NXP_NCI_FW_RESULT_ABORTED_CMD: |
252 | return -EMSGSIZE; |
253 | case NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR: |
254 | return -EADDRNOTAVAIL; |
255 | case NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR: |
256 | return -ENOBUFS; |
257 | case NXP_NCI_FW_RESULT_MEM_BSY: |
258 | return -ENOKEY; |
259 | case NXP_NCI_FW_RESULT_SIGNATURE_ERROR: |
260 | return -EKEYREJECTED; |
261 | case NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR: |
262 | return -EALREADY; |
263 | case NXP_NCI_FW_RESULT_PROTOCOL_ERROR: |
264 | return -EPROTO; |
265 | case NXP_NCI_FW_RESULT_SFWU_DEGRADED: |
266 | return -EHWPOISON; |
267 | case NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK: |
268 | return 0; |
269 | case NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK: |
270 | return 0; |
271 | case NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5: |
272 | return -EINVAL; |
273 | default: |
274 | return -EIO; |
275 | } |
276 | } |
277 | |
278 | static u16 nxp_nci_fw_check_crc(struct sk_buff *skb) |
279 | { |
280 | u16 crc, frame_crc; |
281 | size_t len = skb->len - NXP_NCI_FW_CRC_LEN; |
282 | |
283 | crc = nxp_nci_fw_crc(buffer: skb->data, len); |
284 | frame_crc = get_unaligned_be16(p: skb->data + len); |
285 | |
286 | return (crc ^ frame_crc); |
287 | } |
288 | |
289 | void nxp_nci_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb) |
290 | { |
291 | struct nxp_nci_info *info = nci_get_drvdata(ndev); |
292 | struct nxp_nci_fw_info *fw_info = &info->fw_info; |
293 | |
294 | complete(&fw_info->cmd_completion); |
295 | |
296 | if (skb) { |
297 | if (nxp_nci_fw_check_crc(skb) != 0x00) |
298 | fw_info->cmd_result = -EBADMSG; |
299 | else |
300 | fw_info->cmd_result = nxp_nci_fw_read_status(stat: *(u8 *)skb_pull(skb, NXP_NCI_FW_HDR_LEN)); |
301 | kfree_skb(skb); |
302 | } else { |
303 | fw_info->cmd_result = -EIO; |
304 | } |
305 | |
306 | if (fw_info->fw) |
307 | schedule_work(work: &fw_info->work); |
308 | } |
309 | EXPORT_SYMBOL(nxp_nci_fw_recv_frame); |
310 | |