vboxmod.c revision eb0398a29ff0e9c8456afeef89fbffca86a626e8
/** @file
* vboxadd -- VirtualBox Guest Additions for Linux
*/
/*
* Copyright (C) 2006-2007 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
#include "the-linux-kernel.h"
#include "version-generated.h"
/* #define IRQ_DEBUG */
/* #define IOCTL_DEBUG */
#ifdef IOCTL_DEBUG
# define IOCTL_ENTRY(name, arg) \
do { \
Log(("IOCTL_ENTRY: %s, 0x%x\n", (name), (arg))); \
} while(0)
# define IOCTL_EXIT(name, arg) \
do { \
Log(("IOCTL_EXIT: %s, 0x%x\n", (name), (arg))); \
} while(0)
#else
# define IOCTL_ENTRY(name, arg) do { } while(0)
# define IOCTL_EXIT(name, arg) do { } while(0)
#endif
#ifdef IOCTL_LOG_DEBUG
# define IOCTL_LOG_ENTRY(arg) \
do { \
Log(("IOCTL_ENTRY: Log, 0x%x\n", (arg))); \
} while(0)
# define IOCTL_LOG_EXIT(arg) \
do { \
Log(("IOCTL_EXIT: Log, 0x%x\n", (arg))); \
} while(0)
#else
# define IOCTL_LOG_ENTRY(arg) do { } while(0)
# define IOCTL_LOG_EXIT(arg) do { } while(0)
#endif
#ifdef IOCTL_VMM_DEBUG
# define IOCTL_VMM_ENTRY(arg) \
do { \
Log(("IOCTL_ENTRY: VMMDevReq, 0x%x\n", (arg))); \
} while(0)
# define IOCTL_VMM_EXIT(arg) \
do { \
Log(("IOCTL_EXIT: VMMDevReq, 0x%x\n", (arg))); \
} while(0)
#else
# define IOCTL_VMM_ENTRY(arg) do { } while(0)
# define IOCTL_VMM_EXIT(arg) do { } while(0)
#endif
#include "vboxmod.h"
#include "waitcompat.h"
#include <VBox/log.h>
#include <VBox/VMMDev.h>
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/mem.h>
#include <iprt/memobj.h>
#include <linux/miscdevice.h>
#include <linux/poll.h>
#define xstr(s) str(s)
#define str(s) #s
MODULE_DESCRIPTION("VirtualBox Guest Additions for Linux Module");
MODULE_AUTHOR("Sun Microsystems, Inc.");
MODULE_LICENSE("GPL");
#ifdef MODULE_VERSION
MODULE_VERSION(VBOX_VERSION_STRING " (interface " xstr(VMMDEV_VERSION) ")");
#endif
/** device extension structure (we only support one device instance) */
static VBoxDevice *vboxDev = NULL;
/** our file node major id (set dynamically) */
#ifdef CONFIG_VBOXADD_MAJOR
static unsigned int vbox_major = CONFIG_VBOXADD_MAJOR;
#else
static unsigned int vbox_major = 0;
#endif
DECLVBGL (void *) vboxadd_cmc_open (void)
{
return vboxDev;
}
DECLVBGL (void) vboxadd_cmc_close (void *opaque)
{
(void) opaque;
}
EXPORT_SYMBOL (vboxadd_cmc_open);
EXPORT_SYMBOL (vboxadd_cmc_close);
#define MAX_HGCM_CONNECTIONS 1024
/**
* Structure for keeping track of HGCM connections owned by user space processes, so that
* we can close the connection if a process does not clean up properly (for example if it
* was terminated too abruptly).
*/
/* We just define a fixed number of these so far. This can be changed if it ever becomes
a problem. */
static struct
{
/** Open file structure that this connection handle is associated with */
struct file *filp;
/** HGCM connection ID */
uint32_t client_id;
} hgcm_connections[MAX_HGCM_CONNECTIONS]
=
{
{ 0 }
};
/**
* This function converts a VBox result code into a Linux error number.
* Note that we return 0 (success) for all informational values, as Linux
* has no such concept.
*/
static int vboxadd_convert_result(int vbox_err)
{
if ( vbox_err > -1000
&& vbox_err < 1000)
return RTErrConvertToErrno(vbox_err);
switch (vbox_err)
{
case VERR_HGCM_SERVICE_NOT_FOUND: return ESRCH;
case VINF_HGCM_CLIENT_REJECTED: return 0;
case VERR_HGCM_INVALID_CMD_ADDRESS: return EFAULT;
case VINF_HGCM_ASYNC_EXECUTE: return 0;
case VERR_HGCM_INTERNAL: return EPROTO;
case VERR_HGCM_INVALID_CLIENT_ID: return EINVAL;
case VINF_HGCM_SAVE_STATE: return 0;
/* No reason to return this to a guest */
// case VERR_HGCM_SERVICE_EXISTS: return EEXIST;
}
AssertMsgFailedReturn(("Unhandled error code %Rrc\n", vbox_err), EPROTO);
}
/**
* Register an HGCM connection as being connected with a given file descriptor, so that it
* will be closed automatically when that file descriptor is.
*
* @returns 0 on success or Linux kernel error number
* @param clientID the client ID of the HGCM connection
* @param filep the file structure that the connection is to be associated with
*/
static int vboxadd_register_hgcm_connection(uint32_t client_id, struct file *filp)
{
int i;
bool found = false;
for (i = 0; i < MAX_HGCM_CONNECTIONS; ++i)
{
Assert(hgcm_connections[i].client_id != client_id);
}
for (i = 0; (i < MAX_HGCM_CONNECTIONS) && (false == found); ++i)
{
if (ASMAtomicCmpXchgU32(&hgcm_connections[i].client_id, client_id, 0))
{
hgcm_connections[i].filp = filp;
found = true;
}
}
return found ? 0 : -ENFILE; /* Any ideas for a better error code? */
}
/**
* Unregister an HGCM connection associated with a given file descriptor without closing
* the connection.
*
* @returns 0 on success or Linux kernel error number
* @param clientID the client ID of the HGCM connection
*/
static int vboxadd_unregister_hgcm_connection_no_close(uint32_t client_id)
{
int i;
bool found = false;
for (i = 0; (i < MAX_HGCM_CONNECTIONS) && (false == found); ++i)
{
if (hgcm_connections[i].client_id == client_id)
{
hgcm_connections[i].filp = NULL;
hgcm_connections[i].client_id = 0;
found = true;
}
}
for (i = 0; i < MAX_HGCM_CONNECTIONS; ++i)
{
Assert(hgcm_connections[i].client_id != client_id);
}
return found ? 0 : -ENOENT;
}
/**
* Unregister all HGCM connections associated with a given file descriptor, closing
* the connections in the process. This should be called when a file descriptor is
* closed.
*
* @returns 0 on success or Linux kernel error number
* @param clientID the client ID of the HGCM connection
*/
static int vboxadd_unregister_all_hgcm_connections(struct file *filp)
{
int i;
for (i = 0; i < MAX_HGCM_CONNECTIONS; ++i)
{
if (hgcm_connections[i].filp == filp)
{
VBoxGuestHGCMDisconnectInfo infoDisconnect;
infoDisconnect.u32ClientID = hgcm_connections[i].client_id;
vboxadd_cmc_call(vboxDev, VBOXGUEST_IOCTL_HGCM_DISCONNECT,
&infoDisconnect);
hgcm_connections[i].filp = NULL;
hgcm_connections[i].client_id = 0;
}
}
return 0;
}
/**
* File open handler
*
*/
static int vboxadd_open(struct inode *inode, struct file *filp)
{
/* no checks required */
return 0;
}
static void
vboxadd_wait_for_event (VBoxGuestWaitEventInfo *info)
{
long timeleft;
uint32_t cInterruptions = vboxDev->u32GuestInterruptions;
uint32_t in_mask = info->u32EventMaskIn;
info->u32Result = VBOXGUEST_WAITEVENT_OK;
if (RT_INDEFINITE_WAIT != info->u32TimeoutIn)
{
timeleft = wait_event_interruptible_timeout
(vboxDev->eventq,
(vboxDev->u32Events & in_mask)
|| (vboxDev->u32GuestInterruptions != cInterruptions),
msecs_to_jiffies (info->u32TimeoutIn)
);
if (vboxDev->u32GuestInterruptions != cInterruptions)
{
info->u32Result = VBOXGUEST_WAITEVENT_INTERRUPTED;
}
if (timeleft < 0)
{
info->u32Result = VBOXGUEST_WAITEVENT_INTERRUPTED;
}
if (timeleft == 0)
{
info->u32Result = VBOXGUEST_WAITEVENT_TIMEOUT;
}
}
else
{
if (wait_event_interruptible(vboxDev->eventq,
(vboxDev->u32Events & in_mask)
|| (vboxDev->u32GuestInterruptions != cInterruptions)
)
)
{
info->u32Result = VBOXGUEST_WAITEVENT_INTERRUPTED;
}
}
info->u32EventFlagsOut = vboxDev->u32Events & in_mask;
vboxDev->u32Events &= ~in_mask;
}
/**
* IOCtl handler - wait for an event from the host.
*
* @returns Linux kernel return code
* @param ptr User space pointer to a structure describing the event
*/
static int vboxadd_wait_event(void *ptr)
{
int rc = 0;
VBoxGuestWaitEventInfo info;
if (copy_from_user (&info, ptr, sizeof (info)))
{
LogRelFunc (("VBOXGUEST_IOCTL_WAITEVENT: can not get event info\n"));
rc = -EFAULT;
}
if (0 == rc)
{
vboxadd_wait_for_event (&info);
if (copy_to_user (ptr, &info, sizeof (info)))
{
LogRelFunc (("VBOXGUEST_IOCTL_WAITEVENT: can not put out_mask\n"));
rc = -EFAULT;
}
}
return 0;
}
/**
* IOCTL handler. Initiate an HGCM connection for a user space application. If the connection
* succeeds, it will be associated with the file structure used to open it, so that it will be
* automatically shut down again if the file descriptor is closed.
*
* @returns 0 on success, or a Linux kernel errno value
* @param filp the file structure with which the application opened the driver
* @param userspace_info userspace pointer to the hgcm connection information
* (VBoxGuestHGCMConnectInfo structure)
* @retval userspace_info userspace pointer to the hgcm connection information
*/
static int vboxadd_hgcm_connect(struct file *filp, unsigned long userspace_info)
{
VBoxGuestHGCMConnectInfo info;
int rc = 0;
if (copy_from_user ((void *)&info, (void *)userspace_info,
sizeof (info)) != 0)
{
LogFunc (("VBOXGUEST_IOCTL_HGCM_CONNECT: can not get connection info\n"));
rc = -EFAULT;
}
info.u32ClientID = 0;
if (rc >= 0)
{
int vrc = vboxadd_cmc_call(vboxDev, VBOXGUEST_IOCTL_HGCM_CONNECT,
&info);
rc = RT_FAILURE(vrc) ? -vboxadd_convert_result(vrc)
: -vboxadd_convert_result(info.result);
if (rc < 0)
LogFunc(("hgcm connection failed. internal ioctl result %Rrc, hgcm result %Rrc\n",
vrc, info.result));
if (rc >= 0 && info.result < 0)
rc = info.result;
}
if (rc >= 0)
{
/* Register that the connection is associated with this file pointer. */
LogFunc(("Connected, client ID %u\n", info.u32ClientID));
rc = vboxadd_register_hgcm_connection(info.u32ClientID, filp);
if (rc < 0)
LogFunc(("failed to register the HGCM connection\n"));
}
if ( rc >= 0
&& copy_to_user ((void *)userspace_info, (void *)&info,
sizeof(info)) != 0)
{
LogFunc (("failed to return the connection structure\n"));
rc = -EFAULT;
}
if (rc < 0 && (info.u32ClientID != 0))
/* Unregister again, as we didn't get as far as informing userspace. */
vboxadd_unregister_hgcm_connection_no_close(info.u32ClientID);
if (rc < 0 && info.u32ClientID != 0)
{
/* Disconnect the hgcm connection again, as we told userspace it failed. */
VBoxGuestHGCMDisconnectInfo infoDisconnect;
infoDisconnect.u32ClientID = info.u32ClientID;
vboxadd_cmc_call(vboxDev, VBOXGUEST_IOCTL_HGCM_DISCONNECT,
&infoDisconnect);
}
return rc;
}
/**
* IOCTL handler. Disconnect a specific HGCM connection.
*
* @returns 0 on success, or a Linux kernel errno value
* @param filp the file structure with which the application opened the driver
* @param userspace_info userspace pointer to the hgcm connection information
* (VBoxGuestHGCMConnectInfo structure)
* @retval userspace_info userspace pointer to the hgcm connection information
*/
static int vboxadd_hgcm_disconnect(struct file *filp, unsigned long userspace_info)
{
int rc = 0, vrc = VINF_SUCCESS;
VBoxGuestHGCMDisconnectInfo info;
if (copy_from_user((void *)&info, (void *)userspace_info, sizeof (info)) != 0)
{
LogRelFunc (("VBOXGUEST_IOCTL_HGCM_DISCONNECT: can not get info\n"));
rc = -EFAULT;
}
if (rc >= 0)
{
vrc = vboxadd_cmc_call(vboxDev, VBOXGUEST_IOCTL_HGCM_DISCONNECT, &info);
rc = -vboxadd_convert_result(vrc);
if (rc < 0)
LogFunc(("HGCM disconnect failed, error %Rrc\n", vrc));
}
if ( rc >= 0
&& copy_to_user((void *)userspace_info, (void *)&info, sizeof(info)) != 0)
{
LogRelFunc (("VBOXGUEST_IOCTL_HGCM_DISCONNECT: failed to return the connection structure\n"));
rc = -EFAULT;
}
return rc;
}
/** Bounce buffer structure for hcgm guest-host data copies. */
typedef struct hgcm_bounce_buffer
{
/** Kernel memory address. */
void *pKernel;
/** User memory address. */
void *pUser;
/** Buffer size. */
size_t cb;
} hgcm_bounce_buffer;
/** Create a bounce buffer in kernel space for user space memory. */
static int vboxadd_hgcm_alloc_buffer(hgcm_bounce_buffer **ppBuf, void *pUser,
size_t cb, bool copy)
{
hgcm_bounce_buffer *pBuf = NULL;
void *pKernel = NULL;
int rc = 0;
AssertPtrReturn(ppBuf, -EINVAL);
/* Empty buffers are allowed, but then the user pointer should be NULL. */
AssertReturn((cb > 0 && VALID_PTR(pUser)) || (cb == 0 && pUser == NULL),
-EINVAL);
pBuf = RTMemAlloc(sizeof(*pBuf));
if (pBuf == NULL)
rc = -ENOMEM;
if (rc >= 0)
{
if (cb > 0)
{
pKernel = RTMemAlloc(cb);
if (pKernel == NULL)
rc = -ENOMEM;
if ( rc >= 0
&& copy
&& copy_from_user(pKernel, pUser, cb) != 0)
rc = -EFAULT;
}
else
/* We definitely don't want to copy anything from an empty
* buffer. */
pKernel = NULL;
}
if (rc >= 0)
{
pBuf->pKernel = pKernel;
pBuf->pUser = pUser;
pBuf->cb = cb;
*ppBuf = pBuf;
}
else
{
RTMemFree(pBuf);
RTMemFree(pKernel);
LogFunc(("failed, returning %d\n", rc));
}
return rc;
}
/** Free a kernel space bounce buffer for user space memory. */
static int vboxadd_hgcm_free_buffer(hgcm_bounce_buffer *pBuf, bool copy)
{
int rc = 0;
AssertPtrReturn(pBuf, -EINVAL);
if ((pBuf->cb > 0)
&& copy
&& copy_to_user(pBuf->pUser, pBuf->pKernel, pBuf->cb) != 0)
rc = -EFAULT;
RTMemFree(pBuf->pKernel); /* We want to do this whatever the outcome. */
RTMemFree(pBuf);
if (rc < 0)
LogFunc(("failed, returning %d\n", rc));
return rc;
}
/** Lock down R3 memory as needed for the HGCM call. Copied from
* HGCMInternal.cpp and SysHlp.cpp */
static int vboxadd_buffer_hgcm_parms(void **ppvCtx, VBoxGuestHGCMCallInfo *pCallInfo)
{
uint32_t cbParms = pCallInfo->cParms * sizeof (HGCMFunctionParameter);
int rc = 0;
unsigned iParm;
HGCMFunctionParameter *pParm;
memset (ppvCtx, 0, sizeof(void *) * pCallInfo->cParms);
if (cbParms)
{
/* Lock user buffers. */
pParm = VBOXGUEST_HGCM_CALL_PARMS(pCallInfo);
for (iParm = 0; iParm < pCallInfo->cParms; iParm++, pParm++)
{
switch (pParm->type)
{
case VMMDevHGCMParmType_LinAddr_Locked_In:
pParm->type = VMMDevHGCMParmType_LinAddr_In;
break;
case VMMDevHGCMParmType_LinAddr_Locked_Out:
pParm->type = VMMDevHGCMParmType_LinAddr_Out;
break;
case VMMDevHGCMParmType_LinAddr_Locked:
pParm->type = VMMDevHGCMParmType_LinAddr;
break;
case VMMDevHGCMParmType_LinAddr_In:
case VMMDevHGCMParmType_LinAddr_Out:
case VMMDevHGCMParmType_LinAddr:
{
void *pv = (void *) pParm->u.Pointer.u.linearAddr;
uint32_t u32Size = pParm->u.Pointer.size;
hgcm_bounce_buffer *MemObj = NULL;
rc = vboxadd_hgcm_alloc_buffer(&MemObj, pv, u32Size,
pParm->type != VMMDevHGCMParmType_LinAddr_Out /* copy */);
if (rc >= 0)
{
ppvCtx[iParm] = MemObj;
pParm->u.Pointer.u.linearAddr = (uintptr_t)MemObj->pKernel;
}
else
ppvCtx[iParm] = NULL;
break;
}
default:
/* make gcc happy */
break;
}
if (rc < 0)
break;
}
}
return rc;
}
/** Unlock R3 memory after the HGCM call. Copied from HGCMInternal.cpp and
* SysHlp.cpp */
static int vboxadd_unbuffer_hgcm_parms(void **ppvCtx, VBoxGuestHGCMCallInfo *pCallInfo)
{
int rc = 0;
unsigned iParm;
/* Unlock user buffers. */
HGCMFunctionParameter *pParm = VBOXGUEST_HGCM_CALL_PARMS(pCallInfo);
for (iParm = 0; iParm < pCallInfo->cParms; iParm++, pParm++)
{
if ( pParm->type == VMMDevHGCMParmType_LinAddr_In
|| pParm->type == VMMDevHGCMParmType_LinAddr_Out
|| pParm->type == VMMDevHGCMParmType_LinAddr)
{
if (ppvCtx[iParm] != NULL)
{
hgcm_bounce_buffer *MemObj = (hgcm_bounce_buffer *)ppvCtx[iParm];
int rc2 = vboxadd_hgcm_free_buffer(MemObj,
pParm->type != VMMDevHGCMParmType_LinAddr_In /* copy */);
if (rc >= 0 && rc2 < 0)
rc = rc2; /* Report the first error. */
}
}
else
{
if (ppvCtx[iParm] != NULL)
{
AssertFailed();
rc = -EOVERFLOW; /* Something unlikely to turn up elsewhere so
* we can see where it's coming from. */
}
}
}
return rc;
}
/**
* IOCTL handler. Make an HGCM call.
*
* @returns 0 on success, or a Linux kernel errno value
* @param userspace_info userspace pointer to the hgcm connection information
* (VBoxGuestHGCMConnectInfo structure). This will be
* updated on success.
* @param u32Size the size of the userspace structure
*/
static int vboxadd_hgcm_call(unsigned long userspace_info, uint32_t u32Size)
{
VBoxGuestHGCMCallInfo *pInfo = NULL;
void *apvCtx[VBOX_HGCM_MAX_PARMS];
unsigned haveParms = 0;
int rc = 0;
pInfo = kmalloc(u32Size, GFP_KERNEL);
if (pInfo == NULL)
rc = -ENOMEM;
if ( rc >= 0
&& 0 != copy_from_user ((void *)pInfo, (void *)userspace_info, u32Size))
{
LogRelFunc (("can not get info from user space\n"));
rc = -EFAULT;
}
if ( rc >= 0
&& sizeof(*pInfo) + pInfo->cParms * sizeof(HGCMFunctionParameter) != u32Size)
{
LogRelFunc (("bad parameter size, structure says %d, ioctl says %d\n",
sizeof(*pInfo) + pInfo->cParms * sizeof(HGCMFunctionParameter),
u32Size));
rc = -EINVAL;
}
if (rc >= 0)
{
haveParms = 1;
rc = vboxadd_buffer_hgcm_parms(apvCtx, pInfo);
}
if (rc >= 0)
{
int vrc;
vrc = vboxadd_cmc_call(vboxDev,
VBOXGUEST_IOCTL_HGCM_CALL(u32Size), pInfo);
rc = -vboxadd_convert_result(vrc);
if (rc < 0)
LogFunc(("HGCM call failed, error %Rrc\n", vrc));
if ( rc >= 0
&& copy_to_user ((void *)userspace_info, (void *)pInfo,
u32Size))
{
LogRelFunc (("failed to return the information to user space\n"));
rc = -EFAULT;
}
}
if (haveParms)
{
int rc2 = vboxadd_unbuffer_hgcm_parms(apvCtx, pInfo);
if (rc >= 0 && rc2 < 0)
rc = rc2;
}
if (pInfo != NULL)
kfree(pInfo);
return rc;
}
/**
* IOCTL handler. Make an HGCM call with timeout.
*
* @returns 0 on success, or a Linux kernel errno value
* @param userspace_info userspace pointer to the hgcm connection information
* (VBoxGuestHGCMConnectInfo structure). This will be
* updated on success.
* @param u32Size the size of the userspace structure
*/
static int vboxadd_hgcm_call_timed(unsigned long userspace_info,
uint32_t u32Size)
{
VBoxGuestHGCMCallInfoTimed *pInfo = NULL;
void *apvCtx[VBOX_HGCM_MAX_PARMS];
unsigned haveParms = 0;
int rc = 0;
pInfo = kmalloc(u32Size, GFP_KERNEL);
if (pInfo == NULL)
rc = -ENOMEM;
if ( rc >= 0
&& 0 != copy_from_user ((void *)pInfo, (void *)userspace_info, u32Size))
{
LogRelFunc (("can not get info from user space\n"));
rc = -EFAULT;
}
if ( rc >= 0
&& sizeof(*pInfo) + pInfo->info.cParms * sizeof(HGCMFunctionParameter) != u32Size)
{
LogRelFunc (("bad parameter size, structure says %d, ioctl says %d\n",
sizeof(*pInfo) + pInfo->info.cParms * sizeof(HGCMFunctionParameter),
u32Size));
rc = -EINVAL;
}
if (rc >= 0)
{
haveParms = 1;
rc = vboxadd_buffer_hgcm_parms(apvCtx, &pInfo->info);
}
if (rc >= 0)
{
int vrc;
pInfo->fInterruptible = true; /* User space may not do uninterruptible waits */
vrc = vboxadd_cmc_call(vboxDev,
VBOXGUEST_IOCTL_HGCM_CALL_TIMED(u32Size), pInfo);
rc = -vboxadd_convert_result(vrc);
if (rc < 0)
LogFunc(("HGCM call failed, error %Rrc", vrc));
if ( rc >= 0
&& copy_to_user ((void *)userspace_info, (void *)pInfo, u32Size))
{
LogRelFunc (("failed to return the information to user space\n"));
rc = -EFAULT;
}
}
if (haveParms)
{
int rc2 = vboxadd_unbuffer_hgcm_parms(apvCtx, &pInfo->info);
if (rc >= 0 && rc2 < 0)
rc = rc2;
}
if (pInfo != NULL)
kfree(pInfo);
return rc;
}
/**
* IOCtl handler. Control the interrupt filter mask to specify which VMMDev interrupts
* we know how to handle.
*
* @returns iprt status code
* @param pInfo kernel space pointer to the filter mask change info
*/
static int vboxadd_control_filter_mask(VBoxGuestFilterMaskInfo *pInfo)
{
VMMDevCtlGuestFilterMask *pReq = NULL;
int rc = VbglGRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_CtlGuestFilterMask);
LogFlow(("VBoxGuestCommonIOCtl: CTL_FILTER_MASK: request received, u32OrMask=0x%x, u32NotMask=0x%x\n", pInfo->u32OrMask, pInfo->u32NotMask));
if (RT_FAILURE(rc))
Log(("VBoxGuestCommonIOCtl: CTL_FILTER_MASK: failed to allocate %u (%#x) bytes to cache the request. rc=%d!!\n", sizeof(*pReq), sizeof(*pReq), rc));
else
{
pReq->u32OrMask = pInfo->u32OrMask;
pReq->u32NotMask = pInfo->u32NotMask;
rc = VbglGRPerform(&pReq->header);
}
if (RT_FAILURE(rc))
Log(("VBoxGuestCommonIOCtl: CTL_FILTER_MASK: VbglGRPerform failed, rc=%Rrc!\n", rc));
else if (RT_FAILURE(pReq->header.rc))
{
Log(("VBoxGuestCommonIOCtl: CTL_FILTER_MASK: The request failed; VMMDev rc=%Rrc!\n", pReq->header.rc));
rc = pReq->header.rc;
}
if (pReq)
VbglGRFree(&pReq->header);
return rc;
}
/**
* IOCTL handler for vboxadd
*/
static int vboxadd_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
int rc = 0;
/* Deal with variable size ioctls first. */
if ( VBOXGUEST_IOCTL_STRIP_SIZE(VBOXGUEST_IOCTL_LOG(0))
== VBOXGUEST_IOCTL_STRIP_SIZE(cmd))
{
char *pszMessage;
IOCTL_LOG_ENTRY(arg);
pszMessage = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
if (NULL == pszMessage)
{
LogRelFunc(("VBOXGUEST_IOCTL_LOG: cannot allocate %d bytes of memory!\n",
_IOC_SIZE(cmd)));
rc = -ENOMEM;
}
if ( (0 == rc)
&& copy_from_user(pszMessage, (void*)arg, _IOC_SIZE(cmd)))
{
LogRelFunc(("VBOXGUEST_IOCTL_LOG: copy_from_user failed!\n"));
rc = -EFAULT;
}
if (0 == rc)
{
Log(("%.*s", _IOC_SIZE(cmd), pszMessage));
}
if (NULL != pszMessage)
{
kfree(pszMessage);
}
IOCTL_LOG_EXIT(arg);
}
else if ( VBOXGUEST_IOCTL_STRIP_SIZE(VBOXGUEST_IOCTL_VMMREQUEST(0))
== VBOXGUEST_IOCTL_STRIP_SIZE(cmd))
{
VMMDevRequestHeader reqHeader;
VMMDevRequestHeader *reqFull = NULL;
size_t cbRequestSize = 0;
size_t cbVanillaRequestSize = 0;
IOCTL_VMM_ENTRY(arg);
if (copy_from_user(&reqHeader, (void*)arg, sizeof(reqHeader)))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: copy_from_user failed for vmm request!\n"));
rc = -EFAULT;
}
if (0 == rc)
{
/* get the request size */
cbVanillaRequestSize = vmmdevGetRequestSize(reqHeader.requestType);
if (!cbVanillaRequestSize)
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: invalid request type: %d\n",
reqHeader.requestType));
rc = -EINVAL;
}
}
if (0 == rc)
{
cbRequestSize = reqHeader.size;
if (cbRequestSize < cbVanillaRequestSize)
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: invalid request size: %d min: %d type: %d\n",
cbRequestSize,
cbVanillaRequestSize,
reqHeader.requestType));
rc = -EINVAL;
}
}
if (0 == rc)
{
/* request storage for the full request */
rc = VbglGRAlloc(&reqFull, cbRequestSize, reqHeader.requestType);
if (RT_FAILURE(rc))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: could not allocate request structure! rc = %d\n", rc));
rc = -EFAULT;
}
}
if (0 == rc)
{
/* now get the full request */
if (copy_from_user(reqFull, (void*)arg, cbRequestSize))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: failed to fetch full request from user space!\n"));
rc = -EFAULT;
}
}
/* now issue the request */
if (0 == rc)
{
int rrc = VbglGRPerform(reqFull);
/* asynchronous processing? */
if (rrc == VINF_HGCM_ASYNC_EXECUTE)
{
VMMDevHGCMRequestHeader *reqHGCM = (VMMDevHGCMRequestHeader*)reqFull;
wait_event_interruptible (vboxDev->eventq, reqHGCM->fu32Flags & VBOX_HGCM_REQ_DONE);
rrc = reqFull->rc;
}
/* failed? */
if (RT_FAILURE(rrc) || RT_FAILURE(reqFull->rc))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: request execution failed!\n"));
rc = RT_FAILURE(rrc) ? -RTErrConvertToErrno(rrc)
: -RTErrConvertToErrno(reqFull->rc);
}
else
{
/* success, copy the result data to user space */
if (copy_to_user((void*)arg, (void*)reqFull, cbRequestSize))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: error copying request result to user space!\n"));
rc = -EFAULT;
}
}
}
if (NULL != reqFull)
VbglGRFree(reqFull);
IOCTL_VMM_EXIT(arg);
}
else if ( VBOXGUEST_IOCTL_STRIP_SIZE(VBOXGUEST_IOCTL_HGCM_CALL(0))
== VBOXGUEST_IOCTL_STRIP_SIZE(cmd))
{
/* Do the HGCM call using the Vbgl bits */
IOCTL_ENTRY("VBOXGUEST_IOCTL_HGCM_CALL", arg);
rc = vboxadd_hgcm_call(arg, _IOC_SIZE(cmd));
IOCTL_EXIT("VBOXGUEST_IOCTL_HGCM_CALL", arg);
}
else if ( VBOXGUEST_IOCTL_STRIP_SIZE(VBOXGUEST_IOCTL_HGCM_CALL_TIMED(0))
== VBOXGUEST_IOCTL_STRIP_SIZE(cmd))
{
/* Do the HGCM call using the Vbgl bits */
IOCTL_ENTRY("VBOXGUEST_IOCTL_HGCM_CALL_TIMED", arg);
rc = vboxadd_hgcm_call_timed(arg, _IOC_SIZE(cmd));
IOCTL_EXIT("VBOXGUEST_IOCTL_HGCM_CALL_TIMED", arg);
}
else
{
switch (cmd)
{
case VBOXGUEST_IOCTL_WAITEVENT:
IOCTL_ENTRY("VBOXGUEST_IOCTL_WAITEVENT", arg);
rc = vboxadd_wait_event((void *) arg);
IOCTL_EXIT("VBOXGUEST_IOCTL_WAITEVENT", arg);
break;
case VBOXGUEST_IOCTL_CANCEL_ALL_WAITEVENTS:
IOCTL_ENTRY("VBOXGUEST_IOCTL_CANCEL_ALL_WAITEVENTS", arg);
++vboxDev->u32GuestInterruptions;
IOCTL_EXIT("VBOXGUEST_IOCTL_CANCEL_ALL_WAITEVENTS", arg);
break;
case VBOXGUEST_IOCTL_HGCM_CONNECT:
IOCTL_ENTRY("VBOXGUEST_IOCTL_HGCM_CONNECT", arg);
rc = vboxadd_hgcm_connect(filp, arg);
IOCTL_EXIT("VBOXGUEST_IOCTL_HGCM_CONNECT", arg);
break;
case VBOXGUEST_IOCTL_HGCM_DISCONNECT:
IOCTL_ENTRY("VBOXGUEST_IOCTL_HGCM_DISCONNECT", arg);
vboxadd_hgcm_disconnect(filp, arg);
IOCTL_EXIT("VBOXGUEST_IOCTL_HGCM_DISCONNECT", arg);
break;
case VBOXGUEST_IOCTL_CTL_FILTER_MASK:
{
VBoxGuestFilterMaskInfo info;
IOCTL_ENTRY("VBOXGUEST_IOCTL_CTL_FILTER_MASK", arg);
if (copy_from_user((void*)&info, (void*)arg, sizeof(info)))
{
LogRelFunc(("VBOXGUEST_IOCTL_CTL_FILTER_MASK: error getting parameters from user space!\n"));
rc = -EFAULT;
break;
}
rc = -RTErrConvertToErrno(vboxadd_control_filter_mask(&info));
IOCTL_EXIT("VBOXGUEST_IOCTL_CTL_FILTER_MASK", arg);
break;
}
default:
LogRelFunc(("unknown command: %x\n", cmd));
rc = -EINVAL;
break;
}
}
return rc;
}
/**
* IOCTL handler for vboxuser
* @todo currently this is just a copy of vboxadd_ioctl. We should
* decide if we wish to restrict this. If we do, we should remove
* the more general ioctls (HGCM call, VMM device request) and
* replace them with specific ones. If not, then we should just
* make vboxadd world readable and writable or something.
*/
static int vboxuser_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
int rc = 0;
/* Deal with variable size ioctls first. */
if ( VBOXGUEST_IOCTL_STRIP_SIZE(VBOXGUEST_IOCTL_LOG(0))
== VBOXGUEST_IOCTL_STRIP_SIZE(cmd))
{
char *pszMessage;
IOCTL_LOG_ENTRY(arg);
pszMessage = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
if (NULL == pszMessage)
{
LogRelFunc(("VBOXGUEST_IOCTL_LOG: cannot allocate %d bytes of memory!\n",
_IOC_SIZE(cmd)));
rc = -ENOMEM;
}
if ( (0 == rc)
&& copy_from_user(pszMessage, (void*)arg, _IOC_SIZE(cmd)))
{
LogRelFunc(("VBOXGUEST_IOCTL_LOG: copy_from_user failed!\n"));
rc = -EFAULT;
}
if (0 == rc)
/* This only produces output in debug builds */
Log(("%.*s", _IOC_SIZE(cmd), pszMessage));
if (NULL != pszMessage)
{
kfree(pszMessage);
}
IOCTL_LOG_EXIT(arg);
}
else if ( VBOXGUEST_IOCTL_STRIP_SIZE(VBOXGUEST_IOCTL_VMMREQUEST(0))
== VBOXGUEST_IOCTL_STRIP_SIZE(cmd))
{
VMMDevRequestHeader reqHeader;
VMMDevRequestHeader *reqFull = NULL;
size_t cbRequestSize = 0;
size_t cbVanillaRequestSize = 0;
IOCTL_VMM_ENTRY(arg);
if (copy_from_user(&reqHeader, (void*)arg, sizeof(reqHeader)))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: copy_from_user failed for vmm request!\n"));
rc = -EFAULT;
}
if (0 == rc)
{
/* get the request size */
cbVanillaRequestSize = vmmdevGetRequestSize(reqHeader.requestType);
if (!cbVanillaRequestSize)
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: invalid request type: %d\n",
reqHeader.requestType));
rc = -EINVAL;
}
}
if (0 == rc)
{
cbRequestSize = reqHeader.size;
if (cbRequestSize < cbVanillaRequestSize)
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: invalid request size: %d min: %d type: %d\n",
cbRequestSize,
cbVanillaRequestSize,
reqHeader.requestType));
rc = -EINVAL;
}
}
if (0 == rc)
{
/* request storage for the full request */
rc = VbglGRAlloc(&reqFull, cbRequestSize, reqHeader.requestType);
if (RT_FAILURE(rc))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: could not allocate request structure! rc = %d\n", rc));
rc = -EFAULT;
}
}
if (0 == rc)
{
/* now get the full request */
if (copy_from_user(reqFull, (void*)arg, cbRequestSize))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: failed to fetch full request from user space!\n"));
rc = -EFAULT;
}
}
/* now issue the request */
if (0 == rc)
{
int rrc = VbglGRPerform(reqFull);
/* asynchronous processing? */
if (rrc == VINF_HGCM_ASYNC_EXECUTE)
{
VMMDevHGCMRequestHeader *reqHGCM = (VMMDevHGCMRequestHeader*)reqFull;
wait_event_interruptible (vboxDev->eventq, reqHGCM->fu32Flags & VBOX_HGCM_REQ_DONE);
rrc = reqFull->rc;
}
/* failed? */
if (RT_FAILURE(rrc) || RT_FAILURE(reqFull->rc))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: request execution failed!\n"));
rc = RT_FAILURE(rrc) ? -RTErrConvertToErrno(rrc)
: -RTErrConvertToErrno(reqFull->rc);
}
else
{
/* success, copy the result data to user space */
if (copy_to_user((void*)arg, (void*)reqFull, cbRequestSize))
{
LogRelFunc(("VBOXGUEST_IOCTL_VMMREQUEST: error copying request result to user space!\n"));
rc = -EFAULT;
}
}
}
if (NULL != reqFull)
VbglGRFree(reqFull);
IOCTL_VMM_EXIT(arg);
}
else if ( VBOXGUEST_IOCTL_STRIP_SIZE(VBOXGUEST_IOCTL_HGCM_CALL(0))
== VBOXGUEST_IOCTL_STRIP_SIZE(cmd))
{
/* Do the HGCM call using the Vbgl bits */
IOCTL_ENTRY("VBOXGUEST_IOCTL_HGCM_CALL", arg);
rc = vboxadd_hgcm_call(arg, _IOC_SIZE(cmd));
IOCTL_EXIT("VBOXGUEST_IOCTL_HGCM_CALL", arg);
}
else if ( VBOXGUEST_IOCTL_STRIP_SIZE(VBOXGUEST_IOCTL_HGCM_CALL_TIMED(0))
== VBOXGUEST_IOCTL_STRIP_SIZE(cmd))
{
/* Do the HGCM call using the Vbgl bits */
IOCTL_ENTRY("VBOXGUEST_IOCTL_HGCM_CALL_TIMED", arg);
rc = vboxadd_hgcm_call_timed(arg, _IOC_SIZE(cmd));
IOCTL_EXIT("VBOXGUEST_IOCTL_HGCM_CALL_TIMED", arg);
}
else
{
switch (cmd)
{
case VBOXGUEST_IOCTL_WAITEVENT:
IOCTL_ENTRY("VBOXGUEST_IOCTL_WAITEVENT", arg);
rc = vboxadd_wait_event((void *) arg);
IOCTL_EXIT("VBOXGUEST_IOCTL_WAITEVENT", arg);
break;
case VBOXGUEST_IOCTL_CANCEL_ALL_WAITEVENTS:
IOCTL_ENTRY("VBOXGUEST_IOCTL_CANCEL_ALL_WAITEVENTS", arg);
++vboxDev->u32GuestInterruptions;
IOCTL_EXIT("VBOXGUEST_IOCTL_CANCEL_ALL_WAITEVENTS", arg);
break;
case VBOXGUEST_IOCTL_HGCM_CONNECT:
IOCTL_ENTRY("VBOXGUEST_IOCTL_HGCM_CONNECT", arg);
rc = vboxadd_hgcm_connect(filp, arg);
IOCTL_EXIT("VBOXGUEST_IOCTL_HGCM_CONNECT", arg);
break;
case VBOXGUEST_IOCTL_HGCM_DISCONNECT:
IOCTL_ENTRY("VBOXGUEST_IOCTL_HGCM_DISCONNECT", arg);
vboxadd_hgcm_disconnect(filp, arg);
IOCTL_EXIT("VBOXGUEST_IOCTL_HGCM_DISCONNECT", arg);
break;
case VBOXGUEST_IOCTL_CTL_FILTER_MASK:
{
VBoxGuestFilterMaskInfo info;
IOCTL_ENTRY("VBOXGUEST_IOCTL_CTL_FILTER_MASK", arg);
if (copy_from_user((void*)&info, (void*)arg, sizeof(info)))
{
LogRelFunc(("VBOXGUEST_IOCTL_CTL_FILTER_MASK: error getting parameters from user space!\n"));
rc = -EFAULT;
break;
}
rc = -RTErrConvertToErrno(vboxadd_control_filter_mask(&info));
IOCTL_EXIT("VBOXGUEST_IOCTL_CTL_FILTER_MASK", arg);
break;
}
default:
LogRelFunc(("unknown command: %x\n", cmd));
rc = -EINVAL;
break;
}
}
return rc;
}
/**
* Poll function. This returns "ready to read" if the guest is in absolute
* mouse pointer mode and the pointer position has changed since the last
* poll.
*/
unsigned int
vboxadd_poll (struct file *file, poll_table *wait)
{
int result = 0;
poll_wait(file, &vboxDev->eventq, wait);
if (vboxDev->u32Events & VMMDEV_EVENT_MOUSE_POSITION_CHANGED)
result = (POLLIN | POLLRDNORM);
vboxDev->u32Events &= ~VMMDEV_EVENT_MOUSE_POSITION_CHANGED;
return result;
}
/** Asynchronous notification activation method. */
static int
vboxadd_fasync(int fd, struct file *file, int mode)
{
return fasync_helper(fd, file, mode, &vboxDev->async_queue);
}
/**
* Dummy read function - we only supply this because we implement poll and
* fasync.
*/
static ssize_t
vboxadd_read (struct file *file, char *buf, size_t count, loff_t *loff)
{
if (0 == count || *loff != 0)
{
return -EINVAL;
}
buf[0] = 0;
return 1;
}
/**
* File close handler for vboxadd. Clean up any HGCM connections associated
* with the open file which might still be open.
*/
static int vboxadd_release(struct inode *inode, struct file * filp)
{
vboxadd_unregister_all_hgcm_connections(filp);
/* Deactivate our asynchronous queue. */
vboxadd_fasync(-1, filp, 0);
return 0;
}
/**
* File close handler for vboxuser. Clean up any HGCM connections associated
* with the open file which might still be open.
*/
static int vboxuser_release(struct inode *inode, struct file * filp)
{
vboxadd_unregister_all_hgcm_connections(filp);
return 0;
}
/** file operations for the vboxadd device */
static struct file_operations vboxadd_fops =
{
.owner = THIS_MODULE,
.open = vboxadd_open,
.ioctl = vboxadd_ioctl,
.poll = vboxadd_poll,
.fasync = vboxadd_fasync,
.read = vboxadd_read,
.release = vboxadd_release,
.llseek = no_llseek
};
/** Miscellaneous device allocation for vboxadd */
static struct miscdevice gMiscVBoxAdd =
{
minor: MISC_DYNAMIC_MINOR,
name: VBOXADD_NAME,
fops: &vboxadd_fops
};
/** file operations for the vboxuser device */
static struct file_operations vboxuser_fops =
{
.owner = THIS_MODULE,
.open = vboxadd_open,
.ioctl = vboxuser_ioctl,
.release = vboxuser_release,
.llseek = no_llseek
};
/** Miscellaneous device allocation for vboxuser */
static struct miscdevice gMiscVBoxUser =
{
minor: MISC_DYNAMIC_MINOR,
name: VBOXUSER_NAME,
fops: &vboxuser_fops
};
#ifndef IRQ_RETVAL
/* interrupt handlers in 2.4 kernels don't return anything */
# define irqreturn_t void
# define IRQ_RETVAL(n)
#endif
/**
* vboxadd_irq_handler
*
* Interrupt handler
*
* @returns scsi error code
* @param irq Irq number
* @param dev_id Irq handler parameter
* @param regs Regs
*
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
static irqreturn_t vboxadd_irq_handler(int irq, void *dev_id)
#else
static irqreturn_t vboxadd_irq_handler(int irq, void *dev_id, struct pt_regs *regs)
#endif
{
int fIRQTaken = 0;
int rcVBox;
#ifdef IRQ_DEBUG
Log(("vboxadd IRQ_DEBUG: vboxDev->pVMMDevMemory=%p vboxDev->pVMMDevMemory->fHaveEvents=%d\n",
vboxDev->pVMMDevMemory, vboxDev->pVMMDevMemory->V.V1_04.fHaveEvents));
#endif
/* check if IRQ was asserted by VBox */
if (vboxDev->pVMMDevMemory->V.V1_04.fHaveEvents != 0)
{
#ifdef IRQ_DEBUG
Log(("vboxadd IRQ_DEBUG: got IRQ with event mask 0x%x\n",
vboxDev->irqAckRequest->events));
#endif
/* make a copy of the event mask */
rcVBox = VbglGRPerform (&vboxDev->irqAckRequest->header);
if (RT_SUCCESS(rcVBox) && RT_SUCCESS(vboxDev->irqAckRequest->header.rc))
{
if (RT_LIKELY (vboxDev->irqAckRequest->events))
{
vboxDev->u32Events |= vboxDev->irqAckRequest->events;
if ( vboxDev->irqAckRequest->events
& VMMDEV_EVENT_MOUSE_POSITION_CHANGED)
kill_fasync(&vboxDev->async_queue, SIGIO, POLL_IN);
wake_up (&vboxDev->eventq);
}
}
else
{
/* impossible... */
LogRelFunc(("IRQ was not acknowledged! rc = %Rrc, header.rc = %Rrc\n",
rcVBox, vboxDev->irqAckRequest->header.rc));
BUG ();
}
/* it was ours! */
fIRQTaken = 1;
}
#ifdef IRQ_DEBUG
else
{
/* we might be attached to a shared interrupt together with another device. */
Log(("vboxadd IRQ_DEBUG: stale IRQ mem=%p events=%d devevents=%#x\n",
vboxDev->pVMMDevMemory,
vboxDev->pVMMDevMemory->V.V1_04.fHaveEvents,
vboxDev->u32Events));
}
#endif
/* it was ours */
return IRQ_RETVAL(fIRQTaken);
}
/**
* Helper function to reserve a fixed kernel address space window
* and tell the VMM that it can safely put its hypervisor there.
* This function might fail which is not a critical error.
*/
static int vboxadd_reserve_hypervisor(void)
{
VMMDevReqHypervisorInfo *req = NULL;
int rcVBox;
/* allocate request structure */
rcVBox = VbglGRAlloc(
(VMMDevRequestHeader**)&req,
sizeof(VMMDevReqHypervisorInfo),
VMMDevReq_GetHypervisorInfo
);
if (RT_FAILURE(rcVBox))
{
LogRelFunc(("failed to allocate hypervisor info structure! rc = %Rrc\n", rcVBox));
goto bail_out;
}
/* query the hypervisor information */
rcVBox = VbglGRPerform(&req->header);
if (RT_SUCCESS(rcVBox) && RT_SUCCESS(req->header.rc))
{
/* are we supposed to make a reservation? */
if (req->hypervisorSize)
{
/** @todo repeat this several times until we get an address the host likes */
void *hypervisorArea;
/* reserve another 4MB because the start needs to be 4MB aligned */
uint32_t hypervisorSize = req->hypervisorSize + 0x400000;
/* perform a fictive IO space mapping */
hypervisorArea = ioremap(VBOXGUEST_HYPERVISOR_PHYSICAL_START, hypervisorSize);
if (hypervisorArea)
{
/* communicate result to VMM, align at 4MB */
req->hypervisorStart = (RTGCPTR32)(uintptr_t)RT_ALIGN_P(hypervisorArea, 0x400000);
req->header.requestType = VMMDevReq_SetHypervisorInfo;
req->header.rc = VERR_GENERAL_FAILURE;
rcVBox = VbglGRPerform(&req->header);
if (RT_SUCCESS(rcVBox) && RT_SUCCESS(req->header.rc))
{
/* store mapping for future unmapping */
vboxDev->hypervisorStart = hypervisorArea;
vboxDev->hypervisorSize = hypervisorSize;
}
else
{
LogRelFunc(("failed to set hypervisor region! rc = %Rrc, header.rc = %Rrc\n",
rcVBox, req->header.rc));
goto bail_out;
}
}
else
{
LogRelFunc(("failed to allocate 0x%x bytes of IO space\n", hypervisorSize));
goto bail_out;
}
}
}
else
{
LogRelFunc(("failed to query hypervisor info! rc = %Rrc, header.rc = %Rrc\n",
rcVBox, req->header.rc));
goto bail_out;
}
/* successful return */
VbglGRFree(&req->header);
return 0;
bail_out:
/* error return */
if (req)
VbglGRFree(&req->header);
return 1;
}
/**
* Helper function to free the hypervisor address window
*
*/
static int vboxadd_free_hypervisor(void)
{
VMMDevReqHypervisorInfo *req = NULL;
int rcVBox;
/* allocate request structure */
rcVBox = VbglGRAlloc(
(VMMDevRequestHeader**)&req,
sizeof(VMMDevReqHypervisorInfo),
VMMDevReq_SetHypervisorInfo
);
if (RT_FAILURE(rcVBox))
{
LogRelFunc(("failed to allocate hypervisor info structure! rc = %Rrc\n", rcVBox));
goto bail_out;
}
/* reset the hypervisor information */
req->hypervisorStart = 0;
req->hypervisorSize = 0;
rcVBox = VbglGRPerform(&req->header);
if (RT_SUCCESS(rcVBox) && RT_SUCCESS(req->header.rc))
{
/* now we can free the associated IO space mapping */
iounmap(vboxDev->hypervisorStart);
vboxDev->hypervisorStart = 0;
}
else
{
LogRelFunc(("failed to reset hypervisor info! rc = %Rrc, header.rc = %Rrc\n",
rcVBox, req->header.rc));
goto bail_out;
}
return 0;
bail_out:
if (req)
VbglGRFree(&req->header);
return 1;
}
/**
* Helper to free resources
*
*/
static void free_resources(void)
{
if (vboxDev)
{
{
/* Unregister notifications when the host absolute pointer
* position changes. */
VBoxGuestFilterMaskInfo info;
info.u32OrMask = 0;
info.u32NotMask = VMMDEV_EVENT_MOUSE_POSITION_CHANGED;
vboxadd_control_filter_mask(&info);
}
/* Detach from IRQ before cleaning up! */
if (vboxDev->irq)
free_irq(vboxDev->irq, vboxDev);
if (vboxDev->hypervisorStart)
vboxadd_free_hypervisor();
if (vboxDev->irqAckRequest)
{
VbglGRFree(&vboxDev->irqAckRequest->header);
VbglTerminate();
}
if (vboxDev->pVMMDevMemory)
iounmap(vboxDev->pVMMDevMemory);
if (vboxDev->vmmdevmem)
release_mem_region(vboxDev->vmmdevmem, vboxDev->vmmdevmem_size);
kfree(vboxDev);
vboxDev = NULL;
}
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
#define PCI_DEV_GET(v,d,p) pci_get_device(v,d,p)
#define PCI_DEV_PUT(x) pci_dev_put(x)
#else
#define PCI_DEV_GET(v,d,p) pci_find_device(v,d,p)
#define PCI_DEV_PUT(x) do {} while(0)
#endif
/**
* Module initialization
*
*/
static __init int init(void)
{
int rc = 0, rcVBox = VINF_SUCCESS;
bool fHaveVBoxAdd = false, fHaveVBoxUser = false, fHaveGuestLib = false;
struct pci_dev *pcidev = NULL;
rcVBox = vboxadd_cmc_init();
if (RT_FAILURE(rcVBox))
{
printk (KERN_ERR "vboxadd: could not init cmc, VBox error code %d.\n", rcVBox);
rc = -RTErrConvertToErrno(rcVBox);
}
/* Detect PCI device */
if (!rc)
{
pcidev = PCI_DEV_GET(VMMDEV_VENDORID, VMMDEV_DEVICEID, pcidev);
if (!pcidev)
{
printk(KERN_ERR "vboxadd: VirtualBox Guest PCI device not found.\n");
rc = -ENODEV;
}
}
if (!rc)
{
rc = pci_enable_device (pcidev);
if (rc)
LogRel(("vboxadd: could not enable device: %d\n", rc));
}
if (!rc)
LogRel(("Starting VirtualBox version %s Guest Additions\n",
VBOX_VERSION_STRING));
/* Register vboxadd */
if (!rc && vbox_major > 0) /* Register as a character device in this case */
{
rc = register_chrdev(vbox_major, VBOXADD_NAME, &vboxadd_fops);
if (rc) /* As we pass a non-zero major, rc should be zero on success. */
LogRel(("vboxadd: register_chrdev failed: vbox_major: %d, err = %d\n",
vbox_major, rc));
}
else if (!rc) /* Register as a miscellaneous device otherwise */
{
rc = misc_register(&gMiscVBoxAdd);
if (rc)
LogRel(("vboxadd: misc_register failed for %s (rc=%d)\n",
VBOXADD_NAME, rc));
}
if (!rc)
fHaveVBoxAdd = true;
/* Register our user session device */
if (!rc)
{
rc = misc_register(&gMiscVBoxUser);
if (rc)
LogRel(("vboxadd: misc_register failed for %s (rc=%d)\n",
VBOXUSER_NAME, rc));
}
if (!rc)
fHaveVBoxUser = true;
/* allocate and initialize device extension */
if (!rc)
{
vboxDev = kmalloc(sizeof(*vboxDev), GFP_KERNEL);
if (vboxDev)
memset(vboxDev, 0, sizeof(*vboxDev));
else
{
LogRel(("vboxadd: could not allocate private device structure\n"));
rc = -ENOMEM;
}
}
if (!rc)
{
/* get the IO port region */
vboxDev->io_port = pci_resource_start(pcidev, 0);
/* get the memory region */
vboxDev->vmmdevmem = pci_resource_start(pcidev, 1);
vboxDev->vmmdevmem_size = pci_resource_len(pcidev, 1);
/* all resources found? */
if (!vboxDev->io_port || !vboxDev->vmmdevmem || !vboxDev->vmmdevmem_size)
{
LogRel(("vboxadd: did not find expected hardware resources\n"));
rc = -ENXIO;
}
}
/* request ownership of adapter memory */
if (!rc && !request_mem_region(vboxDev->vmmdevmem, vboxDev->vmmdevmem_size,
VBOXADD_NAME))
{
LogRel(("vboxadd: failed to obtain adapter memory\n"));
rc = -EBUSY;
}
/* map adapter memory into kernel address space and check version */
if (!rc)
{
vboxDev->pVMMDevMemory = (VMMDevMemory *) ioremap(vboxDev->vmmdevmem,
vboxDev->vmmdevmem_size);
if (!vboxDev->pVMMDevMemory)
{
LogRel(("vboxadd: ioremap failed\n"));
rc = -ENOMEM;
}
}
if (!rc && (vboxDev->pVMMDevMemory->u32Version != VMMDEV_MEMORY_VERSION))
{
LogRel(("vboxadd: invalid VMM device memory version! (got 0x%x, expected 0x%x)\n",
vboxDev->pVMMDevMemory->u32Version, VMMDEV_MEMORY_VERSION));
rc = -ENXIO;
}
/* initialize ring 0 guest library */
if (!rc)
{
rcVBox = VbglInit(vboxDev->io_port, vboxDev->pVMMDevMemory);
if (RT_FAILURE(rcVBox))
{
LogRel(("vboxadd: could not initialize VBGL subsystem: %Rrc\n",
rcVBox));
rc = -RTErrConvertToErrno(rcVBox);
}
}
if (!rc)
fHaveGuestLib = true;
/* report guest information to host, this must be done as the very first request */
if (!rc)
{
VMMDevReportGuestInfo *infoReq = NULL;
rcVBox = VbglGRAlloc((VMMDevRequestHeader**)&infoReq,
sizeof(VMMDevReportGuestInfo), VMMDevReq_ReportGuestInfo);
if (RT_FAILURE(rcVBox))
{
LogRel(("vboxadd: could not allocate request structure: %Rrc\n", rcVBox));
rc = -RTErrConvertToErrno(rcVBox);
}
/* report guest version to host, the VMMDev requires that to be done
* before any other VMMDev operations. */
if (infoReq)
{
infoReq->guestInfo.additionsVersion = VMMDEV_VERSION;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 0)
infoReq->guestInfo.osType = VBOXOSTYPE_Linux26;
#else
infoReq->guestInfo.osType = VBOXOSTYPE_Linux24;
#endif
rcVBox = VbglGRPerform(&infoReq->header);
}
if ( infoReq
&& ( RT_FAILURE(rcVBox)
|| RT_FAILURE(infoReq->header.rc)))
{
LogRel(("vboxadd: error reporting guest information to host: %Rrc, header: %Rrc\n",
rcVBox, infoReq->header.rc));
rc = RT_FAILURE(rcVBox) ? -RTErrConvertToErrno(rcVBox)
: -RTErrConvertToErrno(infoReq->header.rc);
}
if (infoReq)
VbglGRFree(&infoReq->header);
}
/* Unset the graphics capability until/unless X is loaded. */
/** @todo check the error code once we bump the additions version.
For now we ignore it for compatibility with older hosts. */
if (!rc)
{
VMMDevReqGuestCapabilities2 *vmmreqGuestCaps;
rcVBox = VbglGRAlloc((VMMDevRequestHeader**)&vmmreqGuestCaps,
sizeof(VMMDevReqGuestCapabilities2),
VMMDevReq_SetGuestCapabilities);
if (RT_FAILURE(rcVBox))
{
LogRel(("vboxadd: could not allocate request structure: %Rrc\n",
rcVBox));
rc = -RTErrConvertToErrno(rcVBox);
}
else
{
vmmreqGuestCaps->u32OrMask = 0;
vmmreqGuestCaps->u32NotMask = VMMDEV_GUEST_SUPPORTS_GRAPHICS;
rcVBox = VbglGRPerform(&vmmreqGuestCaps->header);
if (RT_FAILURE(rcVBox))
{
LogRel(("vboxadd: could not allocate request structure: %Rrc\n",
rcVBox));
rc = -RTErrConvertToErrno(rcVBox);
}
VbglGRFree(&vmmreqGuestCaps->header);
}
}
/* perform hypervisor address space reservation */
if (!rc && vboxadd_reserve_hypervisor())
{
/* we just ignore the error, no address window reservation, non fatal */
}
/* allocate a VMM request structure for use in the ISR */
if (!rc)
{
rcVBox = VbglGRAlloc((VMMDevRequestHeader**)&vboxDev->irqAckRequest,
sizeof(VMMDevEvents), VMMDevReq_AcknowledgeEvents);
if (RT_FAILURE(rcVBox))
{
LogRel(("vboxadd: could not allocate request structure: %Rrc\n",
rcVBox));
rc = -RTErrConvertToErrno(rcVBox);
}
}
/* get ISR */
if (!rc)
{
rc = request_irq(pcidev->irq, vboxadd_irq_handler,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
IRQF_SHARED,
#else
SA_SHIRQ,
#endif
VBOXADD_NAME, vboxDev);
if (rc)
LogRel(("vboxadd: could not request IRQ %d, err: %d\n", pcidev->irq, rc));
else
vboxDev->irq = pcidev->irq;
}
if (!rc)
{
VBoxGuestFilterMaskInfo info;
init_waitqueue_head (&vboxDev->eventq);
/* Register for notification when the host absolute pointer position
* changes. */
info.u32OrMask = VMMDEV_EVENT_MOUSE_POSITION_CHANGED;
info.u32NotMask = 0;
rcVBox = vboxadd_control_filter_mask(&info);
if (RT_FAILURE(rcVBox))
{
LogRel(("vboxadd: failed to register for VMMDEV_EVENT_MOUSE_POSITION_CHANGED events: %Rrc\n",
rcVBox));
rc = -RTErrConvertToErrno(rcVBox);
}
}
if (!rc)
{
/* some useful information for the user but don't show this on the console */
LogRel(("VirtualBox device settings: major %d, IRQ %d, "
"I/O port 0x%x, MMIO at 0x%x (size 0x%x), "
"hypervisor window at 0x%p (size 0x%x)\n",
vbox_major, vboxDev->irq, vboxDev->io_port,
vboxDev->vmmdevmem, vboxDev->vmmdevmem_size,
vboxDev->hypervisorStart, vboxDev->hypervisorSize));
printk(KERN_DEBUG "vboxadd: Successfully loaded version "
VBOX_VERSION_STRING " (interface " xstr(VMMDEV_VERSION) ")\n");
}
else /* Clean up on failure */
{
if (fHaveGuestLib)
VbglTerminate();
if (vboxDev)
free_resources();
if (fHaveVBoxUser)
misc_deregister(&gMiscVBoxUser);
if (fHaveVBoxAdd && vbox_major > 0)
unregister_chrdev(vbox_major, VBOXADD_NAME);
else if (fHaveVBoxAdd)
misc_deregister(&gMiscVBoxAdd);
}
/* We always release this. Presumably because we no longer need to do
* anything with the device structure. */
if (pcidev)
PCI_DEV_PUT(pcidev);
return rc;
}
/**
* Module termination
*
*/
static __exit void fini(void)
{
misc_deregister(&gMiscVBoxUser);
if (vbox_major > 0)
unregister_chrdev(vbox_major, VBOXADD_NAME);
else
misc_deregister(&gMiscVBoxAdd);
free_resources();
vboxadd_cmc_fini ();
}
module_init(init);
module_exit(fini);
/* PCI hotplug structure */
static const struct pci_device_id __devinitdata vmmdev_pci_id[] =
{
{
.vendor = VMMDEV_VENDORID,
.device = VMMDEV_DEVICEID
},
{
/* empty entry */
}
};
MODULE_DEVICE_TABLE(pci, vmmdev_pci_id);
/*
* Local Variables:
* c-mode: bsd
* indent-tabs-mode: nil
* c-plusplus: evil
* End:
*/