DrvDedicatedNic.cpp revision c7814cf6e1240a519cbec0441e033d0e2470ed00
/* $Id$ */
/** @file
* DrvDedicatedNic - Experimental network driver for using a dedicated (V)NIC.
*/
/*
* Copyright (C) 2010-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;
* 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.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DEFAULT
#include <VBox/log.h>
#include <VBox/vmm/pdmcritsect.h>
#include <VBox/vmm/pdmdrv.h>
#include <VBox/vmm/pdmnetifs.h>
#include <VBox/vmm/pdmnetinline.h>
#include <VBox/intnet.h>
#include <VBox/intnetinline.h>
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/mem.h>
#include <iprt/path.h>
#include <iprt/string.h>
#include <iprt/thread.h>
#include <iprt/uuid.h>
#include "VBoxDD.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Instance data for the dedicated (V)NIC driver.
*
* @implements PDMINETWORKUP
*/
typedef struct DRVDEDICATEDNIC
{
/** The network interface. */
PDMINETWORKUP INetworkUpR3;
/** The network interface. */
R3PTRTYPE(PPDMINETWORKDOWN) pIAboveNet;
/** The network config interface.
* Can (in theory at least) be NULL. */
R3PTRTYPE(PPDMINETWORKCONFIG) pIAboveConfigR3;
/** Pointer to the driver instance. */
PPDMDRVINSR3 pDrvInsR3;
/** Ring-3 base interface for the ring-0 context. */
PDMIBASER0 IBaseR0;
/** Ring-3 base interface for the raw-mode context. */
PDMIBASERC IBaseRC;
RTR3PTR R3PtrAlignment;
/** The network interface for the ring-0 context. */
PDMINETWORKUPR0 INetworkUpR0;
/** Pointer to the driver instance. */
PPDMDRVINSR0 pDrvInsR0;
RTR0PTR R0PtrAlignment;
/** The interface we're talking to. */
R0PTRTYPE(PINTNETTRUNKIFPORT) pIfPortR0;
/** Set if the link is up, clear if its down. */
bool fLinkDown;
/** Set if the current transmit operation is done the XMIT thread. If clear,
* we assume its an EMT. */
bool fXmitOnXmitThread;
/** The name of the interface that we're connected to. */
char szIfName[128 + 8 - 2];
/** Critical section serializing transmission. */
PDMCRITSECT XmitLock;
/** The transmit scatter gather buffer (ring-3 -> ring-0). */
PDMSCATTERGATHER XmitSg;
/** The transmit GSO context (when applicable). */
PDMNETWORKGSO XmitGso;
/** The transmit buffer (ring-3 -> ring-0). */
uint8_t abXmitBuf[_64K];
/** The receive scatter gather buffer. */
PDMSCATTERGATHER RecvSg;
/** The receive buffer (ring-0 -> ring-3). */
uint8_t abRecvBuf[_64K];
} DRVDEDICATEDNIC;
/** Pointer to the instance data for the dedicated (V)NIC driver. */
typedef DRVDEDICATEDNIC *PDRVDEDICATEDNIC;
/**
* Ring-0 operations.
*/
typedef enum DRVDEDICATEDNICR0OP
{
/** Invalid zero value.. */
DRVDEDICATEDNICR0OP_INVALID = 0,
/** Initialize the connection to the NIC. */
DRVDEDICATEDNICR0OP_INIT,
/** Terminate the connection to the NIC. */
DRVDEDICATEDNICR0OP_TERM,
/** Suspend the operation. */
DRVDEDICATEDNICR0OP_SUSPEND,
/** Resume the operation. */
DRVDEDICATEDNICR0OP_RESUME,
/** Wait for and do receive work.
* We do this in ring-0 instead of ring-3 to save 1-2 buffer copies and
* unnecessary context switching. */
DRVDEDICATEDNICR0OP_RECV,
/** Wait for and do transmit work.
* We do this in ring-0 instead of ring-3 to save 1-2 buffer copies and
* unnecessary context switching. */
DRVDEDICATEDNICR0OP_SEND,
/** Changes the promiscuousness of the interface (guest point of view). */
DRVDEDICATEDNICR0OP_PROMISC,
/** End of the valid operations. */
DRVDEDICATEDNICR0OP_END,
/** The usual 32-bit hack. */
DRVDEDICATEDNICR0OP_32BIT_HACK = 0x7fffffff
} DRVDEDICATEDNICR0OP;
#ifdef IN_RING0
/**
* @interface_method_impl{FNPDMDRVREQHANDLERR0}
*/
PDMBOTHCBDECL(int) drvR0DedicatedNicReqHandler(PPDMDRVINS pDrvIns, uint32_t uOperation, uint64_t u64Arg)
{
switch ((DRVDEDICATEDNICR0OP)uOperation)
{
case DRVDEDICATEDNICR0OP_INIT:
return VERR_NOT_IMPLEMENTED;//drvR0DedicatedNicReqInit(pDrvIns, u64Arg);
case DRVDEDICATEDNICR0OP_TERM:
return VERR_NOT_IMPLEMENTED;//drvR0DedicatedNicReqTerm(pDrvIns);
case DRVDEDICATEDNICR0OP_SUSPEND:
return VERR_NOT_IMPLEMENTED;//drvR0DedicatedNicReqSuspend(pDrvIns);
case DRVDEDICATEDNICR0OP_RESUME:
return VERR_NOT_IMPLEMENTED;//drvR0DedicatedNicReqResume(pDrvIns);
case DRVDEDICATEDNICR0OP_RECV:
return VERR_NOT_IMPLEMENTED;//drvR0DedicatedNicReqRecv(pDrvIns);
case DRVDEDICATEDNICR0OP_SEND:
return VERR_NOT_IMPLEMENTED;//drvR0DedicatedNicReqSend(pDrvIns);
case DRVDEDICATEDNICR0OP_PROMISC:
return VERR_NOT_IMPLEMENTED;//drvR0DedicatedNicReqPromisc(pDrvIns, !!u64Arg);
case DRVDEDICATEDNICR0OP_END:
default:
return VERR_INVALID_FUNCTION;
}
return VINF_SUCCESS;
}
#endif /* IN_RING0 */
/* -=-=-=-=- PDMINETWORKUP -=-=-=-=- */
/**
* @interface_method_impl{PDMINETWORKUP,pfnBeginXmit}
*/
PDMBOTHCBDECL(int) drvDedicatedNicUp_BeginXmit(PPDMINETWORKUP pInterface, bool fOnWorkerThread)
{
PDRVDEDICATEDNIC pThis = RT_FROM_MEMBER(pInterface, DRVDEDICATEDNIC, CTX_SUFF(INetworkUp));
int rc = PDMCritSectTryEnter(&pThis->XmitLock);
if (RT_SUCCESS(rc))
ASMAtomicUoWriteBool(&pThis->fXmitOnXmitThread, fOnWorkerThread);
return rc;
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnAllocBuf}
*/
PDMBOTHCBDECL(int) drvDedicatedNicUp_AllocBuf(PPDMINETWORKUP pInterface, size_t cbMin,
PCPDMNETWORKGSO pGso, PPPDMSCATTERGATHER ppSgBuf)
{
PDRVDEDICATEDNIC pThis = RT_FROM_MEMBER(pInterface, DRVDEDICATEDNIC, CTX_SUFF(INetworkUp));
Assert(PDMCritSectIsOwner(&pThis->XmitLock));
/*
* If the net is down, we can return immediately.
*/
if (pThis->fLinkDown)
return VERR_NET_DOWN;
#ifdef IN_RING0
/** @todo Ask the driver for a buffer, atomically if we're called on EMT. */
return VERR_TRY_AGAIN;
#else /* IN_RING3 */
/*
* Are we busy or is the request too big?
*/
if (RT_UNLIKELY((pThis->XmitSg.fFlags & PDMSCATTERGATHER_FLAGS_MAGIC_MASK) == PDMSCATTERGATHER_FLAGS_MAGIC))
return VERR_TRY_AGAIN;
if (cbMin > sizeof(pThis->abXmitBuf))
return VERR_NO_MEMORY;
/*
* Initialize the S/G buffer and return.
*/
pThis->XmitSg.fFlags = PDMSCATTERGATHER_FLAGS_MAGIC | PDMSCATTERGATHER_FLAGS_OWNER_1;
pThis->XmitSg.cbUsed = 0;
pThis->XmitSg.cbAvailable = sizeof(pThis->abXmitBuf);
pThis->XmitSg.pvAllocator = NULL;
if (!pGso)
{
pThis->XmitSg.pvUser = NULL;
pThis->XmitGso.u8Type = PDMNETWORKGSOTYPE_INVALID;
}
else
{
pThis->XmitSg.pvUser = &pThis->XmitGso;
pThis->XmitGso = *pGso;
}
pThis->XmitSg.cSegs = 1;
pThis->XmitSg.aSegs[0].cbSeg = pThis->XmitSg.cbAvailable;
pThis->XmitSg.aSegs[0].pvSeg = &pThis->abXmitBuf[0];
# if 0 /* poison */
memset(pThis->XmitSg.aSegs[0].pvSeg, 'F', pThis->XmitSg.aSegs[0].cbSeg);
# endif
*ppSgBuf = &pThis->XmitSg;
return VINF_SUCCESS;
#endif /* IN_RING3 */
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnFreeBuf}
*/
PDMBOTHCBDECL(int) drvDedicatedNicUp_FreeBuf(PPDMINETWORKUP pInterface, PPDMSCATTERGATHER pSgBuf)
{
PDRVDEDICATEDNIC pThis = RT_FROM_MEMBER(pInterface, DRVDEDICATEDNIC, CTX_SUFF(INetworkUp));
Assert(pSgBuf->fFlags == (PDMSCATTERGATHER_FLAGS_MAGIC | PDMSCATTERGATHER_FLAGS_OWNER_1));
Assert(pSgBuf->cbUsed <= pSgBuf->cbAvailable);
Assert(PDMCritSectIsOwner(&pThis->XmitLock));
if (pSgBuf)
{
#ifdef IN_RING0
// ...
#else
Assert(pSgBuf == &pThis->XmitSg);
Assert((pSgBuf->fFlags & PDMSCATTERGATHER_FLAGS_MAGIC_MASK) == PDMSCATTERGATHER_FLAGS_MAGIC);
pSgBuf->fFlags = 0;
#endif
}
return VINF_SUCCESS;
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnSendBuf}
*/
PDMBOTHCBDECL(int) drvDedicatedNicUp_SendBuf(PPDMINETWORKUP pInterface, PPDMSCATTERGATHER pSgBuf, bool fOnWorkerThread)
{
PDRVDEDICATEDNIC pThis = RT_FROM_MEMBER(pInterface, DRVDEDICATEDNIC, CTX_SUFF(INetworkUp));
STAM_PROFILE_START(&pThis->StatTransmit, a);
AssertPtr(pSgBuf);
Assert(pSgBuf->fFlags == (PDMSCATTERGATHER_FLAGS_MAGIC | PDMSCATTERGATHER_FLAGS_OWNER_1));
Assert(pSgBuf->cbUsed <= pSgBuf->cbAvailable);
#ifdef IN_RING0
Assert(pSgBuf == &pThis->XmitSg);
#endif
Assert(PDMCritSectIsOwner(&pThis->XmitLock));
#ifdef IN_RING0
/*
* Tell the driver to send the packet.
*/
return VERR_INTERNAL_ERROR_4;
#else /* IN_RING3 */
/*
* Call ring-0 to start the transfer.
*/
int rc = PDMDrvHlpCallR0(pThis->pDrvInsR3, DRVDEDICATEDNICR0OP_SEND, pSgBuf->cbUsed);
if (RT_FAILURE(rc) && rc != VERR_NET_DOWN)
rc = VERR_NET_NO_BUFFER_SPACE;
pSgBuf->fFlags = 0;
return rc;
#endif /* IN_RING3 */
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnEndXmit}
*/
PDMBOTHCBDECL(void) drvDedicatedNicUp_EndXmit(PPDMINETWORKUP pInterface)
{
PDRVDEDICATEDNIC pThis = RT_FROM_MEMBER(pInterface, DRVDEDICATEDNIC, CTX_SUFF(INetworkUp));
ASMAtomicUoWriteBool(&pThis->fXmitOnXmitThread, false);
PDMCritSectLeave(&pThis->XmitLock);
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnSetPromiscuousMode}
*/
PDMBOTHCBDECL(void) drvDedicatedNicUp_SetPromiscuousMode(PPDMINETWORKUP pInterface, bool fPromiscuous)
{
PDRVDEDICATEDNIC pThis = RT_FROM_MEMBER(pInterface, DRVDEDICATEDNIC, CTX_SUFF(INetworkUp));
/** @todo enable/disable promiscuous mode (should be easy) */
NOREF(pThis);
}
#ifdef IN_RING3
/**
* @interface_method_impl{PDMINETWORKUP,pfnNotifyLinkChanged}
*/
static DECLCALLBACK(void) drvR3DedicatedNicUp_NotifyLinkChanged(PPDMINETWORKUP pInterface, PDMNETWORKLINKSTATE enmLinkState)
{
PDRVDEDICATEDNIC pThis = RT_FROM_MEMBER(pInterface, DRVDEDICATEDNIC, CTX_SUFF(INetworkUp));
bool fLinkDown;
switch (enmLinkState)
{
case PDMNETWORKLINKSTATE_DOWN:
case PDMNETWORKLINKSTATE_DOWN_RESUME:
fLinkDown = true;
break;
default:
AssertMsgFailed(("enmLinkState=%d\n", enmLinkState));
case PDMNETWORKLINKSTATE_UP:
fLinkDown = false;
break;
}
LogFlow(("drvR3DedicatedNicUp_NotifyLinkChanged: enmLinkState=%d %d->%d\n", enmLinkState, pThis->fLinkDown, fLinkDown));
ASMAtomicWriteBool(&pThis->fLinkDown, fLinkDown);
}
/* -=-=-=-=- PDMIBASER0 -=-=-=-=- */
/**
* @interface_method_impl{PDMIBASER0,pfnQueryInterface}
*/
static DECLCALLBACK(RTR0PTR) drvR3DedicatedNicIBaseR0_QueryInterface(PPDMIBASER0 pInterface, const char *pszIID)
{
PDRVDEDICATEDNIC pThis = RT_FROM_MEMBER(pInterface, DRVDEDICATEDNIC, IBaseR0);
PDMIBASER0_RETURN_INTERFACE(pThis->pDrvInsR3, pszIID, PDMINETWORKUP, &pThis->INetworkUpR0);
return NIL_RTR0PTR;
}
/* -=-=-=-=- PDMIBASE -=-=-=-=- */
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
static DECLCALLBACK(void *) drvR3DedicatedNicIBase_QueryInterface(PPDMIBASE pInterface, const char *pszIID)
{
PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
PDRVDEDICATEDNIC pThis = PDMINS_2_DATA(pDrvIns, PDRVDEDICATEDNIC);
PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASER0, &pThis->IBaseR0);
PDMIBASE_RETURN_INTERFACE(pszIID, PDMINETWORKUP, &pThis->INetworkUpR3);
return NULL;
}
/* -=-=-=-=- PDMDRVREG -=-=-=-=- */
/**
* @interface_method_impl{PDMDRVREG,pfnPowerOff}
*/
static DECLCALLBACK(void) drvR3DedicatedNicPowerOff(PPDMDRVINS pDrvIns)
{
LogFlow(("drvR3DedicatedNicPowerOff\n"));
PDRVDEDICATEDNIC pThis = PDMINS_2_DATA(pDrvIns, PDRVDEDICATEDNIC);
int rc = PDMDrvHlpCallR0(pDrvIns, DRVDEDICATEDNICR0OP_SUSPEND, 0);
AssertRC(rc);
}
/**
* @interface_method_impl{PDMDRVREG,pfnResume}
*/
static DECLCALLBACK(void) drvR3DedicatedNicResume(PPDMDRVINS pDrvIns)
{
LogFlow(("drvR3DedicatedNicPowerResume\n"));
PDRVDEDICATEDNIC pThis = PDMINS_2_DATA(pDrvIns, PDRVDEDICATEDNIC);
int rc = PDMDrvHlpCallR0(pDrvIns, DRVDEDICATEDNICR0OP_RESUME, 0);
AssertRC(rc);
}
/**
* @interface_method_impl{PDMDRVREG,pfnSuspend}
*/
static DECLCALLBACK(void) drvR3DedicatedNicSuspend(PPDMDRVINS pDrvIns)
{
LogFlow(("drvR3DedicatedNicPowerSuspend\n"));
PDRVDEDICATEDNIC pThis = PDMINS_2_DATA(pDrvIns, PDRVDEDICATEDNIC);
int rc = PDMDrvHlpCallR0(pDrvIns, DRVDEDICATEDNICR0OP_SUSPEND, 0);
AssertRC(rc);
}
/**
* @interface_method_impl{PDMDRVREG,pfnPowerOn}
*/
static DECLCALLBACK(void) drvR3DedicatedNicPowerOn(PPDMDRVINS pDrvIns)
{
LogFlow(("drvR3DedicatedNicPowerOn\n"));
PDRVDEDICATEDNIC pThis = PDMINS_2_DATA(pDrvIns, PDRVDEDICATEDNIC);
int rc = PDMDrvHlpCallR0(pDrvIns, DRVDEDICATEDNICR0OP_RESUME, 0);
AssertRC(rc);
}
/**
* @interface_method_impl{PDMDRVREG,pfnDestruct}
*/
static DECLCALLBACK(void) drvR3DedicatedNicDestruct(PPDMDRVINS pDrvIns)
{
LogFlow(("drvR3DedicatedNicDestruct\n"));
PDRVDEDICATEDNIC pThis = PDMINS_2_DATA(pDrvIns, PDRVDEDICATEDNIC);
PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
if (pThis->pIfPortR0)
{
int rc = PDMDrvHlpCallR0(pDrvIns, DRVDEDICATEDNICR0OP_TERM, 0);
AssertRC(rc);;
}
}
/**
* @interface_method_impl{PDMDRVREG,pfnConstruct}
*/
static DECLCALLBACK(int) drvR3DedicatedNicConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
{
PDRVDEDICATEDNIC pThis = PDMINS_2_DATA(pDrvIns, PDRVDEDICATEDNIC);
bool f;
PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
/*
* Init the static parts.
*/
pThis->pDrvInsR3 = pDrvIns;
pThis->pDrvInsR0 = PDMDRVINS_2_R0PTR(pDrvIns);
#if 0
pThis->hRecvThread = NIL_RTTHREAD;
pThis->hRecvEvt = NIL_RTSEMEVENT;
pThis->pXmitThread = NULL;
pThis->hXmitEvt = NIL_SUPSEMEVENT;
pThis->pSupDrvSession = PDMDrvHlpGetSupDrvSession(pDrvIns);
pThis->hSgCache = NIL_RTMEMCACHE;
pThis->enmRecvState = RECVSTATE_SUSPENDED;
pThis->fActivateEarlyDeactivateLate = false;
/* IBase* */
pDrvIns->IBase.pfnQueryInterface = drvR3DedicatedNicIBase_QueryInterface;
pThis->IBaseR0.pfnQueryInterface = drvR3DedicatedNicIBaseR0_QueryInterface;
pThis->IBaseRC.pfnQueryInterface = drvR3DedicatedNicIBaseRC_QueryInterface;
/* INetworkUp */
pThis->INetworkUpR3.pfnBeginXmit = drvDedicatedNic_BeginXmit;
pThis->INetworkUpR3.pfnAllocBuf = drvDedicatedNic_AllocBuf;
pThis->INetworkUpR3.pfnFreeBuf = drvDedicatedNic_FreeBuf;
pThis->INetworkUpR3.pfnSendBuf = drvDedicatedNic_SendBuf;
pThis->INetworkUpR3.pfnEndXmit = drvDedicatedNic_EndXmit;
pThis->INetworkUpR3.pfnSetPromiscuousMode = drvDedicatedNic_SetPromiscuousMode;
pThis->INetworkUpR3.pfnNotifyLinkChanged = drvR3DedicatedNicUp_NotifyLinkChanged;
#endif
/** @todo
* Need to create a generic way of calling into the ring-0 side of the driver so
* we can initialize the thing as well as send and receive. Hmm ... the
* sending could be done more efficiently from a ring-0 kernel thread actually
* (saves context switching and 1-2 copy operations). Ditto for receive, except
* we need to tie the thread to the process or we cannot access the guest ram so
* easily.
*/
return VERR_NOT_IMPLEMENTED;
}
/**
* Internal networking transport driver registration record.
*/
const PDMDRVREG g_DrvDedicatedNic =
{
/* u32Version */
PDM_DRVREG_VERSION,
/* szName */
"DedicatedNic",
/* szRCMod */
"",
/* szR0Mod */
"VBoxDDR0.r0",
/* pszDescription */
"Dedicated (V)NIC Driver",
/* fFlags */
PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT | PDM_DRVREG_FLAGS_R0,
/* fClass. */
PDM_DRVREG_CLASS_NETWORK,
/* cMaxInstances */
~0U,
/* cbInstance */
sizeof(DRVDEDICATEDNIC),
/* pfnConstruct */
drvR3DedicatedNicConstruct,
/* pfnDestruct */
drvR3DedicatedNicDestruct,
/* pfnRelocate */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
drvR3DedicatedNicPowerOn,
/* pfnReset */
NULL,
/* pfnSuspend */
drvR3DedicatedNicSuspend,
/* pfnResume */
drvR3DedicatedNicResume,
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnPowerOff */
drvR3DedicatedNicPowerOff,
/* pfnSoftReset */
NULL,
/* u32EndVersion */
PDM_DRVREG_VERSION
};
#endif /* IN_RING3 */