162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Virtual NCI device simulation driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2020 Samsung Electrnoics
662306a36Sopenharmony_ci * Bongsu Jeon <bongsu.jeon@samsung.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/miscdevice.h>
1262306a36Sopenharmony_ci#include <linux/mutex.h>
1362306a36Sopenharmony_ci#include <linux/wait.h>
1462306a36Sopenharmony_ci#include <net/nfc/nci_core.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define IOCTL_GET_NCIDEV_IDX    0
1762306a36Sopenharmony_ci#define VIRTUAL_NFC_PROTOCOLS	(NFC_PROTO_JEWEL_MASK | \
1862306a36Sopenharmony_ci				 NFC_PROTO_MIFARE_MASK | \
1962306a36Sopenharmony_ci				 NFC_PROTO_FELICA_MASK | \
2062306a36Sopenharmony_ci				 NFC_PROTO_ISO14443_MASK | \
2162306a36Sopenharmony_ci				 NFC_PROTO_ISO14443_B_MASK | \
2262306a36Sopenharmony_ci				 NFC_PROTO_ISO15693_MASK)
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistruct virtual_nci_dev {
2562306a36Sopenharmony_ci	struct nci_dev *ndev;
2662306a36Sopenharmony_ci	struct mutex mtx;
2762306a36Sopenharmony_ci	struct sk_buff *send_buff;
2862306a36Sopenharmony_ci	struct wait_queue_head wq;
2962306a36Sopenharmony_ci	bool running;
3062306a36Sopenharmony_ci};
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic int virtual_nci_open(struct nci_dev *ndev)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	struct virtual_nci_dev *vdev = nci_get_drvdata(ndev);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	vdev->running = true;
3762306a36Sopenharmony_ci	return 0;
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic int virtual_nci_close(struct nci_dev *ndev)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	struct virtual_nci_dev *vdev = nci_get_drvdata(ndev);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	mutex_lock(&vdev->mtx);
4562306a36Sopenharmony_ci	kfree_skb(vdev->send_buff);
4662306a36Sopenharmony_ci	vdev->send_buff = NULL;
4762306a36Sopenharmony_ci	vdev->running = false;
4862306a36Sopenharmony_ci	mutex_unlock(&vdev->mtx);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return 0;
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int virtual_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct virtual_nci_dev *vdev = nci_get_drvdata(ndev);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	mutex_lock(&vdev->mtx);
5862306a36Sopenharmony_ci	if (vdev->send_buff || !vdev->running) {
5962306a36Sopenharmony_ci		mutex_unlock(&vdev->mtx);
6062306a36Sopenharmony_ci		kfree_skb(skb);
6162306a36Sopenharmony_ci		return -1;
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci	vdev->send_buff = skb_copy(skb, GFP_KERNEL);
6462306a36Sopenharmony_ci	if (!vdev->send_buff) {
6562306a36Sopenharmony_ci		mutex_unlock(&vdev->mtx);
6662306a36Sopenharmony_ci		kfree_skb(skb);
6762306a36Sopenharmony_ci		return -1;
6862306a36Sopenharmony_ci	}
6962306a36Sopenharmony_ci	mutex_unlock(&vdev->mtx);
7062306a36Sopenharmony_ci	wake_up_interruptible(&vdev->wq);
7162306a36Sopenharmony_ci	consume_skb(skb);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return 0;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic const struct nci_ops virtual_nci_ops = {
7762306a36Sopenharmony_ci	.open = virtual_nci_open,
7862306a36Sopenharmony_ci	.close = virtual_nci_close,
7962306a36Sopenharmony_ci	.send = virtual_nci_send
8062306a36Sopenharmony_ci};
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic ssize_t virtual_ncidev_read(struct file *file, char __user *buf,
8362306a36Sopenharmony_ci				   size_t count, loff_t *ppos)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct virtual_nci_dev *vdev = file->private_data;
8662306a36Sopenharmony_ci	size_t actual_len;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	mutex_lock(&vdev->mtx);
8962306a36Sopenharmony_ci	while (!vdev->send_buff) {
9062306a36Sopenharmony_ci		mutex_unlock(&vdev->mtx);
9162306a36Sopenharmony_ci		if (wait_event_interruptible(vdev->wq, vdev->send_buff))
9262306a36Sopenharmony_ci			return -EFAULT;
9362306a36Sopenharmony_ci		mutex_lock(&vdev->mtx);
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	actual_len = min_t(size_t, count, vdev->send_buff->len);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (copy_to_user(buf, vdev->send_buff->data, actual_len)) {
9962306a36Sopenharmony_ci		mutex_unlock(&vdev->mtx);
10062306a36Sopenharmony_ci		return -EFAULT;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	skb_pull(vdev->send_buff, actual_len);
10462306a36Sopenharmony_ci	if (vdev->send_buff->len == 0) {
10562306a36Sopenharmony_ci		consume_skb(vdev->send_buff);
10662306a36Sopenharmony_ci		vdev->send_buff = NULL;
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci	mutex_unlock(&vdev->mtx);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	return actual_len;
11162306a36Sopenharmony_ci}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic ssize_t virtual_ncidev_write(struct file *file,
11462306a36Sopenharmony_ci				    const char __user *buf,
11562306a36Sopenharmony_ci				    size_t count, loff_t *ppos)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	struct virtual_nci_dev *vdev = file->private_data;
11862306a36Sopenharmony_ci	struct sk_buff *skb;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	skb = alloc_skb(count, GFP_KERNEL);
12162306a36Sopenharmony_ci	if (!skb)
12262306a36Sopenharmony_ci		return -ENOMEM;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	if (copy_from_user(skb_put(skb, count), buf, count)) {
12562306a36Sopenharmony_ci		kfree_skb(skb);
12662306a36Sopenharmony_ci		return -EFAULT;
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	nci_recv_frame(vdev->ndev, skb);
13062306a36Sopenharmony_ci	return count;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic int virtual_ncidev_open(struct inode *inode, struct file *file)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	int ret = 0;
13662306a36Sopenharmony_ci	struct virtual_nci_dev *vdev;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
13962306a36Sopenharmony_ci	if (!vdev)
14062306a36Sopenharmony_ci		return -ENOMEM;
14162306a36Sopenharmony_ci	vdev->ndev = nci_allocate_device(&virtual_nci_ops,
14262306a36Sopenharmony_ci		VIRTUAL_NFC_PROTOCOLS, 0, 0);
14362306a36Sopenharmony_ci	if (!vdev->ndev) {
14462306a36Sopenharmony_ci		kfree(vdev);
14562306a36Sopenharmony_ci		return -ENOMEM;
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	mutex_init(&vdev->mtx);
14962306a36Sopenharmony_ci	init_waitqueue_head(&vdev->wq);
15062306a36Sopenharmony_ci	file->private_data = vdev;
15162306a36Sopenharmony_ci	nci_set_drvdata(vdev->ndev, vdev);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	ret = nci_register_device(vdev->ndev);
15462306a36Sopenharmony_ci	if (ret < 0) {
15562306a36Sopenharmony_ci		nci_free_device(vdev->ndev);
15662306a36Sopenharmony_ci		mutex_destroy(&vdev->mtx);
15762306a36Sopenharmony_ci		kfree(vdev);
15862306a36Sopenharmony_ci		return ret;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	return 0;
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic int virtual_ncidev_close(struct inode *inode, struct file *file)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	struct virtual_nci_dev *vdev = file->private_data;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	nci_unregister_device(vdev->ndev);
16962306a36Sopenharmony_ci	nci_free_device(vdev->ndev);
17062306a36Sopenharmony_ci	mutex_destroy(&vdev->mtx);
17162306a36Sopenharmony_ci	kfree(vdev);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	return 0;
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cistatic long virtual_ncidev_ioctl(struct file *file, unsigned int cmd,
17762306a36Sopenharmony_ci				 unsigned long arg)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	struct virtual_nci_dev *vdev = file->private_data;
18062306a36Sopenharmony_ci	const struct nfc_dev *nfc_dev = vdev->ndev->nfc_dev;
18162306a36Sopenharmony_ci	void __user *p = (void __user *)arg;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	if (cmd != IOCTL_GET_NCIDEV_IDX)
18462306a36Sopenharmony_ci		return -ENOTTY;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (copy_to_user(p, &nfc_dev->idx, sizeof(nfc_dev->idx)))
18762306a36Sopenharmony_ci		return -EFAULT;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic const struct file_operations virtual_ncidev_fops = {
19362306a36Sopenharmony_ci	.owner = THIS_MODULE,
19462306a36Sopenharmony_ci	.read = virtual_ncidev_read,
19562306a36Sopenharmony_ci	.write = virtual_ncidev_write,
19662306a36Sopenharmony_ci	.open = virtual_ncidev_open,
19762306a36Sopenharmony_ci	.release = virtual_ncidev_close,
19862306a36Sopenharmony_ci	.unlocked_ioctl = virtual_ncidev_ioctl
19962306a36Sopenharmony_ci};
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic struct miscdevice miscdev = {
20262306a36Sopenharmony_ci	.minor = MISC_DYNAMIC_MINOR,
20362306a36Sopenharmony_ci	.name = "virtual_nci",
20462306a36Sopenharmony_ci	.fops = &virtual_ncidev_fops,
20562306a36Sopenharmony_ci	.mode = 0600,
20662306a36Sopenharmony_ci};
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cimodule_misc_device(miscdev);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
21162306a36Sopenharmony_ciMODULE_DESCRIPTION("Virtual NCI device simulation driver");
21262306a36Sopenharmony_ciMODULE_AUTHOR("Bongsu Jeon <bongsu.jeon@samsung.com>");
213