1146 lines
30 KiB
C
1146 lines
30 KiB
C
|
/*
|
||
|
* vcdisp.c
|
||
|
*
|
||
|
*
|
||
|
* 32-bit Video Capture driver
|
||
|
* kernel-mode support library - i/o dispatch support
|
||
|
*
|
||
|
* Geraint Davies, Feb 93
|
||
|
*/
|
||
|
|
||
|
#include <vckernel.h>
|
||
|
#include "vckpriv.h"
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdarg.h>
|
||
|
|
||
|
|
||
|
#include "profile.h"
|
||
|
#if DBG
|
||
|
profiling looptime;
|
||
|
#endif
|
||
|
|
||
|
/* --- main i/o dispatch routine -------------------------------------*/
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Synchronization
|
||
|
*
|
||
|
* Contention between multiple simultaneous calls to the driver is
|
||
|
* resolved by holding a Mutex object for the entire VC_Dispatch function,
|
||
|
* thus ensuring that only one request is being executed at once.
|
||
|
*
|
||
|
* Contention between the interrupt routine and other code is resolved
|
||
|
* by calls to KeSynchronizeExecution. This is called (through a vckernel
|
||
|
* wrapper function VC_Synchronize) by the hardware-specific code where
|
||
|
* appropriate (eg during accessing the device registers).
|
||
|
*
|
||
|
* Contention between the DPC (captureservice) function and the passive-level
|
||
|
* requests is resolved by use of the cancel spinlock.
|
||
|
* We cannot hold any spinlock for the entire dpc and the entire request since
|
||
|
* (a) some requests have exception handlers to protect against bad
|
||
|
* user data - exceptions within a spinlock will bring the
|
||
|
* entire system down, and
|
||
|
* (b) the dpc routine can be lengthy, longer than the recommended
|
||
|
* 25 usec maximum.
|
||
|
*
|
||
|
* So protection is limited to accesses to the IRP queue for add-buffer and
|
||
|
* wait-error requests.
|
||
|
*
|
||
|
* We need to interlock accesses to the queue with the cancel spinlock in order
|
||
|
* to make sure that we do not miss a cancel (if the cancel function was called
|
||
|
* after we had set our cancel routine and checked the flag, but before
|
||
|
* adding it to the queue, we would miss the cancel entirely).
|
||
|
* Since we need to interlock access with the cancel spinlock, we do not
|
||
|
* need another device spinlock. We do provide a device-spinlock wrapper
|
||
|
* function (VC_SynchronizeDPC) in case the h/w-specific code needs more
|
||
|
* interlocking.
|
||
|
*/
|
||
|
|
||
|
NTSTATUS
|
||
|
VC_Dispatch(
|
||
|
IN PDEVICE_OBJECT pDeviceObject,
|
||
|
IN PIRP pIrp
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description
|
||
|
|
||
|
Handle IRP requests and distribute them to hardware
|
||
|
specific functions.
|
||
|
|
||
|
Arguments
|
||
|
|
||
|
pDeviceObject - object for target device
|
||
|
pIrp - request packet to be handled
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
|
||
|
PIO_STACK_LOCATION pIoStack;
|
||
|
NTSTATUS Status;
|
||
|
PDEVICE_INFO pDevInfo;
|
||
|
ULONG IoCode;
|
||
|
|
||
|
|
||
|
|
||
|
/* get the device info in the extension */
|
||
|
pDevInfo = (PDEVICE_INFO) pDeviceObject->DeviceExtension;
|
||
|
|
||
|
|
||
|
/* enter device mutex to ensure one request at a time */
|
||
|
KeWaitForSingleObject(&pDevInfo->Mutex,
|
||
|
Executive,
|
||
|
KernelMode,
|
||
|
FALSE,
|
||
|
NULL);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
pIoStack = IoGetCurrentIrpStackLocation(pIrp);
|
||
|
pIrp->IoStatus.Information = 0;
|
||
|
|
||
|
switch(pIoStack->MajorFunction) {
|
||
|
|
||
|
case IRP_MJ_CREATE:
|
||
|
if (pDevInfo->DeviceInUse++ > 0) {
|
||
|
/*
|
||
|
* permit only one open at once
|
||
|
*/
|
||
|
pDevInfo->DeviceInUse--;
|
||
|
Status = STATUS_DEVICE_BUSY;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
Status = STATUS_SUCCESS;
|
||
|
|
||
|
if (pDevInfo->Callback.DeviceOpenFunc != NULL) {
|
||
|
|
||
|
/* release the mutex around open calls */
|
||
|
KeReleaseMutex(&pDevInfo->Mutex, FALSE);
|
||
|
|
||
|
if (!pDevInfo->Callback.DeviceOpenFunc(pDevInfo)) {
|
||
|
Status = STATUS_DRIVER_INTERNAL_ERROR;
|
||
|
}
|
||
|
|
||
|
KeWaitForSingleObject(&pDevInfo->Mutex,
|
||
|
Executive,
|
||
|
KernelMode,
|
||
|
FALSE,
|
||
|
NULL);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IRP_MJ_CLOSE:
|
||
|
Status = STATUS_SUCCESS;
|
||
|
if (--pDevInfo->DeviceInUse > 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* force state to idle on last close */
|
||
|
pDevInfo->State = State_Idle;
|
||
|
|
||
|
if (pDevInfo->Callback.DeviceCloseFunc != NULL) {
|
||
|
if (!pDevInfo->Callback.DeviceCloseFunc(pDevInfo)) {
|
||
|
Status = STATUS_DRIVER_INTERNAL_ERROR;
|
||
|
pDevInfo->DeviceInUse++;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// this is the last close - so clean up the irp q and
|
||
|
// free any allocated system buffer
|
||
|
VC_Close(pDevInfo);
|
||
|
|
||
|
break;
|
||
|
|
||
|
case IRP_MJ_DEVICE_CONTROL:
|
||
|
|
||
|
/*
|
||
|
* in all cases:
|
||
|
* -check that the buffer size is big enough to hold the
|
||
|
* input or output parameters.
|
||
|
*
|
||
|
* -check that the h/w-specific code has installed a
|
||
|
* handler for this function.
|
||
|
*/
|
||
|
|
||
|
IoCode = pIoStack->Parameters.DeviceIoControl.IoControlCode;
|
||
|
|
||
|
switch(IoCode) {
|
||
|
|
||
|
case IOCTL_VIDC_CONFIG_FORMAT:
|
||
|
/* configure the destination bitmap format */
|
||
|
if (pDevInfo->Callback.ConfigFormatFunc != NULL) {
|
||
|
PCONFIG_INFO pConfig;
|
||
|
int length;
|
||
|
|
||
|
/* we know nothing about the format of the buffer except
|
||
|
* that the first ULONG contains its total size
|
||
|
*/
|
||
|
length = pIoStack->Parameters.DeviceIoControl.InputBufferLength;
|
||
|
pConfig = (PCONFIG_INFO) pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
if ((length < sizeof(ULONG)) || (length < (int)pConfig->ulSize)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (pDevInfo->Callback.ConfigFormatFunc(pDevInfo, pConfig)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_CONFIG_SOURCE:
|
||
|
/* configure the video source */
|
||
|
if (pDevInfo->Callback.ConfigSourceFunc != NULL) {
|
||
|
PCONFIG_INFO pConfig;
|
||
|
int length;
|
||
|
|
||
|
/* we know nothing about the format of the buffer except
|
||
|
* that the first ULONG contains its total size
|
||
|
*/
|
||
|
length = pIoStack->Parameters.DeviceIoControl.InputBufferLength;
|
||
|
pConfig = (PCONFIG_INFO) pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
if ((length < sizeof(ULONG)) || (length < (int)pConfig->ulSize)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (pDevInfo->Callback.ConfigSourceFunc(pDevInfo, pConfig)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_CONFIG_DISPLAY:
|
||
|
/* configure the destination bitmap format */
|
||
|
if (pDevInfo->Callback.ConfigDisplayFunc != NULL) {
|
||
|
PCONFIG_INFO pConfig;
|
||
|
int length;
|
||
|
|
||
|
/* we know nothing about the format of the buffer except
|
||
|
* that the first ULONG contains its total size
|
||
|
*/
|
||
|
length = pIoStack->Parameters.DeviceIoControl.InputBufferLength;
|
||
|
pConfig = (PCONFIG_INFO) pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
if ((length < sizeof(ULONG)) || (length < (int)pConfig->ulSize)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (pDevInfo->Callback.ConfigDisplayFunc(pDevInfo, pConfig)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_OVERLAY_ON:
|
||
|
case IOCTL_VIDC_OVERLAY_OFF:
|
||
|
/*
|
||
|
* enable or disable overlay. Enabling overlay
|
||
|
* will ensure that the overlay appears as keyed.
|
||
|
* This assumes that keying (via colour and/or rectangles
|
||
|
* has already been done before enabling overlay.
|
||
|
*/
|
||
|
|
||
|
if (pDevInfo->Callback.OverlayFunc != NULL) {
|
||
|
if (pDevInfo->Callback.OverlayFunc(pDevInfo,
|
||
|
IoCode == IOCTL_VIDC_OVERLAY_ON ? TRUE : FALSE)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_CAPTURE_ON:
|
||
|
case IOCTL_VIDC_CAPTURE_OFF:
|
||
|
/*
|
||
|
* enable/disable video capture.
|
||
|
*
|
||
|
* the capture card digitizes the video stream
|
||
|
* into a video buffer. This buffer may then be
|
||
|
* displayed overlaid onto the VGA display, or copied
|
||
|
* into a buffer and stored.
|
||
|
*
|
||
|
* disabling capture freezes the frame buffer contents as
|
||
|
* they are. Thus if overlay is enabled, calling CAPTURE_OFF
|
||
|
* will have the effect of freezing the display.
|
||
|
*
|
||
|
* when getting data from the frame buffer, capture must
|
||
|
* be off for the cpu to access the buffer. Thus to capture
|
||
|
* a frame, you enable capture, wait for the frame sync interrupt,
|
||
|
* disable capture again and the copy the data.
|
||
|
*/
|
||
|
|
||
|
if (pDevInfo->Callback.CaptureFunc != NULL) {
|
||
|
if (pDevInfo->Callback.CaptureFunc(pDevInfo,
|
||
|
IoCode == IOCTL_VIDC_CAPTURE_ON ? TRUE : FALSE)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_OVERLAY_MODE:
|
||
|
/*
|
||
|
* find out from the driver the overlay keying method.
|
||
|
*
|
||
|
* This may include a colour - which can be rgb or palette index.
|
||
|
* and it may be one or more rectangles.
|
||
|
* The most common case is both a key colour and a single
|
||
|
* rectangle - overlay will appear within the rectangle wherever
|
||
|
* the key colour appears in the vga display.
|
||
|
* the returned flags also indicate if the key colour is
|
||
|
* settable by the caller. RGB key colours are in general not.
|
||
|
*/
|
||
|
if (pDevInfo->Callback.GetOverlayModeFunc != NULL) {
|
||
|
ULONG ulMode;
|
||
|
|
||
|
if (pIoStack->Parameters.DeviceIoControl.OutputBufferLength
|
||
|
< sizeof(ULONG)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ulMode = pDevInfo->Callback.GetOverlayModeFunc(pDevInfo);
|
||
|
*(DWORD *)pIrp->AssociatedIrp.SystemBuffer = ulMode;
|
||
|
pIrp->IoStatus.Information = sizeof(ULONG);
|
||
|
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_SET_KEY_RGB:
|
||
|
|
||
|
/*
|
||
|
* set the overlay key colour as an RGB colour.
|
||
|
*/
|
||
|
if (pDevInfo->Callback.SetKeyRGBFunc != NULL) {
|
||
|
PRGBQUAD pRGB;
|
||
|
|
||
|
if (pIoStack->Parameters.DeviceIoControl.InputBufferLength
|
||
|
< sizeof(RGBQUAD)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pRGB = (PRGBQUAD)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
if (pDevInfo->Callback.SetKeyRGBFunc(pDevInfo, pRGB)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_SET_KEY_PALIDX:
|
||
|
/*
|
||
|
* set the overlay key colour as a VGA palette index.
|
||
|
*/
|
||
|
if (pDevInfo->Callback.SetKeyPalIdxFunc != NULL) {
|
||
|
ULONG ulPal;
|
||
|
|
||
|
if (pIoStack->Parameters.DeviceIoControl.InputBufferLength
|
||
|
< sizeof(ULONG)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ulPal = *(PULONG)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
if (pDevInfo->Callback.SetKeyPalIdxFunc(pDevInfo, ulPal)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_GET_KEY_COLOUR:
|
||
|
|
||
|
/*
|
||
|
* get the overlay key colour in whatever mode the
|
||
|
* getoverlaymode said was supported.
|
||
|
*/
|
||
|
if (pDevInfo->Callback.GetKeyColourFunc != NULL) {
|
||
|
PULONG pul;
|
||
|
|
||
|
if (pIoStack->Parameters.DeviceIoControl.OutputBufferLength
|
||
|
< sizeof(ULONG)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pul = (PULONG)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
*pul = pDevInfo->Callback.GetKeyColourFunc(pDevInfo);
|
||
|
pIrp->IoStatus.Information = sizeof(ULONG);
|
||
|
Status = STATUS_SUCCESS;
|
||
|
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_OVERLAY_RECTS:
|
||
|
/*
|
||
|
* set the overlay key region - a region in device co-ordinates
|
||
|
* in which the overlay should appear.
|
||
|
* The caller should establish beforehand whether he can
|
||
|
* specify a complex region or whether one single bounding
|
||
|
* rectangle only is acceptable.
|
||
|
*
|
||
|
* if more than one rectangle is given, the first is assumed to be
|
||
|
* the bounding rectangle, and the rest define the complex region
|
||
|
* to which overlay is to be clipped.
|
||
|
*
|
||
|
*/
|
||
|
if (pDevInfo->Callback.SetOverlayRectsFunc != NULL) {
|
||
|
POVERLAY_RECTS pOR;
|
||
|
|
||
|
if (pIoStack->Parameters.DeviceIoControl.InputBufferLength
|
||
|
< sizeof(OVERLAY_RECTS)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
pOR = (POVERLAY_RECTS)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
/* the pOR struct can contain any number of rectangles.
|
||
|
* check if more than 1 is claimed, that the structure is
|
||
|
* big enough to hold them (the size OVERLAY_RECTS includes
|
||
|
* 1 rect).
|
||
|
*/
|
||
|
if (pOR->ulCount > 1) {
|
||
|
if (pIoStack->Parameters.DeviceIoControl.InputBufferLength
|
||
|
< (sizeof(OVERLAY_RECTS) + (sizeof(RECT) * (pOR->ulCount-1)))) {
|
||
|
|
||
|
dprintf1(("buffer too small for rect count"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (pDevInfo->Callback.SetOverlayRectsFunc(pDevInfo, pOR)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_OVERLAY_OFFSET:
|
||
|
/*
|
||
|
* set the overlay offset - this specifies the offset of the
|
||
|
* source co-ordinate that should appear in pixel (0,0) of
|
||
|
* the overlay rectangle.
|
||
|
*/
|
||
|
if (pDevInfo->Callback.SetOverlayOffsetFunc != NULL) {
|
||
|
PRECT prc;
|
||
|
|
||
|
if (pIoStack->Parameters.DeviceIoControl.InputBufferLength
|
||
|
< sizeof(RECT)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
prc = (PRECT)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
|
||
|
if (pDevInfo->Callback.SetOverlayOffsetFunc(pDevInfo, prc)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_DRAW_FRAME:
|
||
|
/*
|
||
|
* Draw a frame into the hardware frame buffer for
|
||
|
* playback (optional!).
|
||
|
*/
|
||
|
if (pDevInfo->Callback.DrawFrameFunc != NULL) {
|
||
|
PDRAWBUFFER pDraw;
|
||
|
|
||
|
if (pIoStack->Parameters.DeviceIoControl.InputBufferLength
|
||
|
< sizeof(DRAWBUFFER)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
pDraw = (PDRAWBUFFER)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
|
||
|
if (pDevInfo->Callback.DrawFrameFunc(pDevInfo, pDraw)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DRIVER_INTERNAL_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_ADD_BUFFER:
|
||
|
/* add a output buffer to the queue into which a frame
|
||
|
* of video data should be copied during capture streaming.
|
||
|
*
|
||
|
* This irp will only be completed when the data has been
|
||
|
* copied (in the dpc routine).
|
||
|
*
|
||
|
* Since we need to access this buffer at interrupt time,
|
||
|
* we need to build an MDL and probe and lock down the memory
|
||
|
* here. We are passed a pointer to the CAPTUREBUFFER
|
||
|
* structure, not the data buffer itself. This is because we
|
||
|
* need to return the timestamp of the capture as well as the
|
||
|
* data. So the data that the i/o manager has buffered is the
|
||
|
* CAPTUREBUFFER buffer - we need to pick the data
|
||
|
* pointer out of this and build out own MDL etc here.
|
||
|
*
|
||
|
* As frame buffers can be large, and they are locked down, this
|
||
|
* means we should not queue too many of these requests at once.
|
||
|
* We leave it up to the user-level dll to pass on only a few
|
||
|
* add-buffer requests at once.
|
||
|
*/
|
||
|
{
|
||
|
PCAPTUREBUFFER pCapBuf;
|
||
|
PMDL pMdl;
|
||
|
|
||
|
|
||
|
if (pIoStack->Parameters.DeviceIoControl.InputBufferLength
|
||
|
< sizeof(CAPTUREBUFFER)) {
|
||
|
dprintf1(("buffer header too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
/* we copy this out too - check both ways */
|
||
|
if (pIoStack->Parameters.DeviceIoControl.OutputBufferLength
|
||
|
< sizeof(CAPTUREBUFFER)) {
|
||
|
dprintf1(("buffer header too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pCapBuf = (PCAPTUREBUFFER)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
/*
|
||
|
* check that the data buffer pointed to by this
|
||
|
* buffer header is big enough for the image size
|
||
|
* reported by the h/w dependent code
|
||
|
*/
|
||
|
if ((int)pCapBuf->BufferLength < pDevInfo->ImageSize) {
|
||
|
dprintf1(("data buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
Status = STATUS_PENDING;
|
||
|
|
||
|
|
||
|
/* build a memory descriptor for this virtual address */
|
||
|
pMdl = IoAllocateMdl(pCapBuf->lpData,
|
||
|
pCapBuf->BufferLength,
|
||
|
FALSE, //not secondary
|
||
|
TRUE, //charge-quota (??)
|
||
|
pIrp);
|
||
|
if (pMdl == NULL) {
|
||
|
dprintf(("failed to alloc mdl"));
|
||
|
Status = STATUS_NO_MEMORY;
|
||
|
} else {
|
||
|
|
||
|
try {
|
||
|
MmProbeAndLockPages(pIrp->MdlAddress,
|
||
|
KernelMode,
|
||
|
IoWriteAccess);
|
||
|
|
||
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
dprintf(("add-buffer: failed to lock pages"));
|
||
|
/*
|
||
|
* at this point we need to remove the Mdl
|
||
|
* or the io system will trap when trying to
|
||
|
* unlock the pages
|
||
|
*/
|
||
|
IoFreeMdl(pMdl);
|
||
|
pIrp->MdlAddress = NULL;
|
||
|
Status = STATUS_ACCESS_VIOLATION;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* if there was no error, add to the queue
|
||
|
*/
|
||
|
if (Status == STATUS_PENDING) {
|
||
|
|
||
|
/* interlocked add to the cancellable queue. returns
|
||
|
* false if already cancelled
|
||
|
*/
|
||
|
if (!VC_QueueRequest(pIrp, &pDevInfo->BufferHead, VC_Cancel)) {
|
||
|
|
||
|
Status = STATUS_CANCELLED;
|
||
|
|
||
|
/* free the pages and mdl we allocated */
|
||
|
//MmUnlockPages(pIrp->MdlAddress);
|
||
|
//IoFreeMdl(pIrp->MdlAddress);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
case IOCTL_VIDC_CAP_TO_SYSBUF:
|
||
|
/*
|
||
|
* if a frame-buffer is too big to be page-locked, the user
|
||
|
* will issue this request to capture to a system buffer, and
|
||
|
* then a series of partial-capture requests to copy out of the
|
||
|
* system buffer into the user's buffer.
|
||
|
*
|
||
|
* This request will not complete until the frame has been
|
||
|
* captured.
|
||
|
*
|
||
|
* it will fail if we cannot allocate the memory, or if the
|
||
|
* buffer is already busy (CAP_TO_SYSBUF has been issued and
|
||
|
* FREE_SYSBUF hasnt). Also fails if the h/w driver has not
|
||
|
* set image size
|
||
|
*
|
||
|
* We queue the IRP to the same queue as the add-buffer requests,
|
||
|
* and complete at the same time in the dpc.
|
||
|
*
|
||
|
* no args.
|
||
|
*/
|
||
|
|
||
|
if ((pDevInfo->ImageSize <= 0) || pDevInfo->SysBufInUse) {
|
||
|
Status = STATUS_DRIVER_INTERNAL_ERROR;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (pDevInfo->pSystemBuffer == NULL) {
|
||
|
pDevInfo->pSystemBuffer = ExAllocatePool(NonPagedPool, pDevInfo->ImageSize);
|
||
|
if (pDevInfo->pSystemBuffer == NULL) {
|
||
|
Status = STATUS_NO_MEMORY;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Status = STATUS_PENDING;
|
||
|
|
||
|
/* interlocked add to the cancellable queue. returns
|
||
|
* false if already cancelled
|
||
|
*/
|
||
|
if (!VC_QueueRequest(pIrp, &pDevInfo->BufferHead, VC_Cancel)) {
|
||
|
|
||
|
Status = STATUS_CANCELLED;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
case IOCTL_VIDC_FREE_SYSBUF:
|
||
|
/*
|
||
|
* finished with the system-buffer capture requested by CAP_TO_SYSBUF.
|
||
|
* release the buffer - ie mark it available for
|
||
|
* further captures.
|
||
|
*/
|
||
|
if (pDevInfo->SysBufInUse) {
|
||
|
pDevInfo->SysBufInUse = 0;
|
||
|
}
|
||
|
Status = STATUS_SUCCESS;
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_PARTIAL_CAPTURE:
|
||
|
/*
|
||
|
* copy part or all of the data from the
|
||
|
* system buffer that has been captured, into the
|
||
|
* users buffer.
|
||
|
*
|
||
|
* don't need to pagelock the user's buffer, since we are at
|
||
|
* passive level.
|
||
|
*
|
||
|
* fails if buffer not marked in-use (ie if no data).
|
||
|
*/
|
||
|
{
|
||
|
PCAPTUREBUFFER pCapBuf;
|
||
|
|
||
|
if ((pDevInfo->pSystemBuffer == NULL) || (pDevInfo->SysBufInUse == 0)) {
|
||
|
Status = STATUS_DRIVER_INTERNAL_ERROR;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
if (pIoStack->Parameters.DeviceIoControl.InputBufferLength
|
||
|
< sizeof(CAPTUREBUFFER)) {
|
||
|
dprintf1(("buffer header too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
/* we copy this out too - check both ways */
|
||
|
if (pIoStack->Parameters.DeviceIoControl.OutputBufferLength
|
||
|
< sizeof(CAPTUREBUFFER)) {
|
||
|
dprintf1(("buffer header too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pCapBuf = (PCAPTUREBUFFER)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
/* check the window is valid within buffer, and that
|
||
|
* buffer is big enough.
|
||
|
*/
|
||
|
if ((pCapBuf->dwWindowOffset > pCapBuf->BufferLength) ||
|
||
|
(pCapBuf->dwWindowOffset+pCapBuf->dwWindowLength > pCapBuf->BufferLength)) {
|
||
|
Status = STATUS_INVALID_PARAMETER;
|
||
|
}
|
||
|
if ((int)pCapBuf->BufferLength < pDevInfo->ImageSize) {
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* always write timestamp and size for every partial */
|
||
|
pCapBuf->TimeCaptured = pDevInfo->SysBufTimeStamp;
|
||
|
pCapBuf->BytesUsed = pDevInfo->ImageSize;
|
||
|
|
||
|
Status = STATUS_SUCCESS;
|
||
|
|
||
|
try {
|
||
|
RtlCopyMemory(
|
||
|
pCapBuf->lpData + pCapBuf->dwWindowOffset,
|
||
|
pDevInfo->pSystemBuffer + pCapBuf->dwWindowOffset,
|
||
|
min(pDevInfo->ImageSize - pCapBuf->dwWindowOffset,
|
||
|
pCapBuf->dwWindowLength)
|
||
|
);
|
||
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
dprintf(("access violation in partial capture"));
|
||
|
Status = STATUS_ACCESS_VIOLATION;
|
||
|
}
|
||
|
|
||
|
if (Status == STATUS_SUCCESS) {
|
||
|
pIrp->IoStatus.Information = sizeof(CAPTUREBUFFER);
|
||
|
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
case IOCTL_VIDC_WAIT_ERROR:
|
||
|
/*
|
||
|
* this ioctl completes when we have frame-skips to report.
|
||
|
*/
|
||
|
|
||
|
/* check if it is big enough */
|
||
|
if (pIoStack->Parameters.DeviceIoControl.OutputBufferLength
|
||
|
< sizeof(ULONG)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* queue this request, completing it immediately if cancelled or
|
||
|
* there are already skips to report. Interlock access
|
||
|
* to the queue and to nSkipCount using the cancel spinlock
|
||
|
*/
|
||
|
|
||
|
Status = VC_QueueWaitError(pDevInfo, pIrp);
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_STREAM_INIT:
|
||
|
/*
|
||
|
* set up to start streaming data. We are allowed to take
|
||
|
* time on this call: STREAM_START is expected to start straight
|
||
|
* away.
|
||
|
* In fact, most of the set-up has been done in the user-level
|
||
|
* dll. All we really need to do here is pass the call through
|
||
|
* to the h/w layer for him to set the microsec per frame.
|
||
|
*/
|
||
|
|
||
|
/* check buffer is big enough to hold ms/frame arg */
|
||
|
if (pIoStack->Parameters.DeviceIoControl.InputBufferLength
|
||
|
< sizeof(ULONG)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* check and change device state */
|
||
|
if (pDevInfo->State != State_Idle) {
|
||
|
dprintf1(("state error - init when not idle (%d)", pDevInfo->State));
|
||
|
}
|
||
|
pDevInfo->State = State_Init;
|
||
|
|
||
|
/*
|
||
|
* clear the skip count since we are starting a new session
|
||
|
*/
|
||
|
pDevInfo->nSkipped = 0;
|
||
|
|
||
|
|
||
|
if (pDevInfo->Callback.StreamInitFunc != NULL) {
|
||
|
ULONG ulMsPerFrame;
|
||
|
|
||
|
ulMsPerFrame = *(PULONG)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
if (pDevInfo->Callback.StreamInitFunc(pDevInfo, ulMsPerFrame)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DRIVER_INTERNAL_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
case IOCTL_VIDC_STREAM_FINI:
|
||
|
/*
|
||
|
* shutdown after streaming data. At this point the streaming
|
||
|
* should have been stopped.
|
||
|
*
|
||
|
* All buffers must have been completed - with STREAM_RESET
|
||
|
* if necessary.
|
||
|
*
|
||
|
* no args.
|
||
|
*/
|
||
|
|
||
|
/* cannot finish if there are still buffers in queue */
|
||
|
if ((!IsListEmpty(&pDevInfo->BufferHead)) ||
|
||
|
(!IsListEmpty(&pDevInfo->WaitErrorHead)) ) {
|
||
|
Status = STATUS_DEVICE_BUSY;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* check we're not busy - a STOP should have occured */
|
||
|
if (pDevInfo->State == State_Start) {
|
||
|
Status = STATUS_DEVICE_BUSY;
|
||
|
break;
|
||
|
}
|
||
|
pDevInfo->State = State_Idle;
|
||
|
|
||
|
|
||
|
if (pDevInfo->Callback.StreamFiniFunc != NULL) {
|
||
|
|
||
|
if (pDevInfo->Callback.StreamFiniFunc(pDevInfo)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DRIVER_INTERNAL_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
case IOCTL_VIDC_STREAM_START:
|
||
|
/*
|
||
|
* start streaming data from the capture device into
|
||
|
* queued buffers.
|
||
|
* This request completes immediately - the add-buffer
|
||
|
* requests are completed as the buffers are filled.
|
||
|
* a STREAM_INIT must have been called first.
|
||
|
*/
|
||
|
|
||
|
/* check that an INIT has already been done */
|
||
|
if (pDevInfo->State != State_Init) {
|
||
|
Status = STATUS_INVALID_DEVICE_STATE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
INIT_PROFILING(&looptime);
|
||
|
|
||
|
if (pDevInfo->Callback.StreamStartFunc != NULL) {
|
||
|
|
||
|
if (pDevInfo->Callback.StreamStartFunc(pDevInfo)) {
|
||
|
pDevInfo->State = State_Start;
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DRIVER_INTERNAL_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_STREAM_STOP:
|
||
|
/*
|
||
|
* stop streaming. The 'current buffer' will be completed,
|
||
|
* but all other buffers remain on the queue. - Of course,
|
||
|
* since buffers are filled atomically in the dpc routine,
|
||
|
* there will not be a current, partially-filled buffer
|
||
|
* at this point.
|
||
|
*
|
||
|
* So just stop the device from interrupting, and we're done.
|
||
|
*/
|
||
|
|
||
|
/* check that we are not idle (Init must have been done,
|
||
|
* though not necessarily Start)
|
||
|
*/
|
||
|
if (pDevInfo->State == State_Idle) {
|
||
|
Status = STATUS_INVALID_DEVICE_STATE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (pDevInfo->Callback.StreamStopFunc != NULL) {
|
||
|
|
||
|
if (pDevInfo->Callback.StreamStopFunc(pDevInfo)) {
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_DRIVER_INTERNAL_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
|
||
|
if (PROFILING_COUNT(&looptime) > 1) {
|
||
|
dprintf1(("capture loop %d times took %d usecs/frame",
|
||
|
PROFILING_COUNT(&looptime),
|
||
|
PROFILING_TIME(&looptime)
|
||
|
));
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* cycle back to an Init-ed state after stopping */
|
||
|
pDevInfo->State = State_Init;
|
||
|
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_STREAM_RESET:
|
||
|
/*
|
||
|
* stop streaming and free all buffers - ie complete
|
||
|
* any queued requests. Leave the device in an
|
||
|
* Init-ed state with no more q'd requests.
|
||
|
*
|
||
|
* complete the wait-error request as well as the
|
||
|
* add-buffer requests
|
||
|
*/
|
||
|
|
||
|
/* must be Init-ed or Start-ed, not idle */
|
||
|
if (pDevInfo->State == State_Idle) {
|
||
|
Status = STATUS_INVALID_DEVICE_STATE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* stop the streaming */
|
||
|
if (pDevInfo->State == State_Start) {
|
||
|
if (pDevInfo->Callback.StreamStopFunc != NULL) {
|
||
|
pDevInfo->Callback.StreamStopFunc(pDevInfo);
|
||
|
pDevInfo->State = State_Init;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* complete all the add-buffer requests on the queue
|
||
|
*/
|
||
|
{
|
||
|
PIRP pIrpQ;
|
||
|
|
||
|
for (;;) {
|
||
|
pIrpQ = VC_ExtractNextIrp(&pDevInfo->BufferHead, FALSE);
|
||
|
|
||
|
if (!pIrpQ) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* bytes copied = none */
|
||
|
pIrpQ->IoStatus.Information = 0;
|
||
|
|
||
|
pIrpQ->IoStatus.Status = STATUS_SUCCESS;
|
||
|
IoCompleteRequest(pIrpQ, IO_NO_INCREMENT);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* clear the Wait-Error requests if any waiting
|
||
|
*/
|
||
|
for(;;) {
|
||
|
PULONG pcount;
|
||
|
|
||
|
pIrpQ = VC_ExtractNextIrp(&pDevInfo->WaitErrorHead, FALSE);
|
||
|
if (!pIrpQ) {
|
||
|
break;
|
||
|
}
|
||
|
pcount = (PULONG)pIrpQ->AssociatedIrp.SystemBuffer;
|
||
|
*pcount = pDevInfo->nSkipped;
|
||
|
pIrpQ->IoStatus.Information = sizeof(ULONG);
|
||
|
pDevInfo->nSkipped = 0;
|
||
|
|
||
|
pIrpQ->IoStatus.Status = STATUS_SUCCESS;
|
||
|
IoCompleteRequest(pIrpQ, IO_NO_INCREMENT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Status = STATUS_SUCCESS;
|
||
|
break;
|
||
|
|
||
|
case IOCTL_VIDC_GET_POSITION:
|
||
|
/*
|
||
|
* get the position - ie the time in millisecs
|
||
|
* since recording began, according to the video clock.
|
||
|
*/
|
||
|
|
||
|
|
||
|
if (pDevInfo->Callback.StreamGetPositionFunc != NULL) {
|
||
|
PULONG pul;
|
||
|
|
||
|
/* check buffer is big enough to hold msec return value */
|
||
|
if (pIoStack->Parameters.DeviceIoControl.OutputBufferLength
|
||
|
< sizeof(ULONG)) {
|
||
|
dprintf1(("buffer too small"));
|
||
|
Status = STATUS_BUFFER_TOO_SMALL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pul = (PULONG)pIrp->AssociatedIrp.SystemBuffer;
|
||
|
|
||
|
*pul = pDevInfo->Callback.StreamGetPositionFunc(pDevInfo);
|
||
|
pIrp->IoStatus.Information = sizeof(ULONG);
|
||
|
|
||
|
Status = STATUS_SUCCESS;
|
||
|
} else {
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
default:
|
||
|
dprintf(("Unsupported Ioctl %d", IoCode));
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
default:
|
||
|
|
||
|
dprintf(("Unsupported major function %d", pIoStack->MajorFunction));
|
||
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fill in the status of the IRP, and complete it unless it
|
||
|
* has been queued.
|
||
|
*/
|
||
|
if (Status != STATUS_PENDING) {
|
||
|
pIrp->IoStatus.Status = Status;
|
||
|
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* exit device mutex */
|
||
|
KeReleaseMutex(&pDevInfo->Mutex, FALSE);
|
||
|
|
||
|
return(Status);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Cancel routine - called to cancel a pending wait-error
|
||
|
* or add-buffer Irp.
|
||
|
*
|
||
|
* Look for it on the WaitErrorHead or BufferHead queues and
|
||
|
* complete it if we find it. If we don't find it, it must be
|
||
|
* being processed at the moment
|
||
|
*
|
||
|
* We hold the cancel spinlock - this also gives us access
|
||
|
* to the queues. We need to release it before calling IoCompleteRequest.
|
||
|
*/
|
||
|
VOID
|
||
|
VC_Cancel(
|
||
|
IN PDEVICE_OBJECT pDeviceObject,
|
||
|
IN PIRP pIrp
|
||
|
)
|
||
|
{
|
||
|
PDEVICE_INFO pDevInfo;
|
||
|
PIRP pIrpFound = NULL;
|
||
|
|
||
|
dprintf2(("cancel"));
|
||
|
|
||
|
pDevInfo = (PDEVICE_INFO) pDeviceObject->DeviceExtension;
|
||
|
|
||
|
/* is it the wait-buffer ? */
|
||
|
pIrpFound = VC_ExtractThisIrp(&pDevInfo->WaitErrorHead, pIrp, TRUE);
|
||
|
|
||
|
if (pIrpFound == NULL) {
|
||
|
|
||
|
|
||
|
pIrpFound = VC_ExtractThisIrp(&pDevInfo->BufferHead, pIrp, TRUE);
|
||
|
|
||
|
if (pIrpFound) {
|
||
|
|
||
|
dprintf2(("cancel buffer"));
|
||
|
|
||
|
/* free the pages and mdl we allocated */
|
||
|
//MmUnlockPages(pIrpFound->MdlAddress);
|
||
|
//IoFreeMdl(pIrpFound->MdlAddress);
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
#if DBG
|
||
|
else {
|
||
|
dprintf2(("cancel waiterror"));
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/* release the cancel spinlock before completing the
|
||
|
* request
|
||
|
*/
|
||
|
IoReleaseCancelSpinLock(pIrp->CancelIrql);
|
||
|
|
||
|
|
||
|
if (pIrpFound) {
|
||
|
pIrpFound->IoStatus.Information = 0;
|
||
|
pIrpFound->IoStatus.Status = STATUS_CANCELLED;
|
||
|
|
||
|
IoCompleteRequest(pIrpFound, IO_NO_INCREMENT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|