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