VBoxGuestPnP.cpp revision 4323bf3414eb81836a7e6211b5ced9f6e65d02f3
/** @file
*
* VBoxGuest -- VirtualBox Win32 guest support driver PnP code
*
* Copyright (C) 2006-2007 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.
*/
// enable backdoor logging
//#define LOG_ENABLED
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "VBoxGuestPnP.h"
#include "Helper.h"
#include <VBox/err.h>
#include <VBox/VBoxGuestLib.h>
#include "../../common/VBoxGuest/VBoxHelper.h"
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
extern "C"
{
static NTSTATUS sendIrpSynchronously(PDEVICE_OBJECT pDevObj, PIRP pIrp, BOOLEAN fStrict);
static NTSTATUS pnpIrpComplete(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT event);
static VOID showDeviceResources(PCM_PARTIAL_RESOURCE_LIST pResourceList);
}
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, VBoxGuestPnP)
#pragma alloc_text (PAGE, VBoxGuestPower)
#pragma alloc_text (PAGE, sendIrpSynchronously)
#pragma alloc_text (PAGE, showDeviceResources)
#endif
/* reenable logging, this was #undef'ed on iprt/log.h for RING0 */
#define LOG_ENABLED
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
/**
* Irp completion routine for PnP Irps we send.
*
* @param pDevObj Device object.
* @param pIrp Request packet.
* @param event Semaphore.
* @return NT status code
*/
static NTSTATUS pnpIrpComplete(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT event)
{
KeSetEvent(event, 0, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
/**
* Helper to send a PnP IRP and wait until it's done.
*
* @param pDevObj Device object.
* @param pIrp Request packet.
* @param fStrict When set, returns an error if the IRP gives an error.
* @return NT status code
*/
static NTSTATUS sendIrpSynchronously(PDEVICE_OBJECT pDevObj, PIRP pIrp, BOOLEAN fStrict)
{
KEVENT event;
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
IoCopyCurrentIrpStackLocationToNext(pIrp);
IoSetCompletionRoutine(pIrp, (PIO_COMPLETION_ROUTINE)pnpIrpComplete, &event, TRUE, TRUE, TRUE);
NTSTATUS rc = IoCallDriver(pDevObj, pIrp);
if (rc == STATUS_PENDING)
{
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
rc = pIrp->IoStatus.Status;
}
if (!fStrict
&& (rc == STATUS_NOT_SUPPORTED || rc == STATUS_INVALID_DEVICE_REQUEST))
{
rc = STATUS_SUCCESS;
}
dprintf(("VBoxGuest::sendIrpSynchronously: returning 0x%x\n", rc));
return rc;
}
/**
* PnP Request handler.
*
* @param pDevObj Device object.
* @param pIrp Request packet.
*/
NTSTATUS VBoxGuestPnP(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pDevObj->DeviceExtension;
PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
NTSTATUS rc = STATUS_SUCCESS;
#ifdef LOG_ENABLED
static char* fcnname[] =
{
"IRP_MN_START_DEVICE",
"IRP_MN_QUERY_REMOVE_DEVICE",
"IRP_MN_REMOVE_DEVICE",
"IRP_MN_CANCEL_REMOVE_DEVICE",
"IRP_MN_STOP_DEVICE",
"IRP_MN_QUERY_STOP_DEVICE",
"IRP_MN_CANCEL_STOP_DEVICE",
"IRP_MN_QUERY_DEVICE_RELATIONS",
"IRP_MN_QUERY_INTERFACE",
"IRP_MN_QUERY_CAPABILITIES",
"IRP_MN_QUERY_RESOURCES",
"IRP_MN_QUERY_RESOURCE_REQUIREMENTS",
"IRP_MN_QUERY_DEVICE_TEXT",
"IRP_MN_FILTER_RESOURCE_REQUIREMENTS",
"",
"IRP_MN_READ_CONFIG",
"IRP_MN_WRITE_CONFIG",
"IRP_MN_EJECT",
"IRP_MN_SET_LOCK",
"IRP_MN_QUERY_ID",
"IRP_MN_QUERY_PNP_DEVICE_STATE",
"IRP_MN_QUERY_BUS_INFORMATION",
"IRP_MN_DEVICE_USAGE_NOTIFICATION",
"IRP_MN_SURPRISE_REMOVAL",
};
dprintf(("VBoxGuest::VBoxGuestPnp: MinorFunction: %s\n", pStack->MinorFunction < (sizeof(fcnname)/sizeof(fcnname[0])) ? fcnname[pStack->MinorFunction] : "unknown"));
#endif
switch (pStack->MinorFunction)
{
case IRP_MN_START_DEVICE:
{
rc = sendIrpSynchronously(pDevExt->nextLowerDriver, pIrp, TRUE);
if (NT_SUCCESS(rc) && NT_SUCCESS(pIrp->IoStatus.Status))
{
dprintf(("VBoxGuest::START_DEVICE: pStack->Parameters.StartDevice.AllocatedResources = %p\n", pStack->Parameters.StartDevice.AllocatedResources));
if (!pStack->Parameters.StartDevice.AllocatedResources)
{
dprintf(("VBoxGuest::START_DEVICE: no resources, pDevExt = %p, nextLowerDriver = %p!!!\n", pDevExt, pDevExt? pDevExt->nextLowerDriver: NULL));
rc = STATUS_UNSUCCESSFUL;
}
else
{
showDeviceResources(&pStack->Parameters.StartDevice.AllocatedResources->List[0].PartialResourceList);
VBoxScanPCIResourceList(pStack->Parameters.StartDevice.AllocatedResourcesTranslated,
pDevExt);
/** @todo cleanup and merging codepath with NT */
int rcVBox;
rcVBox = VbglInit (pDevExt->startPortAddress, pDevExt->pVMMDevMemory);
if (!RT_SUCCESS(rcVBox))
{
dprintf(("VBoxGuest::START_DEVICE: VbglInit failed. rcVBox = %d\n", rcVBox));
rc = STATUS_UNSUCCESSFUL;
}
if (NT_SUCCESS(rc))
{
rcVBox = VbglGRAlloc ((VMMDevRequestHeader **)&pDevExt->irqAckEvents, sizeof (VMMDevEvents), VMMDevReq_AcknowledgeEvents);
if (!RT_SUCCESS(rc))
{
dprintf(("VBoxGuest::START_DEVICE: VbglAlloc failed for irqAckEvents. rcVBox = %d\n", rcVBox));
rc = STATUS_UNSUCCESSFUL;
}
}
if (NT_SUCCESS(rc))
{
rcVBox = VbglGRAlloc ((VMMDevRequestHeader **)&pDevExt->powerStateRequest, sizeof (VMMDevPowerStateRequest), VMMDevReq_SetPowerStatus);
if (!RT_SUCCESS(rc))
{
dprintf(("VBoxGuest::START_DEVICE: VbglAlloc failed for powerStateRequest. rcVBox = %d\n", rcVBox));
rc = STATUS_UNSUCCESSFUL;
}
}
if (NT_SUCCESS(rc))
{
// Map physical address of VMMDev memory
rc = hlpVBoxMapVMMDevMemory(pDevExt);
if (!NT_SUCCESS(rc))
{
dprintf(("VBoxGuest::START_DEVICE: can't map physical memory, rc = %d\n", rc));
}
}
if (NT_SUCCESS(rc))
{
// register DPC and ISR
dprintf(("VBoxGuest::VBoxGuestPnp: initializing DPC...\n"));
IoInitializeDpcRequest(pDevExt->deviceObject, VBoxGuestDpcHandler);
rc = IoConnectInterrupt(&pDevExt->interruptObject, // out: interrupt object
(PKSERVICE_ROUTINE)VBoxGuestIsrHandler, // ISR
pDevExt, // context
NULL, // optional spinlock
pDevExt->interruptVector, // interrupt vector
(KIRQL)pDevExt->interruptLevel, // interrupt level
(KIRQL)pDevExt->interruptLevel, // interrupt level
pDevExt->interruptMode, // LevelSensitive or Latched
TRUE, // shareable interrupt
pDevExt->interruptAffinity, // CPU affinity
FALSE); // don't save FPU stack
if (!NT_SUCCESS(rc))
{
dprintf(("VBoxGuest::VBoxGuestPnp: could not connect interrupt: rc = 0x%x\n", rc));
}
}
}
}
/** @todo Don't mix up IPRT rc and NTSTATUS rc above! */
if (NT_SUCCESS(rc))
{
int vrc = VBoxReportGuestInfo(hlpVBoxWinVersionToOSType(winVersion));
if (RT_SUCCESS(vrc))
{
vrc = VBoxInitMemBalloon(pDevExt);
if (RT_SUCCESS(vrc))
{
vrc = VBoxReportGuestDriverStatus(true /* Driver is active */);
if (RT_FAILURE(vrc))
dprintf(("VBoxGuest::VBoxGuestPnp::IRP_MN_START_DEVICE: could not report guest driver status, vrc = %d\n", vrc));
}
else
dprintf(("VBoxGuest::VBoxGuestPnp::IRP_MN_START_DEVICE: could not init mem balloon, vrc = %d\n", vrc));
}
else
dprintf(("VBoxGuest::VBoxGuestPnp::IRP_MN_START_DEVICE: could not report guest information to host, vrc = %d\n", vrc));
if (RT_FAILURE(vrc))
rc = STATUS_UNSUCCESSFUL;
}
if (NT_SUCCESS(rc))
{
createThreads(pDevExt);
// initialize the event notification semaphore
KeInitializeEvent(&pDevExt->keventNotification, NotificationEvent, FALSE);
/* Preallocated constant timeout 250ms for HGCM async waiter. */
pDevExt->HGCMWaitTimeout.QuadPart = 250;
pDevExt->HGCMWaitTimeout.QuadPart *= -10000; /* relative in 100ns units */
// ready to rumble!
dprintf(("VBoxGuest::VBoxGuestPnp: device is ready!\n"));
pDevExt->devState = WORKING;
}
else
{
dprintf(("VBoxGuest::VBoxGuestPnp: error: rc = 0x%x\n", rc));
// need to unmap memory in case of errors
hlpVBoxUnmapVMMDevMemory (pDevExt);
}
pIrp->IoStatus.Status = rc;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
break;
}
case IRP_MN_QUERY_REMOVE_DEVICE:
{
#ifdef VBOX_REBOOT_ON_UNINSTALL
/* The device can not be removed without a reboot. */
if (pDevExt->devState == WORKING)
{
pDevExt->devState = PENDINGREMOVE;
}
pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
rc = STATUS_UNSUCCESSFUL;
dprintf(("VBoxGuest::VBoxGuestPnp: refuse with rc = %p\n", pIrp->IoStatus.Status));
#else
pIrp->IoStatus.Status = STATUS_SUCCESS;
if (pDevExt->devState == WORKING)
{
pDevExt->devState = PENDINGREMOVE;
}
IoSkipCurrentIrpStackLocation(pIrp);
rc = IoCallDriver(pDevExt->nextLowerDriver, pIrp);
#endif /* VBOX_REBOOT_ON_UNINSTALL */
break;
}
case IRP_MN_REMOVE_DEVICE:
{
/* @todo merge Remove and Stop, make a helper for common actions */
pIrp->IoStatus.Status = STATUS_SUCCESS;
unreserveHypervisorMemory(pDevExt);
if (pDevExt->workerThread)
{
dprintf(("VBoxGuest::VBoxGuestPnp: waiting for the worker thread to terminate...\n"));
pDevExt->stopThread = TRUE;
KeSetEvent(&pDevExt->workerThreadRequest, 0, FALSE);
KeWaitForSingleObject(pDevExt->workerThread,
Executive, KernelMode, FALSE, NULL);
dprintf(("VBoxGuest::VBoxGuestPnp: returned from KeWaitForSingleObject for worker thread\n"));
}
if (pDevExt->idleThread)
{
dprintf(("VBoxGuest::VBoxGuestPnp: waiting for the idle thread to terminate...\n"));
pDevExt->stopThread = TRUE;
KeWaitForSingleObject(pDevExt->idleThread,
Executive, KernelMode, FALSE, NULL);
dprintf(("VBoxGuest::VBoxGuestPnp: returned from KeWaitForSingleObject for idle thread\n"));
}
VbglTerminate ();
// according to MSDN we have to unmap previously mapped memory
hlpVBoxUnmapVMMDevMemory (pDevExt);
/* Free the memory balloon (if any) */
VBoxCleanupMemBalloon(pDevExt);
if (pDevExt->nextLowerDriver != NULL)
{
IoDetachDevice(pDevExt->nextLowerDriver);
}
#ifdef VBOX_WITH_HGCM
if (pDevExt->SessionSpinlock != NIL_RTSPINLOCK)
RTSpinlockDestroy(pDevExt->SessionSpinlock);
#endif
UNICODE_STRING win32Name;
RtlInitUnicodeString(&win32Name, VBOXGUEST_DEVICE_NAME_DOS);
IoDeleteSymbolicLink(&win32Name);
IoDeleteDevice(pDevObj);
pDevExt->devState = REMOVED;
IoSkipCurrentIrpStackLocation(pIrp);
rc = IoCallDriver(pDevExt->nextLowerDriver, pIrp);
break;
}
case IRP_MN_QUERY_STOP_DEVICE:
{
#ifdef VBOX_REBOOT_ON_UNINSTALL
dprintf(("VBoxGuest::VBoxGuestPnp: refuse\n"));
/* The device can not be stopped without a reboot. */
if (pDevExt->devState == WORKING)
{
pDevExt->devState = PENDINGSTOP;
}
pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
rc = STATUS_UNSUCCESSFUL;
#else
pIrp->IoStatus.Status = STATUS_SUCCESS;
if (pDevExt->devState == WORKING)
{
pDevExt->devState = PENDINGSTOP;
}
IoSkipCurrentIrpStackLocation(pIrp);
rc = IoCallDriver(pDevExt->nextLowerDriver, pIrp);
#endif /* VBOX_REBOOT_ON_UNINSTALL */
break;
}
case IRP_MN_STOP_DEVICE:
{
pIrp->IoStatus.Status = STATUS_SUCCESS;
if (pDevExt->devState == PENDINGSTOP)
{
VbglTerminate ();
// according to MSDN we have to unmap previously mapped memory
hlpVBoxUnmapVMMDevMemory (pDevExt);
pDevExt->devState = STOPPED;
dprintf(("VBoxGuest::VBoxGuestPnp: device has been disabled\n"));
} else
{
dprintf(("VBoxGuest::VBoxGuestPnp: devState not PENDINGSTOP but %d\n", pDevExt->devState));
}
IoSkipCurrentIrpStackLocation(pIrp);
rc = IoCallDriver(pDevExt->nextLowerDriver, pIrp);
break;
}
default:
{
IoSkipCurrentIrpStackLocation(pIrp);
rc = IoCallDriver(pDevExt->nextLowerDriver, pIrp);
}
}
return rc;
}
/**
* Debug helper to dump a device resource list.
*
* @param pResourceList list of device resources.
*/
static VOID showDeviceResources(PCM_PARTIAL_RESOURCE_LIST pResourceList)
{
#ifdef LOG_ENABLED
PCM_PARTIAL_RESOURCE_DESCRIPTOR resource = pResourceList->PartialDescriptors;
ULONG nres = pResourceList->Count;
ULONG i;
for (i = 0; i < nres; ++i, ++resource)
{
ULONG type = resource->Type;
static char* name[] =
{
"CmResourceTypeNull",
"CmResourceTypePort",
"CmResourceTypeInterrupt",
"CmResourceTypeMemory",
"CmResourceTypeDma",
"CmResourceTypeDeviceSpecific",
"CmResourceTypeBusNumber",
"CmResourceTypeDevicePrivate",
"CmResourceTypeAssignedResource",
"CmResourceTypeSubAllocateFrom",
};
dprintf(("VBoxGuest::showDeviceResources: type %s", type < (sizeof(name)/sizeof(name[0])) ? name[type] : "unknown"));
switch (type)
{
case CmResourceTypePort:
case CmResourceTypeMemory:
dprintf(("VBoxGuest::showDeviceResources: start %8X%8.8lX length %X\n",
resource->u.Port.Start.HighPart, resource->u.Port.Start.LowPart,
resource->u.Port.Length));
break;
case CmResourceTypeInterrupt:
dprintf(("VBoxGuest::showDeviceResources: level %X, vector %X, affinity %X\n",
resource->u.Interrupt.Level, resource->u.Interrupt.Vector,
resource->u.Interrupt.Affinity));
break;
case CmResourceTypeDma:
dprintf(("VBoxGuest::showDeviceResources: channel %d, port %X\n",
resource->u.Dma.Channel, resource->u.Dma.Port));
break;
default:
dprintf(("\n"));
break;
}
}
#endif
}
/**
* Handle the power completion event.
*
* @returns NT status code
* @param devObj targetted device object
* @param irp IO request packet
* @param context context value passed to IoSetCompletionRoutine in VBoxGuestPower
*/
NTSTATUS VBoxGuestPowerComplete(IN PDEVICE_OBJECT devObj,
IN PIRP irp, IN PVOID context)
{
PIO_STACK_LOCATION irpSp;
PVBOXGUESTDEVEXT devExt = (PVBOXGUESTDEVEXT)context;
ASSERT(devExt);
ASSERT(devExt->signature == DEVICE_EXTENSION_SIGNATURE);
irpSp = IoGetCurrentIrpStackLocation(irp);
ASSERT(irpSp->MajorFunction == IRP_MJ_POWER);
if (NT_SUCCESS(irp->IoStatus.Status))
{
switch (irpSp->MinorFunction)
{
case IRP_MN_SET_POWER:
switch (irpSp->Parameters.Power.Type)
{
case DevicePowerState:
switch (irpSp->Parameters.Power.State.DeviceState)
{
case PowerDeviceD0:
break;
}
break;
}
break;
}
}
return STATUS_SUCCESS;
}
/**
* Handle the Power requests.
*
* @returns NT status code
* @param pDevObj device object
* @param pIrp IRP
*/
NTSTATUS VBoxGuestPower(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pDevObj->DeviceExtension;
POWER_STATE_TYPE powerType;
POWER_STATE powerState;
POWER_ACTION powerAction;
dprintf(("VBoxGuest::VBoxGuestPower\n"));
powerType = pStack->Parameters.Power.Type;
powerAction = pStack->Parameters.Power.ShutdownType;
powerState = pStack->Parameters.Power.State;
switch (pStack->MinorFunction)
{
case IRP_MN_SET_POWER:
{
dprintf(("VBoxGuest::VBoxGuestPower: IRP_MN_SET_POWER\n"));
switch (powerType)
{
case SystemPowerState:
{
dprintf(("VBoxGuest::VBoxGuestPower: SystemPowerState\n"));
switch (powerAction)
{
case PowerActionShutdownReset:
{
dprintf(("VBoxGuest::VBoxGuestPower: power action reset!\n"));
/* tell the VMM that we no longer support mouse pointer integration */
VMMDevReqMouseStatus *req = NULL;
int rc = VbglGRAlloc ((VMMDevRequestHeader **)&req, sizeof (VMMDevReqMouseStatus), VMMDevReq_SetMouseStatus);
if (RT_SUCCESS(rc))
{
req->mouseFeatures = 0;
req->pointerXPos = 0;
req->pointerYPos = 0;
rc = VbglGRPerform (&req->header);
if (RT_FAILURE(rc))
{
dprintf(("VBoxGuest::PowerStateRequest: error communicating new power status to VMMDev. "
"rc = %Rrc\n", rc));
}
VbglGRFree (&req->header);
}
break;
}
case PowerActionShutdown:
case PowerActionShutdownOff:
{
dprintf(("VBoxGuest::VBoxGuestPower: power action shutdown!\n"));
if (powerState.SystemState >= PowerSystemShutdown)
{
dprintf(("VBoxGuest::VBoxGuestPower: Telling the VMMDev to close the VM...\n"));
if (pDevExt && pDevExt->powerStateRequest)
{
VMMDevPowerStateRequest *req = pDevExt->powerStateRequest;
req->header.requestType = VMMDevReq_SetPowerStatus;
req->powerState = VMMDevPowerState_PowerOff;
int rc = VbglGRPerform (&req->header);
if (RT_FAILURE(rc))
{
dprintf(("VBoxGuest::PowerStateRequest: error communicating new power status to VMMDev. "
"rc = %Rrc\n", rc));
}
}
}
break;
}
}
break;
}
default:
break;
}
break;
}
default:
break;
}
/*
* Whether we are completing or relaying this power IRP,
* we must call PoStartNextPowerIrp.
*/
PoStartNextPowerIrp(pIrp);
/*
* Send the IRP down the driver stack,
* using PoCallDriver (not IoCallDriver, as for non-power irps).
*/
IoCopyCurrentIrpStackLocationToNext(pIrp);
IoSetCompletionRoutine(pIrp,
VBoxGuestPowerComplete,
(PVOID)pDevExt,
TRUE,
TRUE,
TRUE);
return PoCallDriver(pDevExt->nextLowerDriver, pIrp);
}