xref: /openbmc/linux/drivers/nfc/virtual_ncidev.c (revision 8ffdff6a)
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>");
216