VBoxGuest-solaris-streams.c revision 38f00cf948613cae816f726dfb22d68568a9b44f
/* $Id$ */
/** @file
* VirtualBox Guest Additions Driver for Solaris.
*/
/*
* Copyright (C) 2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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.
*/
/******************************************************************************
* Header Files *
******************************************************************************/
#ifndef TESTCASE
# include <sys/ddi_intr.h>
#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */
#else /* TESTCASE */
# define IN_RING0
#endif /* TESTCASE */
#include "VBoxGuestInternal.h"
#include <iprt/initterm.h>
#ifdef TESTCASE /* Include this last as we . */
#endif /* TESTCASE */
/******************************************************************************
* Defined Constants And Macros *
******************************************************************************/
/** The module name. */
#define DEVICE_NAME "vboxguest"
/** The module description as seen in 'modinfo'. */
#define DEVICE_DESC "VirtualBox GstDrv"
/** The maximum number of open device nodes we support. */
#define MAX_OPEN_NODES 4096
/******************************************************************************
* Internal functions used in global structures *
******************************************************************************/
/******************************************************************************
* Driver global structures *
******************************************************************************/
#ifndef TESTCASE /* I see no value in including these in the test. */
/*
* mod_info: STREAMS module information.
*/
static struct module_info g_vbgr0SolModInfo =
{
0x0ffff, /* module id number */
"vboxguest",
0, /* minimum packet size */
INFPSZ, /* maximum packet size accepted */
512, /* high water mark for data flow control */
128 /* low water mark */
};
/*
* rinit: read queue structure for handling messages coming from below. In
* our case this means the host and the virtual hardware, so we do not need
* the put and service procedures.
*/
static struct qinit g_vbgr0SolRInit =
{
NULL, /* put */
NULL, /* service thread procedure */
NULL, /* reserved */
NULL /* module statistics structure */
};
/*
* winit: write queue structure for handling messages coming from above. Above
* means user space applications: either Guest Additions user space tools or
* applications reading pointer input. Messages from the last most likely pass
* through at least the "consms" console mouse streams module which multiplexes
* hardware pointer drivers to a single virtual pointer.
*/
static struct qinit g_vbgr0SolWInit =
{
NULL, /* service thread procedure */
NULL, /* open */
NULL, /* close */
NULL, /* reserved */
NULL /* module statistics structure */
};
/**
*/
static struct streamtab g_vbgr0SolStreamTab =
{
NULL, /* MUX rinit */
NULL /* MUX winit */
};
/**
*/
static struct cb_ops g_vbgr0SolCbOps =
{
nulldev, /* open */
nulldev, /* close */
nulldev, /* b strategy */
nulldev, /* b dump */
nulldev, /* b print */
nulldev, /* c read */
nulldev, /* c write */
nulldev, /* c ioctl */
nulldev, /* c devmap */
nulldev, /* c mmap */
nulldev, /* c segmap */
nochpoll, /* c poll */
ddi_prop_op, /* property ops */
};
/**
* dev_ops: for driver device operations.
*/
static struct dev_ops g_vbgr0SolDevOps =
{
DEVO_REV, /* driver build revision */
0, /* ref count */
nulldev, /* identify */
nulldev, /* probe */
nodev, /* reset */
(struct bus_ops *)0,
nodev /* power */
};
/**
* modldrv: export driver specifics to the kernel.
*/
static struct modldrv g_vbgr0SolModule =
{
&mod_driverops, /* extern from kernel */
};
/**
*/
static struct modlinkage g_vbgr0SolModLinkage =
{
MODREV_1, /* loadable module system revision */
NULL /* terminate array of linkage structures */
};
#else /* TESTCASE */
static void *g_vbgr0SolModLinkage;
#endif /* TESTCASE */
/**
* State info for each open file handle.
*/
typedef struct
{
/** Pointer to the session handle. */
/** The STREAMS write queue which we need for sending messages up to
* user-space. */
/* The current greatest horizontal pixel offset on the screen, used for
* absolute mouse position reporting.
*/
unsigned cMaxScreenX;
/* The current greatest vertical pixel offset on the screen, used for
* absolute mouse position reporting.
*/
unsigned cMaxScreenY;
} VBGR0STATE, *PVBGR0STATE;
/******************************************************************************
* Global Variables *
******************************************************************************/
/** Device handle (we support only one instance). */
/** Array of state structures for open device nodes. I don't care about
* wasting a few K of memory. */
/** Mutex to protect the queue pointers in the node states from being unset
* during an IRQ. */
static kmutex_t g_StateMutex;
/** Device extention & session data association structure. */
static VBOXGUESTDEVEXT g_DevExt;
/** IO port handle. */
static ddi_acc_handle_t g_PciIOHandle;
/** MMIO handle. */
static ddi_acc_handle_t g_PciMMIOHandle;
/** IO Port. */
static uint16_t g_uIOPortBase;
/** Address of the MMIO region.*/
static char *g_pMMIOBase; /* Actually caddr_t. */
/** Size of the MMIO region. */
/** Pointer to the interrupt handle vector */
static ddi_intr_handle_t *g_pIntr;
/** Number of actually allocated interrupt handles */
static size_t g_cIntrAllocated;
/** The IRQ Mutex */
static kmutex_t g_IrqMutex;
/******************************************************************************
* Kernel entry points *
******************************************************************************/
/** Driver initialisation. */
int _init(void)
{
/*
* Initialize IPRT R0 driver, which internally calls OS-specific r0 init.
*/
if (RT_SUCCESS(rc))
{
static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
if (RT_SUCCESS(rc))
else
/*
* Prevent module autounloading.
*/
if (pModCtl)
else
/* Initialise the node state mutex. This will be taken in the ISR. */
}
else
{
return EINVAL;
}
return rc;
}
#ifdef TESTCASE
/** Simple test of the flow through _init. */
{
}
#endif
/** Driver cleanup. */
int _fini(void)
{
int rc;
RTR0Term();
return rc;
}
/** Driver identification. */
{
}
/******************************************************************************
* Helper routines *
******************************************************************************/
/** Calls the kernel IOCtl to report mouse status to the host on behalf of
* an open session. */
{
}
/** Resets (zeroes) a member in our open node state array in an IRQ-safe way.
*/
{
}
/******************************************************************************
* Main code *
******************************************************************************/
/**
* Open callback for the read queue, which we use as a generic device open
* handler.
*/
{
int rc;
unsigned cInstance;
/*
* Sanity check on the mode parameter - only open as a driver, not a
* module, and we do cloning ourselves. Note that we start at 1, as minor
* zero was allocated to the file system device node in vbgr0SolAttach
*/
if (fMode)
return EINVAL;
{
{
break;
}
}
if (!pState)
{
return ENXIO;
}
/*
* Create a new session.
*/
if (RT_SUCCESS(rc))
{
/* Initialise user data for the queues to our state and vice-versa. */
return 0;
}
/* Failed, clean up. */
return EFAULT;
}
/**
* Close callback for the read queue, which we use as a generic device close
* handler.
*/
{
if (!pState)
{
return EFAULT;
}
if (!pSession)
{
return EFAULT;
}
/*
* Close the session.
*/
return 0;
}
#ifdef TESTCASE
/** Simple test of vbgr0SolOpen and vbgr0SolClose. */
{
int rc;
doInitQueues(&aQueues[0]);
}
#endif
/* Helper for vbgr0SolWPut. */
/**
* Handler for messages sent from above (user-space and upper modules) which
* land in our write queue.
*/
{
{
case M_FLUSH:
/* Flush the write queue if so requested. */
/* Flush the read queue if so requested. */
/* We have no one below us to pass the message on to. */
return 0;
/* M_IOCDATA is additional data attached to (at least) transparent
* IOCtls. We handle the two together here and separate them further
* down. */
case M_IOCTL:
case M_IOCDATA:
{
if (!err)
else
break;
}
}
return 0;
}
#ifdef TESTCASE
/** Test WPut's handling of different IOCtls, which is bulk of the logic in
* this file. */
{
/* Single simple test to start with. We can try to make it more systematic
* next. */
doInitQueues(&aQueues[0]);
}
#endif
/** Data transfer direction of an IOCtl. This is used for describing
* transparent IOCtls, and @a UNSPECIFIED is not a valid value for them. */
enum IOCTLDIRECTION
{
/** This IOCtl transfers no data. */
NONE,
/** This IOCtl only transfers data from user to kernel. */
IN,
/** This IOCtl only transfers data from kernel to user. */
OUT,
/** This IOCtl transfers data from user to kernel and back. */
BOTH,
/** We aren't saying anything about how the IOCtl transfers data. */
};
/**
* IOCtl handler function.
* @returns 0 on success, error code on failure.
* @param cCmd The IOCtl command number.
* @param pvData Buffer for the user data.
* @param cbBuffer Size of the buffer in @a pvData or zero.
* @param pcbData Where to set the size of the data returned. Required for
* handlers which return data.
* @param prc Where to store the return code. Default is zero. Only
* used for IOCtls without data for convenience of
* implemention.
*/
typedef FNVBGR0SOLIOCTL *PFNVBGR0SOLIOCTL;
/* Helpers for vbgr0SolDispatchIOCtl. */
enum IOCTLDIRECTION enmDirection);
/** Table of supported VUID IOCtls. */
struct
{
/** The IOCtl number. */
int cCmd;
/** The size of the buffer which needs to be copied between user and kernel
* space, or zero if unknown (must be known for tranparent IOCtls). */
/** The direction the buffer data needs to be copied. This must be
* specified for transparent IOCtls. */
enum IOCTLDIRECTION enmDirection;
} g_aVUIDIOCtlDescriptions[] =
{
{ VUIDGFORMAT, sizeof(int), OUT },
{ VUIDSFORMAT, 0, NONE },
{ VUIDGADDR, 0, UNSPECIFIED },
{ MSIOSETPARMS, 0, NONE },
{ MSIOBUTTONS, sizeof(int), OUT },
{ VUIDGWHEELCOUNT, sizeof(int), OUT },
{ VUIDGWHEELINFO, 0, UNSPECIFIED },
{ VUIDGWHEELSTATE, 0, UNSPECIFIED },
{ VUIDSWHEELSTATE, 0, UNSPECIFIED }
};
/**
* Handle a STREAMS IOCtl message for our driver on the write stream. This
* function takes care of the IOCtl logic only and does not call qreply() or
* miocnak() at all - the caller must call these on success or failure
* respectively.
* @returns 0 on success or the IOCtl error code on failure.
* @param pWriteQueue pointer to the STREAMS write queue structure.
* @param pMBlk pointer to the STREAMS message block structure.
*/
{
enum IOCTLDIRECTION enmDirection;
switch (cCmdType)
{
case MSIOC:
case VUIOC:
{
unsigned i;
for (i = 0; i < RT_ELEMENTS(g_aVUIDIOCtlDescriptions); ++i)
{
}
return EINVAL;
}
case 'V':
cCmd, 0, UNSPECIFIED);
default:
return ENOTTY;
}
}
/* Helpers for vbgr0SolHandleIOCtl. */
enum IOCTLDIRECTION enmDirection);
enum IOCTLDIRECTION enmDirection);
/**
* Generic code for handling STREAMS-specific IOCtl logic and boilerplate. It
* calls the IOCtl handler passed to it without the handler having to be aware
* of STREAMS structures, or whether this is a transparent (traditional) or an
* I_STR (using a STREAMS structure to describe the data) IOCtl. With the
* caveat that we only support transparent IOCtls which pass all data in a
* single buffer of a fixed size (I_STR IOCtls are restricted to a single
* buffer anyway, but the caller can choose the buffer size).
* @returns 0 on success or the IOCtl error code on failure.
* @param pWriteQueue pointer to the STREAMS write queue structure.
* @param pMBlk pointer to the STREAMS message block structure.
* @param pfnHandler pointer to the right IOCtl handler function for this
* IOCtl number.
* @param cCmd IOCtl command number.
* @param cbTransparent size of the user space buffer for this IOCtl number,
* used for processing transparent IOCtls. Pass zero
* for IOCtls with no maximum buffer size (which will
* not be able to be handled as transparent) or with
* no argument.
* @param enmDirection data transfer direction of the IOCtl.
*/
enum IOCTLDIRECTION enmDirection)
{
return EINVAL;
}
/**
* Helper for vbgr0SolHandleIOCtl. This rather complicated-looking
* code is basically the standard boilerplate for handling any streams IOCtl
* additional data, which we currently only use for transparent IOCtls.
* @copydoc vbgr0SolHandleIOCtl
*/
enum IOCTLDIRECTION enmDirection)
{
{
return EAGAIN;
}
{
int err;
if (cbData < cbTransparent)
return EINVAL;
return EINVAL;
return EINVAL;
return err;
}
else
{
return 0;
}
}
/**
* Helper for vbgr0SolHandleIOCtl. This rather complicated-looking
* code is basically the standard boilerplate for handling transparent IOCtls,
* that is, IOCtls which are not re-packed inside STREAMS IOCtls.
* @copydoc vbgr0SolHandleIOCtl
*/
enum IOCTLDIRECTION enmDirection)
{
|| enmDirection == UNSPECIFIED)
return EINVAL;
{
/* We only need state data if there is something to copy back. */
if (enmDirection == BOTH)
}
else if (enmDirection == OUT)
{
void *pvData;
if (!pMBlkOut)
return EAGAIN;
if (!err)
else
}
else
{
if (!err)
}
return err;
}
/**
* Helper for vbgr0SolHandleIOCtl. This rather complicated-looking
* code is basically the standard boilerplate for handling any streams IOCtl.
* @copydoc vbgr0SolHandleIOCtl
*/
{
return EINVAL;
/* Repack the whole buffer into a single message block if needed. */
if (cbBuffer)
{
if (err)
return err;
}
if (!err)
return err;
}
/**
* Handle a VUID input device IOCtl.
* @copydoc FNVBGR0SOLIOCTL
*/
{
switch (cCmd)
{
case VUIDGFORMAT:
{
LogFlowFunc(("VUIDGFORMAT\n"));
*(int *)pvData = VUID_FIRM_EVENT;
*pcbData = sizeof(int);
return 0;
}
case VUIDSFORMAT:
LogFlowFunc(("VUIDSFORMAT\n"));
/* We define our native format to be VUID_FIRM_EVENT, so there
* is nothing more to do and we exit here on success or on
* failure. */
return 0;
case VUIDGADDR:
case VUIDSADDR:
LogFlowFunc(("VUIDGADDR/VUIDSADDR\n"));
return ENOTTY;
case MSIOGETPARMS:
{
LogFlowFunc(("MSIOGETPARMS\n"));
return 0;
}
case MSIOSETPARMS:
LogFlowFunc(("MSIOSETPARMS\n"));
return 0;
case MSIOSRESOLUTION:
{
int rc;
LogFlowFunc(("MSIOSRESOLUTION\n"));
/* Note: we don't disable this again until session close. */
if (RT_SUCCESS(rc))
return 0;
pState->cMaxScreenX = 0;
pState->cMaxScreenY = 0;
return ENODEV;
}
case MSIOBUTTONS:
{
LogFlowFunc(("MSIOBUTTONS\n"));
*(int *)pvData = 0;
*pcbData = sizeof(int);
return 0;
}
case VUIDGWHEELCOUNT:
{
LogFlowFunc(("VUIDGWHEELCOUNT\n"));
*(int *)pvData = 0;
*pcbData = sizeof(int);
return 0;
}
case VUIDGWHEELINFO:
case VUIDGWHEELSTATE:
case VUIDSWHEELSTATE:
return EINVAL;
default:
return EINVAL;
}
}
/**
* Handle a VBoxGuest IOCtl.
* @copydoc FNVBGR0SOLIOCTL
*/
{
if (RT_SUCCESS(rc))
{
return 0;
}
else
{
/*
* We Log() instead of LogRel() here because VBOXGUEST_IOCTL_WAITEVENT can return VERR_TIMEOUT,
* VBOXGUEST_IOCTL_CANCEL_ALL_EVENTS can return VERR_INTERRUPTED and possibly more in the future;
* which are not really failures that require logging.
*/
return rc;
}
}
/**
* Info entry point, called by solaris kernel for obtaining driver info.
*
* @param pDip The module structure instance (do not use).
* @param enmCmd Information request type.
* @param pvArg Type specific argument.
* @param ppvResult Where to store the requested info.
*
* @return corresponding solaris error code.
*/
void **ppvResult)
{
int rc = DDI_SUCCESS;
switch (enmCmd)
{
case DDI_INFO_DEVT2DEVINFO:
break;
case DDI_INFO_DEVT2INSTANCE:
break;
default:
rc = DDI_FAILURE;
break;
}
return rc;
}
/* Helpers for vbgr0SolAttach and vbgr0SolDetach. */
/**
* Attach entry point, to attach a device to the system or resume it.
*
* @param pDip The module structure instance.
* @param enmCmd Attach type (ddi_attach_cmd_t)
*
* @return corresponding solaris error code.
*/
{
switch (enmCmd)
{
case DDI_ATTACH:
{
if (g_pDip)
{
return DDI_FAILURE;
}
/*
* Enable resources for PCI access.
*/
if (rc == DDI_SUCCESS)
{
/*
* Map the register address space.
*/
char *baseAddr; /* Actually caddr_t. */
if (rc == DDI_SUCCESS)
{
/*
* Read size of the MMIO region.
*/
if (rc == DDI_SUCCESS)
{
if (rc == DDI_SUCCESS)
{
/*
* Add IRQ of VMMDev.
*/
if (rc == DDI_SUCCESS)
{
/*
* Call the common device extension initializer.
*/
#if ARCH_BITS == 64
# define VBOXGUEST_OS_TYPE VBOXOSTYPE_Solaris_x64
#else
# define VBOXGUEST_OS_TYPE VBOXOSTYPE_Solaris
#endif
if (RT_SUCCESS(rc))
{
if (rc == DDI_SUCCESS)
{
return DDI_SUCCESS;
}
}
else
}
else
}
else
}
else
}
else
}
else
return DDI_FAILURE;
}
case DDI_RESUME:
{
/** @todo implement resume for guest driver. */
return DDI_SUCCESS;
}
default:
return DDI_FAILURE;
}
}
/**
* Detach entry point, to detach a device to the system or suspend it.
*
* @param pDip The module structure instance.
* @param enmCmd Attach type (ddi_attach_cmd_t)
*
* @return corresponding solaris error code.
*/
{
switch (enmCmd)
{
case DDI_DETACH:
{
return DDI_SUCCESS;
}
case DDI_SUSPEND:
{
/** @todo implement suspend for guest driver. */
return DDI_SUCCESS;
}
default:
return DDI_FAILURE;
}
}
/* Interrupt service routine installed by vbgr0SolAddIRQ. */
/**
* Sets IRQ for VMMDev.
*
* @returns Solaris error code.
* @param pDip Pointer to the device info structure.
*/
{
if (rc == DDI_SUCCESS)
{
/* We won't need to bother about MSIs. */
if (IntrType & DDI_INTR_TYPE_FIXED)
{
int IntrCount = 0;
if ( rc == DDI_SUCCESS
&& IntrCount > 0)
{
int IntrAvail = 0;
if ( rc == DDI_SUCCESS
&& IntrAvail > 0)
{
/* Allocated kernel memory for the interrupt handles. The allocation size is stored internally. */
if (g_pIntr)
{
unsigned i;
if ( rc == DDI_SUCCESS
&& IntrAllocated > 0)
{
if (rc == DDI_SUCCESS)
{
/* Initialize the mutex. */
/* Assign interrupt handler functions and enable interrupts. */
for (i = 0; i < IntrAllocated; i++)
{
if (rc == DDI_SUCCESS)
if (rc != DDI_SUCCESS)
{
/* Changing local IntrAllocated to hold so-far allocated handles for freeing. */
IntrAllocated = i;
break;
}
}
if (rc == DDI_SUCCESS)
return rc;
/* Remove any assigned handlers */
for (i = 0; i < IntrAllocated; i++)
}
else
/* Remove allocated IRQs, too bad we can free only one handle at a time. */
for (i = 0; i < g_cIntrAllocated; i++)
ddi_intr_free(g_pIntr[i]);
}
else
}
else
}
else
LogRel((DEVICE_NAME "::AddIRQ: failed to get or insufficient available IRQs. rc=%d IntrAvail=%d\n", rc, IntrAvail));
}
else
LogRel((DEVICE_NAME "::AddIRQ: failed to get or insufficient number of IRQs. rc=%d IntrCount=%d\n", rc, IntrCount));
}
else
}
else
return rc;
}
/**
* Removes IRQ for VMMDev.
*
* @param pDip Pointer to the device info structure.
*/
{
unsigned i;
for (i = 0; i < g_cIntrAllocated; i++)
{
if (rc == DDI_SUCCESS)
{
if (rc == DDI_SUCCESS)
ddi_intr_free(g_pIntr[i]);
}
}
}
/**
* Interrupt Service Routine for VMMDev.
*
* @param Arg Private data (unused, will be NULL).
* @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't.
*/
{
bool fOurIRQ;
}
/* Helper for VBoxGuestNativeISRMousePollEvent. */
int cValue);
/**
* Native part of the IRQ service routine, called when the VBoxGuest mouse
* pointer is moved. We send a VUID event up to user space.
*/
{
int rc;
if (RT_FAILURE(rc))
return; /* If kernel memory is short a missed event is acceptable! */
pReq->mouseFeatures = 0;
pReq->pointerXPos = 0;
pReq->pointerYPos = 0;
if (RT_SUCCESS(rc))
{
unsigned i;
for (i = 1; i < MAX_OPEN_NODES; ++i)
{
if (!cMaxScreenX || !cMaxScreenY)
continue;
}
}
}
int cValue)
{
if (!pMBlk)
return; /* If kernel memory is short a missed event is acceptable! */
/* Put the message on the queue immediately if it is not blocked. */
else
}
/* Common code that depends on g_DevExt. */
#ifndef TESTCASE
# include "VBoxGuestIDC-unix.c.h"
#endif
#ifdef TESTCASE
int main(void)
{
if (rc)
return rc;
/*
* Summary.
*/
return RTTestSummaryAndDestroy(hTest);
}
#endif