SUPDrv-darwin.cpp revision cc0958b13956fc40842dbd4c68e36b41125ba7ab
/* $Id$ */
/** @file
* VirtualBox Support Driver - Darwin Specific Code.
*/
/*
* 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;
* 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.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*
* 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.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_SUP_DRV
/*
* Deal with conflicts first.
* PVM - BSD mess, that FreeBSD has correct a long time ago.
*/
#include "../SUPDrvInternal.h"
#include <iprt/initterm.h>
#include <iprt/spinlock.h>
#include <iprt/semaphore.h>
#include <IOKit/IOService.h>
#include <IOKit/IOUserclient.h>
#ifdef VBOX_WITH_HOST_VMX
#endif
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** The module name. */
#define DEVICE_NAME "vboxdrv"
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static int VBoxDrvDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess);
static int VBoxDrvDarwinIOCtlSlow(PSUPDRVSESSION pSession, u_long iCmd, caddr_t pData, struct proc *pProcess);
static int VBoxDrvDarwinErr2DarwinErr(int rc);
static IOReturn VBoxDrvDarwinSleepHandler(void *pvTarget, void *pvRefCon, UInt32 uMessageType, IOService *pProvider, void *pvMessageArgument, vm_size_t argSize);
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* The service class.
* This is just a formality really.
*/
class org_virtualbox_SupDrv : public IOService
{
public:
virtual void free(void);
};
/**
* An attempt at getting that clientDied() notification.
* port right.
*/
class org_virtualbox_SupDrvClient : public IOUserClient
{
private:
public:
virtual IOReturn clientClose(void);
virtual IOReturn clientDied(void);
};
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/**
* Declare the module stuff.
*/
/**
* Device extention & session data association structure.
*/
static SUPDRVDEVEXT g_DevExt;
/**
* The character device switch table for the driver.
*/
{
/** @todo g++ doesn't like this syntax - it worked with gcc before renaming to .cpp. */
/*.d_open = */VBoxDrvDarwinOpen,
/*.d_close = */VBoxDrvDarwinClose,
/*.d_read = */eno_rdwrt,
/*.d_write = */eno_rdwrt,
/*.d_ioctl = */VBoxDrvDarwinIOCtl,
/*.d_stop = */eno_stop,
/*.d_reset = */eno_reset,
/*.d_ttys = */NULL,
/*.d_select= */eno_select,
/*.d_mmap = */eno_mmap,
/*.d_strategy = */eno_strat,
/*.d_getc = */eno_getc,
/*.d_putc = */eno_putc,
/*.d_type = */0
};
/** Major device number. */
static int g_iMajorDeviceNo = -1;
/** Registered devfs device handle. */
static void *g_hDevFsDevice = NULL;
/** Spinlock protecting g_apSessionHashTab. */
/** Hash table */
/** Calculates the index into g_apSessionHashTab.*/
/** The number of open sessions. */
static int32_t volatile g_cSessions = 0;
/** The notifier handle for the sleep callback handler. */
/**
* Start the kernel module.
*/
{
int rc;
#ifdef DEBUG
printf("VBoxDrvDarwinStart\n");
#endif
/*
* Initialize IPRT.
*/
if (RT_SUCCESS(rc))
{
/*
* Initialize the device extension.
*/
if (RT_SUCCESS(rc))
{
/*
* Initialize the session hash table.
*/
if (RT_SUCCESS(rc))
{
/*
* Registering ourselves as a character device.
*/
if (g_iMajorDeviceNo >= 0)
{
#ifdef VBOX_WITH_HARDENING
#else
#endif
if (g_hDevFsDevice)
{
LogRel(("VBoxDrv: version " VBOX_VERSION_STRING " r%d; IOCtl version %#x; IDC version %#x; dev major=%d\n",
if (g_pSleepNotifier == NULL)
return KMOD_RETURN_SUCCESS;
}
g_iMajorDeviceNo = -1;
}
else
}
else
}
else
RTR0Term();
}
else
return KMOD_RETURN_FAILURE;
}
/**
* Stop the kernel module.
*/
{
int rc;
LogFlow(("VBoxDrvDarwinStop\n"));
/** @todo I've got a nagging feeling that we'll have to keep track of users and refuse
* unloading if we're busy. Investigate and implement this! */
/*
* Undo the work done during start (in reverse order).
*/
if (g_pSleepNotifier)
{
}
g_iMajorDeviceNo = -1;
RTR0Term();
#ifdef DEBUG
printf("VBoxDrvDarwinStop - done\n");
#endif
return KMOD_RETURN_SUCCESS;
}
/**
*
* @param pInode Pointer to inode info structure.
* @param pFilp Associated file pointer.
*/
{
#ifdef DEBUG_DARWIN_GIP
char szName[128];
szName[0] = '\0';
#endif
/*
* Find the session created by org_virtualbox_SupDrvClient, fail
* if no such session, and mark it as opened. We set the uid & gid
* here too, since that is more straight forward at this point.
*/
int rc = VINF_SUCCESS;
if (pCred)
{
{
}
if (pSession)
{
{
}
else
}
else
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
#else /* 10.4 */
/* The 10.4u SDK headers and 10.4.11 kernel source have inconsisten defintions
of kauth_cred_unref(), so use the other (now deprecated) API for releasing it. */
#endif /* 10.4 */
}
else
#ifdef DEBUG_DARWIN_GIP
OSDBGPRINT(("VBoxDrvDarwinOpen: pid=%d '%s' pSession=%p rc=%d\n", proc_pid(pProcess), szName, pSession, rc));
#else
Log(("VBoxDrvDarwinOpen: g_DevExt=%p pSession=%p rc=%d pid=%d\n", &g_DevExt, pSession, rc, proc_pid(pProcess)));
#endif
return VBoxDrvDarwinErr2DarwinErr(rc);
}
/**
* Close device.
*/
{
/*
* Hand the session closing to org_virtualbox_SupDrvClient.
*/
return 0;
}
/**
* Device I/O Control entry point.
*
* @returns Darwin for slow IOCtls and VBox status code for the fast ones.
* @param Dev The device number (major+minor).
* @param iCmd The IOCtl command.
* @param pData Pointer to the data (if any it's a SUPDRVIOCTLDATA (kernel copy)).
* @param fFlags Flag saying we're a character device (like we didn't know already).
* @param pProcess The process issuing this request.
*/
static int VBoxDrvDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess)
{
/*
* Find the session.
*/
{
}
if (!pSession)
{
OSDBGPRINT(("VBoxDrvDarwinIOCtl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d iCmd=%#lx\n",
return EINVAL;
}
/*
* Deal with the two high-speed IOCtl that takes it's arguments from
* the session and iCmd, and only returns a VBox status code.
*/
if ( iCmd == SUP_IOCTL_FAST_DO_RAW_RUN
|| iCmd == SUP_IOCTL_FAST_DO_NOP)
}
/**
* Worker for VBoxDrvDarwinIOCtl that takes the slow IOCtl functions.
*
* @returns Darwin errno.
*
* @param pSession The session.
* @param iCmd The IOCtl command.
* @param pData Pointer to the kernel copy of the SUPDRVIOCTLDATA buffer.
* @param pProcess The calling process.
*/
static int VBoxDrvDarwinIOCtlSlow(PSUPDRVSESSION pSession, u_long iCmd, caddr_t pData, struct proc *pProcess)
{
LogFlow(("VBoxDrvDarwinIOCtlSlow: pSession=%p iCmd=%p pData=%p pProcess=%p\n", pSession, iCmd, pData, pProcess));
/*
* Buffered or unbuffered?
*/
user_addr_t pUser = 0;
{
{
OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: cbReq=%#x < %#x; iCmd=%#lx\n", cbReq, (int)sizeof(*pHdr), iCmd));
return EINVAL;
}
{
return EINVAL;
}
{
OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: max(%#x,%#x) != %#x; iCmd=%#lx\n", pHdr->cbIn, pHdr->cbOut, cbReq, iCmd));
return EINVAL;
}
}
{
/*
* Get the header and figure out how much we're gonna have to read.
*/
if (RT_UNLIKELY(rc))
{
OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: copyin(%llx,Hdr,) -> %#x; iCmd=%#lx\n", (unsigned long long)pUser, rc, iCmd));
return rc;
}
{
return EINVAL;
}
{
return EINVAL;
}
/*
* Allocate buffer and copy in the data.
*/
if (!pHdr)
if (RT_UNLIKELY(!pHdr))
{
OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: failed to allocate buffer of %d bytes; iCmd=%#lx\n", cbReq, iCmd));
return ENOMEM;
}
if (RT_UNLIKELY(rc))
{
OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: copyin(%llx,%p,%#x) -> %#x; iCmd=%#lx\n",
if (pvPageBuf)
else
return rc;
}
}
else
{
return EINVAL;
}
/*
* Process the IOCtl.
*/
{
/*
* If not buffered, copy back the buffer before returning.
*/
if (pUser)
{
{
OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: too much output! %#x > %#x; uCmd=%#lx!\n", cbOut, cbReq, iCmd));
}
if (RT_UNLIKELY(rc))
OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: copyout(%p,%llx,%#x) -> %d; uCmd=%#lx!\n",
/* cleanup */
if (pvPageBuf)
else
}
}
else
{
/*
* The request failed, just clean up.
*/
if (pUser)
{
if (pvPageBuf)
else
}
Log(("VBoxDrvDarwinIOCtlSlow: pid=%d iCmd=%lx pData=%p failed, rc=%d\n", proc_pid(pProcess), iCmd, (void *)pData, rc));
}
return rc;
}
/**
* The SUPDRV IDC entry point.
*
* @returns VBox status code, see supdrvIDC.
* @param iReq The request code.
* @param pReq The request.
*/
{
/*
* Some quick validations.
*/
return VERR_INVALID_POINTER;
if (pSession)
{
return VERR_INVALID_PARAMETER;
return VERR_INVALID_PARAMETER;
}
return VERR_INVALID_PARAMETER;
/*
* Do the job.
*/
}
/**
* Initializes any OS specific object creator fields.
*/
{
}
/**
* Checks if the session can access the object.
*
* @returns true if a decision has been made.
* @returns false if the default access policy should be applied.
*
* @param pObj The object in question.
* @param pSession The session wanting to access the object.
* @param pszObjName The object name, can be NULL.
* @param prc Where to store the result when returning true.
*/
bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc)
{
return false;
}
/**
* Callback for blah blah blah.
*/
IOReturn VBoxDrvDarwinSleepHandler(void * /* pvTarget */, void *pvRefCon, UInt32 uMessageType, IOService * /* pProvider */, void * /* pvMessageArgument */, vm_size_t /* argSize */)
{
else if (uMessageType == kIOMessageSystemHasPoweredOn)
return 0;
}
/**
* Enables or disables VT-x using kernel functions.
*
* @returns VBox status code. VERR_NOT_SUPPORTED has a special meaning.
* @param fEnable Whether to enable or disable.
*/
{
/* Zarking amateurish Apple engineering!
host_vmxon is actually buggy and may panic multicore machines. Reason, it
to acquire it. Then it allocate wired memory in the kernel map for each
of the cpus in the system. If anyone else tries to mess around in the
kernel map on another CPU while this is going on, there is a fair chance
that it might cause the host_vmxon thread to block and hence panic since
preemption is disabled. Arrrg! */
#if 0 /*def VBOX_WITH_HOST_VMX*/
int rc;
if (fEnable)
{
if (rc == 0 /* all ok */)
rc = VINF_SUCCESS;
else /* shouldn't happen, but just in case. */
{
}
}
else
{
host_vmxoff();
rc = VINF_SUCCESS;
}
return rc;
#else
return VERR_NOT_SUPPORTED;
#endif
}
{
return false;
}
/**
* Converts a supdrv error code to a darwin error code.
*
* @returns corresponding darwin error code.
* @param rc supdrv error code (SUPDRV_ERR_* defines).
*/
static int VBoxDrvDarwinErr2DarwinErr(int rc)
{
switch (rc)
{
case 0: return 0;
case SUPDRV_ERR_GENERAL_FAILURE: return EACCES;
case SUPDRV_ERR_INVALID_PARAM: return EINVAL;
case SUPDRV_ERR_INVALID_MAGIC: return EILSEQ;
case SUPDRV_ERR_INVALID_HANDLE: return ENXIO;
case SUPDRV_ERR_INVALID_POINTER: return EFAULT;
case SUPDRV_ERR_LOCK_FAILED: return ENOLCK;
case SUPDRV_ERR_ALREADY_LOADED: return EEXIST;
case SUPDRV_ERR_PERMISSION_DENIED: return EPERM;
case SUPDRV_ERR_VERSION_MISMATCH: return ENOSYS;
}
return EPERM;
}
/** @todo move this to assembly where a simple "jmp printf" will to the trick. */
{
char szMsg[512];
return 0;
}
/*
*
* org_virtualbox_SupDrv
*
*/
/**
* Initialize the object.
*/
{
{
/* init members. */
return true;
}
return false;
}
/**
* Free the object.
*/
void org_virtualbox_SupDrv::free(void)
{
LogFlow(("IOService::free([%p])\n", this));
}
/**
* Check if it's ok to start this service.
* It's always ok by us, so it's up to IOService to decide really.
*/
{
LogFlow(("org_virtualbox_SupDrv::probe([%p])\n", this));
}
/**
* Start this service.
*/
{
LogFlow(("org_virtualbox_SupDrv::start([%p])\n", this));
{
/* register the service. */
return true;
}
return false;
}
/**
* Stop this service.
*/
{
}
/**
* Termination request.
*
* @return true if we're ok with shutting down now, false if we're not.
* @param fOptions Flags.
*/
{
bool fRc;
LogFlow(("org_virtualbox_SupDrv::terminate: reference_count=%d g_cSessions=%d (fOptions=%#x)\n",
if ( KMOD_INFO_NAME.reference_count != 0
fRc = false;
else
return fRc;
}
/*
*
* org_virtualbox_SupDrvClient
*
*/
/**
* Initializer called when the client opens the service.
*/
bool org_virtualbox_SupDrvClient::initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type)
{
LogFlow(("org_virtualbox_SupDrvClient::initWithTask([%p], %#x, %p, %#x) (cur pid=%d proc=%p)\n",
AssertMsg((RTR0PROCESS)OwningTask == RTR0ProcHandleSelf(), ("%p %p\n", OwningTask, RTR0ProcHandleSelf()));
if (!OwningTask)
return false;
{
m_Task = OwningTask;
m_pSession = NULL;
m_pProvider = NULL;
return true;
}
return false;
}
/**
* Start the client service.
*/
{
LogFlow(("org_virtualbox_SupDrvClient::start([%p], %p) (cur pid=%d proc=%p)\n",
false);
{
if (m_pProvider)
{
Assert(!m_pSession);
/*
* Create a new session.
*/
if (RT_SUCCESS(rc))
{
m_pSession->fOpened = false;
/* The Uid and Gid fields are set on open. */
/*
* Insert it into the hash table, checking that there isn't
* already one for this process first.
*/
{
}
if (!pCur)
{
m_pSession->pvSupDrvClient = this;
rc = VINF_SUCCESS;
}
else
if (RT_SUCCESS(rc))
{
Log(("org_virtualbox_SupDrvClient::start: created session %p for pid %d\n", m_pSession, (int)RTProcSelf()));
return true;
}
LogFlow(("org_virtualbox_SupDrvClient::start: already got a session for this process (%p)\n", pCur));
}
m_pSession = NULL;
}
else
}
return false;
}
/**
* Common worker for clientClose and VBoxDrvDarwinClose.
*
* It will
*/
{
/*
* Look for the session.
*/
if (pSession)
{
{
}
else
{
while (pSession)
{
{
break;
}
/* next */
}
}
}
if (!pSession)
{
return;
}
/*
* Remove it from the client object.
*/
if (pThis)
{
}
/*
* Close the session.
*/
}
/**
* Client exits normally.
*/
{
LogFlow(("org_virtualbox_SupDrvClient::clientClose([%p]) (cur pid=%d proc=%p)\n", this, RTProcSelf(), RTR0ProcHandleSelf()));
/*
* Clean up the session if it's still around.
*
* We cannot rely 100% on close, and in the case of a dead client
* we'll end up hanging inside vm_map_remove() if we postpone it.
*/
if (m_pSession)
{
Assert(!m_pSession);
}
m_pProvider = NULL;
terminate();
return kIOReturnSuccess;
}
/**
* The client exits abnormally / forgets to do cleanups. (logging)
*/
{
LogFlow(("org_virtualbox_SupDrvClient::clientDied([%p]) m_Task=%p R0Process=%p Process=%d\n",
/* IOUserClient::clientDied() calls clientClose, so we'll just do the work there. */
return IOUserClient::clientDied();
}
/**
* Terminate the service (initiate the destruction). (logging)
*/
{
}
/**
* The final stage of the client service destruction. (logging)
*/
{
}
/**
* Stop the client service. (logging)
*/
{
LogFlow(("org_virtualbox_SupDrvClient::stop([%p])\n", this));
}