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>
enum virtual_ncidev_mode {
virtual_ncidev_enabled ,
virtual_ncidev_disabled ,
virtual_ncidev_disabling ,
} ;
# 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 )
static enum virtual_ncidev_mode state ;
2021-08-17 06:28:11 -07:00
static DECLARE_WAIT_QUEUE_HEAD ( wq ) ;
2021-01-27 22:08:28 +09:00
static struct miscdevice miscdev ;
static struct sk_buff * send_buff ;
static struct nci_dev * ndev ;
static DEFINE_MUTEX ( nci_mutex ) ;
static int virtual_nci_open ( struct nci_dev * ndev )
{
return 0 ;
}
static int virtual_nci_close ( struct nci_dev * ndev )
{
mutex_lock ( & nci_mutex ) ;
kfree_skb ( send_buff ) ;
send_buff = NULL ;
mutex_unlock ( & nci_mutex ) ;
return 0 ;
}
static int virtual_nci_send ( struct nci_dev * ndev , struct sk_buff * skb )
{
mutex_lock ( & nci_mutex ) ;
if ( state ! = virtual_ncidev_enabled ) {
mutex_unlock ( & nci_mutex ) ;
2022-10-20 11:05:05 +08:00
kfree_skb ( skb ) ;
2021-01-27 22:08:28 +09:00
return 0 ;
}
if ( send_buff ) {
mutex_unlock ( & nci_mutex ) ;
2022-10-20 11:05:05 +08:00
kfree_skb ( skb ) ;
2021-01-27 22:08:28 +09:00
return - 1 ;
}
send_buff = skb_copy ( skb , GFP_KERNEL ) ;
mutex_unlock ( & nci_mutex ) ;
2021-08-17 06:28:11 -07:00
wake_up_interruptible ( & 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 )
{
size_t actual_len ;
mutex_lock ( & nci_mutex ) ;
2021-08-17 06:28:11 -07:00
while ( ! send_buff ) {
2021-01-27 22:08:28 +09:00
mutex_unlock ( & nci_mutex ) ;
2021-08-17 06:28:11 -07:00
if ( wait_event_interruptible ( wq , send_buff ) )
return - EFAULT ;
mutex_lock ( & nci_mutex ) ;
2021-01-27 22:08:28 +09:00
}
actual_len = min_t ( size_t , count , send_buff - > len ) ;
if ( copy_to_user ( buf , send_buff - > data , actual_len ) ) {
mutex_unlock ( & nci_mutex ) ;
return - EFAULT ;
}
skb_pull ( send_buff , actual_len ) ;
if ( send_buff - > len = = 0 ) {
consume_skb ( send_buff ) ;
send_buff = NULL ;
}
mutex_unlock ( & nci_mutex ) ;
return actual_len ;
}
static ssize_t virtual_ncidev_write ( struct file * file ,
const char __user * buf ,
size_t count , loff_t * ppos )
{
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 ;
}
nci_recv_frame ( ndev , skb ) ;
return count ;
}
static int virtual_ncidev_open ( struct inode * inode , struct file * file )
{
int ret = 0 ;
mutex_lock ( & nci_mutex ) ;
if ( state ! = virtual_ncidev_disabled ) {
mutex_unlock ( & nci_mutex ) ;
return - EBUSY ;
}
ndev = nci_allocate_device ( & virtual_nci_ops , VIRTUAL_NFC_PROTOCOLS ,
0 , 0 ) ;
if ( ! ndev ) {
mutex_unlock ( & nci_mutex ) ;
return - ENOMEM ;
}
ret = nci_register_device ( ndev ) ;
if ( ret < 0 ) {
nci_free_device ( ndev ) ;
mutex_unlock ( & nci_mutex ) ;
return ret ;
}
state = virtual_ncidev_enabled ;
mutex_unlock ( & nci_mutex ) ;
return 0 ;
}
static int virtual_ncidev_close ( struct inode * inode , struct file * file )
{
mutex_lock ( & nci_mutex ) ;
if ( state = = virtual_ncidev_enabled ) {
state = virtual_ncidev_disabling ;
mutex_unlock ( & nci_mutex ) ;
nci_unregister_device ( ndev ) ;
nci_free_device ( ndev ) ;
mutex_lock ( & nci_mutex ) ;
}
state = virtual_ncidev_disabled ;
mutex_unlock ( & nci_mutex ) ;
return 0 ;
}
static long virtual_ncidev_ioctl ( struct file * flip , unsigned int cmd ,
unsigned long arg )
{
2021-07-29 12:40:15 +02:00
const struct nfc_dev * nfc_dev = 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
} ;
static int __init virtual_ncidev_init ( void )
{
state = virtual_ncidev_disabled ;
miscdev . minor = MISC_DYNAMIC_MINOR ;
miscdev . name = " virtual_nci " ;
miscdev . fops = & virtual_ncidev_fops ;
2021-11-25 11:14:57 -03:00
miscdev . mode = 0600 ;
2021-01-27 22:08:28 +09:00
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> " ) ;