1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Proprietary commands extension for STMicroelectronics NFC NCI Chip |
4 | * |
5 | * Copyright (C) 2014-2015 STMicroelectronics SAS. All rights reserved. |
6 | */ |
7 | |
8 | #include <net/genetlink.h> |
9 | #include <linux/module.h> |
10 | #include <linux/nfc.h> |
11 | #include <linux/delay.h> |
12 | #include <net/nfc/nci_core.h> |
13 | |
14 | #include "st-nci.h" |
15 | |
16 | #define ST_NCI_HCI_DM_GETDATA 0x10 |
17 | #define ST_NCI_HCI_DM_PUTDATA 0x11 |
18 | #define ST_NCI_HCI_DM_LOAD 0x12 |
19 | #define ST_NCI_HCI_DM_GETINFO 0x13 |
20 | #define ST_NCI_HCI_DM_FWUPD_START 0x14 |
21 | #define ST_NCI_HCI_DM_FWUPD_STOP 0x15 |
22 | #define ST_NCI_HCI_DM_UPDATE_AID 0x20 |
23 | #define ST_NCI_HCI_DM_RESET 0x3e |
24 | |
25 | #define ST_NCI_HCI_DM_FIELD_GENERATOR 0x32 |
26 | #define ST_NCI_HCI_DM_VDC_MEASUREMENT_VALUE 0x33 |
27 | #define ST_NCI_HCI_DM_VDC_VALUE_COMPARISON 0x34 |
28 | |
29 | #define ST_NCI_FACTORY_MODE_ON 1 |
30 | #define ST_NCI_FACTORY_MODE_OFF 0 |
31 | |
32 | #define ST_NCI_EVT_POST_DATA 0x02 |
33 | |
34 | struct get_param_data { |
35 | u8 gate; |
36 | u8 data; |
37 | } __packed; |
38 | |
39 | static int st_nci_factory_mode(struct nfc_dev *dev, void *data, |
40 | size_t data_len) |
41 | { |
42 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
43 | struct st_nci_info *info = nci_get_drvdata(ndev); |
44 | |
45 | if (data_len != 1) |
46 | return -EINVAL; |
47 | |
48 | pr_debug("factory mode: %x\n" , ((u8 *)data)[0]); |
49 | |
50 | switch (((u8 *)data)[0]) { |
51 | case ST_NCI_FACTORY_MODE_ON: |
52 | test_and_set_bit(ST_NCI_FACTORY_MODE, addr: &info->flags); |
53 | break; |
54 | case ST_NCI_FACTORY_MODE_OFF: |
55 | clear_bit(ST_NCI_FACTORY_MODE, addr: &info->flags); |
56 | break; |
57 | default: |
58 | return -EINVAL; |
59 | } |
60 | |
61 | return 0; |
62 | } |
63 | |
64 | static int st_nci_hci_clear_all_pipes(struct nfc_dev *dev, void *data, |
65 | size_t data_len) |
66 | { |
67 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
68 | |
69 | return nci_hci_clear_all_pipes(ndev); |
70 | } |
71 | |
72 | static int st_nci_hci_dm_put_data(struct nfc_dev *dev, void *data, |
73 | size_t data_len) |
74 | { |
75 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
76 | |
77 | return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, |
78 | ST_NCI_HCI_DM_PUTDATA, param: data, |
79 | param_len: data_len, NULL); |
80 | } |
81 | |
82 | static int st_nci_hci_dm_update_aid(struct nfc_dev *dev, void *data, |
83 | size_t data_len) |
84 | { |
85 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
86 | |
87 | return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, |
88 | ST_NCI_HCI_DM_UPDATE_AID, param: data, param_len: data_len, NULL); |
89 | } |
90 | |
91 | static int st_nci_hci_dm_get_info(struct nfc_dev *dev, void *data, |
92 | size_t data_len) |
93 | { |
94 | int r; |
95 | struct sk_buff *msg, *skb; |
96 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
97 | |
98 | r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, ST_NCI_HCI_DM_GETINFO, |
99 | param: data, param_len: data_len, skb: &skb); |
100 | if (r) |
101 | return r; |
102 | |
103 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, |
104 | subcmd: HCI_DM_GET_INFO, approxlen: skb->len); |
105 | if (!msg) { |
106 | r = -ENOMEM; |
107 | goto free_skb; |
108 | } |
109 | |
110 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: skb->len, data: skb->data)) { |
111 | kfree_skb(skb: msg); |
112 | r = -ENOBUFS; |
113 | goto free_skb; |
114 | } |
115 | |
116 | r = nfc_vendor_cmd_reply(skb: msg); |
117 | |
118 | free_skb: |
119 | kfree_skb(skb); |
120 | return r; |
121 | } |
122 | |
123 | static int st_nci_hci_dm_get_data(struct nfc_dev *dev, void *data, |
124 | size_t data_len) |
125 | { |
126 | int r; |
127 | struct sk_buff *msg, *skb; |
128 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
129 | |
130 | r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, ST_NCI_HCI_DM_GETDATA, |
131 | param: data, param_len: data_len, skb: &skb); |
132 | if (r) |
133 | return r; |
134 | |
135 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, |
136 | subcmd: HCI_DM_GET_DATA, approxlen: skb->len); |
137 | if (!msg) { |
138 | r = -ENOMEM; |
139 | goto free_skb; |
140 | } |
141 | |
142 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: skb->len, data: skb->data)) { |
143 | kfree_skb(skb: msg); |
144 | r = -ENOBUFS; |
145 | goto free_skb; |
146 | } |
147 | |
148 | r = nfc_vendor_cmd_reply(skb: msg); |
149 | |
150 | free_skb: |
151 | kfree_skb(skb); |
152 | return r; |
153 | } |
154 | |
155 | static int st_nci_hci_dm_fwupd_start(struct nfc_dev *dev, void *data, |
156 | size_t data_len) |
157 | { |
158 | int r; |
159 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
160 | |
161 | dev->fw_download_in_progress = true; |
162 | r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, |
163 | ST_NCI_HCI_DM_FWUPD_START, param: data, param_len: data_len, NULL); |
164 | if (r) |
165 | dev->fw_download_in_progress = false; |
166 | |
167 | return r; |
168 | } |
169 | |
170 | static int st_nci_hci_dm_fwupd_end(struct nfc_dev *dev, void *data, |
171 | size_t data_len) |
172 | { |
173 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
174 | |
175 | return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, |
176 | ST_NCI_HCI_DM_FWUPD_STOP, param: data, param_len: data_len, NULL); |
177 | } |
178 | |
179 | static int st_nci_hci_dm_direct_load(struct nfc_dev *dev, void *data, |
180 | size_t data_len) |
181 | { |
182 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
183 | |
184 | if (dev->fw_download_in_progress) { |
185 | dev->fw_download_in_progress = false; |
186 | return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, |
187 | ST_NCI_HCI_DM_LOAD, param: data, param_len: data_len, NULL); |
188 | } |
189 | return -EPROTO; |
190 | } |
191 | |
192 | static int st_nci_hci_dm_reset(struct nfc_dev *dev, void *data, |
193 | size_t data_len) |
194 | { |
195 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
196 | |
197 | nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, |
198 | ST_NCI_HCI_DM_RESET, param: data, param_len: data_len, NULL); |
199 | msleep(msecs: 200); |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static int st_nci_hci_get_param(struct nfc_dev *dev, void *data, |
205 | size_t data_len) |
206 | { |
207 | int r; |
208 | struct sk_buff *msg, *skb; |
209 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
210 | struct get_param_data *param = (struct get_param_data *)data; |
211 | |
212 | if (data_len < sizeof(struct get_param_data)) |
213 | return -EPROTO; |
214 | |
215 | r = nci_hci_get_param(ndev, gate: param->gate, idx: param->data, skb: &skb); |
216 | if (r) |
217 | return r; |
218 | |
219 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, |
220 | subcmd: HCI_GET_PARAM, approxlen: skb->len); |
221 | if (!msg) { |
222 | r = -ENOMEM; |
223 | goto free_skb; |
224 | } |
225 | |
226 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: skb->len, data: skb->data)) { |
227 | kfree_skb(skb: msg); |
228 | r = -ENOBUFS; |
229 | goto free_skb; |
230 | } |
231 | |
232 | r = nfc_vendor_cmd_reply(skb: msg); |
233 | |
234 | free_skb: |
235 | kfree_skb(skb); |
236 | return r; |
237 | } |
238 | |
239 | static int st_nci_hci_dm_field_generator(struct nfc_dev *dev, void *data, |
240 | size_t data_len) |
241 | { |
242 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
243 | |
244 | return nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, |
245 | ST_NCI_HCI_DM_FIELD_GENERATOR, param: data, param_len: data_len, NULL); |
246 | } |
247 | |
248 | static int st_nci_hci_dm_vdc_measurement_value(struct nfc_dev *dev, void *data, |
249 | size_t data_len) |
250 | { |
251 | int r; |
252 | struct sk_buff *msg, *skb; |
253 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
254 | |
255 | if (data_len != 4) |
256 | return -EPROTO; |
257 | |
258 | r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, |
259 | ST_NCI_HCI_DM_VDC_MEASUREMENT_VALUE, |
260 | param: data, param_len: data_len, skb: &skb); |
261 | if (r) |
262 | return r; |
263 | |
264 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, |
265 | subcmd: HCI_DM_VDC_MEASUREMENT_VALUE, approxlen: skb->len); |
266 | if (!msg) { |
267 | r = -ENOMEM; |
268 | goto free_skb; |
269 | } |
270 | |
271 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: skb->len, data: skb->data)) { |
272 | kfree_skb(skb: msg); |
273 | r = -ENOBUFS; |
274 | goto free_skb; |
275 | } |
276 | |
277 | r = nfc_vendor_cmd_reply(skb: msg); |
278 | |
279 | free_skb: |
280 | kfree_skb(skb); |
281 | return r; |
282 | } |
283 | |
284 | static int st_nci_hci_dm_vdc_value_comparison(struct nfc_dev *dev, void *data, |
285 | size_t data_len) |
286 | { |
287 | int r; |
288 | struct sk_buff *msg, *skb; |
289 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
290 | |
291 | if (data_len != 2) |
292 | return -EPROTO; |
293 | |
294 | r = nci_hci_send_cmd(ndev, ST_NCI_DEVICE_MGNT_GATE, |
295 | ST_NCI_HCI_DM_VDC_VALUE_COMPARISON, |
296 | param: data, param_len: data_len, skb: &skb); |
297 | if (r) |
298 | return r; |
299 | |
300 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, |
301 | subcmd: HCI_DM_VDC_VALUE_COMPARISON, approxlen: skb->len); |
302 | if (!msg) { |
303 | r = -ENOMEM; |
304 | goto free_skb; |
305 | } |
306 | |
307 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: skb->len, data: skb->data)) { |
308 | kfree_skb(skb: msg); |
309 | r = -ENOBUFS; |
310 | goto free_skb; |
311 | } |
312 | |
313 | r = nfc_vendor_cmd_reply(skb: msg); |
314 | |
315 | free_skb: |
316 | kfree_skb(skb); |
317 | return r; |
318 | } |
319 | |
320 | static int st_nci_loopback(struct nfc_dev *dev, void *data, |
321 | size_t data_len) |
322 | { |
323 | int r; |
324 | struct sk_buff *msg, *skb; |
325 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
326 | |
327 | if (data_len <= 0) |
328 | return -EPROTO; |
329 | |
330 | r = nci_nfcc_loopback(ndev, data, data_len, resp: &skb); |
331 | if (r < 0) |
332 | return r; |
333 | |
334 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, |
335 | subcmd: LOOPBACK, approxlen: skb->len); |
336 | if (!msg) { |
337 | r = -ENOMEM; |
338 | goto free_skb; |
339 | } |
340 | |
341 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: skb->len, data: skb->data)) { |
342 | kfree_skb(skb: msg); |
343 | r = -ENOBUFS; |
344 | goto free_skb; |
345 | } |
346 | |
347 | r = nfc_vendor_cmd_reply(skb: msg); |
348 | free_skb: |
349 | kfree_skb(skb); |
350 | return r; |
351 | } |
352 | |
353 | static int st_nci_manufacturer_specific(struct nfc_dev *dev, void *data, |
354 | size_t data_len) |
355 | { |
356 | struct sk_buff *msg; |
357 | struct nci_dev *ndev = nfc_get_drvdata(dev); |
358 | |
359 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST_NCI_VENDOR_OUI, |
360 | subcmd: MANUFACTURER_SPECIFIC, |
361 | approxlen: sizeof(ndev->manufact_specific_info)); |
362 | if (!msg) |
363 | return -ENOMEM; |
364 | |
365 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: sizeof(ndev->manufact_specific_info), |
366 | data: &ndev->manufact_specific_info)) { |
367 | kfree_skb(skb: msg); |
368 | return -ENOBUFS; |
369 | } |
370 | |
371 | return nfc_vendor_cmd_reply(skb: msg); |
372 | } |
373 | |
374 | static const struct nfc_vendor_cmd st_nci_vendor_cmds[] = { |
375 | { |
376 | .vendor_id = ST_NCI_VENDOR_OUI, |
377 | .subcmd = FACTORY_MODE, |
378 | .doit = st_nci_factory_mode, |
379 | }, |
380 | { |
381 | .vendor_id = ST_NCI_VENDOR_OUI, |
382 | .subcmd = HCI_CLEAR_ALL_PIPES, |
383 | .doit = st_nci_hci_clear_all_pipes, |
384 | }, |
385 | { |
386 | .vendor_id = ST_NCI_VENDOR_OUI, |
387 | .subcmd = HCI_DM_PUT_DATA, |
388 | .doit = st_nci_hci_dm_put_data, |
389 | }, |
390 | { |
391 | .vendor_id = ST_NCI_VENDOR_OUI, |
392 | .subcmd = HCI_DM_UPDATE_AID, |
393 | .doit = st_nci_hci_dm_update_aid, |
394 | }, |
395 | { |
396 | .vendor_id = ST_NCI_VENDOR_OUI, |
397 | .subcmd = HCI_DM_GET_INFO, |
398 | .doit = st_nci_hci_dm_get_info, |
399 | }, |
400 | { |
401 | .vendor_id = ST_NCI_VENDOR_OUI, |
402 | .subcmd = HCI_DM_GET_DATA, |
403 | .doit = st_nci_hci_dm_get_data, |
404 | }, |
405 | { |
406 | .vendor_id = ST_NCI_VENDOR_OUI, |
407 | .subcmd = HCI_DM_DIRECT_LOAD, |
408 | .doit = st_nci_hci_dm_direct_load, |
409 | }, |
410 | { |
411 | .vendor_id = ST_NCI_VENDOR_OUI, |
412 | .subcmd = HCI_DM_RESET, |
413 | .doit = st_nci_hci_dm_reset, |
414 | }, |
415 | { |
416 | .vendor_id = ST_NCI_VENDOR_OUI, |
417 | .subcmd = HCI_GET_PARAM, |
418 | .doit = st_nci_hci_get_param, |
419 | }, |
420 | { |
421 | .vendor_id = ST_NCI_VENDOR_OUI, |
422 | .subcmd = HCI_DM_FIELD_GENERATOR, |
423 | .doit = st_nci_hci_dm_field_generator, |
424 | }, |
425 | { |
426 | .vendor_id = ST_NCI_VENDOR_OUI, |
427 | .subcmd = HCI_DM_FWUPD_START, |
428 | .doit = st_nci_hci_dm_fwupd_start, |
429 | }, |
430 | { |
431 | .vendor_id = ST_NCI_VENDOR_OUI, |
432 | .subcmd = HCI_DM_FWUPD_END, |
433 | .doit = st_nci_hci_dm_fwupd_end, |
434 | }, |
435 | { |
436 | .vendor_id = ST_NCI_VENDOR_OUI, |
437 | .subcmd = LOOPBACK, |
438 | .doit = st_nci_loopback, |
439 | }, |
440 | { |
441 | .vendor_id = ST_NCI_VENDOR_OUI, |
442 | .subcmd = HCI_DM_VDC_MEASUREMENT_VALUE, |
443 | .doit = st_nci_hci_dm_vdc_measurement_value, |
444 | }, |
445 | { |
446 | .vendor_id = ST_NCI_VENDOR_OUI, |
447 | .subcmd = HCI_DM_VDC_VALUE_COMPARISON, |
448 | .doit = st_nci_hci_dm_vdc_value_comparison, |
449 | }, |
450 | { |
451 | .vendor_id = ST_NCI_VENDOR_OUI, |
452 | .subcmd = MANUFACTURER_SPECIFIC, |
453 | .doit = st_nci_manufacturer_specific, |
454 | }, |
455 | }; |
456 | |
457 | int st_nci_vendor_cmds_init(struct nci_dev *ndev) |
458 | { |
459 | return nci_set_vendor_cmds(ndev, cmds: st_nci_vendor_cmds, |
460 | n_cmds: sizeof(st_nci_vendor_cmds)); |
461 | } |
462 | EXPORT_SYMBOL(st_nci_vendor_cmds_init); |
463 | |