virtual_ncidev.c (4626B)
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 return 0; 58 } 59 60 if (send_buff) { 61 mutex_unlock(&nci_mutex); 62 return -1; 63 } 64 send_buff = skb_copy(skb, GFP_KERNEL); 65 mutex_unlock(&nci_mutex); 66 wake_up_interruptible(&wq); 67 68 return 0; 69} 70 71static const struct nci_ops virtual_nci_ops = { 72 .open = virtual_nci_open, 73 .close = virtual_nci_close, 74 .send = virtual_nci_send 75}; 76 77static ssize_t virtual_ncidev_read(struct file *file, char __user *buf, 78 size_t count, loff_t *ppos) 79{ 80 size_t actual_len; 81 82 mutex_lock(&nci_mutex); 83 while (!send_buff) { 84 mutex_unlock(&nci_mutex); 85 if (wait_event_interruptible(wq, send_buff)) 86 return -EFAULT; 87 mutex_lock(&nci_mutex); 88 } 89 90 actual_len = min_t(size_t, count, send_buff->len); 91 92 if (copy_to_user(buf, send_buff->data, actual_len)) { 93 mutex_unlock(&nci_mutex); 94 return -EFAULT; 95 } 96 97 skb_pull(send_buff, actual_len); 98 if (send_buff->len == 0) { 99 consume_skb(send_buff); 100 send_buff = NULL; 101 } 102 mutex_unlock(&nci_mutex); 103 104 return actual_len; 105} 106 107static ssize_t virtual_ncidev_write(struct file *file, 108 const char __user *buf, 109 size_t count, loff_t *ppos) 110{ 111 struct sk_buff *skb; 112 113 skb = alloc_skb(count, GFP_KERNEL); 114 if (!skb) 115 return -ENOMEM; 116 117 if (copy_from_user(skb_put(skb, count), buf, count)) { 118 kfree_skb(skb); 119 return -EFAULT; 120 } 121 122 nci_recv_frame(ndev, skb); 123 return count; 124} 125 126static int virtual_ncidev_open(struct inode *inode, struct file *file) 127{ 128 int ret = 0; 129 130 mutex_lock(&nci_mutex); 131 if (state != virtual_ncidev_disabled) { 132 mutex_unlock(&nci_mutex); 133 return -EBUSY; 134 } 135 136 ndev = nci_allocate_device(&virtual_nci_ops, VIRTUAL_NFC_PROTOCOLS, 137 0, 0); 138 if (!ndev) { 139 mutex_unlock(&nci_mutex); 140 return -ENOMEM; 141 } 142 143 ret = nci_register_device(ndev); 144 if (ret < 0) { 145 nci_free_device(ndev); 146 mutex_unlock(&nci_mutex); 147 return ret; 148 } 149 state = virtual_ncidev_enabled; 150 mutex_unlock(&nci_mutex); 151 152 return 0; 153} 154 155static int virtual_ncidev_close(struct inode *inode, struct file *file) 156{ 157 mutex_lock(&nci_mutex); 158 159 if (state == virtual_ncidev_enabled) { 160 state = virtual_ncidev_disabling; 161 mutex_unlock(&nci_mutex); 162 163 nci_unregister_device(ndev); 164 nci_free_device(ndev); 165 166 mutex_lock(&nci_mutex); 167 } 168 169 state = virtual_ncidev_disabled; 170 mutex_unlock(&nci_mutex); 171 172 return 0; 173} 174 175static long virtual_ncidev_ioctl(struct file *flip, unsigned int cmd, 176 unsigned long arg) 177{ 178 const struct nfc_dev *nfc_dev = ndev->nfc_dev; 179 void __user *p = (void __user *)arg; 180 181 if (cmd != IOCTL_GET_NCIDEV_IDX) 182 return -ENOTTY; 183 184 if (copy_to_user(p, &nfc_dev->idx, sizeof(nfc_dev->idx))) 185 return -EFAULT; 186 187 return 0; 188} 189 190static const struct file_operations virtual_ncidev_fops = { 191 .owner = THIS_MODULE, 192 .read = virtual_ncidev_read, 193 .write = virtual_ncidev_write, 194 .open = virtual_ncidev_open, 195 .release = virtual_ncidev_close, 196 .unlocked_ioctl = virtual_ncidev_ioctl 197}; 198 199static int __init virtual_ncidev_init(void) 200{ 201 state = virtual_ncidev_disabled; 202 miscdev.minor = MISC_DYNAMIC_MINOR; 203 miscdev.name = "virtual_nci"; 204 miscdev.fops = &virtual_ncidev_fops; 205 miscdev.mode = 0600; 206 207 return misc_register(&miscdev); 208} 209 210static void __exit virtual_ncidev_exit(void) 211{ 212 misc_deregister(&miscdev); 213} 214 215module_init(virtual_ncidev_init); 216module_exit(virtual_ncidev_exit); 217 218MODULE_LICENSE("GPL"); 219MODULE_DESCRIPTION("Virtual NCI device simulation driver"); 220MODULE_AUTHOR("Bongsu Jeon <bongsu.jeon@samsung.com>");