1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Proprietary commands extension for STMicroelectronics NFC 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 <net/nfc/hci.h> |
12 | #include <net/nfc/llc.h> |
13 | |
14 | #include "st21nfca.h" |
15 | |
16 | #define ST21NFCA_HCI_DM_GETDATA 0x10 |
17 | #define ST21NFCA_HCI_DM_PUTDATA 0x11 |
18 | #define ST21NFCA_HCI_DM_LOAD 0x12 |
19 | #define ST21NFCA_HCI_DM_GETINFO 0x13 |
20 | #define ST21NFCA_HCI_DM_UPDATE_AID 0x20 |
21 | #define ST21NFCA_HCI_DM_RESET 0x3e |
22 | |
23 | #define ST21NFCA_HCI_DM_FIELD_GENERATOR 0x32 |
24 | |
25 | #define ST21NFCA_FACTORY_MODE_ON 1 |
26 | #define ST21NFCA_FACTORY_MODE_OFF 0 |
27 | |
28 | #define ST21NFCA_EVT_POST_DATA 0x02 |
29 | |
30 | struct get_param_data { |
31 | u8 gate; |
32 | u8 data; |
33 | } __packed; |
34 | |
35 | static int st21nfca_factory_mode(struct nfc_dev *dev, void *data, |
36 | size_t data_len) |
37 | { |
38 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
39 | |
40 | if (data_len != 1) |
41 | return -EINVAL; |
42 | |
43 | pr_debug("factory mode: %x\n" , ((u8 *)data)[0]); |
44 | |
45 | switch (((u8 *)data)[0]) { |
46 | case ST21NFCA_FACTORY_MODE_ON: |
47 | test_and_set_bit(ST21NFCA_FACTORY_MODE, addr: &hdev->quirks); |
48 | break; |
49 | case ST21NFCA_FACTORY_MODE_OFF: |
50 | clear_bit(ST21NFCA_FACTORY_MODE, addr: &hdev->quirks); |
51 | break; |
52 | default: |
53 | return -EINVAL; |
54 | } |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | static int st21nfca_hci_clear_all_pipes(struct nfc_dev *dev, void *data, |
60 | size_t data_len) |
61 | { |
62 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
63 | |
64 | return nfc_hci_disconnect_all_gates(hdev); |
65 | } |
66 | |
67 | static int st21nfca_hci_dm_put_data(struct nfc_dev *dev, void *data, |
68 | size_t data_len) |
69 | { |
70 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
71 | |
72 | return nfc_hci_send_cmd(hdev, ST21NFCA_DEVICE_MGNT_GATE, |
73 | ST21NFCA_HCI_DM_PUTDATA, param: data, |
74 | param_len: data_len, NULL); |
75 | } |
76 | |
77 | static int st21nfca_hci_dm_update_aid(struct nfc_dev *dev, void *data, |
78 | size_t data_len) |
79 | { |
80 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
81 | |
82 | return nfc_hci_send_cmd(hdev, ST21NFCA_DEVICE_MGNT_GATE, |
83 | ST21NFCA_HCI_DM_UPDATE_AID, param: data, param_len: data_len, NULL); |
84 | } |
85 | |
86 | static int st21nfca_hci_dm_get_info(struct nfc_dev *dev, void *data, |
87 | size_t data_len) |
88 | { |
89 | int r; |
90 | struct sk_buff *msg, *skb; |
91 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
92 | |
93 | r = nfc_hci_send_cmd(hdev, |
94 | ST21NFCA_DEVICE_MGNT_GATE, |
95 | ST21NFCA_HCI_DM_GETINFO, |
96 | param: data, param_len: data_len, skb: &skb); |
97 | if (r) |
98 | goto exit; |
99 | |
100 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST21NFCA_VENDOR_OUI, |
101 | subcmd: HCI_DM_GET_INFO, approxlen: skb->len); |
102 | if (!msg) { |
103 | r = -ENOMEM; |
104 | goto free_skb; |
105 | } |
106 | |
107 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: skb->len, data: skb->data)) { |
108 | kfree_skb(skb: msg); |
109 | r = -ENOBUFS; |
110 | goto free_skb; |
111 | } |
112 | |
113 | r = nfc_vendor_cmd_reply(skb: msg); |
114 | |
115 | free_skb: |
116 | kfree_skb(skb); |
117 | exit: |
118 | return r; |
119 | } |
120 | |
121 | static int st21nfca_hci_dm_get_data(struct nfc_dev *dev, void *data, |
122 | size_t data_len) |
123 | { |
124 | int r; |
125 | struct sk_buff *msg, *skb; |
126 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
127 | |
128 | r = nfc_hci_send_cmd(hdev, |
129 | ST21NFCA_DEVICE_MGNT_GATE, |
130 | ST21NFCA_HCI_DM_GETDATA, |
131 | param: data, param_len: data_len, skb: &skb); |
132 | if (r) |
133 | goto exit; |
134 | |
135 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST21NFCA_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 | exit: |
153 | return r; |
154 | } |
155 | |
156 | static int st21nfca_hci_dm_load(struct nfc_dev *dev, void *data, |
157 | size_t data_len) |
158 | { |
159 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
160 | |
161 | return nfc_hci_send_cmd(hdev, ST21NFCA_DEVICE_MGNT_GATE, |
162 | ST21NFCA_HCI_DM_LOAD, param: data, param_len: data_len, NULL); |
163 | } |
164 | |
165 | static int st21nfca_hci_dm_reset(struct nfc_dev *dev, void *data, |
166 | size_t data_len) |
167 | { |
168 | int r; |
169 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
170 | |
171 | r = nfc_hci_send_cmd_async(hdev, ST21NFCA_DEVICE_MGNT_GATE, |
172 | ST21NFCA_HCI_DM_RESET, param: data, param_len: data_len, NULL, NULL); |
173 | if (r < 0) |
174 | return r; |
175 | |
176 | r = nfc_llc_stop(llc: hdev->llc); |
177 | if (r < 0) |
178 | return r; |
179 | |
180 | return nfc_llc_start(llc: hdev->llc); |
181 | } |
182 | |
183 | static int st21nfca_hci_get_param(struct nfc_dev *dev, void *data, |
184 | size_t data_len) |
185 | { |
186 | int r; |
187 | struct sk_buff *msg, *skb; |
188 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
189 | struct get_param_data *param = (struct get_param_data *)data; |
190 | |
191 | if (data_len < sizeof(struct get_param_data)) |
192 | return -EPROTO; |
193 | |
194 | r = nfc_hci_get_param(hdev, gate: param->gate, idx: param->data, skb: &skb); |
195 | if (r) |
196 | goto exit; |
197 | |
198 | msg = nfc_vendor_cmd_alloc_reply_skb(dev, ST21NFCA_VENDOR_OUI, |
199 | subcmd: HCI_GET_PARAM, approxlen: skb->len); |
200 | if (!msg) { |
201 | r = -ENOMEM; |
202 | goto free_skb; |
203 | } |
204 | |
205 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: skb->len, data: skb->data)) { |
206 | kfree_skb(skb: msg); |
207 | r = -ENOBUFS; |
208 | goto free_skb; |
209 | } |
210 | |
211 | r = nfc_vendor_cmd_reply(skb: msg); |
212 | |
213 | free_skb: |
214 | kfree_skb(skb); |
215 | exit: |
216 | return r; |
217 | } |
218 | |
219 | static int st21nfca_hci_dm_field_generator(struct nfc_dev *dev, void *data, |
220 | size_t data_len) |
221 | { |
222 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
223 | |
224 | return nfc_hci_send_cmd(hdev, |
225 | ST21NFCA_DEVICE_MGNT_GATE, |
226 | ST21NFCA_HCI_DM_FIELD_GENERATOR, |
227 | param: data, param_len: data_len, NULL); |
228 | } |
229 | |
230 | int st21nfca_hci_loopback_event_received(struct nfc_hci_dev *hdev, u8 event, |
231 | struct sk_buff *skb) |
232 | { |
233 | struct st21nfca_hci_info *info = nfc_hci_get_clientdata(hdev); |
234 | |
235 | switch (event) { |
236 | case ST21NFCA_EVT_POST_DATA: |
237 | info->vendor_info.rx_skb = skb; |
238 | break; |
239 | default: |
240 | nfc_err(&hdev->ndev->dev, "Unexpected event on loopback gate\n" ); |
241 | } |
242 | complete(&info->vendor_info.req_completion); |
243 | return 0; |
244 | } |
245 | EXPORT_SYMBOL(st21nfca_hci_loopback_event_received); |
246 | |
247 | static int st21nfca_hci_loopback(struct nfc_dev *dev, void *data, |
248 | size_t data_len) |
249 | { |
250 | int r; |
251 | struct sk_buff *msg; |
252 | struct nfc_hci_dev *hdev = nfc_get_drvdata(dev); |
253 | struct st21nfca_hci_info *info = nfc_hci_get_clientdata(hdev); |
254 | |
255 | if (data_len <= 0) |
256 | return -EPROTO; |
257 | |
258 | reinit_completion(x: &info->vendor_info.req_completion); |
259 | info->vendor_info.rx_skb = NULL; |
260 | |
261 | r = nfc_hci_send_event(hdev, NFC_HCI_LOOPBACK_GATE, |
262 | ST21NFCA_EVT_POST_DATA, param: data, param_len: data_len); |
263 | if (r < 0) { |
264 | r = -EPROTO; |
265 | goto exit; |
266 | } |
267 | |
268 | wait_for_completion_interruptible(x: &info->vendor_info.req_completion); |
269 | if (!info->vendor_info.rx_skb || |
270 | info->vendor_info.rx_skb->len != data_len) { |
271 | r = -EPROTO; |
272 | goto exit; |
273 | } |
274 | |
275 | msg = nfc_vendor_cmd_alloc_reply_skb(dev: hdev->ndev, |
276 | ST21NFCA_VENDOR_OUI, |
277 | subcmd: HCI_LOOPBACK, |
278 | approxlen: info->vendor_info.rx_skb->len); |
279 | if (!msg) { |
280 | r = -ENOMEM; |
281 | goto free_skb; |
282 | } |
283 | |
284 | if (nla_put(skb: msg, attrtype: NFC_ATTR_VENDOR_DATA, attrlen: info->vendor_info.rx_skb->len, |
285 | data: info->vendor_info.rx_skb->data)) { |
286 | kfree_skb(skb: msg); |
287 | r = -ENOBUFS; |
288 | goto free_skb; |
289 | } |
290 | |
291 | r = nfc_vendor_cmd_reply(skb: msg); |
292 | free_skb: |
293 | kfree_skb(skb: info->vendor_info.rx_skb); |
294 | exit: |
295 | return r; |
296 | } |
297 | |
298 | static const struct nfc_vendor_cmd st21nfca_vendor_cmds[] = { |
299 | { |
300 | .vendor_id = ST21NFCA_VENDOR_OUI, |
301 | .subcmd = FACTORY_MODE, |
302 | .doit = st21nfca_factory_mode, |
303 | }, |
304 | { |
305 | .vendor_id = ST21NFCA_VENDOR_OUI, |
306 | .subcmd = HCI_CLEAR_ALL_PIPES, |
307 | .doit = st21nfca_hci_clear_all_pipes, |
308 | }, |
309 | { |
310 | .vendor_id = ST21NFCA_VENDOR_OUI, |
311 | .subcmd = HCI_DM_PUT_DATA, |
312 | .doit = st21nfca_hci_dm_put_data, |
313 | }, |
314 | { |
315 | .vendor_id = ST21NFCA_VENDOR_OUI, |
316 | .subcmd = HCI_DM_UPDATE_AID, |
317 | .doit = st21nfca_hci_dm_update_aid, |
318 | }, |
319 | { |
320 | .vendor_id = ST21NFCA_VENDOR_OUI, |
321 | .subcmd = HCI_DM_GET_INFO, |
322 | .doit = st21nfca_hci_dm_get_info, |
323 | }, |
324 | { |
325 | .vendor_id = ST21NFCA_VENDOR_OUI, |
326 | .subcmd = HCI_DM_GET_DATA, |
327 | .doit = st21nfca_hci_dm_get_data, |
328 | }, |
329 | { |
330 | .vendor_id = ST21NFCA_VENDOR_OUI, |
331 | .subcmd = HCI_DM_LOAD, |
332 | .doit = st21nfca_hci_dm_load, |
333 | }, |
334 | { |
335 | .vendor_id = ST21NFCA_VENDOR_OUI, |
336 | .subcmd = HCI_DM_RESET, |
337 | .doit = st21nfca_hci_dm_reset, |
338 | }, |
339 | { |
340 | .vendor_id = ST21NFCA_VENDOR_OUI, |
341 | .subcmd = HCI_GET_PARAM, |
342 | .doit = st21nfca_hci_get_param, |
343 | }, |
344 | { |
345 | .vendor_id = ST21NFCA_VENDOR_OUI, |
346 | .subcmd = HCI_DM_FIELD_GENERATOR, |
347 | .doit = st21nfca_hci_dm_field_generator, |
348 | }, |
349 | { |
350 | .vendor_id = ST21NFCA_VENDOR_OUI, |
351 | .subcmd = HCI_LOOPBACK, |
352 | .doit = st21nfca_hci_loopback, |
353 | }, |
354 | }; |
355 | |
356 | int st21nfca_vendor_cmds_init(struct nfc_hci_dev *hdev) |
357 | { |
358 | struct st21nfca_hci_info *info = nfc_hci_get_clientdata(hdev); |
359 | |
360 | init_completion(x: &info->vendor_info.req_completion); |
361 | return nfc_hci_set_vendor_cmds(hdev, cmds: st21nfca_vendor_cmds, |
362 | n_cmds: sizeof(st21nfca_vendor_cmds)); |
363 | } |
364 | EXPORT_SYMBOL(st21nfca_vendor_cmds_init); |
365 | |