diff options
Diffstat (limited to 'drivers/nfc')
-rw-r--r-- | drivers/nfc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/nfc/Makefile | 1 | ||||
-rw-r--r-- | drivers/nfc/nfcsim.c | 541 |
3 files changed, 552 insertions, 0 deletions
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig index 74a852e4e41f..b0b64ccb7d7d 100644 --- a/drivers/nfc/Kconfig +++ b/drivers/nfc/Kconfig | |||
@@ -36,6 +36,16 @@ config NFC_MEI_PHY | |||
36 | 36 | ||
37 | If unsure, say N. | 37 | If unsure, say N. |
38 | 38 | ||
39 | config NFC_SIM | ||
40 | tristate "NFC hardware simulator driver" | ||
41 | help | ||
42 | This driver declares two virtual NFC devices supporting NFC-DEP | ||
43 | protocol. An LLCP connection can be established between them and | ||
44 | all packets sent from one device is sent back to the other, acting as | ||
45 | loopback devices. | ||
46 | |||
47 | If unsure, say N. | ||
48 | |||
39 | source "drivers/nfc/pn544/Kconfig" | 49 | source "drivers/nfc/pn544/Kconfig" |
40 | source "drivers/nfc/microread/Kconfig" | 50 | source "drivers/nfc/microread/Kconfig" |
41 | 51 | ||
diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile index aa6bd657ef40..be7636abcb3f 100644 --- a/drivers/nfc/Makefile +++ b/drivers/nfc/Makefile | |||
@@ -7,5 +7,6 @@ obj-$(CONFIG_NFC_MICROREAD) += microread/ | |||
7 | obj-$(CONFIG_NFC_PN533) += pn533.o | 7 | obj-$(CONFIG_NFC_PN533) += pn533.o |
8 | obj-$(CONFIG_NFC_WILINK) += nfcwilink.o | 8 | obj-$(CONFIG_NFC_WILINK) += nfcwilink.o |
9 | obj-$(CONFIG_NFC_MEI_PHY) += mei_phy.o | 9 | obj-$(CONFIG_NFC_MEI_PHY) += mei_phy.o |
10 | obj-$(CONFIG_NFC_SIM) += nfcsim.o | ||
10 | 11 | ||
11 | ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG | 12 | ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG |
diff --git a/drivers/nfc/nfcsim.c b/drivers/nfc/nfcsim.c new file mode 100644 index 000000000000..c5c30fb1d7bf --- /dev/null +++ b/drivers/nfc/nfcsim.c | |||
@@ -0,0 +1,541 @@ | |||
1 | /* | ||
2 | * NFC hardware simulation driver | ||
3 | * Copyright (c) 2013, Intel Corporation. | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms and conditions of the GNU General Public License, | ||
7 | * version 2, as published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | */ | ||
15 | |||
16 | #include <linux/device.h> | ||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/nfc.h> | ||
20 | #include <net/nfc/nfc.h> | ||
21 | |||
22 | #define DEV_ERR(_dev, fmt, args...) nfc_dev_err(&_dev->nfc_dev->dev, \ | ||
23 | "%s: " fmt, __func__, ## args) | ||
24 | |||
25 | #define DEV_DBG(_dev, fmt, args...) nfc_dev_dbg(&_dev->nfc_dev->dev, \ | ||
26 | "%s: " fmt, __func__, ## args) | ||
27 | |||
28 | #define NFCSIM_VERSION "0.1" | ||
29 | |||
30 | #define NFCSIM_POLL_NONE 0 | ||
31 | #define NFCSIM_POLL_INITIATOR 1 | ||
32 | #define NFCSIM_POLL_TARGET 2 | ||
33 | #define NFCSIM_POLL_DUAL (NFCSIM_POLL_INITIATOR | NFCSIM_POLL_TARGET) | ||
34 | |||
35 | struct nfcsim { | ||
36 | struct nfc_dev *nfc_dev; | ||
37 | |||
38 | struct mutex lock; | ||
39 | |||
40 | struct delayed_work recv_work; | ||
41 | |||
42 | struct sk_buff *clone_skb; | ||
43 | |||
44 | struct delayed_work poll_work; | ||
45 | u8 polling_mode; | ||
46 | u8 curr_polling_mode; | ||
47 | |||
48 | u8 shutting_down; | ||
49 | |||
50 | u8 up; | ||
51 | |||
52 | u8 initiator; | ||
53 | |||
54 | data_exchange_cb_t cb; | ||
55 | void *cb_context; | ||
56 | |||
57 | struct nfcsim *peer_dev; | ||
58 | }; | ||
59 | |||
60 | static struct nfcsim *dev0; | ||
61 | static struct nfcsim *dev1; | ||
62 | |||
63 | struct workqueue_struct *wq; | ||
64 | |||
65 | static void nfcsim_cleanup_dev(struct nfcsim *dev, u8 shutdown) | ||
66 | { | ||
67 | DEV_DBG(dev, "shutdown=%d", shutdown); | ||
68 | |||
69 | mutex_lock(&dev->lock); | ||
70 | |||
71 | dev->polling_mode = NFCSIM_POLL_NONE; | ||
72 | dev->shutting_down = shutdown; | ||
73 | dev->cb = NULL; | ||
74 | dev_kfree_skb(dev->clone_skb); | ||
75 | dev->clone_skb = NULL; | ||
76 | |||
77 | mutex_unlock(&dev->lock); | ||
78 | |||
79 | cancel_delayed_work_sync(&dev->poll_work); | ||
80 | cancel_delayed_work_sync(&dev->recv_work); | ||
81 | } | ||
82 | |||
83 | static int nfcsim_target_found(struct nfcsim *dev) | ||
84 | { | ||
85 | struct nfc_target nfc_tgt; | ||
86 | |||
87 | DEV_DBG(dev, ""); | ||
88 | |||
89 | memset(&nfc_tgt, 0, sizeof(struct nfc_target)); | ||
90 | |||
91 | nfc_tgt.supported_protocols = NFC_PROTO_NFC_DEP_MASK; | ||
92 | nfc_targets_found(dev->nfc_dev, &nfc_tgt, 1); | ||
93 | |||
94 | return 0; | ||
95 | } | ||
96 | |||
97 | static int nfcsim_dev_up(struct nfc_dev *nfc_dev) | ||
98 | { | ||
99 | struct nfcsim *dev = nfc_get_drvdata(nfc_dev); | ||
100 | |||
101 | DEV_DBG(dev, ""); | ||
102 | |||
103 | mutex_lock(&dev->lock); | ||
104 | |||
105 | dev->up = 1; | ||
106 | |||
107 | mutex_unlock(&dev->lock); | ||
108 | |||
109 | return 0; | ||
110 | } | ||
111 | |||
112 | static int nfcsim_dev_down(struct nfc_dev *nfc_dev) | ||
113 | { | ||
114 | struct nfcsim *dev = nfc_get_drvdata(nfc_dev); | ||
115 | |||
116 | DEV_DBG(dev, ""); | ||
117 | |||
118 | mutex_lock(&dev->lock); | ||
119 | |||
120 | dev->up = 0; | ||
121 | |||
122 | mutex_unlock(&dev->lock); | ||
123 | |||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | static int nfcsim_dep_link_up(struct nfc_dev *nfc_dev, | ||
128 | struct nfc_target *target, | ||
129 | u8 comm_mode, u8 *gb, size_t gb_len) | ||
130 | { | ||
131 | int rc; | ||
132 | struct nfcsim *dev = nfc_get_drvdata(nfc_dev); | ||
133 | struct nfcsim *peer = dev->peer_dev; | ||
134 | u8 *remote_gb; | ||
135 | size_t remote_gb_len; | ||
136 | |||
137 | DEV_DBG(dev, "target_idx: %d, comm_mode: %d\n", target->idx, comm_mode); | ||
138 | |||
139 | mutex_lock(&peer->lock); | ||
140 | |||
141 | nfc_tm_activated(peer->nfc_dev, NFC_PROTO_NFC_DEP_MASK, | ||
142 | NFC_COMM_ACTIVE, gb, gb_len); | ||
143 | |||
144 | remote_gb = nfc_get_local_general_bytes(peer->nfc_dev, &remote_gb_len); | ||
145 | if (!remote_gb) { | ||
146 | DEV_ERR(peer, "Can't get remote general bytes"); | ||
147 | |||
148 | mutex_unlock(&peer->lock); | ||
149 | return -EINVAL; | ||
150 | } | ||
151 | |||
152 | mutex_unlock(&peer->lock); | ||
153 | |||
154 | mutex_lock(&dev->lock); | ||
155 | |||
156 | rc = nfc_set_remote_general_bytes(nfc_dev, remote_gb, remote_gb_len); | ||
157 | if (rc) { | ||
158 | DEV_ERR(dev, "Can't set remote general bytes"); | ||
159 | mutex_unlock(&dev->lock); | ||
160 | return rc; | ||
161 | } | ||
162 | |||
163 | rc = nfc_dep_link_is_up(nfc_dev, target->idx, NFC_COMM_ACTIVE, | ||
164 | NFC_RF_INITIATOR); | ||
165 | |||
166 | mutex_unlock(&dev->lock); | ||
167 | |||
168 | return rc; | ||
169 | } | ||
170 | |||
171 | static int nfcsim_dep_link_down(struct nfc_dev *nfc_dev) | ||
172 | { | ||
173 | struct nfcsim *dev = nfc_get_drvdata(nfc_dev); | ||
174 | |||
175 | DEV_DBG(dev, ""); | ||
176 | |||
177 | nfcsim_cleanup_dev(dev, 0); | ||
178 | |||
179 | return 0; | ||
180 | } | ||
181 | |||
182 | static int nfcsim_start_poll(struct nfc_dev *nfc_dev, | ||
183 | u32 im_protocols, u32 tm_protocols) | ||
184 | { | ||
185 | struct nfcsim *dev = nfc_get_drvdata(nfc_dev); | ||
186 | int rc; | ||
187 | |||
188 | mutex_lock(&dev->lock); | ||
189 | |||
190 | if (dev->polling_mode != NFCSIM_POLL_NONE) { | ||
191 | DEV_ERR(dev, "Already in polling mode"); | ||
192 | rc = -EBUSY; | ||
193 | goto exit; | ||
194 | } | ||
195 | |||
196 | if (im_protocols & NFC_PROTO_NFC_DEP_MASK) | ||
197 | dev->polling_mode |= NFCSIM_POLL_INITIATOR; | ||
198 | |||
199 | if (tm_protocols & NFC_PROTO_NFC_DEP_MASK) | ||
200 | dev->polling_mode |= NFCSIM_POLL_TARGET; | ||
201 | |||
202 | if (dev->polling_mode == NFCSIM_POLL_NONE) { | ||
203 | DEV_ERR(dev, "Unsupported polling mode"); | ||
204 | rc = -EINVAL; | ||
205 | goto exit; | ||
206 | } | ||
207 | |||
208 | dev->initiator = 0; | ||
209 | dev->curr_polling_mode = NFCSIM_POLL_NONE; | ||
210 | |||
211 | queue_delayed_work(wq, &dev->poll_work, 0); | ||
212 | |||
213 | DEV_DBG(dev, "Start polling: im: 0x%X, tm: 0x%X", im_protocols, | ||
214 | tm_protocols); | ||
215 | |||
216 | rc = 0; | ||
217 | exit: | ||
218 | mutex_unlock(&dev->lock); | ||
219 | |||
220 | return rc; | ||
221 | } | ||
222 | |||
223 | static void nfcsim_stop_poll(struct nfc_dev *nfc_dev) | ||
224 | { | ||
225 | struct nfcsim *dev = nfc_get_drvdata(nfc_dev); | ||
226 | |||
227 | DEV_DBG(dev, "Stop poll"); | ||
228 | |||
229 | mutex_lock(&dev->lock); | ||
230 | |||
231 | dev->polling_mode = NFCSIM_POLL_NONE; | ||
232 | |||
233 | mutex_unlock(&dev->lock); | ||
234 | |||
235 | cancel_delayed_work_sync(&dev->poll_work); | ||
236 | } | ||
237 | |||
238 | static int nfcsim_activate_target(struct nfc_dev *nfc_dev, | ||
239 | struct nfc_target *target, u32 protocol) | ||
240 | { | ||
241 | struct nfcsim *dev = nfc_get_drvdata(nfc_dev); | ||
242 | |||
243 | DEV_DBG(dev, ""); | ||
244 | |||
245 | return -ENOTSUPP; | ||
246 | } | ||
247 | |||
248 | static void nfcsim_deactivate_target(struct nfc_dev *nfc_dev, | ||
249 | struct nfc_target *target) | ||
250 | { | ||
251 | struct nfcsim *dev = nfc_get_drvdata(nfc_dev); | ||
252 | |||
253 | DEV_DBG(dev, ""); | ||
254 | } | ||
255 | |||
256 | static void nfcsim_wq_recv(struct work_struct *work) | ||
257 | { | ||
258 | struct nfcsim *dev = container_of(work, struct nfcsim, | ||
259 | recv_work.work); | ||
260 | |||
261 | mutex_lock(&dev->lock); | ||
262 | |||
263 | if (dev->shutting_down || !dev->up || !dev->clone_skb) { | ||
264 | dev_kfree_skb(dev->clone_skb); | ||
265 | goto exit; | ||
266 | } | ||
267 | |||
268 | if (dev->initiator) { | ||
269 | if (!dev->cb) { | ||
270 | DEV_ERR(dev, "Null recv callback"); | ||
271 | dev_kfree_skb(dev->clone_skb); | ||
272 | goto exit; | ||
273 | } | ||
274 | |||
275 | dev->cb(dev->cb_context, dev->clone_skb, 0); | ||
276 | dev->cb = NULL; | ||
277 | } else { | ||
278 | nfc_tm_data_received(dev->nfc_dev, dev->clone_skb); | ||
279 | } | ||
280 | |||
281 | exit: | ||
282 | dev->clone_skb = NULL; | ||
283 | |||
284 | mutex_unlock(&dev->lock); | ||
285 | } | ||
286 | |||
287 | static int nfcsim_tx(struct nfc_dev *nfc_dev, struct nfc_target *target, | ||
288 | struct sk_buff *skb, data_exchange_cb_t cb, | ||
289 | void *cb_context) | ||
290 | { | ||
291 | struct nfcsim *dev = nfc_get_drvdata(nfc_dev); | ||
292 | struct nfcsim *peer = dev->peer_dev; | ||
293 | int err; | ||
294 | |||
295 | mutex_lock(&dev->lock); | ||
296 | |||
297 | if (dev->shutting_down || !dev->up) { | ||
298 | mutex_unlock(&dev->lock); | ||
299 | err = -ENODEV; | ||
300 | goto exit; | ||
301 | } | ||
302 | |||
303 | dev->cb = cb; | ||
304 | dev->cb_context = cb_context; | ||
305 | |||
306 | mutex_unlock(&dev->lock); | ||
307 | |||
308 | mutex_lock(&peer->lock); | ||
309 | |||
310 | peer->clone_skb = skb_clone(skb, GFP_KERNEL); | ||
311 | |||
312 | if (!peer->clone_skb) { | ||
313 | DEV_ERR(dev, "skb_clone failed"); | ||
314 | mutex_unlock(&peer->lock); | ||
315 | err = -ENOMEM; | ||
316 | goto exit; | ||
317 | } | ||
318 | |||
319 | /* This simulates an arbitrary transmission delay between the 2 devices. | ||
320 | * If packet transmission occurs immediately between them, we have a | ||
321 | * non-stop flow of several tens of thousands SYMM packets per second | ||
322 | * and a burning cpu. | ||
323 | * | ||
324 | * TODO: Add support for a sysfs entry to control this delay. | ||
325 | */ | ||
326 | queue_delayed_work(wq, &peer->recv_work, msecs_to_jiffies(5)); | ||
327 | |||
328 | mutex_unlock(&peer->lock); | ||
329 | |||
330 | err = 0; | ||
331 | exit: | ||
332 | dev_kfree_skb(skb); | ||
333 | |||
334 | return err; | ||
335 | } | ||
336 | |||
337 | static int nfcsim_im_transceive(struct nfc_dev *nfc_dev, | ||
338 | struct nfc_target *target, struct sk_buff *skb, | ||
339 | data_exchange_cb_t cb, void *cb_context) | ||
340 | { | ||
341 | return nfcsim_tx(nfc_dev, target, skb, cb, cb_context); | ||
342 | } | ||
343 | |||
344 | static int nfcsim_tm_send(struct nfc_dev *nfc_dev, struct sk_buff *skb) | ||
345 | { | ||
346 | return nfcsim_tx(nfc_dev, NULL, skb, NULL, NULL); | ||
347 | } | ||
348 | |||
349 | static struct nfc_ops nfcsim_nfc_ops = { | ||
350 | .dev_up = nfcsim_dev_up, | ||
351 | .dev_down = nfcsim_dev_down, | ||
352 | .dep_link_up = nfcsim_dep_link_up, | ||
353 | .dep_link_down = nfcsim_dep_link_down, | ||
354 | .start_poll = nfcsim_start_poll, | ||
355 | .stop_poll = nfcsim_stop_poll, | ||
356 | .activate_target = nfcsim_activate_target, | ||
357 | .deactivate_target = nfcsim_deactivate_target, | ||
358 | .im_transceive = nfcsim_im_transceive, | ||
359 | .tm_send = nfcsim_tm_send, | ||
360 | }; | ||
361 | |||
362 | static void nfcsim_set_polling_mode(struct nfcsim *dev) | ||
363 | { | ||
364 | if (dev->polling_mode == NFCSIM_POLL_NONE) { | ||
365 | dev->curr_polling_mode = NFCSIM_POLL_NONE; | ||
366 | return; | ||
367 | } | ||
368 | |||
369 | if (dev->curr_polling_mode == NFCSIM_POLL_NONE) { | ||
370 | if (dev->polling_mode & NFCSIM_POLL_INITIATOR) | ||
371 | dev->curr_polling_mode = NFCSIM_POLL_INITIATOR; | ||
372 | else | ||
373 | dev->curr_polling_mode = NFCSIM_POLL_TARGET; | ||
374 | |||
375 | return; | ||
376 | } | ||
377 | |||
378 | if (dev->polling_mode == NFCSIM_POLL_DUAL) { | ||
379 | if (dev->curr_polling_mode == NFCSIM_POLL_TARGET) | ||
380 | dev->curr_polling_mode = NFCSIM_POLL_INITIATOR; | ||
381 | else | ||
382 | dev->curr_polling_mode = NFCSIM_POLL_TARGET; | ||
383 | } | ||
384 | } | ||
385 | |||
386 | static void nfcsim_wq_poll(struct work_struct *work) | ||
387 | { | ||
388 | struct nfcsim *dev = container_of(work, struct nfcsim, poll_work.work); | ||
389 | struct nfcsim *peer = dev->peer_dev; | ||
390 | |||
391 | /* These work items run on an ordered workqueue and are therefore | ||
392 | * serialized. So we can take both mutexes without being dead locked. | ||
393 | */ | ||
394 | mutex_lock(&dev->lock); | ||
395 | mutex_lock(&peer->lock); | ||
396 | |||
397 | nfcsim_set_polling_mode(dev); | ||
398 | |||
399 | if (dev->curr_polling_mode == NFCSIM_POLL_NONE) { | ||
400 | DEV_DBG(dev, "Not polling"); | ||
401 | goto unlock; | ||
402 | } | ||
403 | |||
404 | DEV_DBG(dev, "Polling as %s", | ||
405 | dev->curr_polling_mode == NFCSIM_POLL_INITIATOR ? | ||
406 | "initiator" : "target"); | ||
407 | |||
408 | if (dev->curr_polling_mode == NFCSIM_POLL_TARGET) | ||
409 | goto sched_work; | ||
410 | |||
411 | if (peer->curr_polling_mode == NFCSIM_POLL_TARGET) { | ||
412 | peer->polling_mode = NFCSIM_POLL_NONE; | ||
413 | dev->polling_mode = NFCSIM_POLL_NONE; | ||
414 | |||
415 | dev->initiator = 1; | ||
416 | |||
417 | nfcsim_target_found(dev); | ||
418 | |||
419 | goto unlock; | ||
420 | } | ||
421 | |||
422 | sched_work: | ||
423 | /* This defines the delay for an initiator to check if the other device | ||
424 | * is polling in target mode. | ||
425 | * If the device starts in dual mode polling, it switches between | ||
426 | * initiator and target at every round. | ||
427 | * Because the wq is ordered and only 1 work item is executed at a time, | ||
428 | * we'll always have one device polling as initiator and the other as | ||
429 | * target at some point, even if both are started in dual mode. | ||
430 | */ | ||
431 | queue_delayed_work(wq, &dev->poll_work, msecs_to_jiffies(200)); | ||
432 | |||
433 | unlock: | ||
434 | mutex_unlock(&peer->lock); | ||
435 | mutex_unlock(&dev->lock); | ||
436 | } | ||
437 | |||
438 | static struct nfcsim *nfcsim_init_dev(void) | ||
439 | { | ||
440 | struct nfcsim *dev; | ||
441 | int rc = -ENOMEM; | ||
442 | |||
443 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); | ||
444 | if (dev == NULL) | ||
445 | return ERR_PTR(-ENOMEM); | ||
446 | |||
447 | mutex_init(&dev->lock); | ||
448 | |||
449 | INIT_DELAYED_WORK(&dev->recv_work, nfcsim_wq_recv); | ||
450 | INIT_DELAYED_WORK(&dev->poll_work, nfcsim_wq_poll); | ||
451 | |||
452 | dev->nfc_dev = nfc_allocate_device(&nfcsim_nfc_ops, | ||
453 | NFC_PROTO_NFC_DEP_MASK, | ||
454 | 0, 0); | ||
455 | if (!dev->nfc_dev) | ||
456 | goto error; | ||
457 | |||
458 | nfc_set_drvdata(dev->nfc_dev, dev); | ||
459 | |||
460 | rc = nfc_register_device(dev->nfc_dev); | ||
461 | if (rc) | ||
462 | goto free_nfc_dev; | ||
463 | |||
464 | return dev; | ||
465 | |||
466 | free_nfc_dev: | ||
467 | nfc_free_device(dev->nfc_dev); | ||
468 | |||
469 | error: | ||
470 | kfree(dev); | ||
471 | |||
472 | return ERR_PTR(rc); | ||
473 | } | ||
474 | |||
475 | static void nfcsim_free_device(struct nfcsim *dev) | ||
476 | { | ||
477 | nfc_unregister_device(dev->nfc_dev); | ||
478 | |||
479 | nfc_free_device(dev->nfc_dev); | ||
480 | |||
481 | kfree(dev); | ||
482 | } | ||
483 | |||
484 | int __init nfcsim_init(void) | ||
485 | { | ||
486 | int rc; | ||
487 | |||
488 | /* We need an ordered wq to ensure that poll_work items are executed | ||
489 | * one at a time. | ||
490 | */ | ||
491 | wq = alloc_ordered_workqueue("nfcsim", 0); | ||
492 | if (!wq) { | ||
493 | rc = -ENOMEM; | ||
494 | goto exit; | ||
495 | } | ||
496 | |||
497 | dev0 = nfcsim_init_dev(); | ||
498 | if (IS_ERR(dev0)) { | ||
499 | rc = PTR_ERR(dev0); | ||
500 | goto exit; | ||
501 | } | ||
502 | |||
503 | dev1 = nfcsim_init_dev(); | ||
504 | if (IS_ERR(dev1)) { | ||
505 | kfree(dev0); | ||
506 | |||
507 | rc = PTR_ERR(dev1); | ||
508 | goto exit; | ||
509 | } | ||
510 | |||
511 | dev0->peer_dev = dev1; | ||
512 | dev1->peer_dev = dev0; | ||
513 | |||
514 | pr_debug("NFCsim " NFCSIM_VERSION " initialized\n"); | ||
515 | |||
516 | rc = 0; | ||
517 | exit: | ||
518 | if (rc) | ||
519 | pr_err("Failed to initialize nfcsim driver (%d)\n", | ||
520 | rc); | ||
521 | |||
522 | return rc; | ||
523 | } | ||
524 | |||
525 | void __exit nfcsim_exit(void) | ||
526 | { | ||
527 | nfcsim_cleanup_dev(dev0, 1); | ||
528 | nfcsim_cleanup_dev(dev1, 1); | ||
529 | |||
530 | nfcsim_free_device(dev0); | ||
531 | nfcsim_free_device(dev1); | ||
532 | |||
533 | destroy_workqueue(wq); | ||
534 | } | ||
535 | |||
536 | module_init(nfcsim_init); | ||
537 | module_exit(nfcsim_exit); | ||
538 | |||
539 | MODULE_DESCRIPTION("NFCSim driver ver " NFCSIM_VERSION); | ||
540 | MODULE_VERSION(NFCSIM_VERSION); | ||
541 | MODULE_LICENSE("GPL"); | ||