mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
9178 lines
272 KiB
9178 lines
272 KiB
/*++
|
|
|
|
Copyright (C) Microsoft Corporation, 1991 - 1999
|
|
|
|
Module Name:
|
|
|
|
class.c
|
|
|
|
Abstract:
|
|
|
|
SCSI class driver routines
|
|
|
|
Environment:
|
|
|
|
kernel mode only
|
|
|
|
Notes:
|
|
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#define CLASS_INIT_GUID 1
|
|
#include "classp.h"
|
|
#include "debug.h"
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(INIT, DriverEntry)
|
|
#pragma alloc_text(PAGE, ClassAddDevice)
|
|
#pragma alloc_text(PAGE, ClassClaimDevice)
|
|
#pragma alloc_text(PAGE, ClassCreateDeviceObject)
|
|
#pragma alloc_text(PAGE, ClassDispatchPnp)
|
|
#pragma alloc_text(PAGE, ClassGetDescriptor)
|
|
#pragma alloc_text(PAGE, ClassGetPdoId)
|
|
#pragma alloc_text(PAGE, ClassInitialize)
|
|
#pragma alloc_text(PAGE, ClassInitializeEx)
|
|
#pragma alloc_text(PAGE, ClassInvalidateBusRelations)
|
|
#pragma alloc_text(PAGE, ClassMarkChildMissing)
|
|
#pragma alloc_text(PAGE, ClassMarkChildrenMissing)
|
|
#pragma alloc_text(PAGE, ClassModeSense)
|
|
#pragma alloc_text(PAGE, ClassPnpQueryFdoRelations)
|
|
#pragma alloc_text(PAGE, ClassPnpStartDevice)
|
|
#pragma alloc_text(PAGE, ClassQueryPnpCapabilities)
|
|
#pragma alloc_text(PAGE, ClassQueryTimeOutRegistryValue)
|
|
#pragma alloc_text(PAGE, ClassRemoveDevice)
|
|
#pragma alloc_text(PAGE, ClassRetrieveDeviceRelations)
|
|
#pragma alloc_text(PAGE, ClassUpdateInformationInRegistry)
|
|
#pragma alloc_text(PAGE, ClassSendDeviceIoControlSynchronous)
|
|
#pragma alloc_text(PAGE, ClassUnload)
|
|
#pragma alloc_text(PAGE, ClasspAllocateReleaseRequest)
|
|
#pragma alloc_text(PAGE, ClasspFreeReleaseRequest)
|
|
#pragma alloc_text(PAGE, ClasspInitializeHotplugInfo)
|
|
#pragma alloc_text(PAGE, ClasspRegisterMountedDeviceInterface)
|
|
#pragma alloc_text(PAGE, ClasspScanForClassHacks)
|
|
#pragma alloc_text(PAGE, ClasspScanForSpecialInRegistry)
|
|
#endif
|
|
|
|
ULONG ClassPnpAllowUnload = TRUE;
|
|
|
|
|
|
#define FirstDriveLetter 'C'
|
|
#define LastDriveLetter 'Z'
|
|
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
DriverEntry()
|
|
|
|
Routine Description:
|
|
|
|
Temporary entry point needed to initialize the class system dll.
|
|
It doesn't do anything.
|
|
|
|
Arguments:
|
|
|
|
DriverObject - Pointer to the driver object created by the system.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS
|
|
|
|
--*/
|
|
NTSTATUS
|
|
DriverEntry(
|
|
IN PDRIVER_OBJECT DriverObject,
|
|
IN PUNICODE_STRING RegistryPath
|
|
)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassInitialize()
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by a class driver during its
|
|
DriverEntry routine to initialize the driver.
|
|
|
|
Arguments:
|
|
|
|
Argument1 - Driver Object.
|
|
Argument2 - Registry Path.
|
|
InitializationData - Device-specific driver's initialization data.
|
|
|
|
Return Value:
|
|
|
|
A valid return code for a DriverEntry routine.
|
|
|
|
--*/
|
|
ULONG
|
|
ClassInitialize(
|
|
IN PVOID Argument1,
|
|
IN PVOID Argument2,
|
|
IN PCLASS_INIT_DATA InitializationData
|
|
)
|
|
{
|
|
PDRIVER_OBJECT DriverObject = Argument1;
|
|
PUNICODE_STRING RegistryPath = Argument2;
|
|
|
|
PCLASS_DRIVER_EXTENSION driverExtension;
|
|
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugPrint((3,"\n\nSCSI Class Driver\n"));
|
|
|
|
ClasspInitializeDebugGlobals();
|
|
|
|
//
|
|
// Validate the length of this structure. This is effectively a
|
|
// version check.
|
|
//
|
|
|
|
if (InitializationData->InitializationDataSize != sizeof(CLASS_INIT_DATA)) {
|
|
|
|
//
|
|
// This DebugPrint is to help third-party driver writers
|
|
//
|
|
|
|
DebugPrint((0,"ClassInitialize: Class driver wrong version\n"));
|
|
return (ULONG) STATUS_REVISION_MISMATCH;
|
|
}
|
|
|
|
//
|
|
// Check that each required entry is not NULL. Note that Shutdown, Flush and Error
|
|
// are not required entry points.
|
|
//
|
|
|
|
if ((!InitializationData->FdoData.ClassDeviceControl) ||
|
|
(!((InitializationData->FdoData.ClassReadWriteVerification) ||
|
|
(InitializationData->ClassStartIo))) ||
|
|
(!InitializationData->ClassAddDevice) ||
|
|
(!InitializationData->FdoData.ClassStartDevice)) {
|
|
|
|
//
|
|
// This DebugPrint is to help third-party driver writers
|
|
//
|
|
|
|
DebugPrint((0,
|
|
"ClassInitialize: Class device-specific driver missing required "
|
|
"FDO entry\n"));
|
|
|
|
return (ULONG) STATUS_REVISION_MISMATCH;
|
|
}
|
|
|
|
if ((InitializationData->ClassEnumerateDevice) &&
|
|
((!InitializationData->PdoData.ClassDeviceControl) ||
|
|
(!InitializationData->PdoData.ClassStartDevice) ||
|
|
(!((InitializationData->PdoData.ClassReadWriteVerification) ||
|
|
(InitializationData->ClassStartIo))))) {
|
|
|
|
//
|
|
// This DebugPrint is to help third-party driver writers
|
|
//
|
|
|
|
DebugPrint((0, "ClassInitialize: Class device-specific missing "
|
|
"required PDO entry\n"));
|
|
|
|
return (ULONG) STATUS_REVISION_MISMATCH;
|
|
}
|
|
|
|
if((InitializationData->FdoData.ClassStopDevice == NULL) ||
|
|
((InitializationData->ClassEnumerateDevice != NULL) &&
|
|
(InitializationData->PdoData.ClassStopDevice == NULL))) {
|
|
|
|
//
|
|
// This DebugPrint is to help third-party driver writers
|
|
//
|
|
|
|
DebugPrint((0, "ClassInitialize: Class device-specific missing "
|
|
"required PDO entry\n"));
|
|
ASSERT(FALSE);
|
|
return (ULONG) STATUS_REVISION_MISMATCH;
|
|
}
|
|
|
|
//
|
|
// Setup the default power handlers if the class driver didn't provide
|
|
// any.
|
|
//
|
|
|
|
if(InitializationData->FdoData.ClassPowerDevice == NULL) {
|
|
InitializationData->FdoData.ClassPowerDevice = ClassMinimalPowerHandler;
|
|
}
|
|
|
|
if((InitializationData->ClassEnumerateDevice != NULL) &&
|
|
(InitializationData->PdoData.ClassPowerDevice == NULL)) {
|
|
InitializationData->PdoData.ClassPowerDevice = ClassMinimalPowerHandler;
|
|
}
|
|
|
|
//
|
|
// warn that unload is not supported
|
|
//
|
|
// ISSUE-2000/02/03-peterwie
|
|
// We should think about making this a fatal error.
|
|
//
|
|
|
|
if(InitializationData->ClassUnload == NULL) {
|
|
|
|
//
|
|
// This DebugPrint is to help third-party driver writers
|
|
//
|
|
|
|
DebugPrint((0, "ClassInitialize: driver does not support unload %wZ\n",
|
|
RegistryPath));
|
|
}
|
|
|
|
//
|
|
// Create an extension for the driver object
|
|
//
|
|
|
|
status = IoAllocateDriverObjectExtension(DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY,
|
|
sizeof(CLASS_DRIVER_EXTENSION),
|
|
&driverExtension);
|
|
|
|
if(NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// Copy the registry path into the driver extension so we can use it later
|
|
//
|
|
|
|
driverExtension->RegistryPath.Length = RegistryPath->Length;
|
|
driverExtension->RegistryPath.MaximumLength = RegistryPath->MaximumLength;
|
|
|
|
driverExtension->RegistryPath.Buffer =
|
|
ExAllocatePoolWithTag(PagedPool,
|
|
RegistryPath->MaximumLength,
|
|
'1CcS');
|
|
|
|
if(driverExtension->RegistryPath.Buffer == NULL) {
|
|
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
return status;
|
|
}
|
|
|
|
RtlCopyUnicodeString(
|
|
&(driverExtension->RegistryPath),
|
|
RegistryPath);
|
|
|
|
//
|
|
// Copy the initialization data into the driver extension so we can reuse
|
|
// it during our add device routine
|
|
//
|
|
|
|
RtlCopyMemory(
|
|
&(driverExtension->InitData),
|
|
InitializationData,
|
|
sizeof(CLASS_INIT_DATA));
|
|
|
|
driverExtension->DeviceCount = 0;
|
|
|
|
} else if (status == STATUS_OBJECT_NAME_COLLISION) {
|
|
|
|
//
|
|
// The extension already exists - get a pointer to it
|
|
//
|
|
|
|
driverExtension = IoGetDriverObjectExtension(DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
|
|
ASSERT(driverExtension != NULL);
|
|
|
|
} else {
|
|
|
|
DebugPrint((1, "ClassInitialize: Class driver extension could not be "
|
|
"allocated %lx\n", status));
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Update driver object with entry points.
|
|
//
|
|
|
|
DriverObject->MajorFunction[IRP_MJ_CREATE] = ClassCreateClose;
|
|
DriverObject->MajorFunction[IRP_MJ_CLOSE] = ClassCreateClose;
|
|
DriverObject->MajorFunction[IRP_MJ_READ] = ClassReadWrite;
|
|
DriverObject->MajorFunction[IRP_MJ_WRITE] = ClassReadWrite;
|
|
DriverObject->MajorFunction[IRP_MJ_SCSI] = ClassInternalIoControl;
|
|
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ClassDeviceControlDispatch;
|
|
DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = ClassShutdownFlush;
|
|
DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = ClassShutdownFlush;
|
|
DriverObject->MajorFunction[IRP_MJ_PNP] = ClassDispatchPnp;
|
|
DriverObject->MajorFunction[IRP_MJ_POWER] = ClassDispatchPower;
|
|
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = ClassSystemControl;
|
|
|
|
if (InitializationData->ClassStartIo) {
|
|
DriverObject->DriverStartIo = ClasspStartIo;
|
|
}
|
|
|
|
if ((InitializationData->ClassUnload) && (ClassPnpAllowUnload == TRUE)) {
|
|
DriverObject->DriverUnload = ClassUnload;
|
|
} else {
|
|
DriverObject->DriverUnload = NULL;
|
|
}
|
|
|
|
DriverObject->DriverExtension->AddDevice = ClassAddDevice;
|
|
|
|
status = STATUS_SUCCESS;
|
|
return status;
|
|
} // end ClassInitialize()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassInitializeEx()
|
|
|
|
Routine Description:
|
|
|
|
This routine is allows the caller to do any extra initialization or
|
|
setup that is not done in ClassInitialize. The operation is
|
|
controlled by the GUID that is passed and the contents of the Data
|
|
parameter is dependent upon the GUID.
|
|
|
|
This is the list of supported operations:
|
|
|
|
Guid - GUID_CLASSPNP_QUERY_REGINFOEX
|
|
Data - A PCLASS_QUERY_WMI_REGINFO_EX callback function pointer
|
|
|
|
Initialized classpnp to callback a PCLASS_QUERY_WMI_REGINFO_EX
|
|
callback instead of a PCLASS_QUERY_WMI_REGINFO callback. The
|
|
former callback allows the driver to specify the name of the
|
|
mof resource.
|
|
|
|
Arguments:
|
|
|
|
DriverObject
|
|
Guid
|
|
Data
|
|
|
|
Return Value:
|
|
|
|
Status Code
|
|
|
|
--*/
|
|
ULONG
|
|
ClassInitializeEx(
|
|
IN PDRIVER_OBJECT DriverObject,
|
|
IN LPGUID Guid,
|
|
IN PVOID Data
|
|
)
|
|
{
|
|
PCLASS_DRIVER_EXTENSION driverExtension;
|
|
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
driverExtension = IoGetDriverObjectExtension( DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY
|
|
);
|
|
if (IsEqualGUID(Guid, &ClassGuidQueryRegInfoEx))
|
|
{
|
|
PCLASS_QUERY_WMI_REGINFO_EX_LIST List;
|
|
|
|
//
|
|
// Indicate the device supports PCLASS_QUERY_REGINFO_EX
|
|
// callback instead of PCLASS_QUERY_REGINFO callback.
|
|
//
|
|
List = (PCLASS_QUERY_WMI_REGINFO_EX_LIST)Data;
|
|
|
|
if (List->Size == sizeof(CLASS_QUERY_WMI_REGINFO_EX_LIST))
|
|
{
|
|
driverExtension->ClassFdoQueryWmiRegInfoEx = List->ClassFdoQueryWmiRegInfoEx;
|
|
driverExtension->ClassPdoQueryWmiRegInfoEx = List->ClassPdoQueryWmiRegInfoEx;
|
|
status = STATUS_SUCCESS;
|
|
} else {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
} else {
|
|
status = STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
return(status);
|
|
|
|
} // end ClassInitializeEx()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassUnload()
|
|
|
|
Routine Description:
|
|
|
|
called when there are no more references to the driver. this allows
|
|
drivers to be updated without rebooting.
|
|
|
|
Arguments:
|
|
|
|
DriverObject - a pointer to the driver object that is being unloaded
|
|
|
|
Status:
|
|
|
|
--*/
|
|
VOID
|
|
ClassUnload(
|
|
IN PDRIVER_OBJECT DriverObject
|
|
)
|
|
{
|
|
PCLASS_DRIVER_EXTENSION driverExtension;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( DriverObject->DeviceObject == NULL );
|
|
|
|
driverExtension = IoGetDriverObjectExtension( DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY
|
|
);
|
|
|
|
ASSERT(driverExtension != NULL);
|
|
ASSERT(driverExtension->RegistryPath.Buffer != NULL);
|
|
ASSERT(driverExtension->InitData.ClassUnload != NULL);
|
|
|
|
DebugPrint((1, "ClassUnload: driver unloading %wZ\n",
|
|
&driverExtension->RegistryPath));
|
|
|
|
//
|
|
// attempt to process the driver's unload routine first.
|
|
//
|
|
|
|
driverExtension->InitData.ClassUnload(DriverObject);
|
|
|
|
//
|
|
// free own allocated resources and return
|
|
//
|
|
|
|
ExFreePool( driverExtension->RegistryPath.Buffer );
|
|
driverExtension->RegistryPath.Buffer = NULL;
|
|
driverExtension->RegistryPath.Length = 0;
|
|
driverExtension->RegistryPath.MaximumLength = 0;
|
|
|
|
return;
|
|
} // end ClassUnload()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassAddDevice()
|
|
|
|
Routine Description:
|
|
|
|
SCSI class driver add device routine. This is called by pnp when a new
|
|
physical device come into being.
|
|
|
|
This routine will call out to the class driver to verify that it should
|
|
own this device then will create and attach a device object and then hand
|
|
it to the driver to initialize and create symbolic links
|
|
|
|
Arguments:
|
|
|
|
DriverObject - a pointer to the driver object that this is being created for
|
|
PhysicalDeviceObject - a pointer to the physical device object
|
|
|
|
Status: STATUS_NO_SUCH_DEVICE if the class driver did not want this device
|
|
STATUS_SUCCESS if the creation and attachment was successful
|
|
status of device creation and initialization
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassAddDevice(
|
|
IN PDRIVER_OBJECT DriverObject,
|
|
IN PDEVICE_OBJECT PhysicalDeviceObject
|
|
)
|
|
{
|
|
PCLASS_DRIVER_EXTENSION driverExtension =
|
|
IoGetDriverObjectExtension(DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
status = driverExtension->InitData.ClassAddDevice(DriverObject,
|
|
PhysicalDeviceObject);
|
|
return status;
|
|
} // end ClassAddDevice()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassDispatchPnp()
|
|
|
|
Routine Description:
|
|
|
|
Storage class driver pnp routine. This is called by the io system when
|
|
a PNP request is sent to the device.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - pointer to the device object
|
|
|
|
Irp - pointer to the io request packet
|
|
|
|
Return Value:
|
|
|
|
status
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassDispatchPnp(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
BOOLEAN isFdo = commonExtension->IsFdo;
|
|
|
|
PCLASS_DRIVER_EXTENSION driverExtension;
|
|
PCLASS_INIT_DATA initData;
|
|
PCLASS_DEV_INFO devInfo;
|
|
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
|
|
|
|
NTSTATUS status = Irp->IoStatus.Status;
|
|
BOOLEAN completeRequest = TRUE;
|
|
BOOLEAN lockReleased = FALSE;
|
|
|
|
ULONG isRemoved;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Extract all the useful information out of the driver object
|
|
// extension
|
|
//
|
|
|
|
driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
if (driverExtension){
|
|
|
|
initData = &(driverExtension->InitData);
|
|
|
|
if(isFdo) {
|
|
devInfo = &(initData->FdoData);
|
|
} else {
|
|
devInfo = &(initData->PdoData);
|
|
}
|
|
|
|
isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
|
|
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): minor code %#x for %s %p\n",
|
|
DeviceObject, Irp,
|
|
irpStack->MinorFunction,
|
|
isFdo ? "fdo" : "pdo",
|
|
DeviceObject));
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): previous %#x, current %#x\n",
|
|
DeviceObject, Irp,
|
|
commonExtension->PreviousState,
|
|
commonExtension->CurrentState));
|
|
|
|
switch(irpStack->MinorFunction) {
|
|
|
|
case IRP_MN_START_DEVICE: {
|
|
|
|
//
|
|
// if this is sent to the FDO we should forward it down the
|
|
// attachment chain before we start the FDO.
|
|
//
|
|
|
|
if (isFdo) {
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
}
|
|
else {
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)){
|
|
status = Irp->IoStatus.Status = ClassPnpStartDevice(DeviceObject);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
case IRP_MN_QUERY_DEVICE_RELATIONS: {
|
|
|
|
DEVICE_RELATION_TYPE type =
|
|
irpStack->Parameters.QueryDeviceRelations.Type;
|
|
|
|
PDEVICE_RELATIONS deviceRelations = NULL;
|
|
|
|
if(!isFdo) {
|
|
|
|
if(type == TargetDeviceRelation) {
|
|
|
|
//
|
|
// Device relations has one entry built in to it's size.
|
|
//
|
|
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
deviceRelations = ExAllocatePoolWithTag(PagedPool,
|
|
sizeof(DEVICE_RELATIONS),
|
|
'2CcS');
|
|
|
|
if(deviceRelations != NULL) {
|
|
|
|
RtlZeroMemory(deviceRelations,
|
|
sizeof(DEVICE_RELATIONS));
|
|
|
|
Irp->IoStatus.Information = (ULONG_PTR) deviceRelations;
|
|
|
|
deviceRelations->Count = 1;
|
|
deviceRelations->Objects[0] = DeviceObject;
|
|
ObReferenceObject(deviceRelations->Objects[0]);
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// PDO's just complete enumeration requests without altering
|
|
// the status.
|
|
//
|
|
|
|
status = Irp->IoStatus.Status;
|
|
}
|
|
|
|
break;
|
|
|
|
} else if (type == BusRelations) {
|
|
|
|
ASSERT(commonExtension->IsInitialized);
|
|
|
|
//
|
|
// Make sure we support enumeration
|
|
//
|
|
|
|
if(initData->ClassEnumerateDevice == NULL) {
|
|
|
|
//
|
|
// Just send the request down to the lower driver. Perhaps
|
|
// It can enumerate children.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Re-enumerate the device
|
|
//
|
|
|
|
status = ClassPnpQueryFdoRelations(DeviceObject, Irp);
|
|
|
|
if(!NT_SUCCESS(status)) {
|
|
completeRequest = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
completeRequest = FALSE;
|
|
|
|
break;
|
|
}
|
|
|
|
case IRP_MN_QUERY_ID: {
|
|
|
|
BUS_QUERY_ID_TYPE idType = irpStack->Parameters.QueryId.IdType;
|
|
UNICODE_STRING unicodeString;
|
|
|
|
if(isFdo) {
|
|
|
|
//
|
|
// FDO's should just forward the query down to the lower
|
|
// device objects
|
|
//
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
completeRequest = FALSE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// PDO's need to give an answer - this is easy for now
|
|
//
|
|
|
|
RtlInitUnicodeString(&unicodeString, NULL);
|
|
|
|
status = ClassGetPdoId(DeviceObject,
|
|
idType,
|
|
&unicodeString);
|
|
|
|
if(status == STATUS_NOT_IMPLEMENTED) {
|
|
//
|
|
// The driver doesn't implement this ID (whatever it is).
|
|
// Use the status out of the IRP so that we don't mangle a
|
|
// response from someone else.
|
|
//
|
|
|
|
status = Irp->IoStatus.Status;
|
|
} else if(NT_SUCCESS(status)) {
|
|
Irp->IoStatus.Information = (ULONG_PTR) unicodeString.Buffer;
|
|
} else {
|
|
Irp->IoStatus.Information = (ULONG_PTR) NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case IRP_MN_QUERY_STOP_DEVICE:
|
|
case IRP_MN_QUERY_REMOVE_DEVICE: {
|
|
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): Processing QUERY_%s irp\n",
|
|
DeviceObject, Irp,
|
|
((irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) ?
|
|
"STOP" : "REMOVE")));
|
|
|
|
//
|
|
// If this device is in use for some reason (paging, etc...)
|
|
// then we need to fail the request.
|
|
//
|
|
|
|
if(commonExtension->PagingPathCount != 0) {
|
|
|
|
DebugPrint((1, "ClassDispatchPnp (%p,%p): device is in paging "
|
|
"path and cannot be removed\n",
|
|
DeviceObject, Irp));
|
|
status = STATUS_DEVICE_BUSY;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Check with the class driver to see if the query operation
|
|
// can succeed.
|
|
//
|
|
|
|
if(irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) {
|
|
status = devInfo->ClassStopDevice(DeviceObject,
|
|
irpStack->MinorFunction);
|
|
} else {
|
|
status = devInfo->ClassRemoveDevice(DeviceObject,
|
|
irpStack->MinorFunction);
|
|
}
|
|
|
|
if(NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// ASSERT that we never get two queries in a row, as
|
|
// this will severly mess up the state machine
|
|
//
|
|
ASSERT(commonExtension->CurrentState != irpStack->MinorFunction);
|
|
commonExtension->PreviousState = commonExtension->CurrentState;
|
|
commonExtension->CurrentState = irpStack->MinorFunction;
|
|
|
|
if(isFdo) {
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): Forwarding QUERY_"
|
|
"%s irp\n", DeviceObject, Irp,
|
|
((irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) ?
|
|
"STOP" : "REMOVE")));
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
}
|
|
}
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): Final status == %x\n",
|
|
DeviceObject, Irp, status));
|
|
|
|
break;
|
|
}
|
|
|
|
case IRP_MN_CANCEL_STOP_DEVICE:
|
|
case IRP_MN_CANCEL_REMOVE_DEVICE: {
|
|
|
|
//
|
|
// Check with the class driver to see if the query or cancel
|
|
// operation can succeed.
|
|
//
|
|
|
|
if(irpStack->MinorFunction == IRP_MN_CANCEL_STOP_DEVICE) {
|
|
status = devInfo->ClassStopDevice(DeviceObject,
|
|
irpStack->MinorFunction);
|
|
ASSERTMSG("ClassDispatchPnp !! CANCEL_STOP_DEVICE should "
|
|
"never be failed\n", NT_SUCCESS(status));
|
|
} else {
|
|
status = devInfo->ClassRemoveDevice(DeviceObject,
|
|
irpStack->MinorFunction);
|
|
ASSERTMSG("ClassDispatchPnp !! CANCEL_REMOVE_DEVICE should "
|
|
"never be failed\n", NT_SUCCESS(status));
|
|
}
|
|
|
|
Irp->IoStatus.Status = status;
|
|
|
|
//
|
|
// We got a CANCEL - roll back to the previous state only
|
|
// if the current state is the respective QUERY state.
|
|
//
|
|
|
|
if(((irpStack->MinorFunction == IRP_MN_CANCEL_STOP_DEVICE) &&
|
|
(commonExtension->CurrentState == IRP_MN_QUERY_STOP_DEVICE)
|
|
) ||
|
|
((irpStack->MinorFunction == IRP_MN_CANCEL_REMOVE_DEVICE) &&
|
|
(commonExtension->CurrentState == IRP_MN_QUERY_REMOVE_DEVICE)
|
|
)
|
|
) {
|
|
|
|
commonExtension->CurrentState =
|
|
commonExtension->PreviousState;
|
|
commonExtension->PreviousState = 0xff;
|
|
|
|
}
|
|
|
|
if(isFdo) {
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
completeRequest = FALSE;
|
|
} else {
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case IRP_MN_STOP_DEVICE: {
|
|
|
|
//
|
|
// These all mean nothing to the class driver currently. The
|
|
// port driver will handle all queueing when necessary.
|
|
//
|
|
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): got stop request for %s\n",
|
|
DeviceObject, Irp,
|
|
(isFdo ? "fdo" : "pdo")
|
|
));
|
|
|
|
ASSERT(commonExtension->PagingPathCount == 0);
|
|
|
|
//
|
|
// ISSUE-2000/02/03-peterwie
|
|
// if we stop the timer here then it means no class driver can
|
|
// do i/o in its ClassStopDevice routine. This is because the
|
|
// retry (among other things) is tied into the tick handler
|
|
// and disabling retries could cause the class driver to deadlock.
|
|
// Currently no class driver we're aware of issues i/o in its
|
|
// Stop routine but this is a case we may want to defend ourself
|
|
// against.
|
|
//
|
|
|
|
if (DeviceObject->Timer) {
|
|
IoStopTimer(DeviceObject);
|
|
}
|
|
|
|
status = devInfo->ClassStopDevice(DeviceObject, IRP_MN_STOP_DEVICE);
|
|
|
|
ASSERTMSG("ClassDispatchPnp !! STOP_DEVICE should "
|
|
"never be failed\n", NT_SUCCESS(status));
|
|
|
|
if(isFdo) {
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
}
|
|
|
|
if(NT_SUCCESS(status)) {
|
|
commonExtension->CurrentState = irpStack->MinorFunction;
|
|
commonExtension->PreviousState = 0xff;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case IRP_MN_REMOVE_DEVICE:
|
|
case IRP_MN_SURPRISE_REMOVAL: {
|
|
|
|
PDEVICE_OBJECT lowerDeviceObject = commonExtension->LowerDeviceObject;
|
|
UCHAR removeType = irpStack->MinorFunction;
|
|
|
|
if (commonExtension->PagingPathCount != 0) {
|
|
DBGTRACE(ClassDebugWarning, ("ClassDispatchPnp (%p,%p): paging device is getting removed!", DeviceObject, Irp));
|
|
}
|
|
|
|
//
|
|
// Release the lock for this IRP before calling in.
|
|
//
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
lockReleased = TRUE;
|
|
|
|
/*
|
|
* If a timer was started on the device, stop it.
|
|
*/
|
|
if (DeviceObject->Timer) {
|
|
IoStopTimer(DeviceObject);
|
|
}
|
|
|
|
/*
|
|
* "Fire-and-forget" the remove irp to the lower stack.
|
|
* Don't touch the irp (or the irp stack!) after this.
|
|
*/
|
|
if (isFdo) {
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
ASSERT(NT_SUCCESS(status));
|
|
completeRequest = FALSE;
|
|
}
|
|
else {
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Do our own cleanup and call the class driver's remove
|
|
* cleanup routine.
|
|
* For IRP_MN_REMOVE_DEVICE, this also deletes our device object,
|
|
* so don't touch the extension after this.
|
|
*/
|
|
commonExtension->PreviousState = commonExtension->CurrentState;
|
|
commonExtension->CurrentState = removeType;
|
|
ClassRemoveDevice(DeviceObject, removeType);
|
|
|
|
break;
|
|
}
|
|
|
|
case IRP_MN_DEVICE_USAGE_NOTIFICATION: {
|
|
|
|
switch(irpStack->Parameters.UsageNotification.Type) {
|
|
|
|
case DeviceUsageTypePaging: {
|
|
|
|
BOOLEAN setPagable;
|
|
|
|
if((irpStack->Parameters.UsageNotification.InPath) &&
|
|
(commonExtension->CurrentState != IRP_MN_START_DEVICE)) {
|
|
|
|
//
|
|
// Device isn't started. Don't allow adding a
|
|
// paging file, but allow a removal of one.
|
|
//
|
|
|
|
status = STATUS_DEVICE_NOT_READY;
|
|
break;
|
|
}
|
|
|
|
ASSERT(commonExtension->IsInitialized);
|
|
|
|
//
|
|
// need to synchronize this now...
|
|
//
|
|
|
|
KeEnterCriticalRegion();
|
|
status = KeWaitForSingleObject(&commonExtension->PathCountEvent,
|
|
Executive, KernelMode,
|
|
FALSE, NULL);
|
|
ASSERT(NT_SUCCESS(status));
|
|
status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// If the volume is removable we should try to lock it in
|
|
// place or unlock it once per paging path count
|
|
//
|
|
|
|
if (commonExtension->IsFdo){
|
|
status = ClasspEjectionControl(
|
|
DeviceObject,
|
|
Irp,
|
|
InternalMediaLock,
|
|
(BOOLEAN)irpStack->Parameters.UsageNotification.InPath);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)){
|
|
KeSetEvent(&commonExtension->PathCountEvent, IO_NO_INCREMENT, FALSE);
|
|
KeLeaveCriticalRegion();
|
|
break;
|
|
}
|
|
|
|
//
|
|
// if removing last paging device, need to set DO_POWER_PAGABLE
|
|
// bit here, and possible re-set it below on failure.
|
|
//
|
|
|
|
setPagable = FALSE;
|
|
|
|
if (!irpStack->Parameters.UsageNotification.InPath &&
|
|
commonExtension->PagingPathCount == 1
|
|
) {
|
|
|
|
//
|
|
// removing last paging file
|
|
// must have DO_POWER_PAGABLE bits set, but only
|
|
// if noone set the DO_POWER_INRUSH bit
|
|
//
|
|
|
|
|
|
if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) {
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): Last "
|
|
"paging file removed, but "
|
|
"DO_POWER_INRUSH was set, so NOT "
|
|
"setting DO_POWER_PAGABLE\n",
|
|
DeviceObject, Irp));
|
|
} else {
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): Last "
|
|
"paging file removed, "
|
|
"setting DO_POWER_PAGABLE\n",
|
|
DeviceObject, Irp));
|
|
SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
|
|
setPagable = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// forward the irp before finishing handling the
|
|
// special cases
|
|
//
|
|
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
|
|
//
|
|
// now deal with the failure and success cases.
|
|
// note that we are not allowed to fail the irp
|
|
// once it is sent to the lower drivers.
|
|
//
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
IoAdjustPagingPathCount(
|
|
&commonExtension->PagingPathCount,
|
|
irpStack->Parameters.UsageNotification.InPath);
|
|
|
|
if (irpStack->Parameters.UsageNotification.InPath) {
|
|
if (commonExtension->PagingPathCount == 1) {
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): "
|
|
"Clearing PAGABLE bit\n",
|
|
DeviceObject, Irp));
|
|
CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// cleanup the changes done above
|
|
//
|
|
|
|
if (setPagable == TRUE) {
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): Unsetting "
|
|
"PAGABLE bit due to irp failure\n",
|
|
DeviceObject, Irp));
|
|
CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
|
|
setPagable = FALSE;
|
|
}
|
|
|
|
//
|
|
// relock or unlock the media if needed.
|
|
//
|
|
|
|
if (commonExtension->IsFdo) {
|
|
|
|
ClasspEjectionControl(
|
|
DeviceObject,
|
|
Irp,
|
|
InternalMediaLock,
|
|
(BOOLEAN)!irpStack->Parameters.UsageNotification.InPath);
|
|
}
|
|
}
|
|
|
|
//
|
|
// set the event so the next one can occur.
|
|
//
|
|
|
|
KeSetEvent(&commonExtension->PathCountEvent,
|
|
IO_NO_INCREMENT, FALSE);
|
|
KeLeaveCriticalRegion();
|
|
break;
|
|
}
|
|
|
|
case DeviceUsageTypeHibernation: {
|
|
|
|
IoAdjustPagingPathCount(
|
|
&commonExtension->HibernationPathCount,
|
|
irpStack->Parameters.UsageNotification.InPath
|
|
);
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
if (!NT_SUCCESS(status)) {
|
|
IoAdjustPagingPathCount(
|
|
&commonExtension->HibernationPathCount,
|
|
!irpStack->Parameters.UsageNotification.InPath
|
|
);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case DeviceUsageTypeDumpFile: {
|
|
IoAdjustPagingPathCount(
|
|
&commonExtension->DumpPathCount,
|
|
irpStack->Parameters.UsageNotification.InPath
|
|
);
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
if (!NT_SUCCESS(status)) {
|
|
IoAdjustPagingPathCount(
|
|
&commonExtension->DumpPathCount,
|
|
!irpStack->Parameters.UsageNotification.InPath
|
|
);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IRP_MN_QUERY_CAPABILITIES: {
|
|
|
|
DebugPrint((2, "ClassDispatchPnp (%p,%p): QueryCapabilities\n",
|
|
DeviceObject, Irp));
|
|
|
|
if(!isFdo) {
|
|
|
|
status = ClassQueryPnpCapabilities(
|
|
DeviceObject,
|
|
irpStack->Parameters.DeviceCapabilities.Capabilities
|
|
);
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
PDEVICE_CAPABILITIES deviceCapabilities;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
PCLASS_PRIVATE_FDO_DATA fdoData;
|
|
|
|
fdoExtension = DeviceObject->DeviceExtension;
|
|
fdoData = fdoExtension->PrivateFdoData;
|
|
deviceCapabilities =
|
|
irpStack->Parameters.DeviceCapabilities.Capabilities;
|
|
|
|
//
|
|
// forward the irp before handling the special cases
|
|
//
|
|
|
|
status = ClassForwardIrpSynchronous(commonExtension, Irp);
|
|
if (!NT_SUCCESS(status)) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// we generally want to remove the device from the hotplug
|
|
// applet, which requires the SR-OK bit to be set.
|
|
// only when the user specifies that they are capable of
|
|
// safely removing things do we want to clear this bit
|
|
// (saved in WriteCacheEnableOverride)
|
|
//
|
|
// setting of this bit is done either above, or by the
|
|
// lower driver.
|
|
//
|
|
// note: may not be started, so check we have FDO data first.
|
|
//
|
|
|
|
if (fdoData &&
|
|
fdoData->HotplugInfo.WriteCacheEnableOverride) {
|
|
if (deviceCapabilities->SurpriseRemovalOK) {
|
|
DebugPrint((1, "Classpnp: Clearing SR-OK bit in "
|
|
"device capabilities due to hotplug "
|
|
"device or media\n"));
|
|
}
|
|
deviceCapabilities->SurpriseRemovalOK = FALSE;
|
|
}
|
|
break;
|
|
|
|
} // end QUERY_CAPABILITIES for FDOs
|
|
|
|
ASSERT(FALSE);
|
|
break;
|
|
|
|
|
|
} // end QUERY_CAPABILITIES
|
|
|
|
default: {
|
|
|
|
if (isFdo){
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
|
|
completeRequest = FALSE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
ASSERT(driverExtension);
|
|
status = STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (completeRequest){
|
|
Irp->IoStatus.Status = status;
|
|
|
|
if (!lockReleased){
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
}
|
|
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
DBGTRACE(ClassDebugTrace, ("ClassDispatchPnp (%p,%p): leaving with previous %#x, current %#x.", DeviceObject, Irp, commonExtension->PreviousState, commonExtension->CurrentState));
|
|
}
|
|
else {
|
|
/*
|
|
* The irp is already completed so don't touch it.
|
|
* This may be a remove so don't touch the device extension.
|
|
*/
|
|
DBGTRACE(ClassDebugTrace, ("ClassDispatchPnp (%p,%p): leaving.", DeviceObject, Irp));
|
|
}
|
|
|
|
return status;
|
|
} // end ClassDispatchPnp()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassPnpStartDevice()
|
|
|
|
Routine Description:
|
|
|
|
Storage class driver routine for IRP_MN_START_DEVICE requests.
|
|
This routine kicks off any device specific initialization
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object
|
|
|
|
Irp - a pointer to the io request packet
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
NTSTATUS ClassPnpStartDevice(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
PCLASS_DRIVER_EXTENSION driverExtension;
|
|
PCLASS_INIT_DATA initData;
|
|
|
|
PCLASS_DEV_INFO devInfo;
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
BOOLEAN isFdo = commonExtension->IsFdo;
|
|
|
|
BOOLEAN isMountedDevice = TRUE;
|
|
UNICODE_STRING interfaceName;
|
|
|
|
BOOLEAN timerStarted;
|
|
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
PAGED_CODE();
|
|
|
|
driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
|
|
initData = &(driverExtension->InitData);
|
|
if(isFdo) {
|
|
devInfo = &(initData->FdoData);
|
|
} else {
|
|
devInfo = &(initData->PdoData);
|
|
}
|
|
|
|
ASSERT(devInfo->ClassInitDevice != NULL);
|
|
ASSERT(devInfo->ClassStartDevice != NULL);
|
|
|
|
if (!commonExtension->IsInitialized){
|
|
|
|
//
|
|
// perform FDO/PDO specific initialization
|
|
//
|
|
|
|
if (isFdo){
|
|
STORAGE_PROPERTY_ID propertyId;
|
|
|
|
//
|
|
// allocate a private extension for class data
|
|
//
|
|
|
|
if (fdoExtension->PrivateFdoData == NULL) {
|
|
fdoExtension->PrivateFdoData =
|
|
ExAllocatePoolWithTag(NonPagedPool,
|
|
sizeof(CLASS_PRIVATE_FDO_DATA),
|
|
CLASS_TAG_PRIVATE_DATA
|
|
);
|
|
}
|
|
|
|
if (fdoExtension->PrivateFdoData == NULL) {
|
|
DebugPrint((0, "ClassPnpStartDevice: Cannot allocate for "
|
|
"private fdo data\n"));
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// initialize the struct's various fields.
|
|
//
|
|
|
|
RtlZeroMemory(fdoExtension->PrivateFdoData,
|
|
sizeof(CLASS_PRIVATE_FDO_DATA)
|
|
);
|
|
KeInitializeTimer(&fdoExtension->PrivateFdoData->Retry.Timer);
|
|
KeInitializeDpc(&fdoExtension->PrivateFdoData->Retry.Dpc,
|
|
ClasspRetryRequestDpc,
|
|
DeviceObject);
|
|
KeInitializeSpinLock(&fdoExtension->PrivateFdoData->Retry.Lock);
|
|
fdoExtension->PrivateFdoData->Retry.Granularity =
|
|
KeQueryTimeIncrement();
|
|
commonExtension->Reserved4 = (ULONG_PTR)(' GPH'); // debug aid
|
|
|
|
//
|
|
// NOTE: the old interface allowed the class driver to allocate
|
|
// this. this was unsafe for low-memory conditions. allocate one
|
|
// unconditionally now, and modify our internal functions to use
|
|
// our own exclusively as it is the only safe way to do this.
|
|
//
|
|
|
|
status = ClasspAllocateReleaseQueueIrp(fdoExtension);
|
|
if (!NT_SUCCESS(status)) {
|
|
DebugPrint((0, "ClassPnpStartDevice: Cannot allocate the "
|
|
"private release queue irp\n"));
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Call port driver to get adapter capabilities.
|
|
//
|
|
|
|
propertyId = StorageAdapterProperty;
|
|
|
|
status = ClassGetDescriptor(
|
|
commonExtension->LowerDeviceObject,
|
|
&propertyId,
|
|
&fdoExtension->AdapterDescriptor);
|
|
|
|
if(!NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// This DebugPrint is to help third-party driver writers
|
|
//
|
|
|
|
DebugPrint((0, "ClassPnpStartDevice: ClassGetDescriptor "
|
|
"[ADAPTER] failed %lx\n", status));
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Call port driver to get device descriptor.
|
|
//
|
|
|
|
propertyId = StorageDeviceProperty;
|
|
|
|
status = ClassGetDescriptor(
|
|
commonExtension->LowerDeviceObject,
|
|
&propertyId,
|
|
&fdoExtension->DeviceDescriptor);
|
|
|
|
if(!NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// This DebugPrint is to help third-party driver writers
|
|
//
|
|
|
|
DebugPrint((0, "ClassPnpStartDevice: ClassGetDescriptor "
|
|
"[DEVICE] failed %lx\n", status));
|
|
return status;
|
|
}
|
|
|
|
ClasspScanForSpecialInRegistry(fdoExtension);
|
|
ClassScanForSpecial(fdoExtension,
|
|
ClassBadItems,
|
|
ClasspScanForClassHacks);
|
|
|
|
//
|
|
// allow perf to be re-enabled after a given number of failed IOs
|
|
// require this number to be at least CLASS_PERF_RESTORE_MINIMUM
|
|
//
|
|
|
|
{
|
|
ULONG t = 0;
|
|
ClassGetDeviceParameter(fdoExtension,
|
|
CLASSP_REG_SUBKEY_NAME,
|
|
CLASSP_REG_PERF_RESTORE_VALUE_NAME,
|
|
&t);
|
|
if (t >= CLASS_PERF_RESTORE_MINIMUM) {
|
|
fdoExtension->PrivateFdoData->Perf.ReEnableThreshhold = t;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// compatibility comes first. writable cd media will not
|
|
// get a SYNCH_CACHE on power down.
|
|
//
|
|
|
|
if (fdoExtension->DeviceObject->DeviceType != FILE_DEVICE_DISK) {
|
|
SET_FLAG(fdoExtension->PrivateFdoData->HackFlags,
|
|
FDO_HACK_NO_SYNC_CACHE);
|
|
}
|
|
|
|
//
|
|
// initialize the hotplug information only after the ScanForSpecial
|
|
// routines, as it relies upon the hack flags.
|
|
//
|
|
|
|
status = ClasspInitializeHotplugInfo(fdoExtension);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
DebugPrint((1, "ClassPnpStartDevice: Could not initialize "
|
|
"hotplug information %lx\n", status));
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Allocate/initialize TRANSFER_PACKETs and related resources.
|
|
*/
|
|
status = InitializeTransferPackets(DeviceObject);
|
|
}
|
|
|
|
//
|
|
// ISSUE - drivers need to disable write caching on the media
|
|
// if hotplug and !useroverride. perhaps we should
|
|
// allow registration of a callback to enable/disable
|
|
// write cache instead.
|
|
//
|
|
|
|
if (NT_SUCCESS(status)){
|
|
status = devInfo->ClassInitDevice(DeviceObject);
|
|
}
|
|
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)){
|
|
|
|
//
|
|
// Just bail out - the remove that comes down will clean up the
|
|
// initialized scraps.
|
|
//
|
|
|
|
return status;
|
|
} else {
|
|
commonExtension->IsInitialized = TRUE;
|
|
|
|
if (commonExtension->IsFdo) {
|
|
fdoExtension->PrivateFdoData->Perf.OriginalSrbFlags = fdoExtension->SrbFlags;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// If device requests autorun functionality or a once a second callback
|
|
// then enable the once per second timer.
|
|
//
|
|
// NOTE: This assumes that ClassInitializeMediaChangeDetection is always
|
|
// called in the context of the ClassInitDevice callback. If called
|
|
// after then this check will have already been made and the
|
|
// once a second timer will not have been enabled.
|
|
//
|
|
if ((isFdo) &&
|
|
((initData->ClassTick != NULL) ||
|
|
(fdoExtension->MediaChangeDetectionInfo != NULL) ||
|
|
((fdoExtension->FailurePredictionInfo != NULL) &&
|
|
(fdoExtension->FailurePredictionInfo->Method != FailurePredictionNone))))
|
|
{
|
|
ClasspEnableTimer(DeviceObject);
|
|
timerStarted = TRUE;
|
|
} else {
|
|
timerStarted = FALSE;
|
|
}
|
|
|
|
//
|
|
// NOTE: the timer looks at commonExtension->CurrentState now
|
|
// to prevent Media Change Notification code from running
|
|
// until the device is started, but allows the device
|
|
// specific tick handler to run. therefore it is imperative
|
|
// that commonExtension->CurrentState not be updated until
|
|
// the device specific startdevice handler has finished.
|
|
//
|
|
|
|
status = devInfo->ClassStartDevice(DeviceObject);
|
|
|
|
if(NT_SUCCESS(status)) {
|
|
commonExtension->CurrentState = IRP_MN_START_DEVICE;
|
|
|
|
if((isFdo) && (initData->ClassEnumerateDevice != NULL)) {
|
|
isMountedDevice = FALSE;
|
|
}
|
|
|
|
if((DeviceObject->DeviceType != FILE_DEVICE_DISK) &&
|
|
(DeviceObject->DeviceType != FILE_DEVICE_CD_ROM)) {
|
|
|
|
isMountedDevice = FALSE;
|
|
}
|
|
|
|
|
|
if(isMountedDevice) {
|
|
ClasspRegisterMountedDeviceInterface(DeviceObject);
|
|
}
|
|
|
|
if((commonExtension->IsFdo) &&
|
|
(devInfo->ClassWmiInfo.GuidRegInfo != NULL)) {
|
|
|
|
IoWMIRegistrationControl(DeviceObject, WMIREG_ACTION_REGISTER);
|
|
}
|
|
} else {
|
|
|
|
if (timerStarted) {
|
|
ClasspDisableTimer(DeviceObject);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassReadWrite()
|
|
|
|
Routine Description:
|
|
|
|
This is the system entry point for read and write requests. The
|
|
device-specific handler is invoked to perform any validation necessary.
|
|
|
|
If the device object is a PDO (partition object) then the request will
|
|
simply be adjusted for Partition0 and issued to the lower device driver.
|
|
|
|
IF the device object is an FDO (paritition 0 object), the number of bytes
|
|
in the request are checked against the maximum byte counts that the adapter
|
|
supports and requests are broken up into
|
|
smaller sizes if necessary.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object for this request
|
|
|
|
Irp - IO request
|
|
|
|
Return Value:
|
|
|
|
NT Status
|
|
|
|
--*/
|
|
NTSTATUS ClassReadWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PDEVICE_OBJECT lowerDeviceObject = commonExtension->LowerDeviceObject;
|
|
PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
LARGE_INTEGER startingOffset = currentIrpStack->Parameters.Read.ByteOffset;
|
|
ULONG transferByteCount = currentIrpStack->Parameters.Read.Length;
|
|
ULONG isRemoved;
|
|
NTSTATUS status;
|
|
|
|
/*
|
|
* Grab the remove lock. If we can't acquire it, bail out.
|
|
*/
|
|
isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
|
|
if (isRemoved) {
|
|
Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
}
|
|
else if (TEST_FLAG(DeviceObject->Flags, DO_VERIFY_VOLUME) &&
|
|
(currentIrpStack->MinorFunction != CLASSP_VOLUME_VERIFY_CHECKED) &&
|
|
!TEST_FLAG(currentIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME)){
|
|
|
|
/*
|
|
* DO_VERIFY_VOLUME is set for the device object,
|
|
* but this request is not itself a verify request.
|
|
* So fail this request.
|
|
*/
|
|
IoSetHardErrorOrVerifyDevice(Irp, DeviceObject);
|
|
Irp->IoStatus.Status = STATUS_VERIFY_REQUIRED;
|
|
Irp->IoStatus.Information = 0;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, 0);
|
|
status = STATUS_VERIFY_REQUIRED;
|
|
}
|
|
else {
|
|
|
|
/*
|
|
* Since we've bypassed the verify-required tests we don't need to repeat
|
|
* them with this IRP - in particular we don't want to worry about
|
|
* hitting them at the partition 0 level if the request has come through
|
|
* a non-zero partition.
|
|
*/
|
|
currentIrpStack->MinorFunction = CLASSP_VOLUME_VERIFY_CHECKED;
|
|
|
|
/*
|
|
* Call the miniport driver's pre-pass filter to check if we
|
|
* should continue with this transfer.
|
|
*/
|
|
ASSERT(commonExtension->DevInfo->ClassReadWriteVerification);
|
|
status = commonExtension->DevInfo->ClassReadWriteVerification(DeviceObject, Irp);
|
|
if (!NT_SUCCESS(status)){
|
|
ASSERT(Irp->IoStatus.Status == status);
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest (DeviceObject, Irp, IO_NO_INCREMENT);
|
|
}
|
|
else if (status == STATUS_PENDING){
|
|
/*
|
|
* ClassReadWriteVerification queued this request.
|
|
* So don't touch the irp anymore.
|
|
*/
|
|
}
|
|
else {
|
|
|
|
if (transferByteCount == 0) {
|
|
/*
|
|
* Several parts of the code turn 0 into 0xffffffff,
|
|
* so don't process a zero-length request any further.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = 0;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
/*
|
|
* If the driver has its own StartIo routine, call it.
|
|
*/
|
|
if (commonExtension->DriverExtension->InitData.ClassStartIo) {
|
|
IoMarkIrpPending(Irp);
|
|
IoStartPacket(DeviceObject, Irp, NULL, NULL);
|
|
status = STATUS_PENDING;
|
|
}
|
|
else {
|
|
/*
|
|
* The driver does not have its own StartIo routine.
|
|
* So process this request ourselves.
|
|
*/
|
|
|
|
/*
|
|
* Add partition byte offset to make starting byte relative to
|
|
* beginning of disk.
|
|
*/
|
|
currentIrpStack->Parameters.Read.ByteOffset.QuadPart +=
|
|
commonExtension->StartingOffset.QuadPart;
|
|
|
|
if (commonExtension->IsFdo){
|
|
|
|
/*
|
|
* Add in any skew for the disk manager software.
|
|
*/
|
|
currentIrpStack->Parameters.Read.ByteOffset.QuadPart +=
|
|
commonExtension->PartitionZeroExtension->DMByteSkew;
|
|
|
|
/*
|
|
* Perform the actual transfer(s) on the hardware
|
|
* to service this request.
|
|
*/
|
|
ServiceTransferRequest(DeviceObject, Irp);
|
|
status = STATUS_PENDING;
|
|
}
|
|
else {
|
|
/*
|
|
* This is a child PDO enumerated for our FDO by e.g. disk.sys
|
|
* and owned by e.g. partmgr. Send it down to the next device
|
|
* and the same irp will come back to us for the FDO.
|
|
*/
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(lowerDeviceObject, Irp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassReadDriveCapacity()
|
|
|
|
Routine Description:
|
|
|
|
This routine sends a READ CAPACITY to the requested device, updates
|
|
the geometry information in the device object and returns
|
|
when it is complete. This routine is synchronous.
|
|
|
|
This routine must be called with the remove lock held or some other
|
|
assurance that the Fdo will not be removed while processing.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies a pointer to the device object that represents
|
|
the device whose capacity is to be read.
|
|
|
|
Return Value:
|
|
|
|
Status is returned.
|
|
|
|
--*/
|
|
NTSTATUS ClassReadDriveCapacity(IN PDEVICE_OBJECT Fdo)
|
|
{
|
|
READ_CAPACITY_DATA readCapacityBuffer = {0};
|
|
NTSTATUS status;
|
|
PMDL driveCapMdl;
|
|
|
|
driveCapMdl = BuildDeviceInputMdl(&readCapacityBuffer, sizeof(READ_CAPACITY_DATA));
|
|
if (driveCapMdl){
|
|
|
|
TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE);
|
|
if (pkt){
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
|
|
KEVENT event;
|
|
NTSTATUS pktStatus;
|
|
IRP pseudoIrp = {0};
|
|
|
|
/*
|
|
* Our engine needs an "original irp" to write the status back to
|
|
* and to count down packets (one in this case).
|
|
* Just use a pretend irp for this.
|
|
*/
|
|
pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
|
|
pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
|
|
pseudoIrp.IoStatus.Information = 0;
|
|
pseudoIrp.MdlAddress = driveCapMdl;
|
|
|
|
/*
|
|
* Set this up as a SYNCHRONOUS transfer, submit it,
|
|
* and wait for the packet to complete. The result
|
|
* status will be written to the original irp.
|
|
*/
|
|
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
|
|
SetupDriveCapacityTransferPacket( pkt,
|
|
&readCapacityBuffer,
|
|
sizeof(READ_CAPACITY_DATA),
|
|
&event,
|
|
&pseudoIrp);
|
|
SubmitTransferPacket(pkt);
|
|
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
|
|
|
status = pseudoIrp.IoStatus.Status;
|
|
|
|
/*
|
|
* If we got an UNDERRUN, retry exactly once.
|
|
* (The transfer_packet engine didn't retry because the result
|
|
* status was success).
|
|
*/
|
|
if (NT_SUCCESS(status) &&
|
|
(pseudoIrp.IoStatus.Information < sizeof(READ_CAPACITY_DATA))){
|
|
DBGERR(("ClassReadDriveCapacity: read len (%xh) < %xh, retrying ...", (ULONG)pseudoIrp.IoStatus.Information, sizeof(READ_CAPACITY_DATA)));
|
|
|
|
pkt = DequeueFreeTransferPacket(Fdo, TRUE);
|
|
if (pkt){
|
|
pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
|
|
pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
|
|
pseudoIrp.IoStatus.Information = 0;
|
|
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
|
|
SetupDriveCapacityTransferPacket( pkt,
|
|
&readCapacityBuffer,
|
|
sizeof(READ_CAPACITY_DATA),
|
|
&event,
|
|
&pseudoIrp);
|
|
SubmitTransferPacket(pkt);
|
|
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
|
status = pseudoIrp.IoStatus.Status;
|
|
if (pseudoIrp.IoStatus.Information < sizeof(READ_CAPACITY_DATA)){
|
|
status = STATUS_DEVICE_BUSY;
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
|
|
if (NT_SUCCESS(status)){
|
|
/*
|
|
* The request succeeded.
|
|
* Read out and store the drive information.
|
|
*/
|
|
ULONG cylinderSize;
|
|
ULONG bytesPerSector;
|
|
ULONG tmp;
|
|
ULONG lastSector;
|
|
|
|
/*
|
|
* Read the bytesPerSector value,
|
|
* which is big-endian in the returned buffer.
|
|
* Default to the standard 512 bytes.
|
|
*/
|
|
tmp = readCapacityBuffer.BytesPerBlock;
|
|
((PFOUR_BYTE)&bytesPerSector)->Byte0 = ((PFOUR_BYTE)&tmp)->Byte3;
|
|
((PFOUR_BYTE)&bytesPerSector)->Byte1 = ((PFOUR_BYTE)&tmp)->Byte2;
|
|
((PFOUR_BYTE)&bytesPerSector)->Byte2 = ((PFOUR_BYTE)&tmp)->Byte1;
|
|
((PFOUR_BYTE)&bytesPerSector)->Byte3 = ((PFOUR_BYTE)&tmp)->Byte0;
|
|
if (bytesPerSector == 0) {
|
|
bytesPerSector = 512;
|
|
}
|
|
else {
|
|
/*
|
|
* Clear all but the highest set bit.
|
|
* That will give us a bytesPerSector value that is a power of 2.
|
|
*/
|
|
while (bytesPerSector & (bytesPerSector-1)) {
|
|
bytesPerSector &= bytesPerSector-1;
|
|
}
|
|
}
|
|
fdoExt->DiskGeometry.BytesPerSector = bytesPerSector;
|
|
|
|
//
|
|
// Copy last sector in reverse byte order.
|
|
//
|
|
|
|
tmp = readCapacityBuffer.LogicalBlockAddress;
|
|
((PFOUR_BYTE)&lastSector)->Byte0 = ((PFOUR_BYTE)&tmp)->Byte3;
|
|
((PFOUR_BYTE)&lastSector)->Byte1 = ((PFOUR_BYTE)&tmp)->Byte2;
|
|
((PFOUR_BYTE)&lastSector)->Byte2 = ((PFOUR_BYTE)&tmp)->Byte1;
|
|
((PFOUR_BYTE)&lastSector)->Byte3 = ((PFOUR_BYTE)&tmp)->Byte0;
|
|
|
|
//
|
|
// Calculate sector to byte shift.
|
|
//
|
|
|
|
WHICH_BIT(fdoExt->DiskGeometry.BytesPerSector, fdoExt->SectorShift);
|
|
|
|
DebugPrint((2,"SCSI ClassReadDriveCapacity: Sector size is %d\n",
|
|
fdoExt->DiskGeometry.BytesPerSector));
|
|
|
|
DebugPrint((2,"SCSI ClassReadDriveCapacity: Number of Sectors is %d\n",
|
|
lastSector + 1));
|
|
|
|
if (fdoExt->DMActive){
|
|
DebugPrint((1, "SCSI ClassReadDriveCapacity: reducing number of sectors by %d\n",
|
|
fdoExt->DMSkew));
|
|
lastSector -= fdoExt->DMSkew;
|
|
}
|
|
|
|
/*
|
|
* Check to see if we have a geometry we should be using already.
|
|
*/
|
|
cylinderSize = (fdoExt->DiskGeometry.TracksPerCylinder *
|
|
fdoExt->DiskGeometry.SectorsPerTrack);
|
|
if (cylinderSize == 0){
|
|
DebugPrint((1, "ClassReadDriveCapacity: resetting H & S geometry "
|
|
"values from %#x/%#x to %#x/%#x\n",
|
|
fdoExt->DiskGeometry.TracksPerCylinder,
|
|
fdoExt->DiskGeometry.SectorsPerTrack,
|
|
0xff,
|
|
0x3f));
|
|
|
|
fdoExt->DiskGeometry.TracksPerCylinder = 0xff;
|
|
fdoExt->DiskGeometry.SectorsPerTrack = 0x3f;
|
|
|
|
|
|
cylinderSize = (fdoExt->DiskGeometry.TracksPerCylinder *
|
|
fdoExt->DiskGeometry.SectorsPerTrack);
|
|
}
|
|
|
|
//
|
|
// Calculate number of cylinders.
|
|
//
|
|
|
|
fdoExt->DiskGeometry.Cylinders.QuadPart = (LONGLONG)((lastSector + 1)/cylinderSize);
|
|
|
|
//
|
|
// if there are zero cylinders, then the device lied AND it's
|
|
// smaller than 0xff*0x3f (about 16k sectors, usually 8 meg)
|
|
// this can fit into a single LONGLONG, so create another usable
|
|
// geometry, even if it's unusual looking. This allows small,
|
|
// non-standard devices, such as Sony's Memory Stick, to show
|
|
// up as having a partition.
|
|
//
|
|
|
|
if (fdoExt->DiskGeometry.Cylinders.QuadPart == (LONGLONG)0) {
|
|
fdoExt->DiskGeometry.SectorsPerTrack = 1;
|
|
fdoExt->DiskGeometry.TracksPerCylinder = 1;
|
|
fdoExt->DiskGeometry.Cylinders.QuadPart = lastSector;
|
|
}
|
|
|
|
|
|
//
|
|
// Calculate media capacity in bytes.
|
|
//
|
|
|
|
fdoExt->CommonExtension.PartitionLength.QuadPart =
|
|
((LONGLONG)(lastSector + 1)) << fdoExt->SectorShift;
|
|
|
|
/*
|
|
* Is this removable or fixed media
|
|
*/
|
|
if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){
|
|
fdoExt->DiskGeometry.MediaType = RemovableMedia;
|
|
}
|
|
else {
|
|
fdoExt->DiskGeometry.MediaType = FixedMedia;
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* The request failed.
|
|
*/
|
|
|
|
//
|
|
// ISSUE - 2000/02/04 - henrygab - non-512-byte sector sizes and failed geometry update
|
|
// what happens when the disk's sector size is bigger than
|
|
// 512 bytes and we hit this code path? this is untested.
|
|
//
|
|
// If the read capacity fails, set the geometry to reasonable parameter
|
|
// so things don't fail at unexpected places. Zero the geometry
|
|
// except for the bytes per sector and sector shift.
|
|
//
|
|
|
|
/*
|
|
* This request can sometimes fail legitimately
|
|
* (e.g. when a SCSI device is attached but turned off)
|
|
* so this is not necessarily a device/driver bug.
|
|
*/
|
|
DBGTRACE(ClassDebugWarning, ("ClassReadDriveCapacity on Fdo %xh failed with status %xh.", Fdo, status));
|
|
|
|
/*
|
|
* Write in a default disk geometry which we HOPE is right (??).
|
|
* BUGBUG !!
|
|
*/
|
|
RtlZeroMemory(&fdoExt->DiskGeometry, sizeof(DISK_GEOMETRY));
|
|
fdoExt->DiskGeometry.BytesPerSector = 512;
|
|
fdoExt->SectorShift = 9;
|
|
fdoExt->CommonExtension.PartitionLength.QuadPart = (LONGLONG) 0;
|
|
|
|
/*
|
|
* Is this removable or fixed media
|
|
*/
|
|
if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){
|
|
fdoExt->DiskGeometry.MediaType = RemovableMedia;
|
|
}
|
|
else {
|
|
fdoExt->DiskGeometry.MediaType = FixedMedia;
|
|
}
|
|
}
|
|
|
|
}
|
|
else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
FreeDeviceInputMdl(driveCapMdl);
|
|
}
|
|
else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassSendStartUnit()
|
|
|
|
Routine Description:
|
|
|
|
Send command to SCSI unit to start or power up.
|
|
Because this command is issued asynchronounsly, that is, without
|
|
waiting on it to complete, the IMMEDIATE flag is not set. This
|
|
means that the CDB will not return until the drive has powered up.
|
|
This should keep subsequent requests from being submitted to the
|
|
device before it has completely spun up.
|
|
|
|
This routine is called from the InterpretSense routine, when a
|
|
request sense returns data indicating that a drive must be
|
|
powered up.
|
|
|
|
This routine may also be called from a class driver's error handler,
|
|
or anytime a non-critical start device should be sent to the device.
|
|
|
|
Arguments:
|
|
|
|
Fdo - The functional device object for the stopped device.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
VOID
|
|
ClassSendStartUnit(
|
|
IN PDEVICE_OBJECT Fdo
|
|
)
|
|
{
|
|
PIO_STACK_LOCATION irpStack;
|
|
PIRP irp;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PSCSI_REQUEST_BLOCK srb;
|
|
PCOMPLETION_CONTEXT context;
|
|
PCDB cdb;
|
|
|
|
//
|
|
// Allocate Srb from nonpaged pool.
|
|
//
|
|
|
|
context = ExAllocatePoolWithTag(NonPagedPool,
|
|
sizeof(COMPLETION_CONTEXT),
|
|
'6CcS');
|
|
|
|
if(context == NULL) {
|
|
|
|
//
|
|
// ISSUE-2000/02/03-peterwie
|
|
// This code path was inheritted from the NT 4.0 class2.sys driver.
|
|
// It needs to be changed to survive low-memory conditions.
|
|
//
|
|
|
|
KeBugCheck(SCSI_DISK_DRIVER_INTERNAL);
|
|
}
|
|
|
|
//
|
|
// Save the device object in the context for use by the completion
|
|
// routine.
|
|
//
|
|
|
|
context->DeviceObject = Fdo;
|
|
srb = &context->Srb;
|
|
|
|
//
|
|
// Zero out srb.
|
|
//
|
|
|
|
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
|
|
|
|
//
|
|
// Write length to SRB.
|
|
//
|
|
|
|
srb->Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
|
|
srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
|
|
|
|
//
|
|
// Set timeout value large enough for drive to spin up.
|
|
//
|
|
|
|
srb->TimeOutValue = START_UNIT_TIMEOUT;
|
|
|
|
//
|
|
// Set the transfer length.
|
|
//
|
|
|
|
srb->SrbFlags = SRB_FLAGS_NO_DATA_TRANSFER |
|
|
SRB_FLAGS_DISABLE_AUTOSENSE |
|
|
SRB_FLAGS_DISABLE_SYNCH_TRANSFER;
|
|
|
|
//
|
|
// Build the start unit CDB.
|
|
//
|
|
|
|
srb->CdbLength = 6;
|
|
cdb = (PCDB)srb->Cdb;
|
|
|
|
cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
|
|
cdb->START_STOP.Start = 1;
|
|
cdb->START_STOP.Immediate = 0;
|
|
cdb->START_STOP.LogicalUnitNumber = srb->Lun;
|
|
|
|
//
|
|
// Build the asynchronous request to be sent to the port driver.
|
|
// Since this routine is called from a DPC the IRP should always be
|
|
// available.
|
|
//
|
|
|
|
irp = IoAllocateIrp(Fdo->StackSize, FALSE);
|
|
|
|
if(irp == NULL) {
|
|
|
|
//
|
|
// ISSUE-2000/02/03-peterwie
|
|
// This code path was inheritted from the NT 4.0 class2.sys driver.
|
|
// It needs to be changed to survive low-memory conditions.
|
|
//
|
|
|
|
KeBugCheck(SCSI_DISK_DRIVER_INTERNAL);
|
|
|
|
}
|
|
|
|
ClassAcquireRemoveLock(Fdo, irp);
|
|
|
|
IoSetCompletionRoutine(irp,
|
|
(PIO_COMPLETION_ROUTINE)ClassAsynchronousCompletion,
|
|
context,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE);
|
|
|
|
irpStack = IoGetNextIrpStackLocation(irp);
|
|
irpStack->MajorFunction = IRP_MJ_SCSI;
|
|
srb->OriginalRequest = irp;
|
|
|
|
//
|
|
// Store the SRB address in next stack for port driver.
|
|
//
|
|
|
|
irpStack->Parameters.Scsi.Srb = srb;
|
|
|
|
//
|
|
// Call the port driver with the IRP.
|
|
//
|
|
|
|
IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp);
|
|
|
|
return;
|
|
|
|
} // end StartUnit()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassAsynchronousCompletion() ISSUE-2000/02/18-henrygab - why public?!
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when an asynchronous I/O request
|
|
which was issused by the class driver completes. Examples of such requests
|
|
are release queue or START UNIT. This routine releases the queue if
|
|
necessary. It then frees the context and the IRP.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - The device object for the logical unit; however since this
|
|
is the top stack location the value is NULL.
|
|
|
|
Irp - Supplies a pointer to the Irp to be processed.
|
|
|
|
Context - Supplies the context to be used to process this request.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassAsynchronousCompletion(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp,
|
|
PVOID Context
|
|
)
|
|
{
|
|
PCOMPLETION_CONTEXT context = Context;
|
|
PSCSI_REQUEST_BLOCK srb;
|
|
|
|
if(DeviceObject == NULL) {
|
|
|
|
DeviceObject = context->DeviceObject;
|
|
}
|
|
|
|
srb = &context->Srb;
|
|
|
|
//
|
|
// If this is an execute srb, then check the return status and make sure.
|
|
// the queue is not frozen.
|
|
//
|
|
|
|
if (srb->Function == SRB_FUNCTION_EXECUTE_SCSI) {
|
|
|
|
//
|
|
// Check for a frozen queue.
|
|
//
|
|
|
|
if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) {
|
|
|
|
//
|
|
// Unfreeze the queue getting the device object from the context.
|
|
//
|
|
|
|
ClassReleaseQueue(context->DeviceObject);
|
|
}
|
|
}
|
|
|
|
{ // free port-allocated sense buffer if we can detect
|
|
|
|
if (((PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension))->IsFdo) {
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
|
|
FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
|
|
}
|
|
|
|
} else {
|
|
|
|
ASSERT(!TEST_FLAG(srb->SrbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));
|
|
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Free the context and the Irp.
|
|
//
|
|
|
|
if (Irp->MdlAddress != NULL) {
|
|
MmUnlockPages(Irp->MdlAddress);
|
|
IoFreeMdl(Irp->MdlAddress);
|
|
|
|
Irp->MdlAddress = NULL;
|
|
}
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
ExFreePool(context);
|
|
IoFreeIrp(Irp);
|
|
|
|
//
|
|
// Indicate the I/O system should stop processing the Irp completion.
|
|
//
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
|
|
} // end ClassAsynchronousCompletion()
|
|
|
|
|
|
|
|
VOID ServiceTransferRequest(PDEVICE_OBJECT Fdo, PIRP Irp)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExt = Fdo->DeviceExtension;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
|
|
PSTORAGE_ADAPTER_DESCRIPTOR adapterDesc = commonExt->PartitionZeroExtension->AdapterDescriptor;
|
|
PIO_STACK_LOCATION currentSp = IoGetCurrentIrpStackLocation(Irp);
|
|
ULONG entireXferLen = currentSp->Parameters.Read.Length;
|
|
PUCHAR bufPtr = MmGetMdlVirtualAddress(Irp->MdlAddress);
|
|
LARGE_INTEGER targetLocation = currentSp->Parameters.Read.ByteOffset;
|
|
PTRANSFER_PACKET pkt;
|
|
SINGLE_LIST_ENTRY pktList;
|
|
PSINGLE_LIST_ENTRY slistEntry;
|
|
ULONG numPackets;
|
|
KIRQL oldIrql;
|
|
ULONG i;
|
|
|
|
/*
|
|
* Compute the number of hw xfers we'll have to do.
|
|
* Calculate this without allowing for an overflow condition.
|
|
*/
|
|
ASSERT(fdoData->HwMaxXferLen >= PAGE_SIZE);
|
|
numPackets = entireXferLen/fdoData->HwMaxXferLen;
|
|
if (entireXferLen % fdoData->HwMaxXferLen){
|
|
numPackets++;
|
|
}
|
|
|
|
/*
|
|
* First get all the TRANSFER_PACKETs that we'll need at once.
|
|
* Use our 'simple' slist functions since we don't need interlocked.
|
|
*/
|
|
SimpleInitSlistHdr(&pktList);
|
|
for (i = 0; i < numPackets; i++){
|
|
pkt = DequeueFreeTransferPacket(Fdo, TRUE);
|
|
if (pkt){
|
|
SimplePushSlist(&pktList, &pkt->SlistEntry);
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == numPackets){
|
|
/*
|
|
* Initialize the original IRP's status to success.
|
|
* If any of the packets fail, they will set it to an error status.
|
|
* The IoStatus.Information field will be incremented to the
|
|
* transfer length as the pieces complete.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
/*
|
|
* Store the number of transfer pieces inside the original IRP.
|
|
* It will be used to count down the pieces as they complete.
|
|
*/
|
|
Irp->Tail.Overlay.DriverContext[0] = LongToPtr(numPackets);
|
|
|
|
/*
|
|
* We are proceeding with the transfer.
|
|
* Mark the client IRP pending since it may complete on a different thread.
|
|
*/
|
|
IoMarkIrpPending(Irp);
|
|
|
|
/*
|
|
* Transmit the pieces of the transfer.
|
|
*/
|
|
while (entireXferLen > 0){
|
|
ULONG thisPieceLen = MIN(fdoData->HwMaxXferLen, entireXferLen);
|
|
|
|
/*
|
|
* Set up a TRANSFER_PACKET for this piece and send it.
|
|
*/
|
|
slistEntry = SimplePopSlist(&pktList);
|
|
ASSERT(slistEntry);
|
|
pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
|
|
SetupReadWriteTransferPacket( pkt,
|
|
bufPtr,
|
|
thisPieceLen,
|
|
targetLocation,
|
|
Irp);
|
|
SubmitTransferPacket(pkt);
|
|
|
|
entireXferLen -= thisPieceLen;
|
|
bufPtr += thisPieceLen;
|
|
targetLocation.QuadPart += thisPieceLen;
|
|
}
|
|
ASSERT(SimpleIsSlistEmpty(&pktList));
|
|
}
|
|
else if (i >= 1){
|
|
/*
|
|
* We were unable to get all the TRANSFER_PACKETs we need,
|
|
* but we did get at least one.
|
|
* That means that we are in extreme low-memory stress.
|
|
* We'll try doing this transfer using a single packet.
|
|
* The port driver is certainly also in stress, so use one-page
|
|
* transfers.
|
|
*/
|
|
|
|
/*
|
|
* Free all but one of the TRANSFER_PACKETs.
|
|
*/
|
|
while (i-- > 1){
|
|
slistEntry = SimplePopSlist(&pktList);
|
|
ASSERT(slistEntry);
|
|
pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
|
|
EnqueueFreeTransferPacket(Fdo, pkt);
|
|
}
|
|
|
|
/*
|
|
* Get the single TRANSFER_PACKET that we'll be using.
|
|
*/
|
|
slistEntry = SimplePopSlist(&pktList);
|
|
ASSERT(slistEntry);
|
|
ASSERT(SimpleIsSlistEmpty(&pktList));
|
|
pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
|
|
DBGWARN(("Insufficient packets available in ServiceTransferRequest - entering lowMemRetry with pkt=%xh.", pkt));
|
|
|
|
/*
|
|
* Set default status and the number of transfer packets (one)
|
|
* inside the original irp.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = 0;
|
|
Irp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
|
|
|
|
/*
|
|
* Mark the client irp pending since it may complete on
|
|
* another thread.
|
|
*/
|
|
IoMarkIrpPending(Irp);
|
|
|
|
/*
|
|
* Set up the TRANSFER_PACKET for a lowMem transfer and launch.
|
|
*/
|
|
SetupReadWriteTransferPacket( pkt,
|
|
bufPtr,
|
|
entireXferLen,
|
|
targetLocation,
|
|
Irp);
|
|
InitLowMemRetry(pkt, bufPtr, entireXferLen, targetLocation);
|
|
StepLowMemRetry(pkt);
|
|
}
|
|
else {
|
|
/*
|
|
* We were unable to get ANY TRANSFER_PACKETs.
|
|
* Defer this client irp until some TRANSFER_PACKETs free up.
|
|
*/
|
|
DBGWARN(("No packets available in ServiceTransferRequest - deferring transfer (Irp=%xh)...", Irp));
|
|
IoMarkIrpPending(Irp);
|
|
EnqueueDeferredClientIrp(fdoData, Irp);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassIoComplete()
|
|
|
|
Routine Description:
|
|
|
|
This routine executes when the port driver has completed a request.
|
|
It looks at the SRB status in the completing SRB and if not success
|
|
it checks for valid request sense buffer information. If valid, the
|
|
info is used to update status with more precise message of type of
|
|
error. This routine deallocates the SRB.
|
|
|
|
This routine should only be placed on the stack location for a class
|
|
driver FDO.
|
|
|
|
Arguments:
|
|
|
|
Fdo - Supplies the device object which represents the logical
|
|
unit.
|
|
|
|
Irp - Supplies the Irp which has completed.
|
|
|
|
Context - Supplies a pointer to the SRB.
|
|
|
|
Return Value:
|
|
|
|
NT status
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassIoComplete(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PIRP Irp,
|
|
IN PVOID Context
|
|
)
|
|
{
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PSCSI_REQUEST_BLOCK srb = Context;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
|
|
NTSTATUS status;
|
|
BOOLEAN retry;
|
|
BOOLEAN callStartNextPacket;
|
|
|
|
ASSERT(fdoExtension->CommonExtension.IsFdo);
|
|
|
|
//
|
|
// Check SRB status for success of completing request.
|
|
//
|
|
|
|
if (SRB_STATUS(srb->SrbStatus) != SRB_STATUS_SUCCESS) {
|
|
ULONG retryInterval;
|
|
|
|
DebugPrint((2, "ClassIoComplete: IRP %p, SRB %p\n", Irp, srb));
|
|
|
|
//
|
|
// Release the queue if it is frozen.
|
|
//
|
|
|
|
if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) {
|
|
ClassReleaseQueue(Fdo);
|
|
}
|
|
|
|
retry = ClassInterpretSenseInfo(
|
|
Fdo,
|
|
srb,
|
|
irpStack->MajorFunction,
|
|
irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL ?
|
|
irpStack->Parameters.DeviceIoControl.IoControlCode :
|
|
0,
|
|
MAXIMUM_RETRIES -
|
|
((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4),
|
|
&status,
|
|
&retryInterval);
|
|
|
|
//
|
|
// If the status is verified required and the this request
|
|
// should bypass verify required then retry the request.
|
|
//
|
|
|
|
if (TEST_FLAG(irpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME) &&
|
|
status == STATUS_VERIFY_REQUIRED) {
|
|
|
|
status = STATUS_IO_DEVICE_ERROR;
|
|
retry = TRUE;
|
|
}
|
|
|
|
if (retry && ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4)--) {
|
|
|
|
//
|
|
// Retry request.
|
|
//
|
|
|
|
DebugPrint((1, "Retry request %p\n", Irp));
|
|
|
|
if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
|
|
FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
|
|
}
|
|
|
|
RetryRequest(Fdo, Irp, srb, FALSE, retryInterval);
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Set status for successful request
|
|
//
|
|
fdoData->LoggedTURFailureSinceLastIO = FALSE;
|
|
ClasspPerfIncrementSuccessfulIo(fdoExtension);
|
|
status = STATUS_SUCCESS;
|
|
} // end if (SRB_STATUS(srb->SrbStatus) == SRB_STATUS_SUCCESS)
|
|
|
|
|
|
//
|
|
// ensure we have returned some info, and it matches what the
|
|
// original request wanted for PAGING operations only
|
|
//
|
|
|
|
if ((NT_SUCCESS(status)) && TEST_FLAG(Irp->Flags, IRP_PAGING_IO)) {
|
|
ASSERT(Irp->IoStatus.Information != 0);
|
|
ASSERT(irpStack->Parameters.Read.Length == Irp->IoStatus.Information);
|
|
}
|
|
|
|
//
|
|
// remember if the caller wanted to skip calling IoStartNextPacket.
|
|
// for legacy reasons, we cannot call IoStartNextPacket for IoDeviceControl
|
|
// calls. this setting only affects device objects with StartIo routines.
|
|
//
|
|
|
|
callStartNextPacket = !TEST_FLAG(srb->SrbFlags, SRB_FLAGS_DONT_START_NEXT_PACKET);
|
|
if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
|
|
callStartNextPacket = FALSE;
|
|
}
|
|
|
|
//
|
|
// Free the srb
|
|
//
|
|
|
|
if(!TEST_FLAG(srb->SrbFlags, SRB_CLASS_FLAGS_PERSISTANT)) {
|
|
|
|
if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
|
|
FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
|
|
}
|
|
|
|
if (fdoExtension->CommonExtension.IsSrbLookasideListInitialized){
|
|
ClassFreeOrReuseSrb(fdoExtension, srb);
|
|
}
|
|
else {
|
|
DBGWARN(("ClassIoComplete is freeing an SRB (possibly) on behalf of another driver."));
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
} else {
|
|
|
|
DebugPrint((2, "ClassIoComplete: Not Freeing srb @ %p because "
|
|
"SRB_CLASS_FLAGS_PERSISTANT set\n", srb));
|
|
if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
|
|
DebugPrint((2, "ClassIoComplete: Not Freeing sensebuffer @ %p "
|
|
" because SRB_CLASS_FLAGS_PERSISTANT set\n",
|
|
srb->SenseInfoBuffer));
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Set status in completing IRP.
|
|
//
|
|
|
|
Irp->IoStatus.Status = status;
|
|
|
|
//
|
|
// Set the hard error if necessary.
|
|
//
|
|
|
|
if (!NT_SUCCESS(status) &&
|
|
IoIsErrorUserInduced(status) &&
|
|
(Irp->Tail.Overlay.Thread != NULL)
|
|
) {
|
|
|
|
//
|
|
// Store DeviceObject for filesystem, and clear
|
|
// in IoStatus.Information field.
|
|
//
|
|
|
|
IoSetHardErrorOrVerifyDevice(Irp, Fdo);
|
|
Irp->IoStatus.Information = 0;
|
|
}
|
|
|
|
//
|
|
// If pending has be returned for this irp then mark the current stack as
|
|
// pending.
|
|
//
|
|
|
|
if (Irp->PendingReturned) {
|
|
IoMarkIrpPending(Irp);
|
|
}
|
|
|
|
if (fdoExtension->CommonExtension.DriverExtension->InitData.ClassStartIo) {
|
|
if (callStartNextPacket) {
|
|
KIRQL oldIrql;
|
|
KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
|
|
IoStartNextPacket(Fdo, FALSE);
|
|
KeLowerIrql(oldIrql);
|
|
}
|
|
}
|
|
|
|
ClassReleaseRemoveLock(Fdo, Irp);
|
|
|
|
return status;
|
|
|
|
} // end ClassIoComplete()
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassSendSrbSynchronous()
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by SCSI device controls to complete an
|
|
SRB and send it to the port driver synchronously (ie wait for
|
|
completion). The CDB is already completed along with the SRB CDB
|
|
size and request timeout value.
|
|
|
|
Arguments:
|
|
|
|
Fdo - Supplies the functional device object which represents the target.
|
|
|
|
Srb - Supplies a partially initialized SRB. The SRB cannot come from zone.
|
|
|
|
BufferAddress - Supplies the address of the buffer.
|
|
|
|
BufferLength - Supplies the length in bytes of the buffer.
|
|
|
|
WriteToDevice - Indicates the data should be transfer to the device.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS indicating the final results of the operation.
|
|
|
|
If NT_SUCCESS(), then the amount of usable data is contained in the field
|
|
Srb->DataTransferLength
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassSendSrbSynchronous(
|
|
PDEVICE_OBJECT Fdo,
|
|
PSCSI_REQUEST_BLOCK Srb,
|
|
PVOID BufferAddress,
|
|
ULONG BufferLength,
|
|
BOOLEAN WriteToDevice
|
|
)
|
|
{
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
|
|
IO_STATUS_BLOCK ioStatus;
|
|
ULONG controlType;
|
|
PIRP irp;
|
|
PIO_STACK_LOCATION irpStack;
|
|
KEVENT event;
|
|
PUCHAR senseInfoBuffer;
|
|
ULONG retryCount = MAXIMUM_RETRIES;
|
|
NTSTATUS status;
|
|
BOOLEAN retry;
|
|
|
|
//
|
|
// NOTE: This code is only pagable because we are not freezing
|
|
// the queue. Allowing the queue to be frozen from a pagable
|
|
// routine could leave the queue frozen as we try to page in
|
|
// the code to unfreeze the queue. The result would be a nice
|
|
// case of deadlock. Therefore, since we are unfreezing the
|
|
// queue regardless of the result, just set the NO_FREEZE_QUEUE
|
|
// flag in the SRB.
|
|
//
|
|
|
|
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
|
|
ASSERT(fdoExtension->CommonExtension.IsFdo);
|
|
|
|
//
|
|
// Write length to SRB.
|
|
//
|
|
|
|
Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
|
|
//
|
|
// Set SCSI bus address.
|
|
//
|
|
|
|
Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
|
|
|
|
//
|
|
// Enable auto request sense.
|
|
//
|
|
|
|
Srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
|
|
|
|
//
|
|
// Sense buffer is in aligned nonpaged pool.
|
|
//
|
|
//
|
|
senseInfoBuffer = ExAllocatePoolWithTag(NonPagedPoolCacheAligned,
|
|
SENSE_BUFFER_SIZE,
|
|
'7CcS');
|
|
|
|
if (senseInfoBuffer == NULL) {
|
|
|
|
DebugPrint((1, "ClassSendSrbSynchronous: Can't allocate request sense "
|
|
"buffer\n"));
|
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
Srb->SenseInfoBuffer = senseInfoBuffer;
|
|
Srb->DataBuffer = BufferAddress;
|
|
|
|
//
|
|
// Start retries here.
|
|
//
|
|
|
|
retry:
|
|
|
|
//
|
|
// use fdoextension's flags by default.
|
|
// do not move out of loop, as the flag may change due to errors
|
|
// sending this command.
|
|
//
|
|
|
|
Srb->SrbFlags = fdoExtension->SrbFlags;
|
|
|
|
if(BufferAddress != NULL) {
|
|
if(WriteToDevice) {
|
|
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_DATA_OUT);
|
|
} else {
|
|
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_DATA_IN);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Initialize the QueueAction field.
|
|
//
|
|
|
|
Srb->QueueAction = SRB_SIMPLE_TAG_REQUEST;
|
|
|
|
//
|
|
// Disable synchronous transfer for these requests.
|
|
// Disable freezing the queue, since all we do is unfreeze it anyways.
|
|
//
|
|
|
|
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
|
|
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
|
|
|
|
//
|
|
// Set the event object to the unsignaled state.
|
|
// It will be used to signal request completion.
|
|
//
|
|
|
|
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
|
|
|
//
|
|
// Build device I/O control request with METHOD_NEITHER data transfer.
|
|
// We'll queue a completion routine to cleanup the MDL's and such ourself.
|
|
//
|
|
|
|
irp = IoAllocateIrp(
|
|
(CCHAR) (fdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1),
|
|
FALSE);
|
|
|
|
if(irp == NULL) {
|
|
ExFreePool(senseInfoBuffer);
|
|
DebugPrint((1, "ClassSendSrbSynchronous: Can't allocate Irp\n"));
|
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
//
|
|
// Get next stack location.
|
|
//
|
|
|
|
irpStack = IoGetNextIrpStackLocation(irp);
|
|
|
|
//
|
|
// Set up SRB for execute scsi request. Save SRB address in next stack
|
|
// for the port driver.
|
|
//
|
|
|
|
irpStack->MajorFunction = IRP_MJ_SCSI;
|
|
irpStack->Parameters.Scsi.Srb = Srb;
|
|
|
|
IoSetCompletionRoutine(irp,
|
|
ClasspSendSynchronousCompletion,
|
|
Srb,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE);
|
|
|
|
irp->UserIosb = &ioStatus;
|
|
irp->UserEvent = &event;
|
|
|
|
if(BufferAddress) {
|
|
//
|
|
// Build an MDL for the data buffer and stick it into the irp. The
|
|
// completion routine will unlock the pages and free the MDL.
|
|
//
|
|
|
|
irp->MdlAddress = IoAllocateMdl( BufferAddress,
|
|
BufferLength,
|
|
FALSE,
|
|
FALSE,
|
|
irp );
|
|
if (irp->MdlAddress == NULL) {
|
|
ExFreePool(senseInfoBuffer);
|
|
Srb->SenseInfoBuffer = NULL;
|
|
IoFreeIrp( irp );
|
|
DebugPrint((1, "ClassSendSrbSynchronous: Can't allocate MDL\n"));
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
try {
|
|
|
|
//
|
|
// the io manager unlocks these pages upon completion
|
|
//
|
|
|
|
MmProbeAndLockPages( irp->MdlAddress,
|
|
KernelMode,
|
|
(WriteToDevice ? IoReadAccess :
|
|
IoWriteAccess));
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
status = GetExceptionCode();
|
|
|
|
ExFreePool(senseInfoBuffer);
|
|
Srb->SenseInfoBuffer = NULL;
|
|
IoFreeMdl(irp->MdlAddress);
|
|
IoFreeIrp(irp);
|
|
|
|
DebugPrint((1, "ClassSendSrbSynchronous: Exception %lx "
|
|
"locking buffer\n", status));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the transfer length.
|
|
//
|
|
|
|
Srb->DataTransferLength = BufferLength;
|
|
|
|
//
|
|
// Zero out status.
|
|
//
|
|
|
|
Srb->ScsiStatus = Srb->SrbStatus = 0;
|
|
Srb->NextSrb = 0;
|
|
|
|
//
|
|
// Set up IRP Address.
|
|
//
|
|
|
|
Srb->OriginalRequest = irp;
|
|
|
|
//
|
|
// Call the port driver with the request and wait for it to complete.
|
|
//
|
|
|
|
status = IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp);
|
|
|
|
if (status == STATUS_PENDING) {
|
|
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
|
status = ioStatus.Status;
|
|
}
|
|
|
|
//
|
|
// Check that request completed without error.
|
|
//
|
|
|
|
if (SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS) {
|
|
|
|
ULONG retryInterval;
|
|
|
|
DBGTRACE(ClassDebugWarning, ("ClassSendSrbSynchronous - srb %ph failed (op=%s srbstat=%s(%xh), irpstat=%xh, sense=%s/%s/%s)", Srb, DBGGETSCSIOPSTR(Srb), DBGGETSRBSTATUSSTR(Srb), (ULONG)Srb->SrbStatus, status, DBGGETSENSECODESTR(Srb), DBGGETADSENSECODESTR(Srb), DBGGETADSENSEQUALIFIERSTR(Srb)));
|
|
|
|
//
|
|
// assert that the queue is not frozen
|
|
//
|
|
|
|
ASSERT(!TEST_FLAG(Srb->SrbStatus, SRB_STATUS_QUEUE_FROZEN));
|
|
|
|
//
|
|
// Update status and determine if request should be retried.
|
|
//
|
|
|
|
retry = ClassInterpretSenseInfo(Fdo,
|
|
Srb,
|
|
IRP_MJ_SCSI,
|
|
0,
|
|
MAXIMUM_RETRIES - retryCount,
|
|
&status,
|
|
&retryInterval);
|
|
|
|
|
|
if (retry) {
|
|
|
|
if ((status == STATUS_DEVICE_NOT_READY &&
|
|
((PSENSE_DATA) senseInfoBuffer)->AdditionalSenseCode ==
|
|
SCSI_ADSENSE_LUN_NOT_READY) ||
|
|
(SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)) {
|
|
|
|
LARGE_INTEGER delay;
|
|
|
|
//
|
|
// Delay for at least 2 seconds.
|
|
//
|
|
|
|
if(retryInterval < 2) {
|
|
retryInterval = 2;
|
|
}
|
|
|
|
delay.QuadPart = (LONGLONG)( - 10 * 1000 * (LONGLONG)1000 * retryInterval);
|
|
|
|
//
|
|
// Stall for a while to let the device become ready
|
|
//
|
|
|
|
KeDelayExecutionThread(KernelMode, FALSE, &delay);
|
|
|
|
}
|
|
|
|
//
|
|
// If retries are not exhausted then retry this operation.
|
|
//
|
|
|
|
if (retryCount--) {
|
|
|
|
if (PORT_ALLOCATED_SENSE(fdoExtension, Srb)) {
|
|
FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, Srb);
|
|
}
|
|
|
|
goto retry;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
fdoData->LoggedTURFailureSinceLastIO = FALSE;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// required even though we allocated our own, since the port driver may
|
|
// have allocated one also
|
|
//
|
|
|
|
if (PORT_ALLOCATED_SENSE(fdoExtension, Srb)) {
|
|
FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, Srb);
|
|
}
|
|
|
|
Srb->SenseInfoBuffer = NULL;
|
|
ExFreePool(senseInfoBuffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassInterpretSenseInfo()
|
|
|
|
Routine Description:
|
|
|
|
This routine interprets the data returned from the SCSI
|
|
request sense. It determines the status to return in the
|
|
IRP and whether this request can be retried.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the device object associated with this request.
|
|
|
|
Srb - Supplies the scsi request block which failed.
|
|
|
|
MajorFunctionCode - Supplies the function code to be used for logging.
|
|
|
|
IoDeviceCode - Supplies the device code to be used for logging.
|
|
|
|
Status - Returns the status for the request.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN TRUE: Drivers should retry this request.
|
|
FALSE: Drivers should not retry this request.
|
|
|
|
--*/
|
|
BOOLEAN
|
|
ClassInterpretSenseInfo(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PSCSI_REQUEST_BLOCK Srb,
|
|
IN UCHAR MajorFunctionCode,
|
|
IN ULONG IoDeviceCode,
|
|
IN ULONG RetryCount,
|
|
OUT NTSTATUS *Status,
|
|
OUT OPTIONAL ULONG *RetryInterval
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension;
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
|
|
|
|
PSENSE_DATA senseBuffer = Srb->SenseInfoBuffer;
|
|
|
|
BOOLEAN retry = TRUE;
|
|
BOOLEAN logError = FALSE;
|
|
BOOLEAN unhandledError = FALSE;
|
|
BOOLEAN incrementErrorCount = FALSE;
|
|
|
|
ULONG badSector = 0;
|
|
ULONG uniqueId = 0;
|
|
|
|
NTSTATUS logStatus;
|
|
|
|
ULONG readSector;
|
|
ULONG index;
|
|
|
|
ULONG retryInterval = 0;
|
|
KIRQL oldIrql;
|
|
|
|
|
|
logStatus = -1;
|
|
|
|
if(TEST_FLAG(Srb->SrbFlags, SRB_CLASS_FLAGS_PAGING)) {
|
|
|
|
//
|
|
// Log anything remotely incorrect about paging i/o
|
|
//
|
|
|
|
logError = TRUE;
|
|
uniqueId = 301;
|
|
logStatus = IO_WARNING_PAGING_FAILURE;
|
|
}
|
|
|
|
//
|
|
// Check that request sense buffer is valid.
|
|
//
|
|
|
|
ASSERT(fdoExtension->CommonExtension.IsFdo);
|
|
|
|
|
|
//
|
|
// must handle the SRB_STATUS_INTERNAL_ERROR case first,
|
|
// as it has all the flags set.
|
|
//
|
|
|
|
if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_INTERNAL_ERROR) {
|
|
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: Internal Error code is %x\n",
|
|
Srb->InternalStatus));
|
|
|
|
retry = FALSE;
|
|
*Status = Srb->InternalStatus;
|
|
|
|
} else if ((Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) &&
|
|
(Srb->SenseInfoBufferLength >=
|
|
offsetof(SENSE_DATA, CommandSpecificInformation))) {
|
|
|
|
//
|
|
// Zero the additional sense code and additional sense code qualifier
|
|
// if they were not returned by the device.
|
|
//
|
|
|
|
readSector = senseBuffer->AdditionalSenseLength +
|
|
offsetof(SENSE_DATA, AdditionalSenseLength);
|
|
|
|
if (readSector > Srb->SenseInfoBufferLength) {
|
|
readSector = Srb->SenseInfoBufferLength;
|
|
}
|
|
|
|
if (readSector <= offsetof(SENSE_DATA, AdditionalSenseCode)) {
|
|
senseBuffer->AdditionalSenseCode = 0;
|
|
}
|
|
|
|
if (readSector <= offsetof(SENSE_DATA, AdditionalSenseCodeQualifier)) {
|
|
senseBuffer->AdditionalSenseCodeQualifier = 0;
|
|
}
|
|
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: Error code is %x\n",
|
|
senseBuffer->ErrorCode));
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: Sense key is %x\n",
|
|
senseBuffer->SenseKey));
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: Additional sense code is %x\n",
|
|
senseBuffer->AdditionalSenseCode));
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: Additional sense code qualifier "
|
|
"is %x\n",
|
|
senseBuffer->AdditionalSenseCodeQualifier));
|
|
|
|
|
|
switch (senseBuffer->SenseKey & 0xf) {
|
|
|
|
case SCSI_SENSE_NOT_READY: {
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Device not ready\n"));
|
|
*Status = STATUS_DEVICE_NOT_READY;
|
|
|
|
switch (senseBuffer->AdditionalSenseCode) {
|
|
|
|
case SCSI_ADSENSE_LUN_NOT_READY: {
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Lun not ready\n"));
|
|
|
|
switch (senseBuffer->AdditionalSenseCodeQualifier) {
|
|
|
|
case SCSI_SENSEQ_OPERATION_IN_PROGRESS: {
|
|
DEVICE_EVENT_BECOMING_READY notReady;
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Operation In Progress\n"));
|
|
retryInterval = NOT_READY_RETRY_INTERVAL;
|
|
|
|
RtlZeroMemory(¬Ready, sizeof(DEVICE_EVENT_BECOMING_READY));
|
|
notReady.Version = 1;
|
|
notReady.Reason = 2;
|
|
notReady.Estimated100msToReady = retryInterval * 10;
|
|
ClasspSendNotification(fdoExtension,
|
|
&GUID_IO_DEVICE_BECOMING_READY,
|
|
sizeof(DEVICE_EVENT_BECOMING_READY),
|
|
¬Ready);
|
|
|
|
break;
|
|
}
|
|
|
|
case SCSI_SENSEQ_BECOMING_READY: {
|
|
DEVICE_EVENT_BECOMING_READY notReady;
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"In process of becoming ready\n"));
|
|
retryInterval = NOT_READY_RETRY_INTERVAL;
|
|
|
|
RtlZeroMemory(¬Ready, sizeof(DEVICE_EVENT_BECOMING_READY));
|
|
notReady.Version = 1;
|
|
notReady.Reason = 1;
|
|
notReady.Estimated100msToReady = retryInterval * 10;
|
|
ClasspSendNotification(fdoExtension,
|
|
&GUID_IO_DEVICE_BECOMING_READY,
|
|
sizeof(DEVICE_EVENT_BECOMING_READY),
|
|
¬Ready);
|
|
break;
|
|
}
|
|
|
|
case SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Long write in progress\n"));
|
|
retry = FALSE;
|
|
break;
|
|
}
|
|
|
|
case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Manual intervention required\n"));
|
|
*Status = STATUS_NO_MEDIA_IN_DEVICE;
|
|
retry = FALSE;
|
|
break;
|
|
}
|
|
|
|
case SCSI_SENSEQ_FORMAT_IN_PROGRESS: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Format in progress\n"));
|
|
retry = FALSE;
|
|
break;
|
|
}
|
|
|
|
case SCSI_SENSEQ_CAUSE_NOT_REPORTABLE: {
|
|
|
|
if(!TEST_FLAG(fdoExtension->ScanForSpecialFlags,
|
|
CLASS_SPECIAL_CAUSE_NOT_REPORTABLE_HACK)) {
|
|
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: "
|
|
"not ready, cause unknown\n"));
|
|
/*
|
|
Many non-WHQL certified drives (mostly CD-RW) return
|
|
this when they have no media instead of the obvious
|
|
choice of:
|
|
|
|
SCSI_SENSE_NOT_READY/SCSI_ADSENSE_NO_MEDIA_IN_DEVICE
|
|
|
|
These drives should not pass WHQL certification due
|
|
to this discrepency.
|
|
|
|
*/
|
|
retry = FALSE;
|
|
break;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Treat this as init command required and fall through.
|
|
//
|
|
}
|
|
}
|
|
|
|
case SCSI_SENSEQ_INIT_COMMAND_REQUIRED:
|
|
default: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Initializing command required\n"));
|
|
|
|
//
|
|
// This sense code/additional sense code
|
|
// combination may indicate that the device
|
|
// needs to be started. Send an start unit if this
|
|
// is a disk device.
|
|
//
|
|
|
|
if(TEST_FLAG(fdoExtension->DeviceFlags,
|
|
DEV_SAFE_START_UNIT) &&
|
|
!TEST_FLAG(Srb->SrbFlags,
|
|
SRB_CLASS_FLAGS_LOW_PRIORITY)) {
|
|
ClassSendStartUnit(Fdo);
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
} // end switch (senseBuffer->AdditionalSenseCodeQualifier)
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"No Media in device.\n"));
|
|
*Status = STATUS_NO_MEDIA_IN_DEVICE;
|
|
retry = FALSE;
|
|
|
|
//
|
|
// signal MCN that there isn't any media in the device
|
|
//
|
|
if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) {
|
|
DebugPrint((ClassDebugError, "ClassInterpretSenseInfo: "
|
|
"No Media in a non-removable device %p\n",
|
|
Fdo));
|
|
}
|
|
ClassSetMediaChangeState(fdoExtension, MediaNotPresent, FALSE);
|
|
|
|
break;
|
|
}
|
|
} // end switch (senseBuffer->AdditionalSenseCode)
|
|
|
|
break;
|
|
} // end SCSI_SENSE_NOT_READY
|
|
|
|
case SCSI_SENSE_DATA_PROTECT: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Media write protected\n"));
|
|
*Status = STATUS_MEDIA_WRITE_PROTECTED;
|
|
retry = FALSE;
|
|
break;
|
|
} // end SCSI_SENSE_DATA_PROTECT
|
|
|
|
case SCSI_SENSE_MEDIUM_ERROR: {
|
|
DebugPrint((ClassDebugSenseInfo,"ClassInterpretSenseInfo: "
|
|
"Medium Error (bad block)\n"));
|
|
*Status = STATUS_DEVICE_DATA_ERROR;
|
|
|
|
retry = FALSE;
|
|
logError = TRUE;
|
|
uniqueId = 256;
|
|
logStatus = IO_ERR_BAD_BLOCK;
|
|
|
|
//
|
|
// Check if this error is due to unknown format
|
|
//
|
|
if (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_INVALID_MEDIA){
|
|
|
|
switch (senseBuffer->AdditionalSenseCodeQualifier) {
|
|
|
|
case SCSI_SENSEQ_UNKNOWN_FORMAT: {
|
|
|
|
*Status = STATUS_UNRECOGNIZED_MEDIA;
|
|
|
|
//
|
|
// Log error only if this is a paging request
|
|
//
|
|
if(!TEST_FLAG(Srb->SrbFlags, SRB_CLASS_FLAGS_PAGING)) {
|
|
logError = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED: {
|
|
|
|
*Status = STATUS_CLEANER_CARTRIDGE_INSTALLED;
|
|
logError = FALSE;
|
|
break;
|
|
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
} // end switch AdditionalSenseCodeQualifier
|
|
|
|
} // end SCSI_ADSENSE_INVALID_MEDIA
|
|
|
|
break;
|
|
|
|
} // end SCSI_SENSE_MEDIUM_ERROR
|
|
|
|
case SCSI_SENSE_HARDWARE_ERROR: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Hardware error\n"));
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
logError = TRUE;
|
|
uniqueId = 257;
|
|
logStatus = IO_ERR_CONTROLLER_ERROR;
|
|
break;
|
|
} // end SCSI_SENSE_HARDWARE_ERROR
|
|
|
|
case SCSI_SENSE_ILLEGAL_REQUEST: {
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Illegal SCSI request\n"));
|
|
*Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
retry = FALSE;
|
|
|
|
switch (senseBuffer->AdditionalSenseCode) {
|
|
|
|
case SCSI_ADSENSE_ILLEGAL_COMMAND: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Illegal command\n"));
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_ILLEGAL_BLOCK: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Illegal block address\n"));
|
|
*Status = STATUS_NONEXISTENT_SECTOR;
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_INVALID_LUN: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Invalid LUN\n"));
|
|
*Status = STATUS_NO_SUCH_DEVICE;
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_MUSIC_AREA: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Music area\n"));
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_DATA_AREA: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Data area\n"));
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_VOLUME_OVERFLOW: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Volume overflow\n"));
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_COPY_PROTECTION_FAILURE: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Copy protection failure\n"));
|
|
|
|
*Status = STATUS_COPY_PROTECTION_FAILURE;
|
|
|
|
switch (senseBuffer->AdditionalSenseCodeQualifier) {
|
|
case SCSI_SENSEQ_AUTHENTICATION_FAILURE:
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: "
|
|
"Authentication failure\n"));
|
|
*Status = STATUS_CSS_AUTHENTICATION_FAILURE;
|
|
break;
|
|
case SCSI_SENSEQ_KEY_NOT_PRESENT:
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: "
|
|
"Key not present\n"));
|
|
*Status = STATUS_CSS_KEY_NOT_PRESENT;
|
|
break;
|
|
case SCSI_SENSEQ_KEY_NOT_ESTABLISHED:
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: "
|
|
"Key not established\n"));
|
|
*Status = STATUS_CSS_KEY_NOT_ESTABLISHED;
|
|
break;
|
|
case SCSI_SENSEQ_READ_OF_SCRAMBLED_SECTOR_WITHOUT_AUTHENTICATION:
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: "
|
|
"Read of scrambled sector w/o "
|
|
"authentication\n"));
|
|
*Status = STATUS_CSS_SCRAMBLED_SECTOR;
|
|
break;
|
|
case SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT:
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: "
|
|
"Media region does not logical unit "
|
|
"region\n"));
|
|
*Status = STATUS_CSS_REGION_MISMATCH;
|
|
break;
|
|
case SCSI_SENSEQ_LOGICAL_UNIT_RESET_COUNT_ERROR:
|
|
DebugPrint((ClassDebugSenseInfo,
|
|
"ClassInterpretSenseInfo: "
|
|
"Region set error -- region may "
|
|
"be permanent\n"));
|
|
*Status = STATUS_CSS_RESETS_EXHAUSTED;
|
|
break;
|
|
} // end switch of ASCQ for COPY_PROTECTION_FAILURE
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
case SCSI_ADSENSE_INVALID_CDB: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Invalid CDB\n"));
|
|
|
|
//
|
|
// Note: the retry interval is not typically used.
|
|
// it is set here only because a ClassErrorHandler
|
|
// cannot set the retryInterval, and the error may
|
|
// require a few commands to be sent to clear whatever
|
|
// caused this condition (i.e. disk clears the write
|
|
// cache, requiring at least two commands)
|
|
//
|
|
// hopefully, this shortcoming can be changed for
|
|
// blackcomb.
|
|
//
|
|
|
|
retryInterval = 3;
|
|
break;
|
|
}
|
|
|
|
} // end switch (senseBuffer->AdditionalSenseCode)
|
|
|
|
break;
|
|
} // end SCSI_SENSE_ILLEGAL_REQUEST
|
|
|
|
case SCSI_SENSE_UNIT_ATTENTION: {
|
|
|
|
PVPB vpb;
|
|
ULONG count;
|
|
|
|
//
|
|
// A media change may have occured so increment the change
|
|
// count for the physical device
|
|
//
|
|
|
|
count = InterlockedIncrement(&fdoExtension->MediaChangeCount);
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Media change count for device %d incremented to %#lx\n",
|
|
fdoExtension->DeviceNumber, count));
|
|
|
|
|
|
switch (senseBuffer->AdditionalSenseCode) {
|
|
case SCSI_ADSENSE_MEDIUM_CHANGED: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Media changed\n"));
|
|
|
|
if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) {
|
|
DebugPrint((ClassDebugError, "ClassInterpretSenseInfo: "
|
|
"Media Changed on non-removable device %p\n",
|
|
Fdo));
|
|
}
|
|
ClassSetMediaChangeState(fdoExtension, MediaPresent, FALSE);
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_BUS_RESET: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Bus reset\n"));
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_OPERATOR_REQUEST: {
|
|
switch (senseBuffer->AdditionalSenseCodeQualifier) {
|
|
|
|
case SCSI_SENSEQ_MEDIUM_REMOVAL: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Ejection request received!\n"));
|
|
ClassSendEjectionNotification(fdoExtension);
|
|
break;
|
|
}
|
|
|
|
case SCSI_SENSEQ_WRITE_PROTECT_ENABLE: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Operator selected write permit?! "
|
|
"(unsupported!)\n"));
|
|
break;
|
|
}
|
|
|
|
case SCSI_SENSEQ_WRITE_PROTECT_DISABLE: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Operator selected write protect?! "
|
|
"(unsupported!)\n"));
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
default: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Unit attention\n"));
|
|
break;
|
|
}
|
|
|
|
} // end switch (senseBuffer->AdditionalSenseCode)
|
|
|
|
if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA))
|
|
{
|
|
//
|
|
// TODO : Is the media lockable?
|
|
//
|
|
|
|
if ((ClassGetVpb(Fdo) != NULL) && (ClassGetVpb(Fdo)->Flags & VPB_MOUNTED))
|
|
{
|
|
//
|
|
// Set bit to indicate that media may have changed
|
|
// and volume needs verification.
|
|
//
|
|
|
|
SET_FLAG(Fdo->Flags, DO_VERIFY_VOLUME);
|
|
|
|
*Status = STATUS_VERIFY_REQUIRED;
|
|
retry = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
break;
|
|
|
|
} // end SCSI_SENSE_UNIT_ATTENTION
|
|
|
|
case SCSI_SENSE_ABORTED_COMMAND: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Command aborted\n"));
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
retryInterval = 1;
|
|
break;
|
|
} // end SCSI_SENSE_ABORTED_COMMAND
|
|
|
|
case SCSI_SENSE_BLANK_CHECK: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Media blank check\n"));
|
|
retry = FALSE;
|
|
*Status = STATUS_NO_DATA_DETECTED;
|
|
break;
|
|
} // end SCSI_SENSE_BLANK_CHECK
|
|
|
|
case SCSI_SENSE_RECOVERED_ERROR: {
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Recovered error\n"));
|
|
*Status = STATUS_SUCCESS;
|
|
retry = FALSE;
|
|
logError = TRUE;
|
|
uniqueId = 258;
|
|
|
|
switch(senseBuffer->AdditionalSenseCode) {
|
|
case SCSI_ADSENSE_SEEK_ERROR:
|
|
case SCSI_ADSENSE_TRACK_ERROR: {
|
|
logStatus = IO_ERR_SEEK_ERROR;
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_REC_DATA_NOECC:
|
|
case SCSI_ADSENSE_REC_DATA_ECC: {
|
|
logStatus = IO_RECOVERED_VIA_ECC;
|
|
break;
|
|
}
|
|
|
|
case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: {
|
|
UCHAR wmiEventData[5];
|
|
|
|
*((PULONG)wmiEventData) = sizeof(UCHAR);
|
|
wmiEventData[sizeof(ULONG)] = senseBuffer->AdditionalSenseCodeQualifier;
|
|
|
|
//
|
|
// Don't log another eventlog if we have already logged once
|
|
// NOTE: this should have been interlocked, but the structure
|
|
// was publicly defined to use a BOOLEAN (char). Since
|
|
// media only reports these errors once per X minutes,
|
|
// the potential race condition is nearly non-existant.
|
|
// the worst case is duplicate log entries, so ignore.
|
|
//
|
|
|
|
if (fdoExtension->FailurePredicted == 0) {
|
|
logError = TRUE;
|
|
}
|
|
fdoExtension->FailurePredicted = TRUE;
|
|
fdoExtension->FailureReason = senseBuffer->AdditionalSenseCodeQualifier;
|
|
logStatus = IO_WRN_FAILURE_PREDICTED;
|
|
|
|
ClassNotifyFailurePredicted(fdoExtension,
|
|
(PUCHAR)&wmiEventData,
|
|
sizeof(wmiEventData),
|
|
0,
|
|
4,
|
|
Srb->PathId,
|
|
Srb->TargetId,
|
|
Srb->Lun);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
logStatus = IO_ERR_CONTROLLER_ERROR;
|
|
break;
|
|
}
|
|
|
|
} // end switch(senseBuffer->AdditionalSenseCode)
|
|
|
|
if (senseBuffer->IncorrectLength) {
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Incorrect length detected.\n"));
|
|
*Status = STATUS_INVALID_BLOCK_LENGTH ;
|
|
}
|
|
|
|
break;
|
|
} // end SCSI_SENSE_RECOVERED_ERROR
|
|
|
|
case SCSI_SENSE_NO_SENSE: {
|
|
|
|
//
|
|
// Check other indicators.
|
|
//
|
|
|
|
if (senseBuffer->IncorrectLength) {
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Incorrect length detected.\n"));
|
|
*Status = STATUS_INVALID_BLOCK_LENGTH ;
|
|
retry = FALSE;
|
|
|
|
} else {
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"No specific sense key\n"));
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
retry = TRUE;
|
|
}
|
|
|
|
break;
|
|
} // end SCSI_SENSE_NO_SENSE
|
|
|
|
default: {
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Unrecognized sense code\n"));
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
break;
|
|
}
|
|
|
|
} // end switch (senseBuffer->SenseKey & 0xf)
|
|
|
|
//
|
|
// Try to determine the bad sector from the inquiry data.
|
|
//
|
|
|
|
if ((((PCDB)Srb->Cdb)->CDB10.OperationCode == SCSIOP_READ ||
|
|
((PCDB)Srb->Cdb)->CDB10.OperationCode == SCSIOP_VERIFY ||
|
|
((PCDB)Srb->Cdb)->CDB10.OperationCode == SCSIOP_WRITE)) {
|
|
|
|
for (index = 0; index < 4; index++) {
|
|
badSector = (badSector << 8) | senseBuffer->Information[index];
|
|
}
|
|
|
|
readSector = 0;
|
|
for (index = 0; index < 4; index++) {
|
|
readSector = (readSector << 8) | Srb->Cdb[index+2];
|
|
}
|
|
|
|
index = (((PCDB)Srb->Cdb)->CDB10.TransferBlocksMsb << 8) |
|
|
((PCDB)Srb->Cdb)->CDB10.TransferBlocksLsb;
|
|
|
|
//
|
|
// Make sure the bad sector is within the read sectors.
|
|
//
|
|
|
|
if (!(badSector >= readSector && badSector < readSector + index)) {
|
|
badSector = readSector;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Request sense buffer not valid. No sense information
|
|
// to pinpoint the error. Return general request fail.
|
|
//
|
|
|
|
DebugPrint((ClassDebugSenseInfo, "ClassInterpretSenseInfo: "
|
|
"Request sense info not valid. SrbStatus %2x\n",
|
|
SRB_STATUS(Srb->SrbStatus)));
|
|
retry = TRUE;
|
|
|
|
switch (SRB_STATUS(Srb->SrbStatus)) {
|
|
case SRB_STATUS_INVALID_LUN:
|
|
case SRB_STATUS_INVALID_TARGET_ID:
|
|
case SRB_STATUS_NO_DEVICE:
|
|
case SRB_STATUS_NO_HBA:
|
|
case SRB_STATUS_INVALID_PATH_ID: {
|
|
*Status = STATUS_NO_SUCH_DEVICE;
|
|
retry = FALSE;
|
|
break;
|
|
}
|
|
|
|
case SRB_STATUS_COMMAND_TIMEOUT:
|
|
case SRB_STATUS_TIMEOUT: {
|
|
|
|
//
|
|
// Update the error count for the device.
|
|
//
|
|
|
|
incrementErrorCount = TRUE;
|
|
*Status = STATUS_IO_TIMEOUT;
|
|
break;
|
|
}
|
|
|
|
case SRB_STATUS_ABORTED: {
|
|
|
|
//
|
|
// Update the error count for the device.
|
|
//
|
|
|
|
incrementErrorCount = TRUE;
|
|
*Status = STATUS_IO_TIMEOUT;
|
|
retryInterval = 1;
|
|
break;
|
|
}
|
|
|
|
|
|
case SRB_STATUS_SELECTION_TIMEOUT: {
|
|
logError = TRUE;
|
|
logStatus = IO_ERR_NOT_READY;
|
|
uniqueId = 260;
|
|
*Status = STATUS_DEVICE_NOT_CONNECTED;
|
|
retry = FALSE;
|
|
break;
|
|
}
|
|
|
|
case SRB_STATUS_DATA_OVERRUN: {
|
|
*Status = STATUS_DATA_OVERRUN;
|
|
retry = FALSE;
|
|
break;
|
|
}
|
|
|
|
case SRB_STATUS_PHASE_SEQUENCE_FAILURE: {
|
|
|
|
//
|
|
// Update the error count for the device.
|
|
//
|
|
|
|
incrementErrorCount = TRUE;
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
|
|
//
|
|
// If there was phase sequence error then limit the number of
|
|
// retries.
|
|
//
|
|
|
|
if (RetryCount > 1 ) {
|
|
retry = FALSE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SRB_STATUS_REQUEST_FLUSHED: {
|
|
|
|
//
|
|
// If the status needs verification bit is set. Then set
|
|
// the status to need verification and no retry; otherwise,
|
|
// just retry the request.
|
|
//
|
|
|
|
if (TEST_FLAG(Fdo->Flags, DO_VERIFY_VOLUME)) {
|
|
|
|
*Status = STATUS_VERIFY_REQUIRED;
|
|
retry = FALSE;
|
|
|
|
} else {
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SRB_STATUS_INVALID_REQUEST: {
|
|
*Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
retry = FALSE;
|
|
break;
|
|
}
|
|
|
|
case SRB_STATUS_UNEXPECTED_BUS_FREE:
|
|
case SRB_STATUS_PARITY_ERROR:
|
|
|
|
//
|
|
// Update the error count for the device
|
|
// and fall through to below
|
|
//
|
|
|
|
incrementErrorCount = TRUE;
|
|
|
|
case SRB_STATUS_BUS_RESET: {
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
break;
|
|
}
|
|
|
|
case SRB_STATUS_ERROR: {
|
|
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
if (Srb->ScsiStatus == 0) {
|
|
|
|
//
|
|
// This is some strange return code. Update the error
|
|
// count for the device.
|
|
//
|
|
|
|
incrementErrorCount = TRUE;
|
|
|
|
} if (Srb->ScsiStatus == SCSISTAT_BUSY) {
|
|
|
|
*Status = STATUS_DEVICE_NOT_READY;
|
|
|
|
} if (Srb->ScsiStatus == SCSISTAT_RESERVATION_CONFLICT) {
|
|
|
|
*Status = STATUS_DEVICE_BUSY;
|
|
retry = FALSE;
|
|
logError = FALSE;
|
|
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
logError = TRUE;
|
|
logStatus = IO_ERR_CONTROLLER_ERROR;
|
|
uniqueId = 259;
|
|
*Status = STATUS_IO_DEVICE_ERROR;
|
|
unhandledError = TRUE;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// NTRAID #183546 - if we support GESN subtype NOT_READY events, and
|
|
// we know from a previous poll when the device will be ready (ETA)
|
|
// we should delay the retry more appropriately than just guessing.
|
|
//
|
|
/*
|
|
if (fdoExtension->MediaChangeDetectionInfo &&
|
|
fdoExtension->MediaChangeDetectionInfo->Gesn.Supported &&
|
|
TEST_FLAG(fdoExtension->MediaChangeDetectionInfo->Gesn.EventMask,
|
|
NOTIFICATION_DEVICE_BUSY_CLASS_MASK)
|
|
) {
|
|
// check if Gesn.ReadyTime if greater than current tick count
|
|
// if so, delay that long (from 1 to 30 seconds max?)
|
|
// else, leave the guess of time alone.
|
|
}
|
|
*/
|
|
|
|
}
|
|
|
|
if (incrementErrorCount) {
|
|
|
|
//
|
|
// if any error count occurred, delay the retry of this io by
|
|
// at least one second, if caller supports it.
|
|
//
|
|
|
|
if (retryInterval == 0) {
|
|
retryInterval = 1;
|
|
}
|
|
ClasspPerfIncrementErrorCount(fdoExtension);
|
|
}
|
|
|
|
//
|
|
// If there is a class specific error handler call it.
|
|
//
|
|
|
|
if (fdoExtension->CommonExtension.DevInfo->ClassError != NULL) {
|
|
|
|
fdoExtension->CommonExtension.DevInfo->ClassError(Fdo,
|
|
Srb,
|
|
Status,
|
|
&retry);
|
|
}
|
|
|
|
//
|
|
// If the caller wants to know the suggested retry interval tell them.
|
|
//
|
|
|
|
if(ARGUMENT_PRESENT(RetryInterval)) {
|
|
*RetryInterval = retryInterval;
|
|
}
|
|
|
|
|
|
/*
|
|
* LOG the error:
|
|
* Always log the error in our internal log.
|
|
* If logError is set, also log the error in the system log.
|
|
*/
|
|
{
|
|
ULONG totalSize;
|
|
ULONG senseBufferSize = 0;
|
|
IO_ERROR_LOG_PACKET staticErrLogEntry = {0};
|
|
CLASS_ERROR_LOG_DATA staticErrLogData = {0};
|
|
|
|
//
|
|
// Calculate the total size of the error log entry.
|
|
// add to totalSize in the order that they are used.
|
|
// the advantage to calculating all the sizes here is
|
|
// that we don't have to do a bunch of extraneous checks
|
|
// later on in this code path.
|
|
//
|
|
totalSize = sizeof(IO_ERROR_LOG_PACKET) // required
|
|
- sizeof(ULONG) // struct includes one ULONG
|
|
+ sizeof(CLASS_ERROR_LOG_DATA);// struct for ease
|
|
|
|
//
|
|
// also save any available extra sense data, up to the maximum errlog
|
|
// packet size . WMI should be used for real-time analysis.
|
|
// the event log should only be used for post-mortem debugging.
|
|
//
|
|
if (TEST_FLAG(Srb->SrbStatus, SRB_STATUS_AUTOSENSE_VALID)) {
|
|
ULONG validSenseBytes;
|
|
BOOLEAN validSense;
|
|
|
|
//
|
|
// make sure we can at least access the AdditionalSenseLength field
|
|
//
|
|
validSense = RTL_CONTAINS_FIELD(senseBuffer,
|
|
Srb->SenseInfoBufferLength,
|
|
AdditionalSenseLength);
|
|
if (validSense) {
|
|
|
|
//
|
|
// if extra info exists, copy the maximum amount of available
|
|
// sense data that is safe into the the errlog.
|
|
//
|
|
validSenseBytes = senseBuffer->AdditionalSenseLength
|
|
+ offsetof(SENSE_DATA, AdditionalSenseLength);
|
|
|
|
//
|
|
// this is invalid because it causes overflow!
|
|
// whoever sent this type of request would cause
|
|
// a system crash.
|
|
//
|
|
ASSERT(validSenseBytes < MAX_ADDITIONAL_SENSE_BYTES);
|
|
|
|
//
|
|
// set to save the most sense buffer possible
|
|
//
|
|
senseBufferSize = max(validSenseBytes, sizeof(SENSE_DATA));
|
|
senseBufferSize = min(senseBufferSize, Srb->SenseInfoBufferLength);
|
|
} else {
|
|
//
|
|
// it's smaller than required to read the total number of
|
|
// valid bytes, so just use the SenseInfoBufferLength field.
|
|
//
|
|
senseBufferSize = Srb->SenseInfoBufferLength;
|
|
}
|
|
|
|
/*
|
|
* Bump totalSize by the number of extra senseBuffer bytes
|
|
* (beyond the default sense buffer within CLASS_ERROR_LOG_DATA).
|
|
* Make sure to never allocate more than ERROR_LOG_MAXIMUM_SIZE.
|
|
*/
|
|
if (senseBufferSize > sizeof(SENSE_DATA)){
|
|
totalSize += senseBufferSize-sizeof(SENSE_DATA);
|
|
if (totalSize > ERROR_LOG_MAXIMUM_SIZE){
|
|
senseBufferSize -= totalSize-ERROR_LOG_MAXIMUM_SIZE;
|
|
totalSize = ERROR_LOG_MAXIMUM_SIZE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we've used up all of our retry attempts, set the final status to
|
|
// reflect the appropriate result.
|
|
//
|
|
if (retry && RetryCount < MAXIMUM_RETRIES) {
|
|
staticErrLogEntry.FinalStatus = STATUS_SUCCESS;
|
|
staticErrLogData.ErrorRetried = TRUE;
|
|
} else {
|
|
staticErrLogEntry.FinalStatus = *Status;
|
|
}
|
|
if (TEST_FLAG(Srb->SrbFlags, SRB_CLASS_FLAGS_PAGING)) {
|
|
staticErrLogData.ErrorPaging = TRUE;
|
|
}
|
|
if (unhandledError) {
|
|
staticErrLogData.ErrorUnhandled = TRUE;
|
|
}
|
|
|
|
//
|
|
// Calculate the device offset if there is a geometry.
|
|
//
|
|
staticErrLogEntry.DeviceOffset.QuadPart = (LONGLONG)badSector;
|
|
staticErrLogEntry.DeviceOffset.QuadPart *= (LONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
|
|
if (logStatus == -1){
|
|
staticErrLogEntry.ErrorCode = STATUS_IO_DEVICE_ERROR;
|
|
} else {
|
|
staticErrLogEntry.ErrorCode = logStatus;
|
|
}
|
|
|
|
/*
|
|
* The dump data follows the IO_ERROR_LOG_PACKET,
|
|
* with the first ULONG of dump data inside the packet.
|
|
*/
|
|
staticErrLogEntry.DumpDataSize = (USHORT)totalSize - sizeof(IO_ERROR_LOG_PACKET) + sizeof(ULONG);
|
|
|
|
staticErrLogEntry.SequenceNumber = 0;
|
|
staticErrLogEntry.MajorFunctionCode = MajorFunctionCode;
|
|
staticErrLogEntry.IoControlCode = IoDeviceCode;
|
|
staticErrLogEntry.RetryCount = (UCHAR) RetryCount;
|
|
staticErrLogEntry.UniqueErrorValue = uniqueId;
|
|
|
|
KeQueryTickCount(&staticErrLogData.TickCount);
|
|
staticErrLogData.PortNumber = (ULONG)-1;
|
|
|
|
/*
|
|
* Save the entire contents of the SRB.
|
|
*/
|
|
staticErrLogData.Srb = *Srb;
|
|
|
|
/*
|
|
* For our private log, save just the default length of the SENSE_DATA.
|
|
*/
|
|
if (senseBufferSize != 0){
|
|
RtlCopyMemory(&staticErrLogData.SenseData, senseBuffer, min(senseBufferSize, sizeof(SENSE_DATA)));
|
|
}
|
|
|
|
/*
|
|
* Save the error log in our context.
|
|
* We only save the default sense buffer length.
|
|
*/
|
|
KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
|
|
fdoData->ErrorLogs[fdoData->ErrorLogNextIndex] = staticErrLogData;
|
|
fdoData->ErrorLogNextIndex++;
|
|
fdoData->ErrorLogNextIndex %= NUM_ERROR_LOG_ENTRIES;
|
|
KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
|
|
|
|
/*
|
|
* If logError is set, also save this log in the system's error log.
|
|
* But make sure we don't log TUR failures over and over
|
|
* (e.g. if an external drive was switched off and we're still sending TUR's to it every second).
|
|
*/
|
|
if ((((PCDB)Srb->Cdb)->CDB10.OperationCode == SCSIOP_TEST_UNIT_READY) && logError){
|
|
if (fdoData->LoggedTURFailureSinceLastIO){
|
|
logError = FALSE;
|
|
}
|
|
else {
|
|
fdoData->LoggedTURFailureSinceLastIO = TRUE;
|
|
}
|
|
}
|
|
if (logError){
|
|
PIO_ERROR_LOG_PACKET errorLogEntry;
|
|
PCLASS_ERROR_LOG_DATA errlogData;
|
|
|
|
errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(Fdo, (UCHAR)totalSize);
|
|
if (errorLogEntry){
|
|
errlogData = (PCLASS_ERROR_LOG_DATA)errorLogEntry->DumpData;
|
|
|
|
*errorLogEntry = staticErrLogEntry;
|
|
*errlogData = staticErrLogData;
|
|
|
|
/*
|
|
* For the system log, copy as much of the sense buffer as possible.
|
|
*/
|
|
if (senseBufferSize != 0) {
|
|
RtlCopyMemory(&errlogData->SenseData, senseBuffer, senseBufferSize);
|
|
}
|
|
|
|
/*
|
|
* Write the error log packet to the system error logging thread.
|
|
*/
|
|
IoWriteErrorLogEntry(errorLogEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
return retry;
|
|
|
|
} // end ClassInterpretSenseInfo()
|
|
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassModeSense()
|
|
|
|
Routine Description:
|
|
|
|
This routine sends a mode sense command to a target ID and returns
|
|
when it is complete.
|
|
|
|
Arguments:
|
|
|
|
Fdo - Supplies the functional device object associated with this request.
|
|
|
|
ModeSenseBuffer - Supplies a buffer to store the sense data.
|
|
|
|
Length - Supplies the length in bytes of the mode sense buffer.
|
|
|
|
PageMode - Supplies the page or pages of mode sense data to be retrived.
|
|
|
|
Return Value:
|
|
|
|
Length of the transferred data is returned.
|
|
|
|
--*/
|
|
ULONG ClassModeSense( IN PDEVICE_OBJECT Fdo,
|
|
IN PCHAR ModeSenseBuffer,
|
|
IN ULONG Length,
|
|
IN UCHAR PageMode)
|
|
{
|
|
ULONG lengthTransferred = 0;
|
|
PMDL senseBufferMdl;
|
|
|
|
PAGED_CODE();
|
|
|
|
senseBufferMdl = BuildDeviceInputMdl(ModeSenseBuffer, Length);
|
|
if (senseBufferMdl){
|
|
|
|
TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE);
|
|
if (pkt){
|
|
KEVENT event;
|
|
NTSTATUS pktStatus;
|
|
IRP pseudoIrp = {0};
|
|
|
|
/*
|
|
* Store the number of packets servicing the irp (one)
|
|
* inside the original IRP. It will be used to counted down
|
|
* to zero when the packet completes.
|
|
* Initialize the original IRP's status to success.
|
|
* If the packet fails, we will set it to the error status.
|
|
*/
|
|
pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
|
|
pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
|
|
pseudoIrp.IoStatus.Information = 0;
|
|
pseudoIrp.MdlAddress = senseBufferMdl;
|
|
|
|
/*
|
|
* Set this up as a SYNCHRONOUS transfer, submit it,
|
|
* and wait for the packet to complete. The result
|
|
* status will be written to the original irp.
|
|
*/
|
|
ASSERT(Length <= 0x0ff);
|
|
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
|
|
SetupModeSenseTransferPacket(pkt, &event, ModeSenseBuffer, (UCHAR)Length, PageMode, &pseudoIrp);
|
|
SubmitTransferPacket(pkt);
|
|
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
|
|
|
if (NT_SUCCESS(pseudoIrp.IoStatus.Status)){
|
|
lengthTransferred = (ULONG)pseudoIrp.IoStatus.Information;
|
|
}
|
|
else {
|
|
/*
|
|
* This request can sometimes fail legitimately
|
|
* (e.g. when a SCSI device is attached but turned off)
|
|
* so this is not necessarily a device/driver bug.
|
|
*/
|
|
DBGTRACE(ClassDebugWarning, ("ClassModeSense on Fdo %ph failed with status %xh.", Fdo, pseudoIrp.IoStatus.Status));
|
|
}
|
|
}
|
|
|
|
FreeDeviceInputMdl(senseBufferMdl);
|
|
}
|
|
|
|
return lengthTransferred;
|
|
}
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassFindModePage()
|
|
|
|
Routine Description:
|
|
|
|
This routine scans through the mode sense data and finds the requested
|
|
mode sense page code.
|
|
|
|
Arguments:
|
|
ModeSenseBuffer - Supplies a pointer to the mode sense data.
|
|
|
|
Length - Indicates the length of valid data.
|
|
|
|
PageMode - Supplies the page mode to be searched for.
|
|
|
|
Use6Byte - Indicates whether 6 or 10 byte mode sense was used.
|
|
|
|
Return Value:
|
|
|
|
A pointer to the the requested mode page. If the mode page was not found
|
|
then NULL is return.
|
|
|
|
--*/
|
|
PVOID
|
|
ClassFindModePage(
|
|
IN PCHAR ModeSenseBuffer,
|
|
IN ULONG Length,
|
|
IN UCHAR PageMode,
|
|
IN BOOLEAN Use6Byte
|
|
)
|
|
{
|
|
PUCHAR limit;
|
|
ULONG parameterHeaderLength;
|
|
PVOID result = NULL;
|
|
|
|
limit = ModeSenseBuffer + Length;
|
|
parameterHeaderLength = (Use6Byte) ? sizeof(MODE_PARAMETER_HEADER) : sizeof(MODE_PARAMETER_HEADER10);
|
|
|
|
if (Length >= parameterHeaderLength) {
|
|
|
|
PMODE_PARAMETER_HEADER10 modeParam10;
|
|
ULONG blockDescriptorLength;
|
|
|
|
/*
|
|
* Skip the mode select header and block descriptors.
|
|
*/
|
|
if (Use6Byte){
|
|
blockDescriptorLength = ((PMODE_PARAMETER_HEADER) ModeSenseBuffer)->BlockDescriptorLength;
|
|
}
|
|
else {
|
|
modeParam10 = (PMODE_PARAMETER_HEADER10) ModeSenseBuffer;
|
|
blockDescriptorLength = modeParam10->BlockDescriptorLength[1];
|
|
}
|
|
|
|
ModeSenseBuffer += parameterHeaderLength + blockDescriptorLength;
|
|
|
|
//
|
|
// ModeSenseBuffer now points at pages. Walk the pages looking for the
|
|
// requested page until the limit is reached.
|
|
//
|
|
|
|
while (ModeSenseBuffer +
|
|
RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength) < limit) {
|
|
|
|
if (((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageCode == PageMode) {
|
|
|
|
/*
|
|
* found the mode page. make sure it's safe to touch it all
|
|
* before returning the pointer to caller
|
|
*/
|
|
|
|
if (ModeSenseBuffer + ((PMODE_DISCONNECT_PAGE)ModeSenseBuffer)->PageLength > limit) {
|
|
/*
|
|
* Return NULL since the page is not safe to access in full
|
|
*/
|
|
result = NULL;
|
|
}
|
|
else {
|
|
result = ModeSenseBuffer;
|
|
}
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Advance to the next page which is 4-byte-aligned offset after this page.
|
|
//
|
|
ModeSenseBuffer +=
|
|
((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageLength +
|
|
RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength);
|
|
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} // end ClassFindModePage()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassSendSrbAsynchronous()
|
|
|
|
Routine Description:
|
|
|
|
This routine takes a partially built Srb and an Irp and sends it down to
|
|
the port driver.
|
|
|
|
This routine must be called with the remove lock held for the specified
|
|
Irp.
|
|
|
|
Arguments:
|
|
|
|
Fdo - Supplies the functional device object for the orginal request.
|
|
|
|
Srb - Supplies a paritally build ScsiRequestBlock. In particular, the
|
|
CDB and the SRB timeout value must be filled in. The SRB must not be
|
|
allocated from zone.
|
|
|
|
Irp - Supplies the requesting Irp.
|
|
|
|
BufferAddress - Supplies a pointer to the buffer to be transfered.
|
|
|
|
BufferLength - Supplies the length of data transfer.
|
|
|
|
WriteToDevice - Indicates the data transfer will be from system memory to
|
|
device.
|
|
|
|
Return Value:
|
|
|
|
Returns STATUS_PENDING if the request is dispatched (since the
|
|
completion routine may change the irp's status value we cannot simply
|
|
return the value of the dispatch)
|
|
|
|
or returns a status value to indicate why it failed.
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassSendSrbAsynchronous(
|
|
PDEVICE_OBJECT Fdo,
|
|
PSCSI_REQUEST_BLOCK Srb,
|
|
PIRP Irp,
|
|
PVOID BufferAddress,
|
|
ULONG BufferLength,
|
|
BOOLEAN WriteToDevice
|
|
)
|
|
{
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PIO_STACK_LOCATION irpStack;
|
|
|
|
ULONG savedFlags;
|
|
|
|
//
|
|
// Write length to SRB.
|
|
//
|
|
|
|
Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
|
|
//
|
|
// Set SCSI bus address.
|
|
//
|
|
|
|
Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
|
|
|
|
//
|
|
// This is a violation of the SCSI spec but it is required for
|
|
// some targets.
|
|
//
|
|
|
|
// Srb->Cdb[1] |= deviceExtension->Lun << 5;
|
|
|
|
//
|
|
// Indicate auto request sense by specifying buffer and size.
|
|
//
|
|
|
|
Srb->SenseInfoBuffer = fdoExtension->SenseData;
|
|
Srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
|
|
Srb->DataBuffer = BufferAddress;
|
|
|
|
//
|
|
// Save the class driver specific flags away.
|
|
//
|
|
|
|
savedFlags = Srb->SrbFlags & SRB_FLAGS_CLASS_DRIVER_RESERVED;
|
|
|
|
//
|
|
// Allow the caller to specify that they do not wish
|
|
// IoStartNextPacket() to be called in the completion routine.
|
|
//
|
|
|
|
SET_FLAG(savedFlags, (Srb->SrbFlags & SRB_FLAGS_DONT_START_NEXT_PACKET));
|
|
|
|
if (BufferAddress != NULL) {
|
|
|
|
//
|
|
// Build Mdl if necessary.
|
|
//
|
|
|
|
if (Irp->MdlAddress == NULL) {
|
|
|
|
if (IoAllocateMdl(BufferAddress,
|
|
BufferLength,
|
|
FALSE,
|
|
FALSE,
|
|
Irp) == NULL) {
|
|
|
|
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
//
|
|
// ClassIoComplete() would have free'd the srb
|
|
//
|
|
|
|
if (PORT_ALLOCATED_SENSE(fdoExtension, Srb)) {
|
|
FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, Srb);
|
|
}
|
|
ClassFreeOrReuseSrb(fdoExtension, Srb);
|
|
ClassReleaseRemoveLock(Fdo, Irp);
|
|
ClassCompleteRequest(Fdo, Irp, IO_NO_INCREMENT);
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
MmBuildMdlForNonPagedPool(Irp->MdlAddress);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Make sure the buffer requested matches the MDL.
|
|
//
|
|
|
|
ASSERT(BufferAddress == MmGetMdlVirtualAddress(Irp->MdlAddress));
|
|
}
|
|
|
|
//
|
|
// Set read flag.
|
|
//
|
|
|
|
Srb->SrbFlags = WriteToDevice ? SRB_FLAGS_DATA_OUT : SRB_FLAGS_DATA_IN;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Clear flags.
|
|
//
|
|
|
|
Srb->SrbFlags = SRB_FLAGS_NO_DATA_TRANSFER;
|
|
}
|
|
|
|
//
|
|
// Restore saved flags.
|
|
//
|
|
|
|
SET_FLAG(Srb->SrbFlags, savedFlags);
|
|
|
|
//
|
|
// Disable synchronous transfer for these requests.
|
|
//
|
|
|
|
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
|
|
|
|
//
|
|
// Set the transfer length.
|
|
//
|
|
|
|
Srb->DataTransferLength = BufferLength;
|
|
|
|
//
|
|
// Zero out status.
|
|
//
|
|
|
|
Srb->ScsiStatus = Srb->SrbStatus = 0;
|
|
|
|
Srb->NextSrb = 0;
|
|
|
|
//
|
|
// Save a few parameters in the current stack location.
|
|
//
|
|
|
|
irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
//
|
|
// Save retry count in current Irp stack.
|
|
//
|
|
|
|
irpStack->Parameters.Others.Argument4 = (PVOID)MAXIMUM_RETRIES;
|
|
|
|
//
|
|
// Set up IoCompletion routine address.
|
|
//
|
|
|
|
IoSetCompletionRoutine(Irp, ClassIoComplete, Srb, TRUE, TRUE, TRUE);
|
|
|
|
//
|
|
// Get next stack location and
|
|
// set major function code.
|
|
//
|
|
|
|
irpStack = IoGetNextIrpStackLocation(Irp);
|
|
|
|
irpStack->MajorFunction = IRP_MJ_SCSI;
|
|
|
|
//
|
|
// Save SRB address in next stack for port driver.
|
|
//
|
|
|
|
irpStack->Parameters.Scsi.Srb = Srb;
|
|
|
|
//
|
|
// Set up Irp Address.
|
|
//
|
|
|
|
Srb->OriginalRequest = Irp;
|
|
|
|
//
|
|
// Call the port driver to process the request.
|
|
//
|
|
|
|
IoMarkIrpPending(Irp);
|
|
|
|
IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, Irp);
|
|
|
|
return STATUS_PENDING;
|
|
|
|
} // end ClassSendSrbAsynchronous()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassDeviceControlDispatch()
|
|
|
|
Routine Description:
|
|
|
|
The routine is the common class driver device control dispatch entry point.
|
|
This routine is invokes the device-specific drivers DeviceControl routine,
|
|
(which may call the Class driver's common DeviceControl routine).
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies a pointer to the device object for this request.
|
|
|
|
Irp - Supplies the Irp making the request.
|
|
|
|
Return Value:
|
|
|
|
Returns the status returned from the device-specific driver.
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassDeviceControlDispatch(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp
|
|
)
|
|
{
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
ULONG isRemoved;
|
|
|
|
isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
|
|
|
|
if(isRemoved) {
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
return STATUS_DEVICE_DOES_NOT_EXIST;
|
|
}
|
|
|
|
//
|
|
// Call the class specific driver DeviceControl routine.
|
|
// If it doesn't handle it, it will call back into ClassDeviceControl.
|
|
//
|
|
|
|
ASSERT(commonExtension->DevInfo->ClassDeviceControl);
|
|
|
|
return commonExtension->DevInfo->ClassDeviceControl(DeviceObject,Irp);
|
|
} // end ClassDeviceControlDispatch()
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassDeviceControl()
|
|
|
|
Routine Description:
|
|
|
|
The routine is the common class driver device control dispatch function.
|
|
This routine is called by a class driver when it get an unrecognized
|
|
device control request. This routine will perform the correct action for
|
|
common requests such as lock media. If the device request is unknown it
|
|
passed down to the next level.
|
|
|
|
This routine must be called with the remove lock held for the specified
|
|
irp.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies a pointer to the device object for this request.
|
|
|
|
Irp - Supplies the Irp making the request.
|
|
|
|
Return Value:
|
|
|
|
Returns back a STATUS_PENDING or a completion status.
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassDeviceControl(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp
|
|
)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PIO_STACK_LOCATION nextStack = NULL;
|
|
|
|
ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
|
|
|
|
PSCSI_REQUEST_BLOCK srb = NULL;
|
|
PCDB cdb = NULL;
|
|
|
|
NTSTATUS status;
|
|
ULONG modifiedIoControlCode;
|
|
|
|
//
|
|
// If this is a pass through I/O control, set the minor function code
|
|
// and device address and pass it to the port driver.
|
|
//
|
|
|
|
if ((controlCode == IOCTL_SCSI_PASS_THROUGH) ||
|
|
(controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT)) {
|
|
|
|
PSCSI_PASS_THROUGH scsiPass;
|
|
|
|
//
|
|
// Validiate the user buffer.
|
|
//
|
|
#if defined (_WIN64)
|
|
|
|
if (IoIs32bitProcess(Irp)) {
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(SCSI_PASS_THROUGH32)){
|
|
|
|
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto SetStatusAndReturn;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|
sizeof(SCSI_PASS_THROUGH)) {
|
|
|
|
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto SetStatusAndReturn;
|
|
}
|
|
}
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
nextStack = IoGetNextIrpStackLocation(Irp);
|
|
nextStack->MinorFunction = 1;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
goto SetStatusAndReturn;
|
|
}
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
switch (controlCode) {
|
|
|
|
case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID: {
|
|
|
|
PMOUNTDEV_UNIQUE_ID uniqueId;
|
|
|
|
if (!commonExtension->MountedDeviceInterfaceName.Buffer) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(MOUNTDEV_UNIQUE_ID)) {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);
|
|
break;
|
|
}
|
|
|
|
uniqueId = Irp->AssociatedIrp.SystemBuffer;
|
|
uniqueId->UniqueIdLength =
|
|
commonExtension->MountedDeviceInterfaceName.Length;
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(USHORT) + uniqueId->UniqueIdLength) {
|
|
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);
|
|
break;
|
|
}
|
|
|
|
RtlCopyMemory(uniqueId->UniqueId,
|
|
commonExtension->MountedDeviceInterfaceName.Buffer,
|
|
uniqueId->UniqueIdLength);
|
|
|
|
status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = sizeof(USHORT) +
|
|
uniqueId->UniqueIdLength;
|
|
break;
|
|
}
|
|
|
|
case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME: {
|
|
|
|
PMOUNTDEV_NAME name;
|
|
|
|
ASSERT(commonExtension->DeviceName.Buffer);
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(MOUNTDEV_NAME)) {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);
|
|
break;
|
|
}
|
|
|
|
name = Irp->AssociatedIrp.SystemBuffer;
|
|
name->NameLength = commonExtension->DeviceName.Length;
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(USHORT) + name->NameLength) {
|
|
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);
|
|
break;
|
|
}
|
|
|
|
RtlCopyMemory(name->Name, commonExtension->DeviceName.Buffer,
|
|
name->NameLength);
|
|
|
|
status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = sizeof(USHORT) + name->NameLength;
|
|
break;
|
|
}
|
|
|
|
case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME: {
|
|
|
|
PMOUNTDEV_SUGGESTED_LINK_NAME suggestedName;
|
|
WCHAR driveLetterNameBuffer[10];
|
|
RTL_QUERY_REGISTRY_TABLE queryTable[2];
|
|
PWSTR valueName;
|
|
UNICODE_STRING driveLetterName;
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(MOUNTDEV_SUGGESTED_LINK_NAME)) {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
Irp->IoStatus.Information = sizeof(MOUNTDEV_SUGGESTED_LINK_NAME);
|
|
break;
|
|
}
|
|
|
|
valueName = ExAllocatePoolWithTag(
|
|
PagedPool,
|
|
commonExtension->DeviceName.Length + sizeof(WCHAR),
|
|
'8CcS');
|
|
|
|
if (!valueName) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
RtlCopyMemory(valueName, commonExtension->DeviceName.Buffer,
|
|
commonExtension->DeviceName.Length);
|
|
valueName[commonExtension->DeviceName.Length/sizeof(WCHAR)] = 0;
|
|
|
|
driveLetterName.Buffer = driveLetterNameBuffer;
|
|
driveLetterName.MaximumLength = 20;
|
|
driveLetterName.Length = 0;
|
|
|
|
RtlZeroMemory(queryTable, 2*sizeof(RTL_QUERY_REGISTRY_TABLE));
|
|
queryTable[0].Flags = RTL_QUERY_REGISTRY_REQUIRED |
|
|
RTL_QUERY_REGISTRY_DIRECT;
|
|
queryTable[0].Name = valueName;
|
|
queryTable[0].EntryContext = &driveLetterName;
|
|
|
|
status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
|
|
L"\\Registry\\Machine\\System\\DISK",
|
|
queryTable, NULL, NULL);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
ExFreePool(valueName);
|
|
break;
|
|
}
|
|
|
|
if (driveLetterName.Length == 4 &&
|
|
driveLetterName.Buffer[0] == '%' &&
|
|
driveLetterName.Buffer[1] == ':') {
|
|
|
|
driveLetterName.Buffer[0] = 0xFF;
|
|
|
|
} else if (driveLetterName.Length != 4 ||
|
|
driveLetterName.Buffer[0] < FirstDriveLetter ||
|
|
driveLetterName.Buffer[0] > LastDriveLetter ||
|
|
driveLetterName.Buffer[1] != ':') {
|
|
|
|
status = STATUS_NOT_FOUND;
|
|
ExFreePool(valueName);
|
|
break;
|
|
}
|
|
|
|
suggestedName = Irp->AssociatedIrp.SystemBuffer;
|
|
suggestedName->UseOnlyIfThereAreNoOtherLinks = TRUE;
|
|
suggestedName->NameLength = 28;
|
|
|
|
Irp->IoStatus.Information =
|
|
FIELD_OFFSET(MOUNTDEV_SUGGESTED_LINK_NAME, Name) + 28;
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
Irp->IoStatus.Information) {
|
|
|
|
Irp->IoStatus.Information =
|
|
sizeof(MOUNTDEV_SUGGESTED_LINK_NAME);
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
ExFreePool(valueName);
|
|
break;
|
|
}
|
|
|
|
RtlDeleteRegistryValue(RTL_REGISTRY_ABSOLUTE,
|
|
L"\\Registry\\Machine\\System\\DISK",
|
|
valueName);
|
|
|
|
ExFreePool(valueName);
|
|
|
|
RtlCopyMemory(suggestedName->Name, L"\\DosDevices\\", 24);
|
|
suggestedName->Name[12] = driveLetterName.Buffer[0];
|
|
suggestedName->Name[13] = ':';
|
|
|
|
//
|
|
// NT_SUCCESS(status) based on RtlQueryRegistryValues
|
|
//
|
|
status = STATUS_SUCCESS;
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
status = STATUS_PENDING;
|
|
break;
|
|
}
|
|
|
|
if (status != STATUS_PENDING) {
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
return status;
|
|
}
|
|
|
|
if (commonExtension->IsFdo){
|
|
|
|
PULONG_PTR function;
|
|
|
|
srb = ExAllocatePoolWithTag(NonPagedPool,
|
|
sizeof(SCSI_REQUEST_BLOCK) +
|
|
(sizeof(ULONG_PTR) * 2),
|
|
'9CcS');
|
|
|
|
if (srb == NULL) {
|
|
|
|
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto SetStatusAndReturn;
|
|
}
|
|
|
|
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
|
|
|
|
cdb = (PCDB)srb->Cdb;
|
|
|
|
//
|
|
// Save the function code and the device object in the memory after
|
|
// the SRB.
|
|
//
|
|
|
|
function = (PULONG_PTR) ((PSCSI_REQUEST_BLOCK) (srb + 1));
|
|
*function = (ULONG_PTR) DeviceObject;
|
|
function++;
|
|
*function = (ULONG_PTR) controlCode;
|
|
|
|
} else {
|
|
srb = NULL;
|
|
}
|
|
|
|
//
|
|
// Change the device type to storage for the switch statement, but only
|
|
// if from a legacy device type
|
|
//
|
|
|
|
if (((controlCode & 0xffff0000) == (IOCTL_DISK_BASE << 16)) ||
|
|
((controlCode & 0xffff0000) == (IOCTL_TAPE_BASE << 16)) ||
|
|
((controlCode & 0xffff0000) == (IOCTL_CDROM_BASE << 16))
|
|
) {
|
|
|
|
modifiedIoControlCode = (controlCode & ~0xffff0000);
|
|
modifiedIoControlCode |= (IOCTL_STORAGE_BASE << 16);
|
|
|
|
} else {
|
|
|
|
modifiedIoControlCode = controlCode;
|
|
|
|
}
|
|
|
|
DBGTRACE(ClassDebugTrace, ("> ioctl %xh (%s)", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode)));
|
|
|
|
switch (modifiedIoControlCode) {
|
|
|
|
case IOCTL_STORAGE_GET_HOTPLUG_INFO: {
|
|
|
|
if (srb) {
|
|
ExFreePool(srb);
|
|
srb = NULL;
|
|
}
|
|
|
|
if(irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(STORAGE_HOTPLUG_INFO)) {
|
|
|
|
//
|
|
// Indicate unsuccessful status and no data transferred.
|
|
//
|
|
|
|
Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
|
|
Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
} else if(!commonExtension->IsFdo) {
|
|
|
|
//
|
|
// Just forward this down and return
|
|
//
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
|
|
} else {
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
PSTORAGE_HOTPLUG_INFO info;
|
|
|
|
fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension;
|
|
info = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
*info = fdoExtension->PrivateFdoData->HotplugInfo;
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO);
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
status = STATUS_SUCCESS;
|
|
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IOCTL_STORAGE_SET_HOTPLUG_INFO: {
|
|
|
|
if (srb)
|
|
{
|
|
ExFreePool(srb);
|
|
srb = NULL;
|
|
}
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|
sizeof(STORAGE_HOTPLUG_INFO)) {
|
|
|
|
//
|
|
// Indicate unsuccessful status and no data transferred.
|
|
//
|
|
|
|
Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
goto SetStatusAndReturn;
|
|
|
|
}
|
|
|
|
if(!commonExtension->IsFdo) {
|
|
|
|
//
|
|
// Just forward this down and return
|
|
//
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
|
|
} else {
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension;
|
|
PSTORAGE_HOTPLUG_INFO info = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
if (info->Size != fdoExtension->PrivateFdoData->HotplugInfo.Size)
|
|
{
|
|
status = STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
if (info->MediaRemovable != fdoExtension->PrivateFdoData->HotplugInfo.MediaRemovable)
|
|
{
|
|
status = STATUS_INVALID_PARAMETER_2;
|
|
}
|
|
|
|
if (info->MediaHotplug != fdoExtension->PrivateFdoData->HotplugInfo.MediaHotplug)
|
|
{
|
|
status = STATUS_INVALID_PARAMETER_3;
|
|
}
|
|
|
|
if (info->WriteCacheEnableOverride != fdoExtension->PrivateFdoData->HotplugInfo.WriteCacheEnableOverride)
|
|
{
|
|
status = STATUS_INVALID_PARAMETER_5;
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
fdoExtension->PrivateFdoData->HotplugInfo.DeviceHotplug = info->DeviceHotplug;
|
|
|
|
//
|
|
// Store the user-defined override in the registry
|
|
//
|
|
|
|
ClassSetDeviceParameter(fdoExtension,
|
|
CLASSP_REG_SUBKEY_NAME,
|
|
CLASSP_REG_REMOVAL_POLICY_VALUE_NAME,
|
|
(info->DeviceHotplug) ? RemovalPolicyExpectSurpriseRemoval : RemovalPolicyExpectOrderlyRemoval);
|
|
}
|
|
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case IOCTL_STORAGE_CHECK_VERIFY:
|
|
case IOCTL_STORAGE_CHECK_VERIFY2: {
|
|
|
|
PIRP irp2 = NULL;
|
|
PIO_STACK_LOCATION newStack;
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL;
|
|
|
|
DebugPrint((1,"DeviceIoControl: Check verify\n"));
|
|
|
|
//
|
|
// If a buffer for a media change count was provided, make sure it's
|
|
// big enough to hold the result
|
|
//
|
|
|
|
if(irpStack->Parameters.DeviceIoControl.OutputBufferLength) {
|
|
|
|
//
|
|
// If the buffer is too small to hold the media change count
|
|
// then return an error to the caller
|
|
//
|
|
|
|
if(irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(ULONG)) {
|
|
|
|
DebugPrint((3,"DeviceIoControl: media count "
|
|
"buffer too small\n"));
|
|
|
|
Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
|
|
Irp->IoStatus.Information = sizeof(ULONG);
|
|
|
|
if(srb != NULL) {
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
goto SetStatusAndReturn;
|
|
|
|
}
|
|
}
|
|
|
|
if(!commonExtension->IsFdo) {
|
|
|
|
//
|
|
// If this is a PDO then we should just forward the request down
|
|
//
|
|
ASSERT(!srb);
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
|
|
goto SetStatusAndReturn;
|
|
|
|
} else {
|
|
|
|
fdoExtension = DeviceObject->DeviceExtension;
|
|
|
|
}
|
|
|
|
if(irpStack->Parameters.DeviceIoControl.OutputBufferLength) {
|
|
|
|
//
|
|
// The caller has provided a valid buffer. Allocate an additional
|
|
// irp and stick the CheckVerify completion routine on it. We will
|
|
// then send this down to the port driver instead of the irp the
|
|
// caller sent in
|
|
//
|
|
|
|
DebugPrint((2,"DeviceIoControl: Check verify wants "
|
|
"media count\n"));
|
|
|
|
//
|
|
// Allocate a new irp to send the TestUnitReady to the port driver
|
|
//
|
|
|
|
irp2 = IoAllocateIrp((CCHAR) (DeviceObject->StackSize + 3), FALSE);
|
|
|
|
if(irp2 == NULL) {
|
|
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
Irp->IoStatus.Information = 0;
|
|
ASSERT(srb);
|
|
ExFreePool(srb);
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto SetStatusAndReturn;
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Make sure to acquire the lock for the new irp.
|
|
//
|
|
|
|
ClassAcquireRemoveLock(DeviceObject, irp2);
|
|
|
|
irp2->Tail.Overlay.Thread = Irp->Tail.Overlay.Thread;
|
|
IoSetNextIrpStackLocation(irp2);
|
|
|
|
//
|
|
// Set the top stack location and shove the master Irp into the
|
|
// top location
|
|
//
|
|
|
|
newStack = IoGetCurrentIrpStackLocation(irp2);
|
|
newStack->Parameters.Others.Argument1 = Irp;
|
|
newStack->DeviceObject = DeviceObject;
|
|
|
|
//
|
|
// Stick the check verify completion routine onto the stack
|
|
// and prepare the irp for the port driver
|
|
//
|
|
|
|
IoSetCompletionRoutine(irp2,
|
|
ClassCheckVerifyComplete,
|
|
NULL,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE);
|
|
|
|
IoSetNextIrpStackLocation(irp2);
|
|
newStack = IoGetCurrentIrpStackLocation(irp2);
|
|
newStack->DeviceObject = DeviceObject;
|
|
newStack->MajorFunction = irpStack->MajorFunction;
|
|
newStack->MinorFunction = irpStack->MinorFunction;
|
|
|
|
//
|
|
// Mark the master irp as pending - whether the lower level
|
|
// driver completes it immediately or not this should allow it
|
|
// to go all the way back up.
|
|
//
|
|
|
|
IoMarkIrpPending(Irp);
|
|
|
|
Irp = irp2;
|
|
|
|
}
|
|
|
|
//
|
|
// Test Unit Ready
|
|
//
|
|
|
|
srb->CdbLength = 6;
|
|
cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY;
|
|
|
|
//
|
|
// Set timeout value.
|
|
//
|
|
|
|
srb->TimeOutValue = fdoExtension->TimeOutValue;
|
|
|
|
//
|
|
// If this was a CV2 then mark the request as low-priority so we don't
|
|
// spin up the drive just to satisfy it.
|
|
//
|
|
|
|
if(controlCode == IOCTL_STORAGE_CHECK_VERIFY2) {
|
|
SET_FLAG(srb->SrbFlags, SRB_CLASS_FLAGS_LOW_PRIORITY);
|
|
}
|
|
|
|
//
|
|
// Since this routine will always hand the request to the
|
|
// port driver if there isn't a data transfer to be done
|
|
// we don't have to worry about completing the request here
|
|
// on an error
|
|
//
|
|
|
|
//
|
|
// This routine uses a completion routine so we don't want to release
|
|
// the remove lock until then.
|
|
//
|
|
|
|
status = ClassSendSrbAsynchronous(DeviceObject,
|
|
srb,
|
|
Irp,
|
|
NULL,
|
|
0,
|
|
FALSE);
|
|
|
|
break;
|
|
}
|
|
|
|
case IOCTL_STORAGE_MEDIA_REMOVAL:
|
|
case IOCTL_STORAGE_EJECTION_CONTROL: {
|
|
|
|
PPREVENT_MEDIA_REMOVAL mediaRemoval = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
DebugPrint((3, "DiskIoControl: ejection control\n"));
|
|
|
|
if(srb) {
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
if(irpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|
sizeof(PREVENT_MEDIA_REMOVAL)) {
|
|
|
|
//
|
|
// Indicate unsuccessful status and no data transferred.
|
|
//
|
|
|
|
Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
goto SetStatusAndReturn;
|
|
}
|
|
|
|
if(!commonExtension->IsFdo) {
|
|
|
|
//
|
|
// Just forward this down and return
|
|
//
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
}
|
|
else {
|
|
|
|
// i don't believe this assertion is valid. this is a request
|
|
// from user-mode, so they could request this for any device
|
|
// they want? also, we handle it properly.
|
|
// ASSERT(TEST_FLAG(DeviceObject->Characteristics, FILE_REMOVABLE_MEDIA));
|
|
status = ClasspEjectionControl(
|
|
DeviceObject,
|
|
Irp,
|
|
((modifiedIoControlCode ==
|
|
IOCTL_STORAGE_EJECTION_CONTROL) ? SecureMediaLock :
|
|
SimpleMediaLock),
|
|
mediaRemoval->PreventMediaRemoval);
|
|
|
|
Irp->IoStatus.Status = status;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case IOCTL_STORAGE_MCN_CONTROL: {
|
|
|
|
DebugPrint((3, "DiskIoControl: MCN control\n"));
|
|
|
|
if(irpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|
sizeof(PREVENT_MEDIA_REMOVAL)) {
|
|
|
|
//
|
|
// Indicate unsuccessful status and no data transferred.
|
|
//
|
|
|
|
Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
if(srb) {
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
status = STATUS_INFO_LENGTH_MISMATCH;
|
|
goto SetStatusAndReturn;
|
|
}
|
|
|
|
if(!commonExtension->IsFdo) {
|
|
|
|
//
|
|
// Just forward this down and return
|
|
//
|
|
|
|
if(srb) {
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Call to the FDO - handle the ejection control.
|
|
//
|
|
|
|
status = ClasspMcnControl(DeviceObject->DeviceExtension,
|
|
Irp,
|
|
srb);
|
|
}
|
|
goto SetStatusAndReturn;
|
|
}
|
|
|
|
case IOCTL_STORAGE_RESERVE:
|
|
case IOCTL_STORAGE_RELEASE: {
|
|
|
|
//
|
|
// Reserve logical unit.
|
|
//
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL;
|
|
|
|
if(!commonExtension->IsFdo) {
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
goto SetStatusAndReturn;
|
|
} else {
|
|
fdoExtension = DeviceObject->DeviceExtension;
|
|
}
|
|
|
|
srb->CdbLength = 6;
|
|
|
|
if(modifiedIoControlCode == IOCTL_STORAGE_RESERVE) {
|
|
cdb->CDB6GENERIC.OperationCode = SCSIOP_RESERVE_UNIT;
|
|
} else {
|
|
cdb->CDB6GENERIC.OperationCode = SCSIOP_RELEASE_UNIT;
|
|
}
|
|
|
|
//
|
|
// Set timeout value.
|
|
//
|
|
|
|
srb->TimeOutValue = fdoExtension->TimeOutValue;
|
|
|
|
status = ClassSendSrbAsynchronous(DeviceObject,
|
|
srb,
|
|
Irp,
|
|
NULL,
|
|
0,
|
|
FALSE);
|
|
|
|
break;
|
|
}
|
|
|
|
case IOCTL_STORAGE_EJECT_MEDIA:
|
|
case IOCTL_STORAGE_LOAD_MEDIA:
|
|
case IOCTL_STORAGE_LOAD_MEDIA2:{
|
|
|
|
//
|
|
// Eject media.
|
|
//
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL;
|
|
|
|
if(!commonExtension->IsFdo) {
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
goto SetStatusAndReturn;
|
|
} else {
|
|
fdoExtension = DeviceObject->DeviceExtension;
|
|
}
|
|
|
|
if(commonExtension->PagingPathCount != 0) {
|
|
|
|
DebugPrint((1, "ClassDeviceControl: call to eject paging device - "
|
|
"failure\n"));
|
|
|
|
status = STATUS_FILES_OPEN;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
if(srb) {
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
goto SetStatusAndReturn;
|
|
}
|
|
|
|
//
|
|
// Synchronize with ejection control and ejection cleanup code as
|
|
// well as other eject/load requests.
|
|
//
|
|
|
|
KeEnterCriticalRegion();
|
|
KeWaitForSingleObject(&(fdoExtension->EjectSynchronizationEvent),
|
|
UserRequest,
|
|
UserMode,
|
|
FALSE,
|
|
NULL);
|
|
|
|
if(fdoExtension->ProtectedLockCount != 0) {
|
|
|
|
DebugPrint((1, "ClassDeviceControl: call to eject protected locked "
|
|
"device - failure\n"));
|
|
|
|
status = STATUS_DEVICE_BUSY;
|
|
Irp->IoStatus.Status = status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
if(srb) {
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
KeSetEvent(&fdoExtension->EjectSynchronizationEvent,
|
|
IO_NO_INCREMENT,
|
|
FALSE);
|
|
KeLeaveCriticalRegion();
|
|
|
|
goto SetStatusAndReturn;
|
|
}
|
|
|
|
srb->CdbLength = 6;
|
|
|
|
cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
|
|
cdb->START_STOP.LoadEject = 1;
|
|
|
|
if(modifiedIoControlCode == IOCTL_STORAGE_EJECT_MEDIA) {
|
|
cdb->START_STOP.Start = 0;
|
|
} else {
|
|
cdb->START_STOP.Start = 1;
|
|
}
|
|
|
|
//
|
|
// Set timeout value.
|
|
//
|
|
|
|
srb->TimeOutValue = fdoExtension->TimeOutValue;
|
|
status = ClassSendSrbAsynchronous(DeviceObject,
|
|
srb,
|
|
Irp,
|
|
NULL,
|
|
0,
|
|
FALSE);
|
|
|
|
KeSetEvent(&fdoExtension->EjectSynchronizationEvent, IO_NO_INCREMENT, FALSE);
|
|
KeLeaveCriticalRegion();
|
|
|
|
break;
|
|
}
|
|
|
|
case IOCTL_STORAGE_FIND_NEW_DEVICES: {
|
|
|
|
if(srb) {
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
if(commonExtension->IsFdo) {
|
|
|
|
IoInvalidateDeviceRelations(
|
|
((PFUNCTIONAL_DEVICE_EXTENSION) commonExtension)->LowerPdo,
|
|
BusRelations);
|
|
|
|
status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Status = status;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
}
|
|
else {
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IOCTL_STORAGE_GET_DEVICE_NUMBER: {
|
|
|
|
if(srb) {
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
if(irpStack->Parameters.DeviceIoControl.OutputBufferLength >=
|
|
sizeof(STORAGE_DEVICE_NUMBER)) {
|
|
|
|
PSTORAGE_DEVICE_NUMBER deviceNumber =
|
|
Irp->AssociatedIrp.SystemBuffer;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension =
|
|
commonExtension->PartitionZeroExtension;
|
|
|
|
deviceNumber->DeviceType = fdoExtension->CommonExtension.DeviceObject->DeviceType;
|
|
deviceNumber->DeviceNumber = fdoExtension->DeviceNumber;
|
|
deviceNumber->PartitionNumber = commonExtension->PartitionNumber;
|
|
|
|
status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER);
|
|
|
|
} else {
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER);
|
|
}
|
|
|
|
Irp->IoStatus.Status = status;
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
|
|
DebugPrint((4, "IoDeviceControl: Unsupported device IOCTL %x for %p\n",
|
|
controlCode, DeviceObject));
|
|
|
|
//
|
|
// Pass the device control to the next driver.
|
|
//
|
|
|
|
if(srb) {
|
|
ExFreePool(srb);
|
|
}
|
|
|
|
//
|
|
// Copy the Irp stack parameters to the next stack location.
|
|
//
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
break;
|
|
}
|
|
|
|
} // end switch( ...
|
|
|
|
SetStatusAndReturn:
|
|
|
|
DBGTRACE(ClassDebugTrace, ("< ioctl %xh (%s): status %xh.", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode), status));
|
|
|
|
return status;
|
|
} // end ClassDeviceControl()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassShutdownFlush()
|
|
|
|
Routine Description:
|
|
|
|
This routine is called for a shutdown and flush IRPs. These are sent by the
|
|
system before it actually shuts down or when the file system does a flush.
|
|
If it exists, the device-specific driver's routine will be invoked. If there
|
|
wasn't one specified, the Irp will be completed with an Invalid device request.
|
|
|
|
Arguments:
|
|
|
|
DriverObject - Pointer to device object to being shutdown by system.
|
|
|
|
Irp - IRP involved.
|
|
|
|
Return Value:
|
|
|
|
NT Status
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassShutdownFlush(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
|
|
ULONG isRemoved;
|
|
|
|
NTSTATUS status;
|
|
|
|
isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
|
|
|
|
if(isRemoved) {
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return STATUS_DEVICE_DOES_NOT_EXIST;
|
|
}
|
|
|
|
if (commonExtension->DevInfo->ClassShutdownFlush) {
|
|
|
|
//
|
|
// Call the device-specific driver's routine.
|
|
//
|
|
|
|
return commonExtension->DevInfo->ClassShutdownFlush(DeviceObject, Irp);
|
|
}
|
|
|
|
//
|
|
// Device-specific driver doesn't support this.
|
|
//
|
|
|
|
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
} // end ClassShutdownFlush()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassCreateDeviceObject()
|
|
|
|
Routine Description:
|
|
|
|
This routine creates an object for the physical device specified and
|
|
sets up the deviceExtension's function pointers for each entry point
|
|
in the device-specific driver.
|
|
|
|
Arguments:
|
|
|
|
DriverObject - Pointer to driver object created by system.
|
|
|
|
ObjectNameBuffer - Dir. name of the object to create.
|
|
|
|
LowerDeviceObject - Pointer to the lower device object
|
|
|
|
IsFdo - should this be an fdo or a pdo
|
|
|
|
DeviceObject - Pointer to the device object pointer we will return.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassCreateDeviceObject(
|
|
IN PDRIVER_OBJECT DriverObject,
|
|
IN PCCHAR ObjectNameBuffer,
|
|
IN PDEVICE_OBJECT LowerDevice,
|
|
IN BOOLEAN IsFdo,
|
|
IN OUT PDEVICE_OBJECT *DeviceObject
|
|
)
|
|
{
|
|
BOOLEAN isPartitionable;
|
|
STRING ntNameString;
|
|
UNICODE_STRING ntUnicodeString;
|
|
NTSTATUS status, status2;
|
|
PDEVICE_OBJECT deviceObject = NULL;
|
|
|
|
ULONG characteristics;
|
|
|
|
PCLASS_DRIVER_EXTENSION
|
|
driverExtension = IoGetDriverObjectExtension(DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
|
|
PCLASS_DEV_INFO devInfo;
|
|
|
|
PAGED_CODE();
|
|
|
|
*DeviceObject = NULL;
|
|
RtlInitUnicodeString(&ntUnicodeString, NULL);
|
|
|
|
DebugPrint((2, "ClassCreateFdo: Create device object\n"));
|
|
|
|
ASSERT(LowerDevice);
|
|
|
|
//
|
|
// Make sure that if we're making PDO's we have an enumeration routine
|
|
//
|
|
|
|
isPartitionable = (driverExtension->InitData.ClassEnumerateDevice != NULL);
|
|
|
|
ASSERT(IsFdo || isPartitionable);
|
|
|
|
//
|
|
// Grab the correct dev-info structure out of the init data
|
|
//
|
|
|
|
if(IsFdo) {
|
|
devInfo = &(driverExtension->InitData.FdoData);
|
|
} else {
|
|
devInfo = &(driverExtension->InitData.PdoData);
|
|
}
|
|
|
|
characteristics = devInfo->DeviceCharacteristics;
|
|
|
|
if(ARGUMENT_PRESENT(ObjectNameBuffer)) {
|
|
DebugPrint((2, "ClassCreateFdo: Name is %s\n", ObjectNameBuffer));
|
|
|
|
RtlInitString(&ntNameString, ObjectNameBuffer);
|
|
|
|
status = RtlAnsiStringToUnicodeString(&ntUnicodeString, &ntNameString, TRUE);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
DebugPrint((1,
|
|
"ClassCreateFdo: Cannot convert string %s\n",
|
|
ObjectNameBuffer));
|
|
|
|
ntUnicodeString.Buffer = NULL;
|
|
return status;
|
|
}
|
|
} else {
|
|
DebugPrint((2, "ClassCreateFdo: Object will be unnamed\n"));
|
|
|
|
if(IsFdo == FALSE) {
|
|
|
|
//
|
|
// PDO's have to have some sort of name.
|
|
//
|
|
|
|
SET_FLAG(characteristics, FILE_AUTOGENERATED_DEVICE_NAME);
|
|
}
|
|
|
|
RtlInitUnicodeString(&ntUnicodeString, NULL);
|
|
}
|
|
|
|
status = IoCreateDevice(DriverObject,
|
|
devInfo->DeviceExtensionSize,
|
|
&ntUnicodeString,
|
|
devInfo->DeviceType,
|
|
devInfo->DeviceCharacteristics,
|
|
FALSE,
|
|
&deviceObject);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
DebugPrint((1, "ClassCreateFdo: Can not create device object %lx\n",
|
|
status));
|
|
ASSERT(deviceObject == NULL);
|
|
|
|
//
|
|
// buffer is not used any longer here.
|
|
//
|
|
|
|
if (ntUnicodeString.Buffer != NULL) {
|
|
DebugPrint((1, "ClassCreateFdo: Freeing unicode name buffer\n"));
|
|
ExFreePool(ntUnicodeString.Buffer);
|
|
RtlInitUnicodeString(&ntUnicodeString, NULL);
|
|
}
|
|
|
|
} else {
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = deviceObject->DeviceExtension;
|
|
|
|
RtlZeroMemory(
|
|
deviceObject->DeviceExtension,
|
|
devInfo->DeviceExtensionSize);
|
|
|
|
//
|
|
// Setup version code
|
|
//
|
|
|
|
commonExtension->Version = 0x03;
|
|
|
|
//
|
|
// Setup the remove lock and event
|
|
//
|
|
|
|
commonExtension->IsRemoved = NO_REMOVE;
|
|
commonExtension->RemoveLock = 0;
|
|
KeInitializeEvent(&commonExtension->RemoveEvent,
|
|
SynchronizationEvent,
|
|
FALSE);
|
|
|
|
#if DBG
|
|
KeInitializeSpinLock(&commonExtension->RemoveTrackingSpinlock);
|
|
commonExtension->RemoveTrackingList = NULL;
|
|
#else
|
|
commonExtension->RemoveTrackingSpinlock = (ULONG_PTR) -1;
|
|
commonExtension->RemoveTrackingList = (PVOID) -1;
|
|
#endif
|
|
|
|
//
|
|
// Acquire the lock once. This reference will be released when the
|
|
// remove IRP has been received.
|
|
//
|
|
|
|
ClassAcquireRemoveLock(deviceObject, (PIRP) deviceObject);
|
|
|
|
//
|
|
// Store a pointer to the driver extension so we don't have to do
|
|
// lookups to get it.
|
|
//
|
|
|
|
commonExtension->DriverExtension = driverExtension;
|
|
|
|
//
|
|
// Fill in entry points
|
|
//
|
|
|
|
commonExtension->DevInfo = devInfo;
|
|
|
|
//
|
|
// Initialize some of the common values in the structure
|
|
//
|
|
|
|
commonExtension->DeviceObject = deviceObject;
|
|
|
|
commonExtension->LowerDeviceObject = NULL;
|
|
|
|
if(IsFdo) {
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PVOID) commonExtension;
|
|
|
|
commonExtension->PartitionZeroExtension = deviceObject->DeviceExtension;
|
|
|
|
//
|
|
// Set the initial device object flags.
|
|
//
|
|
|
|
SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE);
|
|
|
|
//
|
|
// Clear the PDO list
|
|
//
|
|
|
|
commonExtension->ChildList = NULL;
|
|
|
|
commonExtension->DriverData =
|
|
((PFUNCTIONAL_DEVICE_EXTENSION) deviceObject->DeviceExtension + 1);
|
|
|
|
if(isPartitionable) {
|
|
|
|
commonExtension->PartitionNumber = 0;
|
|
} else {
|
|
commonExtension->PartitionNumber = (ULONG) (-1L);
|
|
}
|
|
|
|
fdoExtension->DevicePowerState = PowerDeviceD0;
|
|
|
|
KeInitializeEvent(&fdoExtension->EjectSynchronizationEvent,
|
|
SynchronizationEvent,
|
|
TRUE);
|
|
|
|
KeInitializeEvent(&fdoExtension->ChildLock,
|
|
SynchronizationEvent,
|
|
TRUE);
|
|
|
|
status = ClasspAllocateReleaseRequest(deviceObject);
|
|
|
|
if(!NT_SUCCESS(status)) {
|
|
IoDeleteDevice(deviceObject);
|
|
*DeviceObject = NULL;
|
|
|
|
if (ntUnicodeString.Buffer != NULL) {
|
|
DebugPrint((1, "ClassCreateFdo: Freeing unicode name buffer\n"));
|
|
ExFreePool(ntUnicodeString.Buffer);
|
|
RtlInitUnicodeString(&ntUnicodeString, NULL);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
} else {
|
|
|
|
PPHYSICAL_DEVICE_EXTENSION pdoExtension =
|
|
deviceObject->DeviceExtension;
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION p0Extension =
|
|
LowerDevice->DeviceExtension;
|
|
|
|
SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE);
|
|
|
|
commonExtension->PartitionZeroExtension = p0Extension;
|
|
|
|
//
|
|
// Stick this onto the PDO list
|
|
//
|
|
|
|
ClassAddChild(p0Extension, pdoExtension, TRUE);
|
|
|
|
commonExtension->DriverData = (PVOID) (pdoExtension + 1);
|
|
|
|
//
|
|
// Get the top of stack for the lower device - this allows
|
|
// filters to get stuck in between the partitions and the
|
|
// physical disk.
|
|
//
|
|
|
|
commonExtension->LowerDeviceObject =
|
|
IoGetAttachedDeviceReference(LowerDevice);
|
|
|
|
//
|
|
// Pnp will keep a reference to the lower device object long
|
|
// after this partition has been deleted. Dereference now so
|
|
// we don't have to deal with it later.
|
|
//
|
|
|
|
ObDereferenceObject(commonExtension->LowerDeviceObject);
|
|
}
|
|
|
|
KeInitializeEvent(&commonExtension->PathCountEvent, SynchronizationEvent, TRUE);
|
|
|
|
commonExtension->IsFdo = IsFdo;
|
|
|
|
commonExtension->DeviceName = ntUnicodeString;
|
|
|
|
commonExtension->PreviousState = 0xff;
|
|
|
|
InitializeDictionary(&(commonExtension->FileObjectDictionary));
|
|
|
|
commonExtension->CurrentState = IRP_MN_STOP_DEVICE;
|
|
}
|
|
|
|
*DeviceObject = deviceObject;
|
|
|
|
return status;
|
|
} // end ClassCreateDeviceObject()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassClaimDevice()
|
|
|
|
Routine Description:
|
|
|
|
This function claims a device in the port driver. The port driver object
|
|
is updated with the correct driver object if the device is successfully
|
|
claimed.
|
|
|
|
Arguments:
|
|
|
|
LowerDeviceObject - Supplies the base port device object.
|
|
|
|
Release - Indicates the logical unit should be released rather than claimed.
|
|
|
|
Return Value:
|
|
|
|
Returns a status indicating success or failure of the operation.
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassClaimDevice(
|
|
IN PDEVICE_OBJECT LowerDeviceObject,
|
|
IN BOOLEAN Release
|
|
)
|
|
{
|
|
IO_STATUS_BLOCK ioStatus;
|
|
PIRP irp;
|
|
PIO_STACK_LOCATION irpStack;
|
|
KEVENT event;
|
|
NTSTATUS status;
|
|
SCSI_REQUEST_BLOCK srb;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Clear the SRB fields.
|
|
//
|
|
|
|
RtlZeroMemory(&srb, sizeof(SCSI_REQUEST_BLOCK));
|
|
|
|
//
|
|
// Write length to SRB.
|
|
//
|
|
|
|
srb.Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
|
|
srb.Function = Release ? SRB_FUNCTION_RELEASE_DEVICE :
|
|
SRB_FUNCTION_CLAIM_DEVICE;
|
|
|
|
//
|
|
// Set the event object to the unsignaled state.
|
|
// It will be used to signal request completion
|
|
//
|
|
|
|
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
|
|
|
|
//
|
|
// Build synchronous request with no transfer.
|
|
//
|
|
|
|
irp = IoBuildDeviceIoControlRequest(IOCTL_SCSI_EXECUTE_NONE,
|
|
LowerDeviceObject,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0,
|
|
TRUE,
|
|
&event,
|
|
&ioStatus);
|
|
|
|
if (irp == NULL) {
|
|
DebugPrint((1, "ClassClaimDevice: Can't allocate Irp\n"));
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
irpStack = IoGetNextIrpStackLocation(irp);
|
|
|
|
//
|
|
// Save SRB address in next stack for port driver.
|
|
//
|
|
|
|
irpStack->Parameters.Scsi.Srb = &srb;
|
|
|
|
//
|
|
// Set up IRP Address.
|
|
//
|
|
|
|
srb.OriginalRequest = irp;
|
|
|
|
//
|
|
// Call the port driver with the request and wait for it to complete.
|
|
//
|
|
|
|
status = IoCallDriver(LowerDeviceObject, irp);
|
|
if (status == STATUS_PENDING) {
|
|
|
|
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
|
status = ioStatus.Status;
|
|
}
|
|
|
|
//
|
|
// If this is a release request, then just decrement the reference count
|
|
// and return. The status does not matter.
|
|
//
|
|
|
|
if (Release) {
|
|
|
|
// ObDereferenceObject(LowerDeviceObject);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
ASSERT(srb.DataBuffer != NULL);
|
|
ASSERT(!TEST_FLAG(srb.SrbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));
|
|
|
|
return status;
|
|
} // end ClassClaimDevice()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassInternalIoControl()
|
|
|
|
Routine Description:
|
|
|
|
This routine passes internal device controls to the port driver.
|
|
Internal device controls are used by higher level drivers both for ioctls
|
|
and to pass through scsi requests.
|
|
|
|
If the IoControlCode does not match any of the handled ioctls and is
|
|
a valid system address then the request will be treated as an SRB and
|
|
passed down to the lower driver. If the IoControlCode is not a valid
|
|
system address the ioctl will be failed.
|
|
|
|
Callers must therefore be extremely cautious to pass correct, initialized
|
|
values to this function.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies a pointer to the device object for this request.
|
|
|
|
Irp - Supplies the Irp making the request.
|
|
|
|
Return Value:
|
|
|
|
Returns back a STATUS_PENDING or a completion status.
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassInternalIoControl(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(Irp);
|
|
|
|
ULONG isRemoved;
|
|
|
|
PSCSI_REQUEST_BLOCK srb;
|
|
|
|
isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
|
|
|
|
if(isRemoved) {
|
|
|
|
Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
|
|
|
|
return STATUS_DEVICE_DOES_NOT_EXIST;
|
|
}
|
|
|
|
//
|
|
// Get a pointer to the SRB.
|
|
//
|
|
|
|
srb = irpStack->Parameters.Scsi.Srb;
|
|
|
|
//
|
|
// Set the parameters in the next stack location.
|
|
//
|
|
|
|
if(commonExtension->IsFdo) {
|
|
nextStack->Parameters.Scsi.Srb = srb;
|
|
nextStack->MajorFunction = IRP_MJ_SCSI;
|
|
nextStack->MinorFunction = IRP_MN_SCSI_CLASS;
|
|
|
|
} else {
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
}
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
return IoCallDriver(commonExtension->LowerDeviceObject, Irp);
|
|
} // end ClassInternalIoControl()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassQueryTimeOutRegistryValue()
|
|
|
|
Routine Description:
|
|
|
|
This routine determines whether a reg key for a user-specified timeout
|
|
value exists. This should be called at initialization time.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Pointer to the device object we are retrieving the timeout
|
|
value for
|
|
|
|
Return Value:
|
|
|
|
None, but it sets a new default timeout for a class of devices.
|
|
|
|
--*/
|
|
ULONG
|
|
ClassQueryTimeOutRegistryValue(
|
|
IN PDEVICE_OBJECT DeviceObject
|
|
)
|
|
{
|
|
//
|
|
// Find the appropriate reg. key
|
|
//
|
|
|
|
PCLASS_DRIVER_EXTENSION
|
|
driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
|
|
PUNICODE_STRING registryPath = &(driverExtension->RegistryPath);
|
|
|
|
PRTL_QUERY_REGISTRY_TABLE parameters = NULL;
|
|
PWSTR path;
|
|
NTSTATUS status;
|
|
LONG timeOut = 0;
|
|
ULONG zero = 0;
|
|
ULONG size;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (!registryPath) {
|
|
return 0;
|
|
}
|
|
|
|
parameters = ExAllocatePoolWithTag(NonPagedPool,
|
|
sizeof(RTL_QUERY_REGISTRY_TABLE)*2,
|
|
'1BcS');
|
|
|
|
if (!parameters) {
|
|
return 0;
|
|
}
|
|
|
|
size = registryPath->MaximumLength + sizeof(WCHAR);
|
|
path = ExAllocatePoolWithTag(NonPagedPool, size, '2BcS');
|
|
|
|
if (!path) {
|
|
ExFreePool(parameters);
|
|
return 0;
|
|
}
|
|
|
|
RtlZeroMemory(path,size);
|
|
RtlCopyMemory(path, registryPath->Buffer, size - sizeof(WCHAR));
|
|
|
|
|
|
//
|
|
// Check for the Timeout value.
|
|
//
|
|
|
|
RtlZeroMemory(parameters,
|
|
(sizeof(RTL_QUERY_REGISTRY_TABLE)*2));
|
|
|
|
parameters[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
|
parameters[0].Name = L"TimeOutValue";
|
|
parameters[0].EntryContext = &timeOut;
|
|
parameters[0].DefaultType = REG_DWORD;
|
|
parameters[0].DefaultData = &zero;
|
|
parameters[0].DefaultLength = sizeof(ULONG);
|
|
|
|
status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL,
|
|
path,
|
|
parameters,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (!(NT_SUCCESS(status))) {
|
|
timeOut = 0;
|
|
}
|
|
|
|
ExFreePool(parameters);
|
|
ExFreePool(path);
|
|
|
|
DebugPrint((2,
|
|
"ClassQueryTimeOutRegistryValue: Timeout value %d\n",
|
|
timeOut));
|
|
|
|
|
|
return timeOut;
|
|
|
|
} // end ClassQueryTimeOutRegistryValue()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassCheckVerifyComplete() ISSUE-2000/02/18-henrygab - why public?!
|
|
|
|
Routine Description:
|
|
|
|
This routine executes when the port driver has completed a check verify
|
|
ioctl. It will set the status of the master Irp, copy the media change
|
|
count and complete the request.
|
|
|
|
Arguments:
|
|
|
|
Fdo - Supplies the functional device object which represents the logical unit.
|
|
|
|
Irp - Supplies the Irp which has completed.
|
|
|
|
Context - NULL
|
|
|
|
Return Value:
|
|
|
|
NT status
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassCheckVerifyComplete(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PIRP Irp,
|
|
IN PVOID Context
|
|
)
|
|
{
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
|
|
PIRP originalIrp;
|
|
|
|
ASSERT_FDO(Fdo);
|
|
|
|
originalIrp = irpStack->Parameters.Others.Argument1;
|
|
|
|
//
|
|
// Copy the media change count and status
|
|
//
|
|
|
|
*((PULONG) (originalIrp->AssociatedIrp.SystemBuffer)) =
|
|
fdoExtension->MediaChangeCount;
|
|
|
|
DebugPrint((2, "ClassCheckVerifyComplete - Media change count for"
|
|
"device %d is %lx - saved as %lx\n",
|
|
fdoExtension->DeviceNumber,
|
|
fdoExtension->MediaChangeCount,
|
|
*((PULONG) originalIrp->AssociatedIrp.SystemBuffer)));
|
|
|
|
originalIrp->IoStatus.Status = Irp->IoStatus.Status;
|
|
originalIrp->IoStatus.Information = sizeof(ULONG);
|
|
|
|
ClassReleaseRemoveLock(Fdo, originalIrp);
|
|
ClassCompleteRequest(Fdo, originalIrp, IO_DISK_INCREMENT);
|
|
|
|
IoFreeIrp(Irp);
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
|
|
} // end ClassCheckVerifyComplete()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassGetDescriptor()
|
|
|
|
Routine Description:
|
|
|
|
This routine will perform a query for the specified property id and will
|
|
allocate a non-paged buffer to store the data in. It is the responsibility
|
|
of the caller to ensure that this buffer is freed.
|
|
|
|
This routine must be run at IRQL_PASSIVE_LEVEL
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - the device to query
|
|
DeviceInfo - a location to store a pointer to the buffer we allocate
|
|
|
|
Return Value:
|
|
|
|
status
|
|
if status is unsuccessful *DeviceInfo will be set to NULL, else the
|
|
buffer allocated on behalf of the caller.
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassGetDescriptor(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PSTORAGE_PROPERTY_ID PropertyId,
|
|
OUT PSTORAGE_DESCRIPTOR_HEADER *Descriptor
|
|
)
|
|
{
|
|
STORAGE_PROPERTY_QUERY query;
|
|
IO_STATUS_BLOCK ioStatus;
|
|
|
|
PSTORAGE_DESCRIPTOR_HEADER descriptor = NULL;
|
|
ULONG length;
|
|
|
|
UCHAR pass = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Set the passed-in descriptor pointer to NULL as default
|
|
//
|
|
|
|
*Descriptor = NULL;
|
|
|
|
|
|
RtlZeroMemory(&query, sizeof(STORAGE_PROPERTY_QUERY));
|
|
query.PropertyId = *PropertyId;
|
|
query.QueryType = PropertyStandardQuery;
|
|
|
|
//
|
|
// On the first pass we just want to get the first few
|
|
// bytes of the descriptor so we can read it's size
|
|
//
|
|
|
|
descriptor = (PVOID)&query;
|
|
|
|
ASSERT(sizeof(STORAGE_PROPERTY_QUERY) >= (sizeof(ULONG)*2));
|
|
|
|
ClassSendDeviceIoControlSynchronous(
|
|
IOCTL_STORAGE_QUERY_PROPERTY,
|
|
DeviceObject,
|
|
&query,
|
|
sizeof(STORAGE_PROPERTY_QUERY),
|
|
sizeof(ULONG) * 2,
|
|
FALSE,
|
|
&ioStatus
|
|
);
|
|
|
|
if(!NT_SUCCESS(ioStatus.Status)) {
|
|
|
|
DebugPrint((1, "ClassGetDescriptor: error %lx trying to "
|
|
"query properties #1\n", ioStatus.Status));
|
|
return ioStatus.Status;
|
|
}
|
|
|
|
if (descriptor->Size == 0) {
|
|
|
|
//
|
|
// This DebugPrint is to help third-party driver writers
|
|
//
|
|
|
|
DebugPrint((0, "ClassGetDescriptor: size returned was zero?! (status "
|
|
"%x\n", ioStatus.Status));
|
|
return STATUS_UNSUCCESSFUL;
|
|
|
|
}
|
|
|
|
//
|
|
// This time we know how much data there is so we can
|
|
// allocate a buffer of the correct size
|
|
//
|
|
|
|
length = descriptor->Size;
|
|
|
|
descriptor = ExAllocatePoolWithTag(NonPagedPool, length, '4BcS');
|
|
|
|
if(descriptor == NULL) {
|
|
|
|
DebugPrint((1, "ClassGetDescriptor: unable to memory for descriptor "
|
|
"(%d bytes)\n", length));
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// setup the query again, as it was overwritten above
|
|
//
|
|
|
|
RtlZeroMemory(&query, sizeof(STORAGE_PROPERTY_QUERY));
|
|
query.PropertyId = *PropertyId;
|
|
query.QueryType = PropertyStandardQuery;
|
|
|
|
//
|
|
// copy the input to the new outputbuffer
|
|
//
|
|
|
|
RtlCopyMemory(descriptor,
|
|
&query,
|
|
sizeof(STORAGE_PROPERTY_QUERY)
|
|
);
|
|
|
|
ClassSendDeviceIoControlSynchronous(
|
|
IOCTL_STORAGE_QUERY_PROPERTY,
|
|
DeviceObject,
|
|
descriptor,
|
|
sizeof(STORAGE_PROPERTY_QUERY),
|
|
length,
|
|
FALSE,
|
|
&ioStatus
|
|
);
|
|
|
|
if(!NT_SUCCESS(ioStatus.Status)) {
|
|
|
|
DebugPrint((1, "ClassGetDescriptor: error %lx trying to "
|
|
"query properties #1\n", ioStatus.Status));
|
|
ExFreePool(descriptor);
|
|
return ioStatus.Status;
|
|
}
|
|
|
|
//
|
|
// return the memory we've allocated to the caller
|
|
//
|
|
|
|
*Descriptor = descriptor;
|
|
return ioStatus.Status;
|
|
} // end ClassGetDescriptor()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassSignalCompletion()
|
|
|
|
Routine Description:
|
|
|
|
This completion routine will signal the event given as context and then
|
|
return STATUS_MORE_PROCESSING_REQUIRED to stop event completion. It is
|
|
the responsibility of the routine waiting on the event to complete the
|
|
request and free the event.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object
|
|
|
|
Irp - a pointer to the irp
|
|
|
|
Event - a pointer to the event to signal
|
|
|
|
Return Value:
|
|
|
|
STATUS_MORE_PROCESSING_REQUIRED
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassSignalCompletion(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PKEVENT Event
|
|
)
|
|
{
|
|
KeSetEvent(Event, IO_NO_INCREMENT, FALSE);
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
} // end ClassSignalCompletion()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassPnpQueryFdoRelations()
|
|
|
|
Routine Description:
|
|
|
|
This routine will call the driver's enumeration routine to update the
|
|
list of PDO's. It will then build a response to the
|
|
IRP_MN_QUERY_DEVICE_RELATIONS and place it into the information field in
|
|
the irp.
|
|
|
|
Arguments:
|
|
|
|
Fdo - a pointer to the functional device object we are enumerating
|
|
|
|
Irp - a pointer to the enumeration request
|
|
|
|
Return Value:
|
|
|
|
status
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassPnpQueryFdoRelations(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PCLASS_DRIVER_EXTENSION
|
|
driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If there's already an enumeration in progress then don't start another
|
|
// one.
|
|
//
|
|
|
|
if(InterlockedIncrement(&(fdoExtension->EnumerationInterlock)) == 1) {
|
|
status = driverExtension->InitData.ClassEnumerateDevice(Fdo);
|
|
}
|
|
|
|
Irp->IoStatus.Information = (ULONG_PTR) NULL;
|
|
|
|
Irp->IoStatus.Status = ClassRetrieveDeviceRelations(
|
|
Fdo,
|
|
BusRelations,
|
|
&((PDEVICE_RELATIONS) Irp->IoStatus.Information));
|
|
InterlockedDecrement(&(fdoExtension->EnumerationInterlock));
|
|
|
|
return Irp->IoStatus.Status;
|
|
} // end ClassPnpQueryFdoRelations()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassMarkChildrenMissing()
|
|
|
|
Routine Description:
|
|
|
|
This routine will call ClassMarkChildMissing() for all children.
|
|
It acquires the ChildLock before calling ClassMarkChildMissing().
|
|
|
|
Arguments:
|
|
|
|
Fdo - the "bus's" device object, such as the disk FDO for non-removable
|
|
disks with multiple partitions.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
VOID
|
|
ClassMarkChildrenMissing(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION Fdo
|
|
)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = &(Fdo->CommonExtension);
|
|
PPHYSICAL_DEVICE_EXTENSION nextChild = commonExtension->ChildList;
|
|
|
|
PAGED_CODE();
|
|
|
|
ClassAcquireChildLock(Fdo);
|
|
|
|
while (nextChild){
|
|
PPHYSICAL_DEVICE_EXTENSION tmpChild;
|
|
|
|
/*
|
|
* ClassMarkChildMissing will also dequeue the child extension.
|
|
* So get the next pointer before calling ClassMarkChildMissing.
|
|
*/
|
|
tmpChild = nextChild;
|
|
nextChild = tmpChild->CommonExtension.ChildList;
|
|
ClassMarkChildMissing(tmpChild, FALSE);
|
|
}
|
|
ClassReleaseChildLock(Fdo);
|
|
return;
|
|
} // end ClassMarkChildrenMissing()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassMarkChildMissing()
|
|
|
|
Routine Description:
|
|
|
|
This routine will make an active child "missing." If the device has never
|
|
been enumerated then it will be deleted on the spot. If the device has
|
|
not been enumerated then it will be marked as missing so that we can
|
|
not report it in the next device enumeration.
|
|
|
|
Arguments:
|
|
|
|
Child - the child device to be marked as missing.
|
|
|
|
AcquireChildLock - TRUE if the child lock should be acquired before removing
|
|
the missing child. FALSE if the child lock is already
|
|
acquired by this thread.
|
|
|
|
Return Value:
|
|
|
|
returns whether or not the child device object has previously been reported
|
|
to PNP.
|
|
|
|
--*/
|
|
BOOLEAN
|
|
ClassMarkChildMissing(
|
|
IN PPHYSICAL_DEVICE_EXTENSION Child,
|
|
IN BOOLEAN AcquireChildLock
|
|
)
|
|
{
|
|
BOOLEAN returnValue = Child->IsEnumerated;
|
|
|
|
PAGED_CODE();
|
|
ASSERT_PDO(Child->DeviceObject);
|
|
|
|
Child->IsMissing = TRUE;
|
|
|
|
//
|
|
// Make sure this child is not in the active list.
|
|
//
|
|
|
|
ClassRemoveChild(Child->CommonExtension.PartitionZeroExtension,
|
|
Child,
|
|
AcquireChildLock);
|
|
|
|
if(Child->IsEnumerated == FALSE) {
|
|
ClassRemoveDevice(Child->DeviceObject, IRP_MN_REMOVE_DEVICE);
|
|
}
|
|
|
|
return returnValue;
|
|
} // end ClassMarkChildMissing()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassRetrieveDeviceRelations()
|
|
|
|
Routine Description:
|
|
|
|
This routine will allocate a buffer to hold the specified list of
|
|
relations. It will then fill in the list with referenced device pointers
|
|
and will return the request.
|
|
|
|
Arguments:
|
|
|
|
Fdo - pointer to the FDO being queried
|
|
|
|
RelationType - what type of relations are being queried
|
|
|
|
DeviceRelations - a location to store a pointer to the response
|
|
|
|
Return Value:
|
|
|
|
status
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassRetrieveDeviceRelations(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN DEVICE_RELATION_TYPE RelationType,
|
|
OUT PDEVICE_RELATIONS *DeviceRelations
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
|
|
ULONG count = 0;
|
|
ULONG i;
|
|
|
|
PPHYSICAL_DEVICE_EXTENSION nextChild;
|
|
|
|
ULONG relationsSize;
|
|
PDEVICE_RELATIONS deviceRelations = NULL;
|
|
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
ClassAcquireChildLock(fdoExtension);
|
|
|
|
nextChild = fdoExtension->CommonExtension.ChildList;
|
|
|
|
//
|
|
// Count the number of PDO's attached to this disk
|
|
//
|
|
|
|
while(nextChild != NULL) {
|
|
PCOMMON_DEVICE_EXTENSION commonExtension;
|
|
|
|
commonExtension = &(nextChild->CommonExtension);
|
|
|
|
ASSERTMSG("ClassPnp internal error: missing child on active list\n",
|
|
(nextChild->IsMissing == FALSE));
|
|
|
|
nextChild = commonExtension->ChildList;
|
|
|
|
count++;
|
|
};
|
|
|
|
relationsSize = (sizeof(DEVICE_RELATIONS) +
|
|
(count * sizeof(PDEVICE_OBJECT)));
|
|
|
|
deviceRelations = ExAllocatePoolWithTag(PagedPool, relationsSize, '5BcS');
|
|
|
|
if(deviceRelations == NULL) {
|
|
|
|
DebugPrint((1, "ClassRetrieveDeviceRelations: unable to allocate "
|
|
"%d bytes for device relations\n", relationsSize));
|
|
|
|
ClassReleaseChildLock(fdoExtension);
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory(deviceRelations, relationsSize);
|
|
|
|
nextChild = fdoExtension->CommonExtension.ChildList;
|
|
i = count - 1;
|
|
|
|
while(nextChild != NULL) {
|
|
PCOMMON_DEVICE_EXTENSION commonExtension;
|
|
|
|
commonExtension = &(nextChild->CommonExtension);
|
|
|
|
ASSERTMSG("ClassPnp internal error: missing child on active list\n",
|
|
(nextChild->IsMissing == FALSE));
|
|
|
|
deviceRelations->Objects[i--] = nextChild->DeviceObject;
|
|
|
|
status = ObReferenceObjectByPointer(
|
|
nextChild->DeviceObject,
|
|
0,
|
|
NULL,
|
|
KernelMode);
|
|
ASSERT(NT_SUCCESS(status));
|
|
|
|
nextChild->IsEnumerated = TRUE;
|
|
nextChild = commonExtension->ChildList;
|
|
}
|
|
|
|
ASSERTMSG("Child list has changed: ", i == -1);
|
|
|
|
deviceRelations->Count = count;
|
|
*DeviceRelations = deviceRelations;
|
|
ClassReleaseChildLock(fdoExtension);
|
|
return STATUS_SUCCESS;
|
|
} // end ClassRetrieveDeviceRelations()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassGetPdoId()
|
|
|
|
Routine Description:
|
|
|
|
This routine will call into the driver to retrieve a copy of one of it's
|
|
id strings.
|
|
|
|
Arguments:
|
|
|
|
Pdo - a pointer to the pdo being queried
|
|
|
|
IdType - which type of id string is being queried
|
|
|
|
IdString - an allocated unicode string structure which the driver
|
|
can fill in.
|
|
|
|
Return Value:
|
|
|
|
status
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassGetPdoId(
|
|
IN PDEVICE_OBJECT Pdo,
|
|
IN BUS_QUERY_ID_TYPE IdType,
|
|
IN PUNICODE_STRING IdString
|
|
)
|
|
{
|
|
PCLASS_DRIVER_EXTENSION
|
|
driverExtension = IoGetDriverObjectExtension(Pdo->DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
|
|
ASSERT_PDO(Pdo);
|
|
ASSERT(driverExtension->InitData.ClassQueryId);
|
|
|
|
PAGED_CODE();
|
|
|
|
return driverExtension->InitData.ClassQueryId( Pdo, IdType, IdString);
|
|
} // end ClassGetPdoId()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassQueryPnpCapabilities()
|
|
|
|
Routine Description:
|
|
|
|
This routine will call into the class driver to retrieve it's pnp
|
|
capabilities.
|
|
|
|
Arguments:
|
|
|
|
PhysicalDeviceObject - The physical device object to retrieve properties
|
|
for.
|
|
|
|
Return Value:
|
|
|
|
status
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassQueryPnpCapabilities(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PDEVICE_CAPABILITIES Capabilities
|
|
)
|
|
{
|
|
PCLASS_DRIVER_EXTENSION driverExtension =
|
|
ClassGetDriverExtension(DeviceObject->DriverObject);
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
|
|
PCLASS_QUERY_PNP_CAPABILITIES queryRoutine = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(DeviceObject);
|
|
ASSERT(Capabilities);
|
|
|
|
if(commonExtension->IsFdo) {
|
|
queryRoutine = driverExtension->InitData.FdoData.ClassQueryPnpCapabilities;
|
|
} else {
|
|
queryRoutine = driverExtension->InitData.PdoData.ClassQueryPnpCapabilities;
|
|
}
|
|
|
|
if(queryRoutine) {
|
|
return queryRoutine(DeviceObject,
|
|
Capabilities);
|
|
} else {
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
} // end ClassQueryPnpCapabilities()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassInvalidateBusRelations()
|
|
|
|
Routine Description:
|
|
|
|
This routine re-enumerates the devices on the "bus". It will call into
|
|
the driver's ClassEnumerate routine to update the device objects
|
|
immediately. It will then schedule a bus re-enumeration for pnp by calling
|
|
IoInvalidateDeviceRelations.
|
|
|
|
Arguments:
|
|
|
|
Fdo - a pointer to the functional device object for this bus
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
VOID
|
|
ClassInvalidateBusRelations(
|
|
IN PDEVICE_OBJECT Fdo
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PCLASS_DRIVER_EXTENSION
|
|
driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_FDO(Fdo);
|
|
ASSERT(driverExtension->InitData.ClassEnumerateDevice != NULL);
|
|
|
|
if(InterlockedIncrement(&(fdoExtension->EnumerationInterlock)) == 1) {
|
|
status = driverExtension->InitData.ClassEnumerateDevice(Fdo);
|
|
}
|
|
InterlockedDecrement(&(fdoExtension->EnumerationInterlock));
|
|
|
|
if(!NT_SUCCESS(status)) {
|
|
|
|
DebugPrint((1, "ClassInvalidateBusRelations: EnumerateDevice routine "
|
|
"returned %lx\n", status));
|
|
}
|
|
|
|
IoInvalidateDeviceRelations(fdoExtension->LowerPdo, BusRelations);
|
|
|
|
return;
|
|
} // end ClassInvalidateBusRelations()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassRemoveDevice() ISSUE-2000/02/18-henrygab - why public?!
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to handle the "removal" of a device. It will
|
|
forward the request downwards if necesssary, call into the driver
|
|
to release any necessary resources (memory, events, etc) and then
|
|
will delete the device object.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - a pointer to the device object being removed
|
|
|
|
RemoveType - indicates what type of remove this is (regular or surprise).
|
|
|
|
Return Value:
|
|
|
|
status
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassRemoveDevice(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN UCHAR RemoveType
|
|
)
|
|
{
|
|
PCLASS_DRIVER_EXTENSION
|
|
driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject,
|
|
CLASS_DRIVER_EXTENSION_KEY);
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PDEVICE_OBJECT lowerDeviceObject = commonExtension->LowerDeviceObject;
|
|
PCLASS_WMI_INFO classWmiInfo;
|
|
BOOLEAN proceedWithRemove = TRUE;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
commonExtension->IsRemoved = REMOVE_PENDING;
|
|
|
|
/*
|
|
* Deregister from WMI.
|
|
*/
|
|
classWmiInfo = commonExtension->IsFdo ?
|
|
&driverExtension->InitData.FdoData.ClassWmiInfo :
|
|
&driverExtension->InitData.PdoData.ClassWmiInfo;
|
|
if (classWmiInfo->GuidRegInfo){
|
|
status = IoWMIRegistrationControl(DeviceObject, WMIREG_ACTION_DEREGISTER);
|
|
DBGTRACE(ClassDebugInfo, ("ClassRemoveDevice: IoWMIRegistrationControl(%p, WMI_ACTION_DEREGISTER) --> %lx", DeviceObject, status));
|
|
}
|
|
|
|
/*
|
|
* If we exposed a "shingle" (a named device interface openable by CreateFile)
|
|
* then delete it now.
|
|
*/
|
|
if (commonExtension->MountedDeviceInterfaceName.Buffer){
|
|
IoSetDeviceInterfaceState(&commonExtension->MountedDeviceInterfaceName, FALSE);
|
|
RtlFreeUnicodeString(&commonExtension->MountedDeviceInterfaceName);
|
|
RtlInitUnicodeString(&commonExtension->MountedDeviceInterfaceName, NULL);
|
|
}
|
|
|
|
//
|
|
// If this is a surprise removal we leave the device around - which means
|
|
// we don't have to (or want to) drop the remove lock and wait for pending
|
|
// requests to complete.
|
|
//
|
|
|
|
if (RemoveType == IRP_MN_REMOVE_DEVICE){
|
|
|
|
//
|
|
// Release the lock we acquired when the device object was created.
|
|
//
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, (PIRP) DeviceObject);
|
|
|
|
DebugPrint((1, "ClasspRemoveDevice - Reference count is now %d\n",
|
|
commonExtension->RemoveLock));
|
|
|
|
KeWaitForSingleObject(&commonExtension->RemoveEvent,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
|
|
DebugPrint((1, "ClasspRemoveDevice - removing device %p\n", DeviceObject));
|
|
|
|
if(commonExtension->IsFdo) {
|
|
|
|
DebugPrint((1, "ClasspRemoveDevice - FDO %p has received a "
|
|
"remove request.\n", DeviceObject));
|
|
|
|
}
|
|
else {
|
|
PPHYSICAL_DEVICE_EXTENSION pdoExtension = DeviceObject->DeviceExtension;
|
|
|
|
if (pdoExtension->IsMissing){
|
|
/*
|
|
* The child partition PDO is missing, so we are going to go ahead
|
|
* and delete it for the remove.
|
|
*/
|
|
DBGTRACE(ClassDebugWarning, ("ClasspRemoveDevice - PDO %p is missing and will be removed", DeviceObject));
|
|
}
|
|
else {
|
|
/*
|
|
* We got a remove for a child partition PDO which is not actually missing.
|
|
* So we will NOT actually delete it.
|
|
*/
|
|
DBGTRACE(ClassDebugWarning, ("ClasspRemoveDevice - PDO %p still exists and will be removed when it disappears", DeviceObject));
|
|
|
|
//
|
|
// Reacquire the remove lock for the next time this comes around.
|
|
//
|
|
|
|
ClassAcquireRemoveLock(DeviceObject, (PIRP) DeviceObject);
|
|
|
|
//
|
|
// the device wasn't missing so it's not really been removed.
|
|
//
|
|
|
|
commonExtension->IsRemoved = NO_REMOVE;
|
|
|
|
IoInvalidateDeviceRelations(
|
|
commonExtension->PartitionZeroExtension->LowerPdo,
|
|
BusRelations);
|
|
|
|
proceedWithRemove = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (proceedWithRemove){
|
|
|
|
/*
|
|
* Call the class driver's remove handler.
|
|
* All this is supposed to do is clean up its data and device interfaces.
|
|
*/
|
|
ASSERT(commonExtension->DevInfo->ClassRemoveDevice);
|
|
status = commonExtension->DevInfo->ClassRemoveDevice(DeviceObject, RemoveType);
|
|
ASSERT(NT_SUCCESS(status));
|
|
status = STATUS_SUCCESS;
|
|
|
|
if (commonExtension->IsFdo){
|
|
PDEVICE_OBJECT pdo;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
|
|
|
ClasspDisableTimer(fdoExtension->DeviceObject);
|
|
|
|
if (RemoveType == IRP_MN_REMOVE_DEVICE){
|
|
|
|
PPHYSICAL_DEVICE_EXTENSION child;
|
|
|
|
//
|
|
// Cleanup the media detection resources now that the class driver
|
|
// has stopped it's timer (if any) and we can be sure they won't
|
|
// call us to do detection again.
|
|
//
|
|
|
|
ClassCleanupMediaChangeDetection(fdoExtension);
|
|
|
|
//
|
|
// Cleanup any Failure Prediction stuff
|
|
//
|
|
if (fdoExtension->FailurePredictionInfo) {
|
|
ExFreePool(fdoExtension->FailurePredictionInfo);
|
|
fdoExtension->FailurePredictionInfo = NULL;
|
|
}
|
|
|
|
/*
|
|
* Ordinarily all child PDOs will be removed by the time
|
|
* that the parent gets the REMOVE_DEVICE.
|
|
* However, if a child PDO has been created but has not
|
|
* been announced in a QueryDeviceRelations, then it is
|
|
* just a private data structure unknown to pnp, and we have
|
|
* to delete it ourselves.
|
|
*/
|
|
ClassAcquireChildLock(fdoExtension);
|
|
while (child = ClassRemoveChild(fdoExtension, NULL, FALSE)){
|
|
|
|
//
|
|
// Yank the pdo. This routine will unlink the device from the
|
|
// pdo list so NextPdo will point to the next one when it's
|
|
// complete.
|
|
//
|
|
child->IsMissing = TRUE;
|
|
ClassRemoveDevice(child->DeviceObject, IRP_MN_REMOVE_DEVICE);
|
|
}
|
|
ClassReleaseChildLock(fdoExtension);
|
|
}
|
|
else if (RemoveType == IRP_MN_SURPRISE_REMOVAL){
|
|
/*
|
|
* This is a surprise-remove on the parent FDO.
|
|
* We will mark the child PDOs as missing so that they
|
|
* will actually get deleted when they get a REMOVE_DEVICE.
|
|
*/
|
|
ClassMarkChildrenMissing(fdoExtension);
|
|
}
|
|
|
|
ClasspFreeReleaseRequest(DeviceObject);
|
|
|
|
if (RemoveType == IRP_MN_REMOVE_DEVICE){
|
|
|
|
//
|
|
// Free FDO-specific data structs
|
|
//
|
|
if (fdoExtension->PrivateFdoData){
|
|
|
|
DestroyAllTransferPackets(DeviceObject);
|
|
|
|
ExFreePool(fdoExtension->PrivateFdoData);
|
|
fdoExtension->PrivateFdoData = NULL;
|
|
}
|
|
|
|
if (commonExtension->DeviceName.Buffer) {
|
|
ExFreePool(commonExtension->DeviceName.Buffer);
|
|
RtlInitUnicodeString(&commonExtension->DeviceName, NULL);
|
|
}
|
|
|
|
if (fdoExtension->AdapterDescriptor) {
|
|
ExFreePool(fdoExtension->AdapterDescriptor);
|
|
fdoExtension->AdapterDescriptor = NULL;
|
|
}
|
|
|
|
if (fdoExtension->DeviceDescriptor) {
|
|
ExFreePool(fdoExtension->DeviceDescriptor);
|
|
fdoExtension->DeviceDescriptor = NULL;
|
|
}
|
|
|
|
//
|
|
// Detach our device object from the stack - there's no reason
|
|
// to hold off our cleanup any longer.
|
|
//
|
|
|
|
IoDetachDevice(lowerDeviceObject);
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* This is a child partition PDO.
|
|
* We have already determined that it was previously marked
|
|
* as missing. So if this is a REMOVE_DEVICE, we will actually
|
|
* delete it.
|
|
*/
|
|
if (RemoveType == IRP_MN_REMOVE_DEVICE){
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension =
|
|
commonExtension->PartitionZeroExtension;
|
|
PPHYSICAL_DEVICE_EXTENSION pdoExtension =
|
|
(PPHYSICAL_DEVICE_EXTENSION) commonExtension;
|
|
|
|
//
|
|
// See if this device is in the child list (if this was a suprise
|
|
// removal it might be) and remove it.
|
|
//
|
|
|
|
ClassRemoveChild(fdoExtension, pdoExtension, TRUE);
|
|
}
|
|
}
|
|
|
|
commonExtension->PartitionLength.QuadPart = 0;
|
|
|
|
if (RemoveType == IRP_MN_REMOVE_DEVICE){
|
|
IoDeleteDevice(DeviceObject);
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
} // end ClassRemoveDevice()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassGetDriverExtension()
|
|
|
|
Routine Description:
|
|
|
|
This routine will return the classpnp's driver extension.
|
|
|
|
Arguments:
|
|
|
|
DriverObject - the driver object for which to get classpnp's extension
|
|
|
|
Return Value:
|
|
|
|
Either NULL if none, or a pointer to the driver extension
|
|
|
|
--*/
|
|
PCLASS_DRIVER_EXTENSION
|
|
ClassGetDriverExtension(
|
|
IN PDRIVER_OBJECT DriverObject
|
|
)
|
|
{
|
|
return IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY);
|
|
} // end ClassGetDriverExtension()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClasspStartIo()
|
|
|
|
Routine Description:
|
|
|
|
This routine wraps the class driver's start io routine. If the device
|
|
is being removed it will complete any requests with
|
|
STATUS_DEVICE_DOES_NOT_EXIST and fire up the next packet.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
VOID
|
|
ClasspStartIo(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
|
|
//
|
|
// We're already holding the remove lock so just check the variable and
|
|
// see what's going on.
|
|
//
|
|
|
|
if(commonExtension->IsRemoved) {
|
|
|
|
Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
|
|
ClassAcquireRemoveLock(DeviceObject, (PIRP) ClasspStartIo);
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
ClassCompleteRequest(DeviceObject, Irp, IO_DISK_INCREMENT);
|
|
IoStartNextPacket(DeviceObject, FALSE);
|
|
ClassReleaseRemoveLock(DeviceObject, (PIRP) ClasspStartIo);
|
|
return;
|
|
}
|
|
|
|
commonExtension->DriverExtension->InitData.ClassStartIo(
|
|
DeviceObject,
|
|
Irp);
|
|
|
|
return;
|
|
} // ClasspStartIo()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassUpdateInformationInRegistry()
|
|
|
|
Routine Description:
|
|
|
|
This routine has knowledge about the layout of the device map information
|
|
in the registry. It will update this information to include a value
|
|
entry specifying the dos device name that is assumed to get assigned
|
|
to this NT device name. For more information on this assigning of the
|
|
dos device name look in the drive support routine in the hal that assigns
|
|
all dos names.
|
|
|
|
Since some versions of some device's firmware did not work and some
|
|
vendors did not bother to follow the specification, the entire inquiry
|
|
information must also be stored in the registry so than someone can
|
|
figure out the firmware version.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - A pointer to the device object for the tape device.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
VOID
|
|
ClassUpdateInformationInRegistry(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PCHAR DeviceName,
|
|
IN ULONG DeviceNumber,
|
|
IN PINQUIRYDATA InquiryData,
|
|
IN ULONG InquiryDataLength
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
NTSTATUS status;
|
|
SCSI_ADDRESS scsiAddress;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
PUCHAR buffer;
|
|
STRING string;
|
|
UNICODE_STRING unicodeName;
|
|
UNICODE_STRING unicodeRegistryPath;
|
|
UNICODE_STRING unicodeData;
|
|
HANDLE targetKey;
|
|
IO_STATUS_BLOCK ioStatus;
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(DeviceName);
|
|
fdoExtension = Fdo->DeviceExtension;
|
|
buffer = NULL;
|
|
targetKey = NULL;
|
|
RtlZeroMemory(&unicodeName, sizeof(UNICODE_STRING));
|
|
RtlZeroMemory(&unicodeData, sizeof(UNICODE_STRING));
|
|
RtlZeroMemory(&unicodeRegistryPath, sizeof(UNICODE_STRING));
|
|
|
|
TRY {
|
|
|
|
//
|
|
// Issue GET_ADDRESS Ioctl to determine path, target, and lun information.
|
|
//
|
|
|
|
ClassSendDeviceIoControlSynchronous(
|
|
IOCTL_SCSI_GET_ADDRESS,
|
|
Fdo,
|
|
&scsiAddress,
|
|
0,
|
|
sizeof(SCSI_ADDRESS),
|
|
FALSE,
|
|
&ioStatus
|
|
);
|
|
|
|
if (!NT_SUCCESS(ioStatus.Status)) {
|
|
|
|
status = ioStatus.Status;
|
|
DebugPrint((1,
|
|
"UpdateInformationInRegistry: Get Address failed %lx\n",
|
|
status));
|
|
LEAVE;
|
|
|
|
} else {
|
|
|
|
DebugPrint((1,
|
|
"GetAddress: Port %x, Path %x, Target %x, Lun %x\n",
|
|
scsiAddress.PortNumber,
|
|
scsiAddress.PathId,
|
|
scsiAddress.TargetId,
|
|
scsiAddress.Lun));
|
|
|
|
}
|
|
|
|
//
|
|
// Allocate a buffer for the reg. spooge.
|
|
//
|
|
|
|
buffer = ExAllocatePoolWithTag(PagedPool, 1024, '6BcS');
|
|
|
|
if (buffer == NULL) {
|
|
|
|
//
|
|
// There is not return value for this. Since this is done at
|
|
// claim device time (currently only system initialization) getting
|
|
// the registry information correct will be the least of the worries.
|
|
//
|
|
|
|
LEAVE;
|
|
}
|
|
|
|
sprintf(buffer,
|
|
"\\Registry\\Machine\\Hardware\\DeviceMap\\Scsi\\Scsi Port %d\\Scsi Bus %d\\Target Id %d\\Logical Unit Id %d",
|
|
scsiAddress.PortNumber,
|
|
scsiAddress.PathId,
|
|
scsiAddress.TargetId,
|
|
scsiAddress.Lun);
|
|
|
|
RtlInitString(&string, buffer);
|
|
|
|
status = RtlAnsiStringToUnicodeString(&unicodeRegistryPath,
|
|
&string,
|
|
TRUE);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LEAVE;
|
|
}
|
|
|
|
//
|
|
// Open the registry key for the scsi information for this
|
|
// scsibus, target, lun.
|
|
//
|
|
|
|
InitializeObjectAttributes(&objectAttributes,
|
|
&unicodeRegistryPath,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
status = ZwOpenKey(&targetKey,
|
|
KEY_READ | KEY_WRITE,
|
|
&objectAttributes);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LEAVE;
|
|
}
|
|
|
|
//
|
|
// Now construct and attempt to create the registry value
|
|
// specifying the device name in the appropriate place in the
|
|
// device map.
|
|
//
|
|
|
|
RtlInitUnicodeString(&unicodeName, L"DeviceName");
|
|
|
|
sprintf(buffer, "%s%d", DeviceName, DeviceNumber);
|
|
RtlInitString(&string, buffer);
|
|
status = RtlAnsiStringToUnicodeString(&unicodeData,
|
|
&string,
|
|
TRUE);
|
|
if (NT_SUCCESS(status)) {
|
|
status = ZwSetValueKey(targetKey,
|
|
&unicodeName,
|
|
0,
|
|
REG_SZ,
|
|
unicodeData.Buffer,
|
|
unicodeData.Length);
|
|
}
|
|
|
|
//
|
|
// if they sent in data, update the registry
|
|
//
|
|
|
|
if (InquiryDataLength) {
|
|
|
|
ASSERT(InquiryData);
|
|
|
|
RtlInitUnicodeString(&unicodeName, L"InquiryData");
|
|
status = ZwSetValueKey(targetKey,
|
|
&unicodeName,
|
|
0,
|
|
REG_BINARY,
|
|
InquiryData,
|
|
InquiryDataLength);
|
|
}
|
|
|
|
// that's all, except to clean up.
|
|
|
|
} FINALLY {
|
|
|
|
if (unicodeData.Buffer) {
|
|
RtlFreeUnicodeString(&unicodeData);
|
|
}
|
|
if (unicodeRegistryPath.Buffer) {
|
|
RtlFreeUnicodeString(&unicodeRegistryPath);
|
|
}
|
|
if (targetKey) {
|
|
ZwClose(targetKey);
|
|
}
|
|
if (buffer) {
|
|
ExFreePool(buffer);
|
|
}
|
|
|
|
}
|
|
|
|
} // end ClassUpdateInformationInRegistry()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClasspSendSynchronousCompletion()
|
|
|
|
Routine Description:
|
|
|
|
This completion routine will set the user event in the irp after
|
|
freeing the irp and the associated MDL (if any).
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - the device object which requested the completion routine
|
|
|
|
Irp - the irp being completed
|
|
|
|
Context - unused
|
|
|
|
Return Value:
|
|
|
|
STATUS_MORE_PROCESSING_REQUIRED
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClasspSendSynchronousCompletion(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PVOID Context
|
|
)
|
|
{
|
|
DebugPrint((3, "ClasspSendSynchronousCompletion: %p %p %p\n",
|
|
DeviceObject, Irp, Context));
|
|
//
|
|
// First set the status and information fields in the io status block
|
|
// provided by the caller.
|
|
//
|
|
|
|
*(Irp->UserIosb) = Irp->IoStatus;
|
|
|
|
//
|
|
// Unlock the pages for the data buffer.
|
|
//
|
|
|
|
if(Irp->MdlAddress) {
|
|
MmUnlockPages(Irp->MdlAddress);
|
|
IoFreeMdl(Irp->MdlAddress);
|
|
}
|
|
|
|
//
|
|
// Signal the caller's event.
|
|
//
|
|
|
|
KeSetEvent(Irp->UserEvent, IO_NO_INCREMENT, FALSE);
|
|
|
|
//
|
|
// Free the MDL and the IRP.
|
|
//
|
|
|
|
IoFreeIrp(Irp);
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
} // end ClasspSendSynchronousCompletion()
|
|
|
|
/*++
|
|
|
|
ISSUE-2000/02/20-henrygab Not documented ClasspRegisterMountedDeviceInterface
|
|
|
|
--*/
|
|
VOID
|
|
ClasspRegisterMountedDeviceInterface(
|
|
IN PDEVICE_OBJECT DeviceObject
|
|
)
|
|
{
|
|
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
BOOLEAN isFdo = commonExtension->IsFdo;
|
|
|
|
PDEVICE_OBJECT pdo;
|
|
UNICODE_STRING interfaceName;
|
|
|
|
NTSTATUS status;
|
|
|
|
if(isFdo) {
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION functionalExtension;
|
|
|
|
functionalExtension =
|
|
(PFUNCTIONAL_DEVICE_EXTENSION) commonExtension;
|
|
pdo = functionalExtension->LowerPdo;
|
|
} else {
|
|
pdo = DeviceObject;
|
|
}
|
|
|
|
status = IoRegisterDeviceInterface(
|
|
pdo,
|
|
&MOUNTDEV_MOUNTED_DEVICE_GUID,
|
|
NULL,
|
|
&interfaceName
|
|
);
|
|
|
|
if(NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// Copy the interface name before setting the interface state - the
|
|
// name is needed by the components we notify.
|
|
//
|
|
|
|
commonExtension->MountedDeviceInterfaceName = interfaceName;
|
|
status = IoSetDeviceInterfaceState(&interfaceName, TRUE);
|
|
|
|
if(!NT_SUCCESS(status)) {
|
|
RtlFreeUnicodeString(&interfaceName);
|
|
}
|
|
}
|
|
|
|
if(!NT_SUCCESS(status)) {
|
|
RtlInitUnicodeString(&(commonExtension->MountedDeviceInterfaceName),
|
|
NULL);
|
|
}
|
|
return;
|
|
} // end ClasspRegisterMountedDeviceInterface()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassSendDeviceIoControlSynchronous()
|
|
|
|
Routine Description:
|
|
|
|
This routine is based upon IoBuildDeviceIoControlRequest(). It has been
|
|
modified to reduce code and memory by not double-buffering the io, using
|
|
the same buffer for both input and output, allocating and deallocating
|
|
the mdl on behalf of the caller, and waiting for the io to complete.
|
|
|
|
This routine also works around the rare cases in which APC's are disabled.
|
|
Since IoBuildDeviceIoControl() used APC's to signal completion, this had
|
|
led to a number of difficult-to-detect hangs, where the irp was completed,
|
|
but the event passed to IoBuild..() was still being waited upon by the
|
|
caller.
|
|
|
|
Arguments:
|
|
|
|
IoControlCode - the IOCTL to send
|
|
|
|
TargetDeviceObject - the device object that should handle the ioctl
|
|
|
|
Buffer - the input and output buffer, or NULL if no input/output
|
|
|
|
InputBufferLength - the number of bytes prepared for the IOCTL in Buffer
|
|
|
|
OutputBufferLength - the number of bytes to be filled in upon success
|
|
|
|
InternalDeviceIoControl - if TRUE, uses IRP_MJ_INTERNAL_DEVICE_CONTROL
|
|
|
|
IoStatus - the status block that contains the results of the operation
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
VOID
|
|
ClassSendDeviceIoControlSynchronous(
|
|
IN ULONG IoControlCode,
|
|
IN PDEVICE_OBJECT TargetDeviceObject,
|
|
IN OUT PVOID Buffer OPTIONAL,
|
|
IN ULONG InputBufferLength,
|
|
IN ULONG OutputBufferLength,
|
|
IN BOOLEAN InternalDeviceIoControl,
|
|
OUT PIO_STATUS_BLOCK IoStatus
|
|
)
|
|
{
|
|
PIRP irp;
|
|
PIO_STACK_LOCATION irpSp;
|
|
ULONG method;
|
|
|
|
PAGED_CODE();
|
|
|
|
irp = NULL;
|
|
method = IoControlCode & 3;
|
|
|
|
|
|
#if DBG // Begin Argument Checking (nop in fre version)
|
|
|
|
ASSERT(ARGUMENT_PRESENT(IoStatus));
|
|
|
|
if ((InputBufferLength != 0) || (OutputBufferLength != 0)) {
|
|
ASSERT(ARGUMENT_PRESENT(Buffer));
|
|
}
|
|
else {
|
|
ASSERT(!ARGUMENT_PRESENT(Buffer));
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Begin by allocating the IRP for this request. Do not charge quota to
|
|
// the current process for this IRP.
|
|
//
|
|
|
|
irp = IoAllocateIrp(TargetDeviceObject->StackSize, FALSE);
|
|
if (!irp) {
|
|
(*IoStatus).Information = 0;
|
|
(*IoStatus).Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Get a pointer to the stack location of the first driver which will be
|
|
// invoked. This is where the function codes and the parameters are set.
|
|
//
|
|
|
|
irpSp = IoGetNextIrpStackLocation(irp);
|
|
|
|
//
|
|
// Set the major function code based on the type of device I/O control
|
|
// function the caller has specified.
|
|
//
|
|
|
|
if (InternalDeviceIoControl) {
|
|
irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
|
|
} else {
|
|
irpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
|
|
}
|
|
|
|
//
|
|
// Copy the caller's parameters to the service-specific portion of the
|
|
// IRP for those parameters that are the same for all four methods.
|
|
//
|
|
|
|
irpSp->Parameters.DeviceIoControl.OutputBufferLength = OutputBufferLength;
|
|
irpSp->Parameters.DeviceIoControl.InputBufferLength = InputBufferLength;
|
|
irpSp->Parameters.DeviceIoControl.IoControlCode = IoControlCode;
|
|
|
|
//
|
|
// Get the method bits from the I/O control code to determine how the
|
|
// buffers are to be passed to the driver.
|
|
//
|
|
|
|
switch (method) {
|
|
// case 0
|
|
case METHOD_BUFFERED: {
|
|
if ((InputBufferLength != 0) || (OutputBufferLength != 0)) {
|
|
|
|
irp->AssociatedIrp.SystemBuffer =
|
|
ExAllocatePoolWithTag(NonPagedPoolCacheAligned,
|
|
max(InputBufferLength, OutputBufferLength),
|
|
CLASS_TAG_DEVICE_CONTROL
|
|
);
|
|
|
|
if (irp->AssociatedIrp.SystemBuffer == NULL) {
|
|
IoFreeIrp(irp);
|
|
(*IoStatus).Information = 0;
|
|
(*IoStatus).Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
return;
|
|
}
|
|
|
|
if (InputBufferLength != 0) {
|
|
RtlCopyMemory(irp->AssociatedIrp.SystemBuffer,
|
|
Buffer,
|
|
InputBufferLength);
|
|
}
|
|
} // end of buffering
|
|
|
|
irp->UserBuffer = Buffer;
|
|
break;
|
|
}
|
|
|
|
// case 1, case 2
|
|
case METHOD_IN_DIRECT:
|
|
case METHOD_OUT_DIRECT: {
|
|
|
|
|
|
if (InputBufferLength != 0) {
|
|
irp->AssociatedIrp.SystemBuffer = Buffer;
|
|
}
|
|
|
|
if (OutputBufferLength != 0) {
|
|
|
|
irp->MdlAddress = IoAllocateMdl(Buffer,
|
|
OutputBufferLength,
|
|
FALSE, FALSE,
|
|
(PIRP) NULL);
|
|
|
|
if (irp->MdlAddress == NULL) {
|
|
IoFreeIrp(irp);
|
|
(*IoStatus).Information = 0;
|
|
(*IoStatus).Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
return;
|
|
}
|
|
|
|
if (method == METHOD_IN_DIRECT) {
|
|
MmProbeAndLockPages(irp->MdlAddress,
|
|
KernelMode,
|
|
IoReadAccess);
|
|
} else if (method == METHOD_OUT_DIRECT) {
|
|
MmProbeAndLockPages(irp->MdlAddress,
|
|
KernelMode,
|
|
IoWriteAccess);
|
|
} else {
|
|
ASSERT(!"If other methods reach here, code is out of date");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// case 3
|
|
case METHOD_NEITHER: {
|
|
|
|
ASSERT(!"This routine does not support METHOD_NEITHER ioctls");
|
|
IoStatus->Information = 0;
|
|
IoStatus->Status = STATUS_NOT_SUPPORTED;
|
|
return;
|
|
break;
|
|
}
|
|
} // end of switch(method)
|
|
|
|
irp->Tail.Overlay.Thread = PsGetCurrentThread();
|
|
|
|
//
|
|
// send the irp synchronously
|
|
//
|
|
|
|
ClassSendIrpSynchronous(TargetDeviceObject, irp);
|
|
|
|
//
|
|
// copy the iostatus block for the caller
|
|
//
|
|
|
|
*IoStatus = irp->IoStatus;
|
|
|
|
//
|
|
// free any allocated resources
|
|
//
|
|
|
|
switch (method) {
|
|
case METHOD_BUFFERED: {
|
|
|
|
ASSERT(irp->UserBuffer == Buffer);
|
|
|
|
//
|
|
// first copy the buffered result, if any
|
|
// Note that there are no security implications in
|
|
// not checking for success since only drivers can
|
|
// call into this routine anyways...
|
|
//
|
|
|
|
if (OutputBufferLength != 0) {
|
|
RtlCopyMemory(Buffer, // irp->UserBuffer
|
|
irp->AssociatedIrp.SystemBuffer,
|
|
OutputBufferLength
|
|
);
|
|
}
|
|
|
|
//
|
|
// then free the memory allocated to buffer the io
|
|
//
|
|
|
|
if ((InputBufferLength !=0) || (OutputBufferLength != 0)) {
|
|
ExFreePool(irp->AssociatedIrp.SystemBuffer);
|
|
irp->AssociatedIrp.SystemBuffer = NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case METHOD_IN_DIRECT:
|
|
case METHOD_OUT_DIRECT: {
|
|
|
|
//
|
|
// we alloc a mdl if there is an output buffer specified
|
|
// free it here after unlocking the pages
|
|
//
|
|
|
|
if (OutputBufferLength != 0) {
|
|
ASSERT(irp->MdlAddress != NULL);
|
|
MmUnlockPages(irp->MdlAddress);
|
|
IoFreeMdl(irp->MdlAddress);
|
|
irp->MdlAddress = (PMDL) NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case METHOD_NEITHER: {
|
|
ASSERT(!"Code is out of date");
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// we always have allocated an irp. free it here.
|
|
//
|
|
|
|
IoFreeIrp(irp);
|
|
irp = (PIRP) NULL;
|
|
|
|
//
|
|
// return the io status block's status to the caller
|
|
//
|
|
|
|
return;
|
|
} // end ClassSendDeviceIoControlSynchronous()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassForwardIrpSynchronous()
|
|
|
|
Routine Description:
|
|
|
|
Forwards a given irp to the next lower device object.
|
|
|
|
Arguments:
|
|
|
|
CommonExtension - the common class extension
|
|
|
|
Irp - the request to forward down the stack
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassForwardIrpSynchronous(
|
|
IN PCOMMON_DEVICE_EXTENSION CommonExtension,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
return ClassSendIrpSynchronous(CommonExtension->LowerDeviceObject, Irp);
|
|
} // end ClassForwardIrpSynchronous()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassSendIrpSynchronous()
|
|
|
|
Routine Description:
|
|
|
|
This routine sends the given irp to the given device object, and waits for
|
|
it to complete. On debug versions, will print out a debug message and
|
|
optionally assert for "lost" irps based upon classpnp's globals
|
|
|
|
Arguments:
|
|
|
|
TargetDeviceObject - the device object to handle this irp
|
|
|
|
Irp - the request to be sent
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassSendIrpSynchronous(
|
|
IN PDEVICE_OBJECT TargetDeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
KEVENT event;
|
|
NTSTATUS status;
|
|
|
|
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
ASSERT(TargetDeviceObject != NULL);
|
|
ASSERT(Irp != NULL);
|
|
ASSERT(Irp->StackCount >= TargetDeviceObject->StackSize);
|
|
|
|
//
|
|
// ISSUE-2000/02/20-henrygab What if APCs are disabled?
|
|
// May need to enter critical section before IoCallDriver()
|
|
// until the event is hit?
|
|
//
|
|
|
|
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
|
|
IoSetCompletionRoutine(Irp, ClassSignalCompletion, &event,
|
|
TRUE, TRUE, TRUE);
|
|
|
|
status = IoCallDriver(TargetDeviceObject, Irp);
|
|
|
|
if (status == STATUS_PENDING) {
|
|
|
|
#if DBG
|
|
LARGE_INTEGER timeout;
|
|
|
|
timeout.QuadPart = (LONGLONG)(-1 * 10 * 1000 * (LONGLONG)1000 *
|
|
ClasspnpGlobals.SecondsToWaitForIrps);
|
|
|
|
do {
|
|
status = KeWaitForSingleObject(&event,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
&timeout);
|
|
|
|
|
|
if (status == STATUS_TIMEOUT) {
|
|
|
|
//
|
|
// This DebugPrint should almost always be investigated by the
|
|
// party who sent the irp and/or the current owner of the irp.
|
|
// Synchronous Irps should not take this long (currently 30
|
|
// seconds) without good reason. This points to a potentially
|
|
// serious problem in the underlying device stack.
|
|
//
|
|
|
|
DebugPrint((0, "ClassSendIrpSynchronous: (%p) irp %p did not "
|
|
"complete within %x seconds\n",
|
|
TargetDeviceObject, Irp,
|
|
ClasspnpGlobals.SecondsToWaitForIrps
|
|
));
|
|
|
|
if (ClasspnpGlobals.BreakOnLostIrps != 0) {
|
|
ASSERT(!" - Irp failed to complete within 30 seconds - ");
|
|
}
|
|
}
|
|
|
|
|
|
} while (status==STATUS_TIMEOUT);
|
|
#else
|
|
KeWaitForSingleObject(&event,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
#endif
|
|
|
|
status = Irp->IoStatus.Status;
|
|
}
|
|
|
|
return status;
|
|
} // end ClassSendIrpSynchronous()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassGetVpb()
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the current VPB (Volume Parameter Block) for the
|
|
given device object.
|
|
The Vpb field is only visible in the ntddk.h (not the wdm.h) definition
|
|
of DEVICE_OBJECT; hence this exported function.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - the device to get the VPB for
|
|
|
|
Return Value:
|
|
|
|
the VPB, or NULL if none.
|
|
|
|
--*/
|
|
PVPB
|
|
ClassGetVpb(
|
|
IN PDEVICE_OBJECT DeviceObject
|
|
)
|
|
{
|
|
return DeviceObject->Vpb;
|
|
} // end ClassGetVpb()
|
|
|
|
/*++
|
|
|
|
ISSUE-2000/02/20-henrygab Not documented ClasspAllocateReleaseRequest
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClasspAllocateReleaseRequest(
|
|
IN PDEVICE_OBJECT Fdo
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PIO_STACK_LOCATION irpStack;
|
|
|
|
KeInitializeSpinLock(&(fdoExtension->ReleaseQueueSpinLock));
|
|
|
|
fdoExtension->ReleaseQueueNeeded = FALSE;
|
|
fdoExtension->ReleaseQueueInProgress = FALSE;
|
|
fdoExtension->ReleaseQueueIrpFromPool = FALSE;
|
|
|
|
//
|
|
// The class driver is responsible for allocating a properly sized irp,
|
|
// or ClassReleaseQueue will attempt to do it on the first error.
|
|
//
|
|
|
|
fdoExtension->ReleaseQueueIrp = NULL;
|
|
|
|
//
|
|
// Write length to SRB.
|
|
//
|
|
|
|
fdoExtension->ReleaseQueueSrb.Length = sizeof(SCSI_REQUEST_BLOCK);
|
|
|
|
return STATUS_SUCCESS;
|
|
} // end ClasspAllocateReleaseRequest()
|
|
|
|
/*++
|
|
|
|
ISSUE-2000/02/20-henrygab Not documented ClasspFreeReleaseRequest
|
|
|
|
--*/
|
|
VOID
|
|
ClasspFreeReleaseRequest(
|
|
IN PDEVICE_OBJECT Fdo
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
//KIRQL oldIrql;
|
|
|
|
ASSERT(fdoExtension->CommonExtension.IsRemoved != NO_REMOVE);
|
|
|
|
//
|
|
// free anything the driver allocated
|
|
//
|
|
|
|
if (fdoExtension->ReleaseQueueIrp) {
|
|
if (fdoExtension->ReleaseQueueIrpFromPool) {
|
|
ExFreePool(fdoExtension->ReleaseQueueIrp);
|
|
} else {
|
|
IoFreeIrp(fdoExtension->ReleaseQueueIrp);
|
|
}
|
|
fdoExtension->ReleaseQueueIrp = NULL;
|
|
}
|
|
|
|
//
|
|
// free anything that we allocated
|
|
//
|
|
|
|
if ((fdoExtension->PrivateFdoData) &&
|
|
(fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated)) {
|
|
|
|
ExFreePool(fdoExtension->PrivateFdoData->ReleaseQueueIrp);
|
|
fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = FALSE;
|
|
fdoExtension->PrivateFdoData->ReleaseQueueIrp = NULL;
|
|
}
|
|
|
|
return;
|
|
} // end ClasspFreeReleaseRequest()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassReleaseQueue()
|
|
|
|
Routine Description:
|
|
|
|
This routine issues an internal device control command
|
|
to the port driver to release a frozen queue. The call
|
|
is issued asynchronously as ClassReleaseQueue will be invoked
|
|
from the IO completion DPC (and will have no context to
|
|
wait for a synchronous call to complete).
|
|
|
|
This routine must be called with the remove lock held.
|
|
|
|
Arguments:
|
|
|
|
Fdo - The functional device object for the device with the frozen queue.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
VOID
|
|
ClassReleaseQueue(
|
|
IN PDEVICE_OBJECT Fdo
|
|
)
|
|
{
|
|
ClasspReleaseQueue(Fdo, NULL);
|
|
return;
|
|
} // end ClassReleaseQueue()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClasspAllocateReleaseQueueIrp()
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates the release queue irp held in classpnp's private
|
|
extension. This was added to allow no-memory conditions to be more
|
|
survivable.
|
|
|
|
Return Value:
|
|
|
|
NT_SUCCESS value.
|
|
|
|
Notes:
|
|
|
|
Does not grab the spinlock. Should only be called from StartDevice()
|
|
routine. May be called elsewhere for poorly-behaved drivers that cause
|
|
the queue to lockup before the device is started. This should *never*
|
|
occur, since it's illegal to send a request to a non-started PDO. This
|
|
condition is checked for in ClasspReleaseQueue().
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClasspAllocateReleaseQueueIrp(
|
|
PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
UCHAR lowerStackSize;
|
|
|
|
//
|
|
// do an initial check w/o the spinlock
|
|
//
|
|
|
|
if (FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
lowerStackSize = FdoExtension->CommonExtension.LowerDeviceObject->StackSize;
|
|
|
|
//
|
|
// don't allocate one if one is in progress! this means whoever called
|
|
// this routine didn't check if one was in progress.
|
|
//
|
|
|
|
ASSERT(!(FdoExtension->ReleaseQueueInProgress));
|
|
|
|
FdoExtension->PrivateFdoData->ReleaseQueueIrp =
|
|
ExAllocatePoolWithTag(NonPagedPool,
|
|
IoSizeOfIrp(lowerStackSize),
|
|
CLASS_TAG_RELEASE_QUEUE
|
|
);
|
|
|
|
if (FdoExtension->PrivateFdoData->ReleaseQueueIrp == NULL) {
|
|
DebugPrint((0, "ClassPnpStartDevice: Cannot allocate for "
|
|
"release queue irp\n"));
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
IoInitializeIrp(FdoExtension->PrivateFdoData->ReleaseQueueIrp,
|
|
IoSizeOfIrp(lowerStackSize),
|
|
lowerStackSize);
|
|
FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = TRUE;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClasspReleaseQueue()
|
|
|
|
Routine Description:
|
|
|
|
This routine issues an internal device control command
|
|
to the port driver to release a frozen queue. The call
|
|
is issued asynchronously as ClassReleaseQueue will be invoked
|
|
from the IO completion DPC (and will have no context to
|
|
wait for a synchronous call to complete).
|
|
|
|
This routine must be called with the remove lock held.
|
|
|
|
Arguments:
|
|
|
|
Fdo - The functional device object for the device with the frozen queue.
|
|
|
|
ReleaseQueueIrp - If this irp is supplied then the test to determine whether
|
|
a release queue request is in progress will be ignored.
|
|
The irp provided must be the IRP originally allocated
|
|
for release queue requests (so this parameter can only
|
|
really be provided by the release queue completion
|
|
routine.)
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
VOID
|
|
ClasspReleaseQueue(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PIRP ReleaseQueueIrp OPTIONAL
|
|
)
|
|
{
|
|
PIO_STACK_LOCATION irpStack;
|
|
PIRP irp;
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PDEVICE_OBJECT lowerDevice;
|
|
PSCSI_REQUEST_BLOCK srb;
|
|
KIRQL currentIrql;
|
|
|
|
lowerDevice = fdoExtension->CommonExtension.LowerDeviceObject;
|
|
|
|
//
|
|
// we raise irql seperately so we're not swapped out or suspended
|
|
// while holding the release queue irp in this routine. this lets
|
|
// us release the spin lock before lowering irql.
|
|
//
|
|
|
|
KeRaiseIrql(DISPATCH_LEVEL, ¤tIrql);
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&(fdoExtension->ReleaseQueueSpinLock));
|
|
|
|
//
|
|
// make sure that if they passed us an irp, it matches our allocated irp.
|
|
//
|
|
|
|
ASSERT((ReleaseQueueIrp == NULL) ||
|
|
(ReleaseQueueIrp == fdoExtension->PrivateFdoData->ReleaseQueueIrp));
|
|
|
|
//
|
|
// ASSERT that we've already allocated this. (should not occur)
|
|
// try to allocate it anyways, then finally bugcheck if
|
|
// there's still no memory...
|
|
//
|
|
|
|
ASSERT(fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated);
|
|
if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) {
|
|
ClasspAllocateReleaseQueueIrp(fdoExtension);
|
|
}
|
|
if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) {
|
|
KeBugCheckEx(SCSI_DISK_DRIVER_INTERNAL, 0x12, (ULONG_PTR)Fdo, 0x0, 0x0);
|
|
}
|
|
|
|
if ((fdoExtension->ReleaseQueueInProgress) && (ReleaseQueueIrp == NULL)) {
|
|
|
|
//
|
|
// Someone is already using the irp - just set the flag to indicate that
|
|
// we need to release the queue again.
|
|
//
|
|
|
|
fdoExtension->ReleaseQueueNeeded = TRUE;
|
|
KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock));
|
|
KeLowerIrql(currentIrql);
|
|
return;
|
|
|
|
}
|
|
|
|
//
|
|
// Mark that there is a release queue in progress and drop the spinlock.
|
|
//
|
|
|
|
fdoExtension->ReleaseQueueInProgress = TRUE;
|
|
if (ReleaseQueueIrp) {
|
|
irp = ReleaseQueueIrp;
|
|
} else {
|
|
irp = fdoExtension->PrivateFdoData->ReleaseQueueIrp;
|
|
}
|
|
srb = &(fdoExtension->ReleaseQueueSrb);
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock));
|
|
|
|
ASSERT(irp != NULL);
|
|
|
|
irpStack = IoGetNextIrpStackLocation(irp);
|
|
|
|
irpStack->MajorFunction = IRP_MJ_SCSI;
|
|
|
|
srb->OriginalRequest = irp;
|
|
|
|
//
|
|
// Store the SRB address in next stack for port driver.
|
|
//
|
|
|
|
irpStack->Parameters.Scsi.Srb = srb;
|
|
|
|
//
|
|
// If this device is removable then flush the queue. This will also
|
|
// release it.
|
|
//
|
|
|
|
if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){
|
|
srb->Function = SRB_FUNCTION_FLUSH_QUEUE;
|
|
}
|
|
else {
|
|
srb->Function = SRB_FUNCTION_RELEASE_QUEUE;
|
|
}
|
|
|
|
ClassAcquireRemoveLock(Fdo, irp);
|
|
|
|
IoSetCompletionRoutine(irp,
|
|
ClassReleaseQueueCompletion,
|
|
Fdo,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE);
|
|
|
|
IoCallDriver(lowerDevice, irp);
|
|
|
|
KeLowerIrql(currentIrql);
|
|
|
|
return;
|
|
|
|
} // end ClassReleaseQueue()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassReleaseQueueCompletion()
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when an asynchronous I/O request
|
|
which was issused by the class driver completes. Examples of such requests
|
|
are release queue or START UNIT. This routine releases the queue if
|
|
necessary. It then frees the context and the IRP.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - The device object for the logical unit; however since this
|
|
is the top stack location the value is NULL.
|
|
|
|
Irp - Supplies a pointer to the Irp to be processed.
|
|
|
|
Context - Supplies the context to be used to process this request.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
NTSTATUS
|
|
ClassReleaseQueueCompletion(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp,
|
|
PVOID Context
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
KIRQL oldIrql;
|
|
|
|
BOOLEAN releaseQueueNeeded;
|
|
|
|
DeviceObject = Context;
|
|
|
|
fdoExtension = DeviceObject->DeviceExtension;
|
|
|
|
ClassReleaseRemoveLock(DeviceObject, Irp);
|
|
|
|
//
|
|
// Grab the spinlock and clear the release queue in progress flag so others
|
|
// can run. Save (and clear) the state of the release queue needed flag
|
|
// so that we can issue a new release queue outside the spinlock.
|
|
//
|
|
|
|
KeAcquireSpinLock(&(fdoExtension->ReleaseQueueSpinLock), &oldIrql);
|
|
|
|
releaseQueueNeeded = fdoExtension->ReleaseQueueNeeded;
|
|
|
|
fdoExtension->ReleaseQueueNeeded = FALSE;
|
|
fdoExtension->ReleaseQueueInProgress = FALSE;
|
|
|
|
KeReleaseSpinLock(&(fdoExtension->ReleaseQueueSpinLock), oldIrql);
|
|
|
|
//
|
|
// If we need a release queue then issue one now. Another processor may
|
|
// have already started one in which case we'll try to issue this one after
|
|
// it is done - but we should never recurse more than one deep.
|
|
//
|
|
|
|
if(releaseQueueNeeded) {
|
|
ClasspReleaseQueue(DeviceObject, Irp);
|
|
}
|
|
|
|
//
|
|
// Indicate the I/O system should stop processing the Irp completion.
|
|
//
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
|
|
} // ClassAsynchronousCompletion()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassAcquireChildLock()
|
|
|
|
Routine Description:
|
|
|
|
This routine acquires the lock protecting children PDOs. It may be
|
|
acquired recursively by the same thread, but must be release by the
|
|
thread once for each acquisition.
|
|
|
|
Arguments:
|
|
|
|
FdoExtension - the device whose child list is protected.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
VOID
|
|
ClassAcquireChildLock(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
|
|
)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if(FdoExtension->ChildLockOwner != KeGetCurrentThread()) {
|
|
KeWaitForSingleObject(&FdoExtension->ChildLock,
|
|
Executive, KernelMode,
|
|
FALSE, NULL);
|
|
|
|
ASSERT(FdoExtension->ChildLockOwner == NULL);
|
|
ASSERT(FdoExtension->ChildLockAcquisitionCount == 0);
|
|
|
|
FdoExtension->ChildLockOwner = KeGetCurrentThread();
|
|
} else {
|
|
ASSERT(FdoExtension->ChildLockAcquisitionCount != 0);
|
|
}
|
|
|
|
FdoExtension->ChildLockAcquisitionCount++;
|
|
return;
|
|
}
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassReleaseChildLock() ISSUE-2000/02/18-henrygab - not documented
|
|
|
|
Routine Description:
|
|
|
|
This routine releases the lock protecting children PDOs. It must be
|
|
called once for each time ClassAcquireChildLock was called.
|
|
|
|
Arguments:
|
|
|
|
FdoExtension - the device whose child list is protected
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
VOID
|
|
ClassReleaseChildLock(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
|
|
)
|
|
{
|
|
ASSERT(FdoExtension->ChildLockOwner == KeGetCurrentThread());
|
|
ASSERT(FdoExtension->ChildLockAcquisitionCount != 0);
|
|
|
|
FdoExtension->ChildLockAcquisitionCount -= 1;
|
|
|
|
if(FdoExtension->ChildLockAcquisitionCount == 0) {
|
|
FdoExtension->ChildLockOwner = NULL;
|
|
KeSetEvent(&FdoExtension->ChildLock, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
|
|
return;
|
|
} // end ClassReleaseChildLock(
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassAddChild()
|
|
|
|
Routine Description:
|
|
|
|
This routine will insert a new child into the head of the child list.
|
|
|
|
Arguments:
|
|
|
|
Parent - the child's parent (contains the head of the list)
|
|
Child - the child to be inserted.
|
|
AcquireLock - whether the child lock should be acquired (TRUE) or whether
|
|
it's already been acquired by or on behalf of the caller
|
|
(FALSE).
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
VOID
|
|
ClassAddChild(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION Parent,
|
|
IN PPHYSICAL_DEVICE_EXTENSION Child,
|
|
IN BOOLEAN AcquireLock
|
|
)
|
|
{
|
|
if(AcquireLock) {
|
|
ClassAcquireChildLock(Parent);
|
|
}
|
|
|
|
#if DBG
|
|
//
|
|
// Make sure this child's not already in the list.
|
|
//
|
|
{
|
|
PPHYSICAL_DEVICE_EXTENSION testChild;
|
|
|
|
for (testChild = Parent->CommonExtension.ChildList;
|
|
testChild != NULL;
|
|
testChild = testChild->CommonExtension.ChildList) {
|
|
|
|
ASSERT(testChild != Child);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Child->CommonExtension.ChildList = Parent->CommonExtension.ChildList;
|
|
Parent->CommonExtension.ChildList = Child;
|
|
|
|
if(AcquireLock) {
|
|
ClassReleaseChildLock(Parent);
|
|
}
|
|
return;
|
|
} // end ClassAddChild()
|
|
|
|
/*++////////////////////////////////////////////////////////////////////////////
|
|
|
|
ClassRemoveChild()
|
|
|
|
Routine Description:
|
|
|
|
This routine will remove a child from the child list.
|
|
|
|
Arguments:
|
|
|
|
Parent - the parent to be removed from.
|
|
|
|
Child - the child to be removed or NULL if the first child should be
|
|
removed.
|
|
|
|
AcquireLock - whether the child lock should be acquired (TRUE) or whether
|
|
it's already been acquired by or on behalf of the caller
|
|
(FALSE).
|
|
|
|
Return Value:
|
|
|
|
A pointer to the child which was removed or NULL if no such child could
|
|
be found in the list (or if Child was NULL but the list is empty).
|
|
|
|
--*/
|
|
PPHYSICAL_DEVICE_EXTENSION
|
|
ClassRemoveChild(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION Parent,
|
|
IN PPHYSICAL_DEVICE_EXTENSION Child,
|
|
IN BOOLEAN AcquireLock
|
|
)
|
|
{
|
|
if(AcquireLock) {
|
|
ClassAcquireChildLock(Parent);
|
|
}
|
|
|
|
TRY {
|
|
PCOMMON_DEVICE_EXTENSION previousChild = &Parent->CommonExtension;
|
|
|
|
//
|
|
// If the list is empty then bail out now.
|
|
//
|
|
|
|
if(Parent->CommonExtension.ChildList == NULL) {
|
|
Child = NULL;
|
|
LEAVE;
|
|
}
|
|
|
|
//
|
|
// If the caller specified a child then find the child object before
|
|
// it. If none was specified then the FDO is the child object before
|
|
// the one we want to remove.
|
|
//
|
|
|
|
if(Child != NULL) {
|
|
|
|
//
|
|
// Scan through the child list to find the entry which points to
|
|
// this one.
|
|
//
|
|
|
|
do {
|
|
ASSERT(previousChild != &Child->CommonExtension);
|
|
|
|
if(previousChild->ChildList == Child) {
|
|
break;
|
|
}
|
|
|
|
previousChild = &previousChild->ChildList->CommonExtension;
|
|
} while(previousChild != NULL);
|
|
|
|
if(previousChild == NULL) {
|
|
Child = NULL;
|
|
LEAVE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Save the next child away then unlink it from the list.
|
|
//
|
|
|
|
Child = previousChild->ChildList;
|
|
previousChild->ChildList = Child->CommonExtension.ChildList;
|
|
Child->CommonExtension.ChildList = NULL;
|
|
|
|
} FINALLY {
|
|
if(AcquireLock) {
|
|
ClassReleaseChildLock(Parent);
|
|
}
|
|
}
|
|
return Child;
|
|
} // end ClassRemoveChild()
|
|
|
|
|
|
/*++
|
|
|
|
ISSUE-2000/02/20-henrygab Not documented ClasspRetryRequestDpc
|
|
|
|
--*/
|
|
VOID
|
|
ClasspRetryRequestDpc(
|
|
IN PKDPC Dpc,
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PVOID Arg1,
|
|
IN PVOID Arg2
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
PCOMMON_DEVICE_EXTENSION commonExtension;
|
|
PCLASS_PRIVATE_FDO_DATA fdoData;
|
|
PCLASS_RETRY_INFO retryList;
|
|
KIRQL irql;
|
|
|
|
|
|
commonExtension = DeviceObject->DeviceExtension;
|
|
ASSERT(commonExtension->IsFdo);
|
|
fdoExtension = DeviceObject->DeviceExtension;
|
|
fdoData = fdoExtension->PrivateFdoData;
|
|
|
|
|
|
KeAcquireSpinLock(&fdoData->Retry.Lock, &irql);
|
|
{
|
|
LARGE_INTEGER now;
|
|
KeQueryTickCount(&now);
|
|
|
|
//
|
|
// if CurrentTick is less than now
|
|
// fire another DPC
|
|
// else
|
|
// retry entire list
|
|
// endif
|
|
//
|
|
|
|
if (now.QuadPart < fdoData->Retry.Tick.QuadPart) {
|
|
|
|
ClasspRetryDpcTimer(fdoData);
|
|
retryList = NULL;
|
|
|
|
} else {
|
|
|
|
retryList = fdoData->Retry.ListHead;
|
|
fdoData->Retry.ListHead = NULL;
|
|
fdoData->Retry.Delta.QuadPart = (LONGLONG)0;
|
|
fdoData->Retry.Tick.QuadPart = (LONGLONG)0;
|
|
|
|
}
|
|
}
|
|
KeReleaseSpinLock(&fdoData->Retry.Lock, irql);
|
|
|
|
while (retryList != NULL) {
|
|
|
|
PIRP irp;
|
|
|
|
irp = CONTAINING_RECORD(retryList, IRP, Tail.Overlay.DriverContext[0]);
|
|
DebugPrint((ClassDebugDelayedRetry, "ClassRetry: -- %p\n", irp));
|
|
retryList = retryList->Next;
|
|
#if DBG
|
|
irp->Tail.Overlay.DriverContext[0] = ULongToPtr(0xdddddddd); // invalidate data
|
|
irp->Tail.Overlay.DriverContext[1] = ULongToPtr(0xdddddddd); // invalidate data
|
|
irp->Tail.Overlay.DriverContext[2] = ULongToPtr(0xdddddddd); // invalidate data
|
|
irp->Tail.Overlay.DriverContext[3] = ULongToPtr(0xdddddddd); // invalidate data
|
|
#endif
|
|
|
|
IoCallDriver(commonExtension->LowerDeviceObject, irp);
|
|
|
|
}
|
|
return;
|
|
|
|
} // end ClasspRetryRequestDpc()
|
|
|
|
/*++
|
|
|
|
ISSUE-2000/02/20-henrygab Not documented ClassRetryRequest
|
|
|
|
--*/
|
|
VOID
|
|
ClassRetryRequest(
|
|
IN PDEVICE_OBJECT SelfDeviceObject,
|
|
IN PIRP Irp,
|
|
IN LARGE_INTEGER TimeDelta100ns // in 100ns units
|
|
)
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
|
|
PCLASS_PRIVATE_FDO_DATA fdoData;
|
|
PCLASS_RETRY_INFO retryInfo;
|
|
PCLASS_RETRY_INFO *previousNext;
|
|
LARGE_INTEGER delta;
|
|
KIRQL irql;
|
|
|
|
//
|
|
// this checks we aren't destroying irps
|
|
//
|
|
ASSERT(sizeof(CLASS_RETRY_INFO) <= (4*sizeof(PVOID)));
|
|
|
|
fdoExtension = SelfDeviceObject->DeviceExtension;
|
|
fdoData = fdoExtension->PrivateFdoData;
|
|
|
|
if (!fdoExtension->CommonExtension.IsFdo) {
|
|
|
|
//
|
|
// this debug print/assertion should ALWAYS be investigated.
|
|
// ClassRetryRequest can currently only be used by FDO's
|
|
//
|
|
|
|
DebugPrint((ClassDebugError, "ClassRetryRequestEx: LOST IRP %p\n", Irp));
|
|
ASSERT(!"ClassRetryRequestEx Called From PDO? LOST IRP");
|
|
return;
|
|
|
|
}
|
|
|
|
if (TimeDelta100ns.QuadPart < 0) {
|
|
ASSERT(!"ClassRetryRequest - must use positive delay");
|
|
TimeDelta100ns.QuadPart *= -1;
|
|
}
|
|
|
|
//
|
|
// prepare what we can out of the loop
|
|
//
|
|
|
|
retryInfo = (PCLASS_RETRY_INFO)(&Irp->Tail.Overlay.DriverContext[0]);
|
|
RtlZeroMemory(retryInfo, sizeof(CLASS_RETRY_INFO));
|
|
|
|
delta.QuadPart = (TimeDelta100ns.QuadPart / fdoData->Retry.Granularity);
|
|
if (TimeDelta100ns.QuadPart % fdoData->Retry.Granularity) {
|
|
delta.QuadPart ++; // round up to next tick
|
|
}
|
|
if (delta.QuadPart == (LONGLONG)0) {
|
|
delta.QuadPart = MINIMUM_RETRY_UNITS;
|
|
}
|
|
|
|
//
|
|
// now determine if we should fire another DPC or not
|
|
//
|
|
|
|
KeAcquireSpinLock(&fdoData->Retry.Lock, &irql);
|
|
|
|
//
|
|
// always add request to the list
|
|
//
|
|
|
|
retryInfo->Next = fdoData->Retry.ListHead;
|
|
fdoData->Retry.ListHead = retryInfo;
|
|
|
|
if (fdoData->Retry.Delta.QuadPart == (LONGLONG)0) {
|
|
|
|
DebugPrint((ClassDebugDelayedRetry, "ClassRetry: +++ %p\n", Irp));
|
|
|
|
//
|
|
// must be exactly one item on list
|
|
//
|
|
|
|
ASSERT(fdoData->Retry.ListHead != NULL);
|
|
ASSERT(fdoData->Retry.ListHead->Next == NULL);
|
|
|
|
//
|
|
// if currentDelta is zero, always fire a DPC
|
|
//
|
|
|
|
KeQueryTickCount(&fdoData->Retry.Tick);
|
|
fdoData->Retry.Tick.QuadPart += delta.QuadPart;
|
|
fdoData->Retry.Delta.QuadPart = delta.QuadPart;
|
|
ClasspRetryDpcTimer(fdoData);
|
|
|
|
} else if (delta.QuadPart > fdoData->Retry.Delta.QuadPart) {
|
|
|
|
//
|
|
// if delta is greater than the list's current delta,
|
|
// increase the DPC handling time by difference
|
|
// and update the delta to new larger value
|
|
// allow the DPC to re-fire itself if needed
|
|
//
|
|
|
|
DebugPrint((ClassDebugDelayedRetry, "ClassRetry: ++ %p\n", Irp));
|
|
|
|
//
|
|
// must be at least two items on list
|
|
//
|
|
|
|
ASSERT(fdoData->Retry.ListHead != NULL);
|
|
ASSERT(fdoData->Retry.ListHead->Next != NULL);
|
|
|
|
fdoData->Retry.Tick.QuadPart -= fdoData->Retry.Delta.QuadPart;
|
|
fdoData->Retry.Tick.QuadPart += delta.QuadPart;
|
|
|
|
fdoData->Retry.Delta.QuadPart = delta.QuadPart;
|
|
|
|
} else {
|
|
|
|
//
|
|
// just inserting it on the list was enough
|
|
//
|
|
|
|
DebugPrint((ClassDebugDelayedRetry, "ClassRetry: ++ %p\n", Irp));
|
|
|
|
}
|
|
|
|
|
|
KeReleaseSpinLock(&fdoData->Retry.Lock, irql);
|
|
|
|
|
|
} // end ClassRetryRequest()
|
|
|
|
/*++
|
|
|
|
ISSUE-2000/02/20-henrygab Not documented ClasspRetryDpcTimer
|
|
|
|
--*/
|
|
VOID
|
|
ClasspRetryDpcTimer(
|
|
IN PCLASS_PRIVATE_FDO_DATA FdoData
|
|
)
|
|
{
|
|
LARGE_INTEGER fire;
|
|
|
|
ASSERT(FdoData->Retry.Tick.QuadPart != (LONGLONG)0);
|
|
ASSERT(FdoData->Retry.ListHead != NULL); // never fire an empty list
|
|
|
|
//
|
|
// fire == (CurrentTick - now) * (100ns per tick)
|
|
//
|
|
// NOTE: Overflow is nearly impossible and is ignored here
|
|
//
|
|
|
|
KeQueryTickCount(&fire);
|
|
fire.QuadPart = FdoData->Retry.Tick.QuadPart - fire.QuadPart;
|
|
fire.QuadPart *= FdoData->Retry.Granularity;
|
|
|
|
//
|
|
// fire is now multiples of 100ns until should fire the timer.
|
|
// if timer should already have expired, or would fire too quickly,
|
|
// fire it in some arbitrary number of ticks to prevent infinitely
|
|
// recursing.
|
|
//
|
|
|
|
if (fire.QuadPart < MINIMUM_RETRY_UNITS) {
|
|
fire.QuadPart = MINIMUM_RETRY_UNITS;
|
|
}
|
|
|
|
DebugPrint((ClassDebugDelayedRetry,
|
|
"ClassRetry: ======= %I64x ticks\n",
|
|
fire.QuadPart));
|
|
|
|
//
|
|
// must use negative to specify relative time to fire
|
|
//
|
|
|
|
fire.QuadPart = fire.QuadPart * ((LONGLONG)-1);
|
|
|
|
//
|
|
// set the timer, since this is the first addition
|
|
//
|
|
|
|
KeSetTimerEx(&FdoData->Retry.Timer, fire, 0, &FdoData->Retry.Dpc);
|
|
|
|
return;
|
|
} // end ClasspRetryDpcTimer()
|
|
|
|
NTSTATUS
|
|
ClasspInitializeHotplugInfo(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
|
|
)
|
|
{
|
|
PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
|
|
DEVICE_REMOVAL_POLICY deviceRemovalPolicy;
|
|
NTSTATUS status;
|
|
ULONG resultLength = 0;
|
|
ULONG writeCacheOverride;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// start with some default settings
|
|
//
|
|
RtlZeroMemory(&(fdoData->HotplugInfo), sizeof(STORAGE_HOTPLUG_INFO));
|
|
|
|
//
|
|
// set the size (aka version)
|
|
//
|
|
|
|
fdoData->HotplugInfo.Size = sizeof(STORAGE_HOTPLUG_INFO);
|
|
|
|
//
|
|
// set if the device has removable media
|
|
//
|
|
|
|
if (FdoExtension->DeviceDescriptor->RemovableMedia) {
|
|
fdoData->HotplugInfo.MediaRemovable = TRUE;
|
|
} else {
|
|
fdoData->HotplugInfo.MediaRemovable = FALSE;
|
|
}
|
|
|
|
//
|
|
// this refers to devices which, for reasons not yet understood,
|
|
// do not fail PREVENT_MEDIA_REMOVAL requests even though they
|
|
// have no way to lock the media into the drive. this allows
|
|
// the filesystems to turn off delayed-write caching for these
|
|
// devices as well.
|
|
//
|
|
|
|
if (TEST_FLAG(FdoExtension->PrivateFdoData->HackFlags,
|
|
FDO_HACK_CANNOT_LOCK_MEDIA)) {
|
|
fdoData->HotplugInfo.MediaHotplug = TRUE;
|
|
} else {
|
|
fdoData->HotplugInfo.MediaHotplug = FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Look into the registry to see if the user has chosen
|
|
// to override the default setting for the removal policy
|
|
//
|
|
|
|
RtlZeroMemory(&deviceRemovalPolicy, sizeof(DEVICE_REMOVAL_POLICY));
|
|
|
|
ClassGetDeviceParameter(FdoExtension,
|
|
CLASSP_REG_SUBKEY_NAME,
|
|
CLASSP_REG_REMOVAL_POLICY_VALUE_NAME,
|
|
(PULONG)&deviceRemovalPolicy);
|
|
|
|
if (deviceRemovalPolicy == 0)
|
|
{
|
|
//
|
|
// Query the default removal policy from the kernel
|
|
//
|
|
|
|
status = IoGetDeviceProperty(FdoExtension->LowerPdo,
|
|
DevicePropertyRemovalPolicy,
|
|
sizeof(DEVICE_REMOVAL_POLICY),
|
|
(PVOID)&deviceRemovalPolicy,
|
|
&resultLength);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
if (resultLength != sizeof(DEVICE_REMOVAL_POLICY))
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// use this info to set the DeviceHotplug setting
|
|
// don't rely on DeviceCapabilities, since it can't properly
|
|
// determine device relations, etc. let the kernel figure this
|
|
// stuff out instead.
|
|
//
|
|
|
|
if (deviceRemovalPolicy == RemovalPolicyExpectSurpriseRemoval) {
|
|
fdoData->HotplugInfo.DeviceHotplug = TRUE;
|
|
} else {
|
|
fdoData->HotplugInfo.DeviceHotplug = FALSE;
|
|
}
|
|
|
|
//
|
|
// this refers to the *filesystem* caching, but has to be included
|
|
// here since it's a per-device setting. this may change to be
|
|
// stored by the system in the future.
|
|
//
|
|
|
|
writeCacheOverride = FALSE;
|
|
ClassGetDeviceParameter(FdoExtension,
|
|
CLASSP_REG_SUBKEY_NAME,
|
|
CLASSP_REG_WRITE_CACHE_VALUE_NAME,
|
|
&writeCacheOverride);
|
|
|
|
if (writeCacheOverride) {
|
|
fdoData->HotplugInfo.WriteCacheEnableOverride = TRUE;
|
|
} else {
|
|
fdoData->HotplugInfo.WriteCacheEnableOverride = FALSE;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
ClasspScanForClassHacks(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
|
|
IN ULONG_PTR Data
|
|
)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// remove invalid flags and save
|
|
//
|
|
|
|
CLEAR_FLAG(Data, FDO_HACK_INVALID_FLAGS);
|
|
SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, Data);
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
ClasspScanForSpecialInRegistry(
|
|
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
|
|
)
|
|
{
|
|
HANDLE deviceParameterHandle; // device instance key
|
|
HANDLE classParameterHandle; // classpnp subkey
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
UNICODE_STRING subkeyName;
|
|
NTSTATUS status;
|
|
|
|
//
|
|
// seeded in the ENUM tree by ClassInstaller
|
|
//
|
|
ULONG deviceHacks;
|
|
RTL_QUERY_REGISTRY_TABLE queryTable[2]; // null terminated array
|
|
|
|
PAGED_CODE();
|
|
|
|
deviceParameterHandle = NULL;
|
|
classParameterHandle = NULL;
|
|
deviceHacks = 0;
|
|
|
|
status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
|
|
PLUGPLAY_REGKEY_DEVICE,
|
|
KEY_WRITE,
|
|
&deviceParameterHandle
|
|
);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto cleanupScanForSpecial;
|
|
}
|
|
|
|
RtlInitUnicodeString(&subkeyName, CLASSP_REG_SUBKEY_NAME);
|
|
InitializeObjectAttributes(&objectAttributes,
|
|
&subkeyName,
|
|
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
|
deviceParameterHandle,
|
|
NULL
|
|
);
|
|
|
|
status = ZwOpenKey( &classParameterHandle,
|
|
KEY_READ,
|
|
&objectAttributes
|
|
);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto cleanupScanForSpecial;
|
|
}
|
|
|
|
//
|
|
// Zero out the memory
|
|
//
|
|
|
|
RtlZeroMemory(&queryTable[0], 2*sizeof(RTL_QUERY_REGISTRY_TABLE));
|
|
|
|
//
|
|
// Setup the structure to read
|
|
//
|
|
|
|
queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
|
queryTable[0].Name = CLASSP_REG_HACK_VALUE_NAME;
|
|
queryTable[0].EntryContext = &deviceHacks;
|
|
queryTable[0].DefaultType = REG_DWORD;
|
|
queryTable[0].DefaultData = &deviceHacks;
|
|
queryTable[0].DefaultLength = 0;
|
|
|
|
//
|
|
// read values
|
|
//
|
|
|
|
status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
|
|
(PWSTR)classParameterHandle,
|
|
&queryTable[0],
|
|
NULL,
|
|
NULL
|
|
);
|
|
if (!NT_SUCCESS(status)) {
|
|
goto cleanupScanForSpecial;
|
|
}
|
|
|
|
//
|
|
// remove unknown values and save...
|
|
//
|
|
|
|
KdPrintEx((DPFLTR_CLASSPNP_ID, DPFLTR_ERROR_LEVEL,
|
|
"Classpnp => ScanForSpecial: HackFlags %#08x\n",
|
|
deviceHacks));
|
|
|
|
CLEAR_FLAG(deviceHacks, FDO_HACK_INVALID_FLAGS);
|
|
SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, deviceHacks);
|
|
|
|
|
|
cleanupScanForSpecial:
|
|
|
|
if (deviceParameterHandle) {
|
|
ZwClose(deviceParameterHandle);
|
|
}
|
|
|
|
if (classParameterHandle) {
|
|
ZwClose(classParameterHandle);
|
|
}
|
|
|
|
//
|
|
// we should modify the system hive to include another key for us to grab
|
|
// settings from. in this case: Classpnp\HackFlags
|
|
//
|
|
// the use of a DWORD value for the HackFlags allows 32 hacks w/o
|
|
// significant use of the registry, and also reduces OEM exposure.
|
|
//
|
|
// definition of bit flags:
|
|
// 0x00000001 -- Device succeeds PREVENT_MEDIUM_REMOVAL, but
|
|
// cannot actually prevent removal.
|
|
// 0x00000002 -- Device hard-hangs or times out for GESN requests.
|
|
// 0xfffffffc -- Currently reserved, may be used later.
|
|
//
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|