2019-05-29 16:57:36 -07:00
// SPDX-License-Identifier: GPL-2.0-only
2013-01-08 15:55:45 -08:00
/*
* VMware VMCI Driver
*
* Copyright ( C ) 2012 VMware , Inc . All rights reserved .
*/
# include <linux/vmw_vmci_defs.h>
# include <linux/vmw_vmci_api.h>
# include <linux/miscdevice.h>
# include <linux/interrupt.h>
# include <linux/highmem.h>
# include <linux/atomic.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/sched.h>
2017-02-02 17:54:15 +01:00
# include <linux/cred.h>
2013-01-10 15:41:43 -08:00
# include <linux/slab.h>
2013-01-08 15:55:45 -08:00
# include <linux/file.h>
# include <linux/init.h>
# include <linux/poll.h>
# include <linux/pci.h>
# include <linux/smp.h>
# include <linux/fs.h>
# include <linux/io.h>
# include "vmci_handle_array.h"
# include "vmci_queue_pair.h"
# include "vmci_datagram.h"
# include "vmci_doorbell.h"
# include "vmci_resource.h"
# include "vmci_context.h"
# include "vmci_driver.h"
# include "vmci_event.h"
# define VMCI_UTIL_NUM_RESOURCES 1
enum {
VMCI_NOTIFY_RESOURCE_QUEUE_PAIR = 0 ,
VMCI_NOTIFY_RESOURCE_DOOR_BELL = 1 ,
} ;
enum {
VMCI_NOTIFY_RESOURCE_ACTION_NOTIFY = 0 ,
VMCI_NOTIFY_RESOURCE_ACTION_CREATE = 1 ,
VMCI_NOTIFY_RESOURCE_ACTION_DESTROY = 2 ,
} ;
/*
* VMCI driver initialization . This block can also be used to
* pass initial group membership etc .
*/
struct vmci_init_blk {
u32 cid ;
u32 flags ;
} ;
/* VMCIqueue_pairAllocInfo_VMToVM */
struct vmci_qp_alloc_info_vmvm {
struct vmci_handle handle ;
u32 peer ;
u32 flags ;
u64 produce_size ;
u64 consume_size ;
u64 produce_page_file ; /* User VA. */
u64 consume_page_file ; /* User VA. */
u64 produce_page_file_size ; /* Size of the file name array. */
u64 consume_page_file_size ; /* Size of the file name array. */
s32 result ;
u32 _pad ;
} ;
/* VMCISetNotifyInfo: Used to pass notify flag's address to the host driver. */
struct vmci_set_notify_info {
u64 notify_uva ;
s32 result ;
u32 _pad ;
} ;
/*
* Per - instance host state
*/
struct vmci_host_dev {
struct vmci_ctx * context ;
int user_version ;
enum vmci_obj_type ct_type ;
struct mutex lock ; /* Mutex lock for vmci context access */
} ;
static struct vmci_ctx * host_context ;
static bool vmci_host_device_initialized ;
static atomic_t vmci_host_active_users = ATOMIC_INIT ( 0 ) ;
/*
* Determines whether the VMCI host personality is
* available . Since the core functionality of the host driver is
* always present , all guests could possibly use the host
* personality . However , to minimize the deviation from the
* pre - unified driver state of affairs , we only consider the host
* device active if there is no active guest device or if there
* are VMX ' en with active VMCI contexts using the host device .
*/
bool vmci_host_code_active ( void )
{
return vmci_host_device_initialized & &
( ! vmci_guest_code_active ( ) | |
atomic_read ( & vmci_host_active_users ) > 0 ) ;
}
2019-11-14 10:57:47 +01:00
int vmci_host_users ( void )
{
return atomic_read ( & vmci_host_active_users ) ;
}
2013-01-08 15:55:45 -08:00
/*
* Called on open of / dev / vmci .
*/
static int vmci_host_open ( struct inode * inode , struct file * filp )
{
struct vmci_host_dev * vmci_host_dev ;
vmci_host_dev = kzalloc ( sizeof ( struct vmci_host_dev ) , GFP_KERNEL ) ;
if ( vmci_host_dev = = NULL )
return - ENOMEM ;
vmci_host_dev - > ct_type = VMCIOBJ_NOT_SET ;
mutex_init ( & vmci_host_dev - > lock ) ;
filp - > private_data = vmci_host_dev ;
return 0 ;
}
/*
* Called on close of / dev / vmci , most often when the process
* exits .
*/
static int vmci_host_close ( struct inode * inode , struct file * filp )
{
struct vmci_host_dev * vmci_host_dev = filp - > private_data ;
if ( vmci_host_dev - > ct_type = = VMCIOBJ_CONTEXT ) {
vmci_ctx_destroy ( vmci_host_dev - > context ) ;
vmci_host_dev - > context = NULL ;
/*
* The number of active contexts is used to track whether any
* VMX ' en are using the host personality . It is incremented when
* a context is created through the IOCTL_VMCI_INIT_CONTEXT
* ioctl .
*/
atomic_dec ( & vmci_host_active_users ) ;
}
vmci_host_dev - > ct_type = VMCIOBJ_NOT_SET ;
kfree ( vmci_host_dev ) ;
filp - > private_data = NULL ;
return 0 ;
}
/*
* This is used to wake up the VMX when a VMCI call arrives , or
* to wake up select ( ) or poll ( ) at the next clock tick .
*/
2017-07-03 06:39:46 -04:00
static __poll_t vmci_host_poll ( struct file * filp , poll_table * wait )
2013-01-08 15:55:45 -08:00
{
struct vmci_host_dev * vmci_host_dev = filp - > private_data ;
struct vmci_ctx * context = vmci_host_dev - > context ;
2017-07-03 06:39:46 -04:00
__poll_t mask = 0 ;
2013-01-08 15:55:45 -08:00
if ( vmci_host_dev - > ct_type = = VMCIOBJ_CONTEXT ) {
/* Check for VMCI calls to this VM context. */
if ( wait )
poll_wait ( filp , & context - > host_context . wait_queue ,
wait ) ;
spin_lock ( & context - > lock ) ;
if ( context - > pending_datagrams > 0 | |
vmci_handle_arr_get_size (
context - > pending_doorbell_array ) > 0 ) {
2018-02-11 14:34:03 -08:00
mask = EPOLLIN ;
2013-01-08 15:55:45 -08:00
}
spin_unlock ( & context - > lock ) ;
}
return mask ;
}
/*
* Copies the handles of a handle array into a user buffer , and
* returns the new length in userBufferSize . If the copy to the
* user buffer fails , the functions still returns VMCI_SUCCESS ,
* but retval ! = 0.
*/
static int drv_cp_harray_to_user ( void __user * user_buf_uva ,
u64 * user_buf_size ,
struct vmci_handle_arr * handle_array ,
int * retval )
{
u32 array_size = 0 ;
struct vmci_handle * handles ;
if ( handle_array )
array_size = vmci_handle_arr_get_size ( handle_array ) ;
if ( array_size * sizeof ( * handles ) > * user_buf_size )
return VMCI_ERROR_MORE_DATA ;
* user_buf_size = array_size * sizeof ( * handles ) ;
if ( * user_buf_size )
* retval = copy_to_user ( user_buf_uva ,
vmci_handle_arr_get_handles
( handle_array ) , * user_buf_size ) ;
return VMCI_SUCCESS ;
}
/*
2015-01-14 11:10:19 -08:00
* Sets up a given context for notify to work . Maps the notify
* boolean in user VA into kernel space .
2013-01-08 15:55:45 -08:00
*/
static int vmci_host_setup_notify ( struct vmci_ctx * context ,
unsigned long uva )
{
int retval ;
if ( context - > notify_page ) {
pr_devel ( " %s: Notify mechanism is already set up \n " , __func__ ) ;
return VMCI_ERROR_DUPLICATE_ENTRY ;
}
/*
* We are using ' bool ' internally , but let ' s make sure we explicit
* about the size .
*/
BUILD_BUG_ON ( sizeof ( bool ) ! = sizeof ( u8 ) ) ;
/*
* Lock physical page backing a given user VA .
*/
2019-05-13 17:17:11 -07:00
retval = get_user_pages_fast ( uva , 1 , FOLL_WRITE , & context - > notify_page ) ;
2015-01-14 11:10:19 -08:00
if ( retval ! = 1 ) {
context - > notify_page = NULL ;
2013-01-08 15:55:45 -08:00
return VMCI_ERROR_GENERIC ;
2015-01-14 11:10:19 -08:00
}
2013-01-08 15:55:45 -08:00
/*
* Map the locked page and set up notify pointer .
*/
2015-01-14 11:10:19 -08:00
context - > notify = kmap ( context - > notify_page ) + ( uva & ( PAGE_SIZE - 1 ) ) ;
2013-01-08 15:55:45 -08:00
vmci_ctx_check_signal_notify ( context ) ;
return VMCI_SUCCESS ;
}
static int vmci_host_get_version ( struct vmci_host_dev * vmci_host_dev ,
unsigned int cmd , void __user * uptr )
{
if ( cmd = = IOCTL_VMCI_VERSION2 ) {
int __user * vptr = uptr ;
if ( get_user ( vmci_host_dev - > user_version , vptr ) )
return - EFAULT ;
}
/*
* The basic logic here is :
*
* If the user sends in a version of 0 tell it our version .
* If the user didn ' t send in a version , tell it our version .
* If the user sent in an old version , tell it - its - version .
* If the user sent in an newer version , tell it our version .
*
* The rationale behind telling the caller its version is that
* Workstation 6.5 required that VMX and VMCI kernel module were
* version sync ' d . All new VMX users will be programmed to
* handle the VMCI kernel module version .
*/
if ( vmci_host_dev - > user_version > 0 & &
vmci_host_dev - > user_version < VMCI_VERSION_HOSTQP ) {
return vmci_host_dev - > user_version ;
}
return VMCI_VERSION ;
}
# define vmci_ioctl_err(fmt, ...) \
pr_devel ( " %s: " fmt , ioctl_name , # # __VA_ARGS__ )
static int vmci_host_do_init_context ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_init_blk init_block ;
const struct cred * cred ;
int retval ;
if ( copy_from_user ( & init_block , uptr , sizeof ( init_block ) ) ) {
vmci_ioctl_err ( " error reading init block \n " ) ;
return - EFAULT ;
}
mutex_lock ( & vmci_host_dev - > lock ) ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_NOT_SET ) {
vmci_ioctl_err ( " received VMCI init on initialized handle \n " ) ;
retval = - EINVAL ;
goto out ;
}
if ( init_block . flags & ~ VMCI_PRIVILEGE_FLAG_RESTRICTED ) {
vmci_ioctl_err ( " unsupported VMCI restriction flag \n " ) ;
retval = - EINVAL ;
goto out ;
}
cred = get_current_cred ( ) ;
vmci_host_dev - > context = vmci_ctx_create ( init_block . cid ,
init_block . flags , 0 ,
vmci_host_dev - > user_version ,
cred ) ;
put_cred ( cred ) ;
if ( IS_ERR ( vmci_host_dev - > context ) ) {
retval = PTR_ERR ( vmci_host_dev - > context ) ;
vmci_ioctl_err ( " error initializing context \n " ) ;
goto out ;
}
/*
* Copy cid to userlevel , we do this to allow the VMX
* to enforce its policy on cid generation .
*/
init_block . cid = vmci_ctx_get_id ( vmci_host_dev - > context ) ;
if ( copy_to_user ( uptr , & init_block , sizeof ( init_block ) ) ) {
vmci_ctx_destroy ( vmci_host_dev - > context ) ;
vmci_host_dev - > context = NULL ;
vmci_ioctl_err ( " error writing init block \n " ) ;
retval = - EFAULT ;
goto out ;
}
vmci_host_dev - > ct_type = VMCIOBJ_CONTEXT ;
atomic_inc ( & vmci_host_active_users ) ;
2019-11-14 10:57:47 +01:00
vmci_call_vsock_callback ( true ) ;
2013-01-08 15:55:45 -08:00
retval = 0 ;
out :
mutex_unlock ( & vmci_host_dev - > lock ) ;
return retval ;
}
static int vmci_host_do_send_datagram ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_datagram_snd_rcv_info send_info ;
struct vmci_datagram * dg = NULL ;
u32 cid ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & send_info , uptr , sizeof ( send_info ) ) )
return - EFAULT ;
if ( send_info . len > VMCI_MAX_DG_SIZE ) {
vmci_ioctl_err ( " datagram is too big (size=%d) \n " ,
send_info . len ) ;
return - EINVAL ;
}
if ( send_info . len < sizeof ( * dg ) ) {
vmci_ioctl_err ( " datagram is too small (size=%d) \n " ,
send_info . len ) ;
return - EINVAL ;
}
2016-05-20 17:48:56 +05:30
dg = memdup_user ( ( void __user * ) ( uintptr_t ) send_info . addr ,
send_info . len ) ;
if ( IS_ERR ( dg ) ) {
2013-01-08 15:55:45 -08:00
vmci_ioctl_err (
" cannot allocate memory to dispatch datagram \n " ) ;
2016-05-20 17:48:56 +05:30
return PTR_ERR ( dg ) ;
2013-01-08 15:55:45 -08:00
}
2015-02-19 10:33:56 -08:00
if ( VMCI_DG_SIZE ( dg ) ! = send_info . len ) {
vmci_ioctl_err ( " datagram size mismatch \n " ) ;
kfree ( dg ) ;
return - EINVAL ;
}
2013-01-08 15:55:45 -08:00
pr_devel ( " Datagram dst (handle=0x%x:0x%x) src (handle=0x%x:0x%x), payload (size=%llu bytes) \n " ,
dg - > dst . context , dg - > dst . resource ,
dg - > src . context , dg - > src . resource ,
( unsigned long long ) dg - > payload_size ) ;
/* Get source context id. */
cid = vmci_ctx_get_id ( vmci_host_dev - > context ) ;
send_info . result = vmci_datagram_dispatch ( cid , dg , true ) ;
kfree ( dg ) ;
return copy_to_user ( uptr , & send_info , sizeof ( send_info ) ) ? - EFAULT : 0 ;
}
static int vmci_host_do_receive_datagram ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_datagram_snd_rcv_info recv_info ;
struct vmci_datagram * dg = NULL ;
int retval ;
size_t size ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & recv_info , uptr , sizeof ( recv_info ) ) )
return - EFAULT ;
size = recv_info . len ;
recv_info . result = vmci_ctx_dequeue_datagram ( vmci_host_dev - > context ,
& size , & dg ) ;
if ( recv_info . result > = VMCI_SUCCESS ) {
void __user * ubuf = ( void __user * ) ( uintptr_t ) recv_info . addr ;
retval = copy_to_user ( ubuf , dg , VMCI_DG_SIZE ( dg ) ) ;
kfree ( dg ) ;
if ( retval ! = 0 )
return - EFAULT ;
}
return copy_to_user ( uptr , & recv_info , sizeof ( recv_info ) ) ? - EFAULT : 0 ;
}
static int vmci_host_do_alloc_queuepair ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_handle handle ;
int vmci_status ;
int __user * retptr ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( vmci_host_dev - > user_version < VMCI_VERSION_NOVMVM ) {
struct vmci_qp_alloc_info_vmvm alloc_info ;
struct vmci_qp_alloc_info_vmvm __user * info = uptr ;
if ( copy_from_user ( & alloc_info , uptr , sizeof ( alloc_info ) ) )
return - EFAULT ;
handle = alloc_info . handle ;
retptr = & info - > result ;
vmci_status = vmci_qp_broker_alloc ( alloc_info . handle ,
alloc_info . peer ,
alloc_info . flags ,
VMCI_NO_PRIVILEGE_FLAGS ,
alloc_info . produce_size ,
alloc_info . consume_size ,
NULL ,
vmci_host_dev - > context ) ;
if ( vmci_status = = VMCI_SUCCESS )
vmci_status = VMCI_SUCCESS_QUEUEPAIR_CREATE ;
} else {
struct vmci_qp_alloc_info alloc_info ;
struct vmci_qp_alloc_info __user * info = uptr ;
struct vmci_qp_page_store page_store ;
if ( copy_from_user ( & alloc_info , uptr , sizeof ( alloc_info ) ) )
return - EFAULT ;
handle = alloc_info . handle ;
retptr = & info - > result ;
page_store . pages = alloc_info . ppn_va ;
page_store . len = alloc_info . num_ppns ;
vmci_status = vmci_qp_broker_alloc ( alloc_info . handle ,
alloc_info . peer ,
alloc_info . flags ,
VMCI_NO_PRIVILEGE_FLAGS ,
alloc_info . produce_size ,
alloc_info . consume_size ,
& page_store ,
vmci_host_dev - > context ) ;
}
if ( put_user ( vmci_status , retptr ) ) {
if ( vmci_status > = VMCI_SUCCESS ) {
vmci_status = vmci_qp_broker_detach ( handle ,
vmci_host_dev - > context ) ;
}
return - EFAULT ;
}
return 0 ;
}
static int vmci_host_do_queuepair_setva ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_qp_set_va_info set_va_info ;
struct vmci_qp_set_va_info __user * info = uptr ;
s32 result ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( vmci_host_dev - > user_version < VMCI_VERSION_NOVMVM ) {
vmci_ioctl_err ( " is not allowed \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & set_va_info , uptr , sizeof ( set_va_info ) ) )
return - EFAULT ;
if ( set_va_info . va ) {
/*
* VMX is passing down a new VA for the queue
* pair mapping .
*/
result = vmci_qp_broker_map ( set_va_info . handle ,
vmci_host_dev - > context ,
set_va_info . va ) ;
} else {
/*
* The queue pair is about to be unmapped by
* the VMX .
*/
result = vmci_qp_broker_unmap ( set_va_info . handle ,
vmci_host_dev - > context , 0 ) ;
}
return put_user ( result , & info - > result ) ? - EFAULT : 0 ;
}
static int vmci_host_do_queuepair_setpf ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_qp_page_file_info page_file_info ;
struct vmci_qp_page_file_info __user * info = uptr ;
s32 result ;
if ( vmci_host_dev - > user_version < VMCI_VERSION_HOSTQP | |
vmci_host_dev - > user_version > = VMCI_VERSION_NOVMVM ) {
vmci_ioctl_err ( " not supported on this VMX (version=%d) \n " ,
vmci_host_dev - > user_version ) ;
return - EINVAL ;
}
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & page_file_info , uptr , sizeof ( * info ) ) )
return - EFAULT ;
/*
* Communicate success pre - emptively to the caller . Note that the
* basic premise is that it is incumbent upon the caller not to look at
* the info . result field until after the ioctl ( ) returns . And then ,
* only if the ioctl ( ) result indicates no error . We send up the
* SUCCESS status before calling SetPageStore ( ) store because failing
* to copy up the result code means unwinding the SetPageStore ( ) .
*
* It turns out the logic to unwind a SetPageStore ( ) opens a can of
* worms . For example , if a host had created the queue_pair and a
* guest attaches and SetPageStore ( ) is successful but writing success
* fails , then . . . the host has to be stopped from writing ( anymore )
* data into the queue_pair . That means an additional test in the
* VMCI_Enqueue ( ) code path . Ugh .
*/
if ( put_user ( VMCI_SUCCESS , & info - > result ) ) {
/*
* In this case , we can ' t write a result field of the
* caller ' s info block . So , we don ' t even try to
* SetPageStore ( ) .
*/
return - EFAULT ;
}
result = vmci_qp_broker_set_page_store ( page_file_info . handle ,
page_file_info . produce_va ,
page_file_info . consume_va ,
vmci_host_dev - > context ) ;
if ( result < VMCI_SUCCESS ) {
if ( put_user ( result , & info - > result ) ) {
/*
* Note that in this case the SetPageStore ( )
* call failed but we were unable to
* communicate that to the caller ( because the
* copy_to_user ( ) call failed ) . So , if we
* simply return an error ( in this case
* - EFAULT ) then the caller will know that the
* SetPageStore failed even though we couldn ' t
* put the result code in the result field and
* indicate exactly why it failed .
*
* That says nothing about the issue where we
* were once able to write to the caller ' s info
* memory and now can ' t . Something more
* serious is probably going on than the fact
* that SetPageStore ( ) didn ' t work .
*/
return - EFAULT ;
}
}
return 0 ;
}
static int vmci_host_do_qp_detach ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_qp_dtch_info detach_info ;
struct vmci_qp_dtch_info __user * info = uptr ;
s32 result ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & detach_info , uptr , sizeof ( detach_info ) ) )
return - EFAULT ;
result = vmci_qp_broker_detach ( detach_info . handle ,
vmci_host_dev - > context ) ;
if ( result = = VMCI_SUCCESS & &
vmci_host_dev - > user_version < VMCI_VERSION_NOVMVM ) {
result = VMCI_SUCCESS_LAST_DETACH ;
}
return put_user ( result , & info - > result ) ? - EFAULT : 0 ;
}
static int vmci_host_do_ctx_add_notify ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_ctx_info ar_info ;
struct vmci_ctx_info __user * info = uptr ;
s32 result ;
u32 cid ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & ar_info , uptr , sizeof ( ar_info ) ) )
return - EFAULT ;
cid = vmci_ctx_get_id ( vmci_host_dev - > context ) ;
result = vmci_ctx_add_notification ( cid , ar_info . remote_cid ) ;
return put_user ( result , & info - > result ) ? - EFAULT : 0 ;
}
static int vmci_host_do_ctx_remove_notify ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_ctx_info ar_info ;
struct vmci_ctx_info __user * info = uptr ;
u32 cid ;
int result ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & ar_info , uptr , sizeof ( ar_info ) ) )
return - EFAULT ;
cid = vmci_ctx_get_id ( vmci_host_dev - > context ) ;
result = vmci_ctx_remove_notification ( cid ,
ar_info . remote_cid ) ;
return put_user ( result , & info - > result ) ? - EFAULT : 0 ;
}
static int vmci_host_do_ctx_get_cpt_state ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_ctx_chkpt_buf_info get_info ;
u32 cid ;
void * cpt_buf ;
int retval ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & get_info , uptr , sizeof ( get_info ) ) )
return - EFAULT ;
cid = vmci_ctx_get_id ( vmci_host_dev - > context ) ;
get_info . result = vmci_ctx_get_chkpt_state ( cid , get_info . cpt_type ,
& get_info . buf_size , & cpt_buf ) ;
if ( get_info . result = = VMCI_SUCCESS & & get_info . buf_size ) {
void __user * ubuf = ( void __user * ) ( uintptr_t ) get_info . cpt_buf ;
retval = copy_to_user ( ubuf , cpt_buf , get_info . buf_size ) ;
kfree ( cpt_buf ) ;
if ( retval )
return - EFAULT ;
}
return copy_to_user ( uptr , & get_info , sizeof ( get_info ) ) ? - EFAULT : 0 ;
}
static int vmci_host_do_ctx_set_cpt_state ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_ctx_chkpt_buf_info set_info ;
u32 cid ;
void * cpt_buf ;
int retval ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & set_info , uptr , sizeof ( set_info ) ) )
return - EFAULT ;
2018-12-10 19:36:07 +08:00
cpt_buf = memdup_user ( ( void __user * ) ( uintptr_t ) set_info . cpt_buf ,
set_info . buf_size ) ;
if ( IS_ERR ( cpt_buf ) )
return PTR_ERR ( cpt_buf ) ;
2013-01-08 15:55:45 -08:00
cid = vmci_ctx_get_id ( vmci_host_dev - > context ) ;
set_info . result = vmci_ctx_set_chkpt_state ( cid , set_info . cpt_type ,
set_info . buf_size , cpt_buf ) ;
retval = copy_to_user ( uptr , & set_info , sizeof ( set_info ) ) ? - EFAULT : 0 ;
kfree ( cpt_buf ) ;
return retval ;
}
static int vmci_host_do_get_context_id ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
u32 __user * u32ptr = uptr ;
return put_user ( VMCI_HOST_CONTEXT_ID , u32ptr ) ? - EFAULT : 0 ;
}
static int vmci_host_do_set_notify ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_set_notify_info notify_info ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & notify_info , uptr , sizeof ( notify_info ) ) )
return - EFAULT ;
if ( notify_info . notify_uva ) {
notify_info . result =
vmci_host_setup_notify ( vmci_host_dev - > context ,
notify_info . notify_uva ) ;
} else {
vmci_ctx_unset_notify ( vmci_host_dev - > context ) ;
notify_info . result = VMCI_SUCCESS ;
}
return copy_to_user ( uptr , & notify_info , sizeof ( notify_info ) ) ?
- EFAULT : 0 ;
}
static int vmci_host_do_notify_resource ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_dbell_notify_resource_info info ;
u32 cid ;
if ( vmci_host_dev - > user_version < VMCI_VERSION_NOTIFY ) {
vmci_ioctl_err ( " invalid for current VMX versions \n " ) ;
return - EINVAL ;
}
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & info , uptr , sizeof ( info ) ) )
return - EFAULT ;
cid = vmci_ctx_get_id ( vmci_host_dev - > context ) ;
switch ( info . action ) {
case VMCI_NOTIFY_RESOURCE_ACTION_NOTIFY :
if ( info . resource = = VMCI_NOTIFY_RESOURCE_DOOR_BELL ) {
u32 flags = VMCI_NO_PRIVILEGE_FLAGS ;
info . result = vmci_ctx_notify_dbell ( cid , info . handle ,
flags ) ;
} else {
info . result = VMCI_ERROR_UNAVAILABLE ;
}
break ;
case VMCI_NOTIFY_RESOURCE_ACTION_CREATE :
info . result = vmci_ctx_dbell_create ( cid , info . handle ) ;
break ;
case VMCI_NOTIFY_RESOURCE_ACTION_DESTROY :
info . result = vmci_ctx_dbell_destroy ( cid , info . handle ) ;
break ;
default :
vmci_ioctl_err ( " got unknown action (action=%d) \n " ,
info . action ) ;
info . result = VMCI_ERROR_INVALID_ARGS ;
}
return copy_to_user ( uptr , & info , sizeof ( info ) ) ? - EFAULT : 0 ;
}
static int vmci_host_do_recv_notifications ( struct vmci_host_dev * vmci_host_dev ,
const char * ioctl_name ,
void __user * uptr )
{
struct vmci_ctx_notify_recv_info info ;
struct vmci_handle_arr * db_handle_array ;
struct vmci_handle_arr * qp_handle_array ;
void __user * ubuf ;
u32 cid ;
int retval = 0 ;
if ( vmci_host_dev - > ct_type ! = VMCIOBJ_CONTEXT ) {
vmci_ioctl_err ( " only valid for contexts \n " ) ;
return - EINVAL ;
}
if ( vmci_host_dev - > user_version < VMCI_VERSION_NOTIFY ) {
vmci_ioctl_err ( " not supported for the current vmx version \n " ) ;
return - EINVAL ;
}
if ( copy_from_user ( & info , uptr , sizeof ( info ) ) )
return - EFAULT ;
if ( ( info . db_handle_buf_size & & ! info . db_handle_buf_uva ) | |
( info . qp_handle_buf_size & & ! info . qp_handle_buf_uva ) ) {
return - EINVAL ;
}
cid = vmci_ctx_get_id ( vmci_host_dev - > context ) ;
info . result = vmci_ctx_rcv_notifications_get ( cid ,
& db_handle_array , & qp_handle_array ) ;
if ( info . result ! = VMCI_SUCCESS )
return copy_to_user ( uptr , & info , sizeof ( info ) ) ? - EFAULT : 0 ;
ubuf = ( void __user * ) ( uintptr_t ) info . db_handle_buf_uva ;
info . result = drv_cp_harray_to_user ( ubuf , & info . db_handle_buf_size ,
db_handle_array , & retval ) ;
if ( info . result = = VMCI_SUCCESS & & ! retval ) {
ubuf = ( void __user * ) ( uintptr_t ) info . qp_handle_buf_uva ;
info . result = drv_cp_harray_to_user ( ubuf ,
& info . qp_handle_buf_size ,
qp_handle_array , & retval ) ;
}
if ( ! retval & & copy_to_user ( uptr , & info , sizeof ( info ) ) )
retval = - EFAULT ;
vmci_ctx_rcv_notifications_release ( cid ,
db_handle_array , qp_handle_array ,
info . result = = VMCI_SUCCESS & & ! retval ) ;
return retval ;
}
static long vmci_host_unlocked_ioctl ( struct file * filp ,
unsigned int iocmd , unsigned long ioarg )
{
# define VMCI_DO_IOCTL(ioctl_name, ioctl_fn) do { \
2021-03-02 18:33:46 +03:00
char * name = " IOCTL_VMCI_ " # ioctl_name ; \
2013-01-08 15:55:45 -08:00
return vmci_host_do_ # # ioctl_fn ( \
vmci_host_dev , name , uptr ) ; \
} while ( 0 )
struct vmci_host_dev * vmci_host_dev = filp - > private_data ;
void __user * uptr = ( void __user * ) ioarg ;
switch ( iocmd ) {
case IOCTL_VMCI_INIT_CONTEXT :
VMCI_DO_IOCTL ( INIT_CONTEXT , init_context ) ;
case IOCTL_VMCI_DATAGRAM_SEND :
VMCI_DO_IOCTL ( DATAGRAM_SEND , send_datagram ) ;
case IOCTL_VMCI_DATAGRAM_RECEIVE :
VMCI_DO_IOCTL ( DATAGRAM_RECEIVE , receive_datagram ) ;
case IOCTL_VMCI_QUEUEPAIR_ALLOC :
VMCI_DO_IOCTL ( QUEUEPAIR_ALLOC , alloc_queuepair ) ;
case IOCTL_VMCI_QUEUEPAIR_SETVA :
VMCI_DO_IOCTL ( QUEUEPAIR_SETVA , queuepair_setva ) ;
case IOCTL_VMCI_QUEUEPAIR_SETPAGEFILE :
VMCI_DO_IOCTL ( QUEUEPAIR_SETPAGEFILE , queuepair_setpf ) ;
case IOCTL_VMCI_QUEUEPAIR_DETACH :
VMCI_DO_IOCTL ( QUEUEPAIR_DETACH , qp_detach ) ;
case IOCTL_VMCI_CTX_ADD_NOTIFICATION :
VMCI_DO_IOCTL ( CTX_ADD_NOTIFICATION , ctx_add_notify ) ;
case IOCTL_VMCI_CTX_REMOVE_NOTIFICATION :
VMCI_DO_IOCTL ( CTX_REMOVE_NOTIFICATION , ctx_remove_notify ) ;
case IOCTL_VMCI_CTX_GET_CPT_STATE :
VMCI_DO_IOCTL ( CTX_GET_CPT_STATE , ctx_get_cpt_state ) ;
case IOCTL_VMCI_CTX_SET_CPT_STATE :
VMCI_DO_IOCTL ( CTX_SET_CPT_STATE , ctx_set_cpt_state ) ;
case IOCTL_VMCI_GET_CONTEXT_ID :
VMCI_DO_IOCTL ( GET_CONTEXT_ID , get_context_id ) ;
case IOCTL_VMCI_SET_NOTIFY :
VMCI_DO_IOCTL ( SET_NOTIFY , set_notify ) ;
case IOCTL_VMCI_NOTIFY_RESOURCE :
VMCI_DO_IOCTL ( NOTIFY_RESOURCE , notify_resource ) ;
case IOCTL_VMCI_NOTIFICATIONS_RECEIVE :
VMCI_DO_IOCTL ( NOTIFICATIONS_RECEIVE , recv_notifications ) ;
case IOCTL_VMCI_VERSION :
case IOCTL_VMCI_VERSION2 :
return vmci_host_get_version ( vmci_host_dev , iocmd , uptr ) ;
default :
pr_devel ( " %s: Unknown ioctl (iocmd=%d) \n " , __func__ , iocmd ) ;
return - EINVAL ;
}
# undef VMCI_DO_IOCTL
}
static const struct file_operations vmuser_fops = {
. owner = THIS_MODULE ,
. open = vmci_host_open ,
. release = vmci_host_close ,
. poll = vmci_host_poll ,
. unlocked_ioctl = vmci_host_unlocked_ioctl ,
2018-09-11 21:59:08 +02:00
. compat_ioctl = compat_ptr_ioctl ,
2013-01-08 15:55:45 -08:00
} ;
static struct miscdevice vmci_host_miscdev = {
. name = " vmci " ,
. minor = MISC_DYNAMIC_MINOR ,
. fops = & vmuser_fops ,
} ;
int __init vmci_host_init ( void )
{
int error ;
host_context = vmci_ctx_create ( VMCI_HOST_CONTEXT_ID ,
VMCI_DEFAULT_PROC_PRIVILEGE_FLAGS ,
- 1 , VMCI_VERSION , NULL ) ;
if ( IS_ERR ( host_context ) ) {
error = PTR_ERR ( host_context ) ;
pr_warn ( " Failed to initialize VMCIContext (error%d) \n " ,
error ) ;
return error ;
}
error = misc_register ( & vmci_host_miscdev ) ;
if ( error ) {
pr_warn ( " Module registration error (name=%s, major=%d, minor=%d, err=%d) \n " ,
vmci_host_miscdev . name ,
MISC_MAJOR , vmci_host_miscdev . minor ,
error ) ;
pr_warn ( " Unable to initialize host personality \n " ) ;
vmci_ctx_destroy ( host_context ) ;
return error ;
}
pr_info ( " VMCI host device registered (name=%s, major=%d, minor=%d) \n " ,
vmci_host_miscdev . name , MISC_MAJOR , vmci_host_miscdev . minor ) ;
vmci_host_device_initialized = true ;
return 0 ;
}
void __exit vmci_host_exit ( void )
{
vmci_host_device_initialized = false ;
2015-07-30 15:59:57 -07:00
misc_deregister ( & vmci_host_miscdev ) ;
2013-01-08 15:55:45 -08:00
vmci_ctx_destroy ( host_context ) ;
vmci_qp_broker_exit ( ) ;
pr_debug ( " VMCI host driver module unloaded \n " ) ;
}