Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Virtual NCI device simulation driver
4 *
5 * Copyright (C) 2020 Samsung Electrnoics
6 * Bongsu Jeon <bongsu.jeon@samsung.com>
7 */
8
9#include <linux/kernel.h>
10#include <linux/module.h>
11#include <linux/miscdevice.h>
12#include <linux/mutex.h>
13#include <linux/wait.h>
14#include <net/nfc/nci_core.h>
15
16enum virtual_ncidev_mode {
17 virtual_ncidev_enabled,
18 virtual_ncidev_disabled,
19 virtual_ncidev_disabling,
20};
21
22#define IOCTL_GET_NCIDEV_IDX 0
23#define VIRTUAL_NFC_PROTOCOLS (NFC_PROTO_JEWEL_MASK | \
24 NFC_PROTO_MIFARE_MASK | \
25 NFC_PROTO_FELICA_MASK | \
26 NFC_PROTO_ISO14443_MASK | \
27 NFC_PROTO_ISO14443_B_MASK | \
28 NFC_PROTO_ISO15693_MASK)
29
30static enum virtual_ncidev_mode state;
31static DECLARE_WAIT_QUEUE_HEAD(wq);
32static struct miscdevice miscdev;
33static struct sk_buff *send_buff;
34static struct nci_dev *ndev;
35static DEFINE_MUTEX(nci_mutex);
36
37static int virtual_nci_open(struct nci_dev *ndev)
38{
39 return 0;
40}
41
42static int virtual_nci_close(struct nci_dev *ndev)
43{
44 mutex_lock(&nci_mutex);
45 kfree_skb(send_buff);
46 send_buff = NULL;
47 mutex_unlock(&nci_mutex);
48
49 return 0;
50}
51
52static int virtual_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
53{
54 mutex_lock(&nci_mutex);
55 if (state != virtual_ncidev_enabled) {
56 mutex_unlock(&nci_mutex);
57 kfree_skb(skb);
58 return 0;
59 }
60
61 if (send_buff) {
62 mutex_unlock(&nci_mutex);
63 kfree_skb(skb);
64 return -1;
65 }
66 send_buff = skb_copy(skb, GFP_KERNEL);
67 mutex_unlock(&nci_mutex);
68 wake_up_interruptible(&wq);
69 consume_skb(skb);
70
71 return 0;
72}
73
74static const struct nci_ops virtual_nci_ops = {
75 .open = virtual_nci_open,
76 .close = virtual_nci_close,
77 .send = virtual_nci_send
78};
79
80static ssize_t virtual_ncidev_read(struct file *file, char __user *buf,
81 size_t count, loff_t *ppos)
82{
83 size_t actual_len;
84
85 mutex_lock(&nci_mutex);
86 while (!send_buff) {
87 mutex_unlock(&nci_mutex);
88 if (wait_event_interruptible(wq, send_buff))
89 return -EFAULT;
90 mutex_lock(&nci_mutex);
91 }
92
93 actual_len = min_t(size_t, count, send_buff->len);
94
95 if (copy_to_user(buf, send_buff->data, actual_len)) {
96 mutex_unlock(&nci_mutex);
97 return -EFAULT;
98 }
99
100 skb_pull(send_buff, actual_len);
101 if (send_buff->len == 0) {
102 consume_skb(send_buff);
103 send_buff = NULL;
104 }
105 mutex_unlock(&nci_mutex);
106
107 return actual_len;
108}
109
110static ssize_t virtual_ncidev_write(struct file *file,
111 const char __user *buf,
112 size_t count, loff_t *ppos)
113{
114 struct sk_buff *skb;
115
116 skb = alloc_skb(count, GFP_KERNEL);
117 if (!skb)
118 return -ENOMEM;
119
120 if (copy_from_user(skb_put(skb, count), buf, count)) {
121 kfree_skb(skb);
122 return -EFAULT;
123 }
124
125 nci_recv_frame(ndev, skb);
126 return count;
127}
128
129static int virtual_ncidev_open(struct inode *inode, struct file *file)
130{
131 int ret = 0;
132
133 mutex_lock(&nci_mutex);
134 if (state != virtual_ncidev_disabled) {
135 mutex_unlock(&nci_mutex);
136 return -EBUSY;
137 }
138
139 ndev = nci_allocate_device(&virtual_nci_ops, VIRTUAL_NFC_PROTOCOLS,
140 0, 0);
141 if (!ndev) {
142 mutex_unlock(&nci_mutex);
143 return -ENOMEM;
144 }
145
146 ret = nci_register_device(ndev);
147 if (ret < 0) {
148 nci_free_device(ndev);
149 mutex_unlock(&nci_mutex);
150 return ret;
151 }
152 state = virtual_ncidev_enabled;
153 mutex_unlock(&nci_mutex);
154
155 return 0;
156}
157
158static int virtual_ncidev_close(struct inode *inode, struct file *file)
159{
160 mutex_lock(&nci_mutex);
161
162 if (state == virtual_ncidev_enabled) {
163 state = virtual_ncidev_disabling;
164 mutex_unlock(&nci_mutex);
165
166 nci_unregister_device(ndev);
167 nci_free_device(ndev);
168
169 mutex_lock(&nci_mutex);
170 }
171
172 state = virtual_ncidev_disabled;
173 mutex_unlock(&nci_mutex);
174
175 return 0;
176}
177
178static long virtual_ncidev_ioctl(struct file *flip, unsigned int cmd,
179 unsigned long arg)
180{
181 const struct nfc_dev *nfc_dev = ndev->nfc_dev;
182 void __user *p = (void __user *)arg;
183
184 if (cmd != IOCTL_GET_NCIDEV_IDX)
185 return -ENOTTY;
186
187 if (copy_to_user(p, &nfc_dev->idx, sizeof(nfc_dev->idx)))
188 return -EFAULT;
189
190 return 0;
191}
192
193static const struct file_operations virtual_ncidev_fops = {
194 .owner = THIS_MODULE,
195 .read = virtual_ncidev_read,
196 .write = virtual_ncidev_write,
197 .open = virtual_ncidev_open,
198 .release = virtual_ncidev_close,
199 .unlocked_ioctl = virtual_ncidev_ioctl
200};
201
202static int __init virtual_ncidev_init(void)
203{
204 state = virtual_ncidev_disabled;
205 miscdev.minor = MISC_DYNAMIC_MINOR;
206 miscdev.name = "virtual_nci";
207 miscdev.fops = &virtual_ncidev_fops;
208 miscdev.mode = 0600;
209
210 return misc_register(&miscdev);
211}
212
213static void __exit virtual_ncidev_exit(void)
214{
215 misc_deregister(&miscdev);
216}
217
218module_init(virtual_ncidev_init);
219module_exit(virtual_ncidev_exit);
220
221MODULE_LICENSE("GPL");
222MODULE_DESCRIPTION("Virtual NCI device simulation driver");
223MODULE_AUTHOR("Bongsu Jeon <bongsu.jeon@samsung.com>");