2021-01-27 22:08:28 +09:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Virtual NCI device simulation driver
*
* Copyright ( C ) 2020 Samsung Electrnoics
* Bongsu Jeon < bongsu . jeon @ samsung . com >
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/miscdevice.h>
# include <linux/mutex.h>
2021-08-17 06:28:11 -07:00
# include <linux/wait.h>
2021-01-27 22:08:28 +09:00
# include <net/nfc/nci_core.h>
# define IOCTL_GET_NCIDEV_IDX 0
# define VIRTUAL_NFC_PROTOCOLS (NFC_PROTO_JEWEL_MASK | \
NFC_PROTO_MIFARE_MASK | \
NFC_PROTO_FELICA_MASK | \
NFC_PROTO_ISO14443_MASK | \
NFC_PROTO_ISO14443_B_MASK | \
NFC_PROTO_ISO15693_MASK )
2022-11-15 11:00:17 +01:00
struct virtual_nci_dev {
struct nci_dev * ndev ;
struct mutex mtx ;
struct sk_buff * send_buff ;
struct wait_queue_head wq ;
} ;
2021-01-27 22:08:28 +09:00
static int virtual_nci_open ( struct nci_dev * ndev )
{
return 0 ;
}
static int virtual_nci_close ( struct nci_dev * ndev )
{
2022-11-15 11:00:17 +01:00
struct virtual_nci_dev * vdev = nci_get_drvdata ( ndev ) ;
mutex_lock ( & vdev - > mtx ) ;
kfree_skb ( vdev - > send_buff ) ;
vdev - > send_buff = NULL ;
mutex_unlock ( & vdev - > mtx ) ;
2021-01-27 22:08:28 +09:00
return 0 ;
}
static int virtual_nci_send ( struct nci_dev * ndev , struct sk_buff * skb )
{
2022-11-15 11:00:17 +01:00
struct virtual_nci_dev * vdev = nci_get_drvdata ( ndev ) ;
mutex_lock ( & vdev - > mtx ) ;
if ( vdev - > send_buff ) {
mutex_unlock ( & vdev - > mtx ) ;
2022-10-20 11:05:05 +08:00
kfree_skb ( skb ) ;
2022-11-15 11:00:17 +01:00
return - 1 ;
2021-01-27 22:08:28 +09:00
}
2022-11-15 11:00:17 +01:00
vdev - > send_buff = skb_copy ( skb , GFP_KERNEL ) ;
if ( ! vdev - > send_buff ) {
mutex_unlock ( & vdev - > mtx ) ;
2022-10-20 11:05:05 +08:00
kfree_skb ( skb ) ;
2021-01-27 22:08:28 +09:00
return - 1 ;
}
2022-11-15 11:00:17 +01:00
mutex_unlock ( & vdev - > mtx ) ;
wake_up_interruptible ( & vdev - > wq ) ;
2022-10-20 11:05:05 +08:00
consume_skb ( skb ) ;
2021-01-27 22:08:28 +09:00
return 0 ;
}
2021-07-24 23:47:33 +02:00
static const struct nci_ops virtual_nci_ops = {
2021-01-27 22:08:28 +09:00
. open = virtual_nci_open ,
. close = virtual_nci_close ,
. send = virtual_nci_send
} ;
static ssize_t virtual_ncidev_read ( struct file * file , char __user * buf ,
size_t count , loff_t * ppos )
{
2022-11-15 11:00:17 +01:00
struct virtual_nci_dev * vdev = file - > private_data ;
2021-01-27 22:08:28 +09:00
size_t actual_len ;
2022-11-15 11:00:17 +01:00
mutex_lock ( & vdev - > mtx ) ;
while ( ! vdev - > send_buff ) {
mutex_unlock ( & vdev - > mtx ) ;
if ( wait_event_interruptible ( vdev - > wq , vdev - > send_buff ) )
2021-08-17 06:28:11 -07:00
return - EFAULT ;
2022-11-15 11:00:17 +01:00
mutex_lock ( & vdev - > mtx ) ;
2021-01-27 22:08:28 +09:00
}
2022-11-15 11:00:17 +01:00
actual_len = min_t ( size_t , count , vdev - > send_buff - > len ) ;
2021-01-27 22:08:28 +09:00
2022-11-15 11:00:17 +01:00
if ( copy_to_user ( buf , vdev - > send_buff - > data , actual_len ) ) {
mutex_unlock ( & vdev - > mtx ) ;
2021-01-27 22:08:28 +09:00
return - EFAULT ;
}
2022-11-15 11:00:17 +01:00
skb_pull ( vdev - > send_buff , actual_len ) ;
if ( vdev - > send_buff - > len = = 0 ) {
consume_skb ( vdev - > send_buff ) ;
vdev - > send_buff = NULL ;
2021-01-27 22:08:28 +09:00
}
2022-11-15 11:00:17 +01:00
mutex_unlock ( & vdev - > mtx ) ;
2021-01-27 22:08:28 +09:00
return actual_len ;
}
static ssize_t virtual_ncidev_write ( struct file * file ,
const char __user * buf ,
size_t count , loff_t * ppos )
{
2022-11-15 11:00:17 +01:00
struct virtual_nci_dev * vdev = file - > private_data ;
2021-01-27 22:08:28 +09:00
struct sk_buff * skb ;
skb = alloc_skb ( count , GFP_KERNEL ) ;
if ( ! skb )
return - ENOMEM ;
if ( copy_from_user ( skb_put ( skb , count ) , buf , count ) ) {
kfree_skb ( skb ) ;
return - EFAULT ;
}
2022-11-15 11:00:17 +01:00
nci_recv_frame ( vdev - > ndev , skb ) ;
2021-01-27 22:08:28 +09:00
return count ;
}
static int virtual_ncidev_open ( struct inode * inode , struct file * file )
{
int ret = 0 ;
2022-11-15 11:00:17 +01:00
struct virtual_nci_dev * vdev ;
2021-01-27 22:08:28 +09:00
2022-11-15 11:00:17 +01:00
vdev = kzalloc ( sizeof ( * vdev ) , GFP_KERNEL ) ;
if ( ! vdev )
return - ENOMEM ;
vdev - > ndev = nci_allocate_device ( & virtual_nci_ops ,
VIRTUAL_NFC_PROTOCOLS , 0 , 0 ) ;
if ( ! vdev - > ndev ) {
kfree ( vdev ) ;
2021-01-27 22:08:28 +09:00
return - ENOMEM ;
}
2022-11-15 11:00:17 +01:00
mutex_init ( & vdev - > mtx ) ;
init_waitqueue_head ( & vdev - > wq ) ;
file - > private_data = vdev ;
nci_set_drvdata ( vdev - > ndev , vdev ) ;
ret = nci_register_device ( vdev - > ndev ) ;
2021-01-27 22:08:28 +09:00
if ( ret < 0 ) {
2022-11-15 11:00:17 +01:00
nci_free_device ( vdev - > ndev ) ;
mutex_destroy ( & vdev - > mtx ) ;
kfree ( vdev ) ;
2021-01-27 22:08:28 +09:00
return ret ;
}
return 0 ;
}
static int virtual_ncidev_close ( struct inode * inode , struct file * file )
{
2022-11-15 11:00:17 +01:00
struct virtual_nci_dev * vdev = file - > private_data ;
2021-01-27 22:08:28 +09:00
2022-11-15 11:00:17 +01:00
nci_unregister_device ( vdev - > ndev ) ;
nci_free_device ( vdev - > ndev ) ;
mutex_destroy ( & vdev - > mtx ) ;
kfree ( vdev ) ;
2021-01-27 22:08:28 +09:00
return 0 ;
}
2022-11-15 11:00:17 +01:00
static long virtual_ncidev_ioctl ( struct file * file , unsigned int cmd ,
2021-01-27 22:08:28 +09:00
unsigned long arg )
{
2022-11-15 11:00:17 +01:00
struct virtual_nci_dev * vdev = file - > private_data ;
const struct nfc_dev * nfc_dev = vdev - > ndev - > nfc_dev ;
2021-01-27 22:08:28 +09:00
void __user * p = ( void __user * ) arg ;
if ( cmd ! = IOCTL_GET_NCIDEV_IDX )
return - ENOTTY ;
if ( copy_to_user ( p , & nfc_dev - > idx , sizeof ( nfc_dev - > idx ) ) )
return - EFAULT ;
return 0 ;
}
static const struct file_operations virtual_ncidev_fops = {
. owner = THIS_MODULE ,
. read = virtual_ncidev_read ,
. write = virtual_ncidev_write ,
. open = virtual_ncidev_open ,
. release = virtual_ncidev_close ,
. unlocked_ioctl = virtual_ncidev_ioctl
} ;
2022-11-15 11:00:17 +01:00
static struct miscdevice miscdev = {
. minor = MISC_DYNAMIC_MINOR ,
. name = " virtual_nci " ,
. fops = & virtual_ncidev_fops ,
. mode = 0600 ,
} ;
2021-01-27 22:08:28 +09:00
static int __init virtual_ncidev_init ( void )
{
return misc_register ( & miscdev ) ;
}
static void __exit virtual_ncidev_exit ( void )
{
misc_deregister ( & miscdev ) ;
}
module_init ( virtual_ncidev_init ) ;
module_exit ( virtual_ncidev_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Virtual NCI device simulation driver " ) ;
MODULE_AUTHOR ( " Bongsu Jeon <bongsu.jeon@samsung.com> " ) ;