Windows2003-3790/drivers/storage/crcfilter/filter.c
2020-09-30 16:53:55 +02:00

1411 lines
43 KiB
C

/*++
Copyright (c) 2001-2002 Microsoft Corporation
Module Name:
Filter.c
Abstract:
A storage lower filter (to disk) driver that verifies each read/write
disk I/O on a per sector basis.
Environment:
kernel mode only
Notes:
--*/
#include "Filter.h"
#include "Device.h"
#include "CRC.h"
#include "Util.h"
#include <safeboot.h>
#if DBG_WMI_TRACING
//
// for any file that has software tracing printouts, you must include a
// header file <filename>.tmh
// this file will be generated by the WPP processing phase
//
#include "Filter.tmh"
#endif
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, InitiateCRCTable)
#pragma alloc_text (PAGE, DataVerFilter_AddDevice)
#pragma alloc_text (PAGE, DataVerFilter_DispatchPnp)
#pragma alloc_text (PAGE, DataVerFilter_StartDevice)
#pragma alloc_text (PAGE, DataVerFilter_RemoveDevice)
#pragma alloc_text (PAGE, DataVerFilter_Unload)
#endif
/*
* Counter used to produce unique disk id.
*/
ULONG g_UniqueDiskId = 0;
#if DBG
volatile BOOLEAN DebugTrapOnWarn = FALSE;
#endif
/*
* This pointer is declared in ntoskrnl.lib.
* At load time, it is set to point to a ulong in the kernel which indicates whether we are in safe mode.
*/
extern PULONG InitSafeBootMode;
/*
* Can poke this in the debugger to cause trapping on a particular sector
*/
volatile ULONG DbgTrapSector = (ULONG)-1;
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
Installable driver initialization entry point.
This entry point is called directly by the I/O manager. The driver object
is set up and then the Pnp manager calls DataVerFilter_AddDevice to attach
to the boot devices.
Arguments:
DriverObject - The disk performance driver object.
RegistryPath - pointer to a unicode string representing the path,
to driver-specific key in the registry.
Return Value:
STATUS_SUCCESS if successful
--*/
{
if (*InitSafeBootMode == 0){
ULONG ulIndex;
#if DBG_WMI_TRACING
//
// Enable software tracing by registering using the WPP macro.
//
WPP_INIT_TRACING(DriverObject, RegistryPath);
#endif
for (ulIndex = 0; ulIndex <= IRP_MJ_MAXIMUM_FUNCTION; ulIndex++){
DriverObject->MajorFunction[ ulIndex ] = DataVerFilter_DispatchAny;
}
//
// Set up the device driver entry points.
//
DriverObject->MajorFunction[IRP_MJ_SCSI] = CrcScsi;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DataVerFilter_DeviceControl;
DriverObject->MajorFunction[IRP_MJ_PNP] = DataVerFilter_DispatchPnp;
DriverObject->MajorFunction[IRP_MJ_POWER] = DataVerFilter_DispatchPower;
DriverObject->DriverExtension->AddDevice = DataVerFilter_AddDevice;
DriverObject->DriverUnload = DataVerFilter_Unload;
}
else {
/*
* The user chose safe boot at startup.
* By not setting AddDevice, crcdisk will not be inserted in the disk stack.
*/
ASSERT(!DriverObject->DriverExtension->AddDevice);
}
return STATUS_SUCCESS;
}
NTSTATUS
InitiateCRCTable (
PDEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
Initiate the CRC Array. This will be used to store CRC's on a per
sector basis. It's a simple array of pointers, each of which points
to a memory block of fixed size.
Assumes SyncEvent is HELD.
Arguments:
DeviceExtension - Device extension of the specific disk
Return Value:
STATUS_SUCCESS iff successful; error code otherwise
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PAGED_CODE();
ASSERT(DeviceExtension->ulNumSectors);
ASSERT(DeviceExtension->ulSectorSize);
if (!DeviceExtension->CRCMdlLists.pMdlItems){
DeviceExtension->CRCMdlLists.ulMaxItems = DeviceExtension->ulNumSectors / CRC_MDL_LOGIC_BLOCK_SIZE + 1;
DeviceExtension->CRCMdlLists.pMdlItems = AllocPool(
DeviceExtension,
NonPagedPool,
DeviceExtension->CRCMdlLists.ulMaxItems*sizeof(CRC_MDL_ITEM),
TRUE);
if (DeviceExtension->CRCMdlLists.pMdlItems){
ULONG i;
InitializeListHead(&DeviceExtension->CRCMdlLists.LockedLRUList);
for (i = 0; i < DeviceExtension->CRCMdlLists.ulMaxItems; i++){
InitializeListHead(&DeviceExtension->CRCMdlLists.pMdlItems[i].LockedLRUListEntry);
}
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
return status;
}
NTSTATUS
DataVerFilter_AddDevice(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
)
/*++
Routine Description:
Creates and initializes a new filter device object FDO for the
corresponding PDO. Then it attaches the device object to the device
stack of the drivers for the device.
Arguments:
DriverObject - Filter driver object.
PhysicalDeviceObject - Physical Device Object from the underlying driver
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_OBJECT filterDeviceObject = NULL;
PDEVICE_EXTENSION deviceExtension;
PAGED_CODE();
//
// Create a filter device object for this device stack.
//
status = IoCreateDevice(DriverObject,
sizeof(DEVICE_EXTENSION),
NULL,
PhysicalDeviceObject->DeviceType,
FILE_DEVICE_UNKNOWN,
FALSE,
&filterDeviceObject);
if ( !NT_SUCCESS(status) ) {
return status;
}
deviceExtension = (PDEVICE_EXTENSION) filterDeviceObject->DeviceExtension;
RtlZeroMemory(deviceExtension, sizeof(DEVICE_EXTENSION));
KeInitializeSpinLock(&deviceExtension->SpinLock);
KeInitializeEvent(&deviceExtension->SyncEvent, SynchronizationEvent, TRUE);
InitializeListHead(&deviceExtension->DeferredCheckSumList);
InitializeListHead(&deviceExtension->AllContextsListEntry);
InitializeListHead(&deviceExtension->CRCMdlLists.LockedLRUList);
deviceExtension->DeviceObject = filterDeviceObject;
//
// From this point forward, any error will have side effects that need to
// be cleaned up. Using a try-finally block allows us to modify the program
// easily without losing track of the side effects.
//
__try
{
//
// Attaches the device object to the highest device object in the chain and
// return the previously highest device object, which is passed to
// IoCallDriver when pass IRPs down the device stack
//
deviceExtension->LowerDeviceObject =
IoAttachDeviceToDeviceStack(filterDeviceObject, PhysicalDeviceObject);
if (deviceExtension->LowerDeviceObject == NULL) {
status = STATUS_DEVICE_REMOVED;
__leave;
}
deviceExtension->ReadCapacityWorkItem = IoAllocateWorkItem(PhysicalDeviceObject);
if (!deviceExtension->ReadCapacityWorkItem){
status = STATUS_INSUFFICIENT_RESOURCES;
__leave;
}
deviceExtension->CheckSumWorkItem = IoAllocateWorkItem(PhysicalDeviceObject);
if (!deviceExtension->CheckSumWorkItem){
status = STATUS_INSUFFICIENT_RESOURCES;
__leave;
}
//
// default to DIRECT_IO.
//
SET_FLAG(filterDeviceObject->Flags, DO_DIRECT_IO);
deviceExtension->ulSectorSize = 512; // default sector size
deviceExtension->ulNumSectors = 0;
deviceExtension->State = DEVSTATE_INITIALIZED;
InsertTailList(&AllContextsList, &deviceExtension->AllContextsListEntry);
//
// Clear the DO_DEVICE_INITIALIZING flag, so we can get IRPs
//
CLEAR_FLAG(filterDeviceObject->Flags, DO_DEVICE_INITIALIZING);
}
__finally
{
if ( !NT_SUCCESS( status ) )
{
//
// full clean up.
//
if (deviceExtension->ReadCapacityWorkItem){
IoFreeWorkItem(deviceExtension->ReadCapacityWorkItem);
}
if (deviceExtension->CheckSumWorkItem){
IoFreeWorkItem(deviceExtension->CheckSumWorkItem);
}
if ( deviceExtension->LowerDeviceObject ){
IoDetachDevice(deviceExtension->LowerDeviceObject);
}
IoDeleteDevice( filterDeviceObject );
}
}
return status;
}
NTSTATUS
DataVerFilter_StartDevice(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine is called when a Pnp Start Irp is received.
Arguments:
DeviceObject - a pointer to the device object
Irp - a pointer to the irp
Return Value:
Status of processing the Start Irp
--*/
{
PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
NTSTATUS status;
ULONG ulPropFlags;
PAGED_CODE();
/*
* Forward the start irp down the stack and synchronize with its completion first.
* This will allow us to talk to the started stack before we return from the start.
*/
status = DataVerFilter_ForwardIrpSyn(DeviceObject, Irp);
ASSERT(NT_SUCCESS(status));
if (deviceExtension->State == DEVSTATE_INITIALIZED){
/*
* This is the first start. Do one-time initialization.
*/
status = GetDeviceDescriptor(deviceExtension, StorageDeviceProperty, &deviceExtension->StorageDeviceDesc);
if (NT_SUCCESS(status)){
deviceExtension->ulDiskId = InterlockedIncrement(&g_UniqueDiskId) - 1;
//
// Propogate all useful flags from target to Filter. MountMgr will look
// at the Filter object capabilities to figure out if the disk is
// a removable and perhaps other things.
//
ulPropFlags = deviceExtension->LowerDeviceObject->Flags & FILTER_DEVICE_PROPOGATE_FLAGS;
SET_FLAG(deviceExtension->LowerDeviceObject->Flags, ulPropFlags);
ulPropFlags = deviceExtension->LowerDeviceObject->Characteristics & FILTER_DEVICE_PROPOGATE_CHARACTERISTICS;
SET_FLAG(deviceExtension->LowerDeviceObject->Characteristics, ulPropFlags);
}
else {
ASSERT(NT_SUCCESS(status));
}
}
else if (deviceExtension->State == DEVSTATE_STOPPED){
/*
* This is a start following a stop. No need to do any one-time initialization.
*/
}
else {
ASSERT(!"unexpected pnp state");
}
deviceExtension->State = NT_SUCCESS(status) ? DEVSTATE_STARTED : DEVSTATE_START_FAILED;
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
NTSTATUS
DataVerFilter_RemoveDevice(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine is called when the device is to be removed.
It will detach itself from the stack before deleting itself.
Remove lock was acquired before this was called.
Arguments:
DeviceObject - a pointer to the device object
Irp - a pointer to the irp
Return Value:
Status of removing the device
--*/
{
PDEVICE_EXTENSION deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
NTSTATUS status;
PAGED_CODE();
/*
* We should not have been forwarded the remove irp if there is outstanding I/O.
*/
ASSERT(!deviceExtension->CompletedReadCapacityIrp);
/*
* The right way to handle REMOVE:
*
* 1. Synchronize with downward I/O (we don't originate I/O, so we don't need to do this)
* 2. Pass the remove down asynchronously.
* 3. Detach from the lower device object.
* 4. Free resources and delete own device object.
*/
deviceExtension->State = DEVSTATE_REMOVED;
status = DataVerFilter_ForwardIrpAsyn(DeviceObject, Irp, NULL, NULL);
IoDetachDevice(deviceExtension->LowerDeviceObject);
FreeAllPages(deviceExtension);
if (deviceExtension->CRCMdlLists.pMdlItems) FreePool(deviceExtension, deviceExtension->CRCMdlLists.pMdlItems, NonPagedPool);
if (deviceExtension->StorageDeviceDesc) FreePool(deviceExtension, deviceExtension->StorageDeviceDesc, NonPagedPool);
IoFreeWorkItem(deviceExtension->ReadCapacityWorkItem);
IoFreeWorkItem(deviceExtension->CheckSumWorkItem);
ASSERT(!deviceExtension->DbgNumPagedAllocs);
ASSERT(!deviceExtension->DbgNumNonPagedAllocs);
RemoveEntryList(&deviceExtension->AllContextsListEntry);
InitializeListHead(&deviceExtension->AllContextsListEntry);
IoDeleteDevice(DeviceObject);
return status;
}
NTSTATUS
DataVerFilter_DispatchPnp(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
Dispatch for PNP
Arguments:
DeviceObject - Supplies the device object.
Irp - Supplies the I/O request packet.
Return Value:
NTSTATUS
--*/
{
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
NTSTATUS status;
PAGED_CODE();
switch(irpStack->MinorFunction)
{
case IRP_MN_START_DEVICE:
//
// Call the Start Routine handler.
//
status = DataVerFilter_StartDevice(DeviceObject, Irp);
break;
case IRP_MN_STOP_DEVICE:
case IRP_MN_SURPRISE_REMOVAL:
deviceExtension->State = DEVSTATE_STOPPED;
status = DataVerFilter_ForwardIrpAsyn(DeviceObject, Irp, NULL, NULL);
break;
case IRP_MN_REMOVE_DEVICE:
//
// Call the Remove Routine handler.
//
status = DataVerFilter_RemoveDevice(DeviceObject, Irp);
break;
default:
//
// Simply forward all other Irps
//
status = DataVerFilter_ForwardIrpAsyn(DeviceObject, Irp, NULL, NULL);
break;
}
return status;
}
/*++
Routine Description:
Dispatch for Power IRP
Arguments:
DeviceObject - Supplies the device object.
Irp - Supplies the I/O request packet.
Return Value:
NTSTATUS
--*/
NTSTATUS
DataVerFilter_DispatchPower(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
NTSTATUS status;
if (irpSp->MinorFunction == IRP_MN_SET_POWER){
if ((irpSp->Parameters.Power.State.DeviceState == PowerDeviceD3) &&
(irpSp->Parameters.Power.ShutdownType == PowerActionHibernate)){
/*
* We are about to hibernate.
* System state will be written out via the crashdump path (i.e. not through crcdisk),
* but will be read back in via crcdisk.
* That means that we could get false positives on resume.
* So invalidate all our stored checksums.
*/
KIRQL oldIrql;
KeAcquireSpinLock(&deviceExtension->SpinLock, &oldIrql);
deviceExtension->NeedCriticalRecovery = TRUE;
if (!deviceExtension->IsCheckSumWorkItemOutstanding){
deviceExtension->IsCheckSumWorkItemOutstanding = TRUE;
IoQueueWorkItem(deviceExtension->CheckSumWorkItem, CheckSumWorkItemCallback, CriticalWorkQueue, deviceExtension);
}
deviceExtension->DbgNumHibernations++;
KeReleaseSpinLock(&deviceExtension->SpinLock, oldIrql);
}
}
PoStartNextPowerIrp(Irp);
IoSkipCurrentIrpStackLocation(Irp);
status = PoCallDriver(deviceExtension->LowerDeviceObject, Irp);
return status;
}
/*++
Routine Description:
Dispatch for Any Unhandled IRP
Arguments:
DeviceObject - Supplies the device object.
Irp - Supplies the I/O request packet.
Return Value:
NTSTATUS
--*/
NTSTATUS
DataVerFilter_DispatchAny(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_EXTENSION deviceExtension;
deviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
status = DataVerFilter_ForwardIrpAsyn( DeviceObject, Irp, NULL, NULL );
return status;
}
/*++
Routine Description:
Dispatch for Device Control IRP
Arguments:
DeviceObject - Supplies the device object.
Irp - Supplies the I/O request packet.
Return Value:
NTSTATUS
--*/
NTSTATUS
DataVerFilter_DeviceControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_EXTENSION deviceExtension;
deviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
//
// now, we don't handle anything. But later we could use this
// or IRP_MJ_SCSI to receive controls from user mode component.
//
status = DataVerFilter_ForwardIrpAsyn( DeviceObject, Irp, NULL, NULL );
return status;
}
/*++
Routine Description:
SCSIRead completion routine. Caculate the checksum before return.
Arguments:
DeviceObject - the device object of the WMI driver
Irp - the WMI irp that was just completed
pContext - pContext == > 0(changeId): memory is locked safe to use.
== = 0 : memory is not locked.
Return Value:
STATUS_MORE_PROCESSING_REQUIRED
--*/
NTSTATUS CrcScsiReadCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID pContext)
{
PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PCRC_COMPLETION_CONTEXT PCrcContext = pContext;
/*
* Pass parameters to workItem callback inside the irp
*/
Irp->Tail.Overlay.DriverContext[0] = ULongToPtr(0); // is not write
Irp->Tail.Overlay.DriverContext[1] = PCrcContext;
CompleteXfer(deviceExtension, Irp);
if (Irp->PendingReturned){
IoMarkIrpPending(Irp);
}
return STATUS_SUCCESS;
}
VOID CompleteXfer(PDEVICE_EXTENSION DeviceExtension, PIRP Irp)
{
if (DeviceExtension->CRCMdlLists.mdlItemsAllocated){
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PSCSI_REQUEST_BLOCK pSRB = irpStack->Parameters.Scsi.Srb;
ULONG ulLength = pSRB->DataTransferLength;
ULONG ulBlocks = ulLength / DeviceExtension->ulSectorSize;
UCHAR srbStat = SRB_STATUS(pSRB->SrbStatus);
PCDB pCdb = (PCDB)pSRB->Cdb;
BOOLEAN isWrite = (BOOLEAN)Irp->Tail.Overlay.DriverContext[0];
PCRC_COMPLETION_CONTEXT PCrcContext = Irp->Tail.Overlay.DriverContext[1];
PUCHAR pDataBuf = NULL;
ULONG ulLogicalBlockAddr;
NTSTATUS status;
BOOLEAN bCRCOk;
ULONG tmp;
ASSERT(DeviceExtension->ulSectorSize > 0);
ASSERT((ulLength % DeviceExtension->ulSectorSize) == 0);
/*
* Get the LBA out from our stack location where we stashed it.
*/
ulLogicalBlockAddr = (ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4;
REVERSE_BYTES(&tmp, &pCdb->CDB10.LogicalBlockByte0);
ASSERT(tmp == ulLogicalBlockAddr);
if ((DbgTrapSector >= ulLogicalBlockAddr) && (DbgTrapSector < ulLogicalBlockAddr+ulBlocks)){
RETAIL_TRAP("hit trap sector (completion)");
}
if (isWrite){
if (DeviceExtension->ulNumSectors && DeviceExtension->CRCMdlLists.mdlItemsAllocated){
if (srbStat == SRB_STATUS_SUCCESS){
ASSERT(Irp->IoStatus.Status == STATUS_SUCCESS);
pDataBuf = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
if (!pDataBuf){
pDataBuf = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, HighPagePriority);
}
if (pDataBuf){
pDataBuf = pDataBuf + ( ((PUCHAR) pSRB->DataBuffer)
- ((PUCHAR)MmGetMdlVirtualAddress(Irp->MdlAddress)) );
bCRCOk = VerifyCheckSum(DeviceExtension, Irp, ulLogicalBlockAddr, ulLength, pDataBuf, TRUE);
ASSERT(bCRCOk);
}
else {
/*
* We couldn't read the written data block and update the checksum,
* so invalidate our checksum value.
*/
ASSERT(pDataBuf);
InvalidateChecksums(DeviceExtension, ulLogicalBlockAddr, ulLength);
}
}
else {
/*
* The contents of the disk blocks targeted by the failed write my be indeterminate,
* so invalidate our checksum for the part of the disk for which the write failed.
*/
InvalidateChecksums(DeviceExtension, ulLogicalBlockAddr, ulLength);
LogCRCWriteFailure(DeviceExtension->ulDiskId, ulLogicalBlockAddr, 0, srbStat);
DeviceExtension->DbgNumWriteFailures++;
}
}
}
else {
ULONG ulCRCIndex = ulLogicalBlockAddr / CRC_MDL_LOGIC_BLOCK_SIZE;
ASSERT(PCrcContext);
if (PCrcContext->AllocMapped){
if (srbStat == SRB_STATUS_SUCCESS){
pDataBuf = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
if (pDataBuf){
ASSERT(pDataBuf == PCrcContext->DbgDataBufPtrCopy);
pDataBuf = pDataBuf + ( (PUCHAR)(pSRB->DataBuffer)
- (PUCHAR)(MmGetMdlVirtualAddress(Irp->MdlAddress)) );
bCRCOk = VerifyCheckSum(DeviceExtension, Irp, ulLogicalBlockAddr, ulLength, pDataBuf, FALSE);
}
else {
DBGERR(("Temporary MDL Assignment Failed"));
}
}
else {
LogCRCReadFailure(DeviceExtension->ulDiskId, ulLogicalBlockAddr, ulBlocks, srbStat);
}
ASSERT(pSRB->DataBuffer == PCrcContext->DbgDataBufPtrCopy);
RtlCopyBytes(PCrcContext->VirtualDataBuff, pSRB->DataBuffer, ulLength);
IoFreeMdl(Irp->MdlAddress);
FreePool(DeviceExtension, pSRB->DataBuffer, NonPagedPool);
Irp->MdlAddress = PCrcContext->OriginalMdl;
pSRB->DataBuffer = PCrcContext->OriginalDataBuff;
}
}
FreePool(DeviceExtension, PCrcContext, NonPagedPool);
}
else {
ASSERT(DeviceExtension->CRCMdlLists.mdlItemsAllocated);
}
}
/*++
Routine Description:
Dispatch for SCSIRead
[should have acquired the remove locker before calling this funciton]
Arguments:
DeviceObject - Supplies the device object.
Irp - Supplies the I/O request packet.
Return Value:
NTSTATUS
--*/
NTSTATUS
CrcScsiRead(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
//
// Read is simple since we have to wait for the IRP complete before
// we could calculate the checksum.
//
PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PSCSI_REQUEST_BLOCK pSRB = irpStack->Parameters.Scsi.Srb;
PCDB pCdb = (PCDB)pSRB->Cdb;
ULONG ulLogicalBlockAddr;
ULONG ulLength = pSRB->DataTransferLength;
ULONG ulBlocks = ulLength / deviceExtension->ulSectorSize;
NTSTATUS status;
PMDL TempMdl = NULL;
PUCHAR TempDataBuff = NULL;
PCRC_COMPLETION_CONTEXT PCrcContext;
REVERSE_BYTES(&ulLogicalBlockAddr, &pCdb->CDB10.LogicalBlockByte0);
ASSERT(pSRB->QueueSortKey == ulLogicalBlockAddr); // class uses LBA as QueueSortKey
if ((DbgTrapSector >= ulLogicalBlockAddr) && (DbgTrapSector < ulLogicalBlockAddr+ulBlocks)){
RETAIL_TRAP("hit trap sector (read)");
}
ASSERT(deviceExtension->ulSectorSize);
ASSERT(Irp->MdlAddress);
deviceExtension->DbgNumReads++;
if (pSRB->SrbFlags & SRB_CLASS_FLAGS_PAGING) deviceExtension->DbgNumPagingReads++;
if (!deviceExtension->ulNumSectors || !deviceExtension->CRCMdlLists.mdlItemsAllocated){
return DataVerFilter_ForwardIrpAsyn(DeviceObject, Irp, NULL, NULL);
}
/*
* Save the LBA in our stack location
*/
irpStack->Parameters.Others.Argument4 = (PVOID)(ULONG_PTR)ulLogicalBlockAddr;
/*
* VERY VERY IMPORTANT
* We double-buffer every read request, because the memory manager
* in some cases maps the same physical page for multiple linear pages,
* causing the same physical page to get written repeatedly with data
* from different sectors. A reused physical page is a 'garbage' page used by MM
* to fill in gaps in a spanning read. It may even be included in multiple outstanding
* requests at once, defeating any attempt by us to scan the MDL for this condition here.
* For our purposes, we need to make sure the data we're checksumming corresponds
* to consecutive sectors, so we have to double-buffer.
*/
PCrcContext = AllocPool(deviceExtension, NonPagedPool, sizeof(CRC_COMPLETION_CONTEXT), FALSE);
if (PCrcContext == NULL){
return DataVerFilter_ForwardIrpAsyn(DeviceObject, Irp, NULL, NULL);
}
PCrcContext->AllocMapped = TRUE;
TempDataBuff = AllocPool(deviceExtension, NonPagedPool, ulLength, FALSE);
if (TempDataBuff){
TempMdl = IoAllocateMdl(TempDataBuff,ulLength, FALSE, FALSE, NULL);
if (TempMdl){
PUCHAR sysAddr;
MmBuildMdlForNonPagedPool(TempMdl);
PCrcContext->OriginalMdl = Irp->MdlAddress;
sysAddr = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
if (sysAddr == NULL) {
PCrcContext->AllocMapped = FALSE;
FreePool(deviceExtension, TempDataBuff, NonPagedPool);
IoFreeMdl(TempMdl);
return DataVerFilter_ForwardIrpAsyn( DeviceObject,
Irp,
CrcScsiReadCompletion,
(PVOID)PCrcContext );
}
PCrcContext->OriginalDataBuff = (PUCHAR)pSRB->DataBuffer;
PCrcContext->VirtualDataBuff = sysAddr +
(ULONG)((PUCHAR)pSRB->DataBuffer -
(PCCHAR)MmGetMdlVirtualAddress(Irp->MdlAddress));
Irp->MdlAddress = TempMdl;
pSRB->DataBuffer = TempDataBuff;
PCrcContext->DbgDataBufPtrCopy = TempDataBuff;
}
else {
PCrcContext->AllocMapped = FALSE;
FreePool(deviceExtension, TempDataBuff, NonPagedPool);
}
}
else {
PCrcContext->AllocMapped = FALSE;
}
return DataVerFilter_ForwardIrpAsyn( DeviceObject,
Irp,
CrcScsiReadCompletion,
(PVOID)PCrcContext );
}
/*++
Routine Description:
SCSIWrite completion routine. Check the flag of the WorkItem,
free WorkItem if needed.
Arguments:
DeviceObject - the device object of the WMI driver
Irp - the WMI irp that was just completed
pContext - point to the WorkItem that forwarder will wait on
Return Value:
STATUS_MORE_PROCESSING_REQUIRED
--*/
NTSTATUS CrcScsiWriteCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID pContext)
{
PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PCRC_COMPLETION_CONTEXT PCrcContext = pContext;
/*
* Pass parameters to workItem callback inside the irp
*/
Irp->Tail.Overlay.DriverContext[0] = ULongToPtr(1); // is write
Irp->Tail.Overlay.DriverContext[1] = PCrcContext;
CompleteXfer(deviceExtension, Irp);
if (Irp->PendingReturned){
IoMarkIrpPending(Irp);
}
return STATUS_SUCCESS;
}
/*++
Routine Description:
Dispatch for SCSIWrite
[removeLocker is already acquired by the DispatchScsi routine.]
Arguments:
DeviceObject - Supplies the device object.
Irp - Supplies the I/O request packet.
Return Value:
NTSTATUS
--*/
NTSTATUS
CrcScsiWrite(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
KIRQL oldIrql;
PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PSCSI_REQUEST_BLOCK pSRB = irpStack->Parameters.Scsi.Srb;
ULONG ulLength = pSRB->DataTransferLength;
ULONG ulBlocks = ulLength / deviceExtension->ulSectorSize;
PCDB pCdb = (PCDB)pSRB->Cdb;
PCRC_COMPLETION_CONTEXT pCrcContext;
ULONG ulLogicalBlockAddr;
REVERSE_BYTES(&ulLogicalBlockAddr, &pCdb->CDB10.LogicalBlockByte0);
ASSERT(ulLogicalBlockAddr == pSRB->QueueSortKey); // class uses LBA as QueueSortKey
deviceExtension->DbgNumWrites++;
if (pSRB->SrbFlags & SRB_CLASS_FLAGS_PAGING) deviceExtension->DbgNumPagingWrites++;
if ((DbgTrapSector >= ulLogicalBlockAddr) && (DbgTrapSector < ulLogicalBlockAddr+ulBlocks)){
RETAIL_TRAP("hit trap sector (write)");
}
if (!deviceExtension->ulNumSectors || !deviceExtension->CRCMdlLists.mdlItemsAllocated){
status = DataVerFilter_ForwardIrpAsyn(DeviceObject, Irp, NULL, NULL);
}
else {
pCrcContext = AllocPool(deviceExtension, NonPagedPool, sizeof(CRC_COMPLETION_CONTEXT), FALSE);
if (pCrcContext){
/*
* Save the LBA in our stack location
*/
irpStack->Parameters.Others.Argument4 = (PVOID)(ULONG_PTR)ulLogicalBlockAddr;
status = DataVerFilter_ForwardIrpAsyn( DeviceObject,
Irp,
CrcScsiWriteCompletion,
(PVOID)pCrcContext);
}
else {
/*
* We won't be able to record these checksums, so invalidate them.
*/
InvalidateChecksums(deviceExtension, ulLogicalBlockAddr, ulLength);
status = DataVerFilter_ForwardIrpAsyn(DeviceObject, Irp, NULL, NULL);
}
}
return status;
}
/*++
Routine Description:
SCSIWrite completion routine. Check the flag of the WorkItem,
free WorkItem if needed.
Arguments:
DeviceObject - the device object of the WMI driver
Irp - the WMI irp that was just completed
pContext - point to the WorkItem that forwarder will wait on
Return Value:
STATUS_MORE_PROCESSING_REQUIRED
--*/
NTSTATUS CrcScsiReadCapacityCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID pContext)
{
PDEVICE_EXTENSION deviceExtension = pContext;
BOOLEAN queuedIrp;
KIRQL oldIrql;
NTSTATUS status;
KeAcquireSpinLock(&deviceExtension->SpinLock, &oldIrql);
if (NT_SUCCESS(Irp->IoStatus.Status) && (Irp->IoStatus.Information >= sizeof(READ_CAPACITY_DATA))){
if (deviceExtension->CompletedReadCapacityIrp){
DBGWARN(("overlapping read capacity irps"));
queuedIrp = FALSE;
}
else {
deviceExtension->CompletedReadCapacityIrp = Irp;
queuedIrp = TRUE;
}
}
else {
queuedIrp = FALSE;
}
KeReleaseSpinLock(&deviceExtension->SpinLock, oldIrql);
if (queuedIrp){
IoQueueWorkItem(deviceExtension->ReadCapacityWorkItem, ReadCapacityWorkItemCallback, DelayedWorkQueue, deviceExtension);
status = STATUS_MORE_PROCESSING_REQUIRED;
}
else {
if (Irp->PendingReturned){
IoMarkIrpPending(Irp);
}
status = STATUS_SUCCESS;
}
return status;
}
VOID ReadCapacityWorkItemCallback(PDEVICE_OBJECT DevObj, PVOID Context)
{
PDEVICE_EXTENSION deviceExtension = Context;
PIRP readCapacityIrp;
PIO_STACK_LOCATION irpStack;
PSCSI_REQUEST_BLOCK pSRB;
BOOLEAN needToReinitialize;
PREAD_CAPACITY_DATA pReadCapacityData;
ULONG ulSectorSize;
ULONG ulNumSectors;
KIRQL oldIrql;
/*
* Use a spinlock to synchronize with completion routine.
*/
KeAcquireSpinLock(&deviceExtension->SpinLock, &oldIrql);
ASSERT(deviceExtension->CompletedReadCapacityIrp);
readCapacityIrp = deviceExtension->CompletedReadCapacityIrp;
deviceExtension->CompletedReadCapacityIrp = NULL;
KeReleaseSpinLock(&deviceExtension->SpinLock, oldIrql);
irpStack = IoGetCurrentIrpStackLocation(readCapacityIrp);
pSRB = irpStack->Parameters.Scsi.Srb;
pReadCapacityData = pSRB->DataBuffer;
ASSERT(NT_SUCCESS(readCapacityIrp->IoStatus.Status));
ASSERT(readCapacityIrp->IoStatus.Information >= sizeof(READ_CAPACITY_DATA));
REVERSE_BYTES(&ulSectorSize, &pReadCapacityData->BytesPerBlock);
REVERSE_BYTES(&ulNumSectors, &pReadCapacityData->LogicalBlockAddress);
ulNumSectors++;
/*
* If the drive has removable media, then we have to reinitialize the checksums
* every time we see readCapacity completing, because the media may have changed.
* For fixed media, we only have to reinitialize if the readCapacity result indicates that the disk has grown,
* which is basically never (except for some specific raid boxes that allow expansion of logical drives at runtime).
*/
if (deviceExtension->StorageDeviceDesc->RemovableMedia){
needToReinitialize = TRUE;
}
else {
if (ulSectorSize != deviceExtension->ulSectorSize){
ASSERT(deviceExtension->ulSectorSize == 0);
needToReinitialize = TRUE;
}
else if (ulNumSectors != deviceExtension->ulNumSectors){
if (deviceExtension->ulNumSectors != 0){
ASSERT(!"coverage -- logical disk extended");
}
ASSERT(ulNumSectors > deviceExtension->ulNumSectors);
needToReinitialize = TRUE;
}
else {
needToReinitialize = FALSE;
}
}
if (needToReinitialize){
BOOLEAN needToFree;
/*
* Use an event (which does not raise irql like a spinlock)
* for synchronizing accesses to paged pool.
*/
AcquirePassiveLevelLock(deviceExtension);
KeAcquireSpinLock(&deviceExtension->SpinLock, &oldIrql);
needToFree = deviceExtension->CRCMdlLists.mdlItemsAllocated;
deviceExtension->CRCMdlLists.mdlItemsAllocated = FALSE;
KeReleaseSpinLock(&deviceExtension->SpinLock, oldIrql);
if (needToFree){
FreeAllPages(deviceExtension);
FreePool(deviceExtension, deviceExtension->CRCMdlLists.pMdlItems, NonPagedPool);
deviceExtension->CRCMdlLists.pMdlItems = NULL;
}
if (ulSectorSize == 0){
ulSectorSize = 512;
}
else {
//
// clear all but the highest set bit.
//
while (ulSectorSize & (ulSectorSize - 1)){
ulSectorSize &= (ulSectorSize - 1);
}
}
deviceExtension->ulSectorSize = ulSectorSize;
deviceExtension->ulNumSectors = ulNumSectors;
if ((deviceExtension->ulSectorSize > 0) && (deviceExtension->ulNumSectors > 0)){
NTSTATUS status;
status = InitiateCRCTable(deviceExtension);
deviceExtension->CRCMdlLists.mdlItemsAllocated = NT_SUCCESS(status) ? TRUE : FALSE;
deviceExtension->DbgNumReallocations++;
KeQueryTickCount(&deviceExtension->DbgLastReallocTime);
}
ReleasePassiveLevelLock(deviceExtension);
}
/*
* Complete the irp
* Note: Always mark the irp pending (not only if irp->PendingReturned is true),
* because even if the lower stack completed the irp on the same thread,
* we are causing it to complete on a different thread at this layer due to the workItem.
*/
IoMarkIrpPending(readCapacityIrp);
IoCompleteRequest(readCapacityIrp, IO_NO_INCREMENT);
}
/*++
Routine Description:
Dispatch for SCSI
Arguments:
DeviceObject - Supplies the device object.
Irp - Supplies the I/O request packet.
Return Value:
NTSTATUS
--*/
NTSTATUS
CrcScsi(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PSCSI_REQUEST_BLOCK pSRB;
PCDB pCDB;
pSRB = irpStack->Parameters.Scsi.Srb;
pCDB = (PCDB)pSRB->Cdb;
if ( pSRB->Function == SRB_FUNCTION_EXECUTE_SCSI &&
pCDB->CDB10.OperationCode == SCSIOP_READ )
{
status = CrcScsiRead( DeviceObject, Irp );
}
else if ( pSRB->Function == SRB_FUNCTION_EXECUTE_SCSI &&
pCDB->CDB10.OperationCode == SCSIOP_WRITE )
{
status = CrcScsiWrite( DeviceObject, Irp );
}
else /*if ( (deviceExtension->ulSectorSize == 0 ||
deviceExtension->ulNumSectors ==0 ) &&
pSRB->Function == SRB_FUNCTION_EXECUTE_SCSI &&
pCDB->CDB10.OperationCode == SCSIOP_READ_CAPACITY )*/
if( pSRB->Function == SRB_FUNCTION_EXECUTE_SCSI &&
pCDB->CDB10.OperationCode == SCSIOP_READ_CAPACITY )
{
//
// let's setup the disk property info. (only do this once.)
//
status = DataVerFilter_ForwardIrpAsyn( DeviceObject,
Irp,
CrcScsiReadCapacityCompletion,
deviceExtension );
}
else
{
//
// pass them done directly
//
status = DataVerFilter_ForwardIrpAsyn( DeviceObject, Irp, NULL, NULL );
}
return status;
}
/*++
Routine Description:
Free all the allocated resources, etc.
Arguments:
DriverObject - pointer to a driver object.
Return Value:
VOID.
--*/
VOID
DataVerFilter_Unload(
IN PDRIVER_OBJECT DriverObject
)
{
NTSTATUS status = STATUS_SUCCESS;
ULONG ulIndex = 0;
USHORT usDepth = 0;
PAGED_CODE();
#if DBG_WMI_TRACING
//
// WPP_CLEANUP can only occur after all KdPrintEx routines
// since it deallocates any resources used by that and also
// deregisters from WMI...
//
WPP_CLEANUP(DriverObject);
#endif
}
/*
* DoCriticalRecovery
*
* Reinitialize all checksums after a critical failure where we could not allocate a workItem entry
* to defer-write a checksum.
*
* Must be called at PASSIVE IRQL with SyncEvent HELD but SPINLOCK NOT HELD.
*/
VOID DoCriticalRecovery(PDEVICE_EXTENSION DeviceExtension)
{
NTSTATUS status;
ASSERT(DeviceExtension->NeedCriticalRecovery);
ASSERT(DeviceExtension->State != DEVSTATE_REMOVED);
DBGWARN(("> Critical recovery (devObj=%ph) ...", DeviceExtension->DeviceObject));
if (DeviceExtension->CRCMdlLists.mdlItemsAllocated){
FreeAllPages(DeviceExtension);
}
/*
* We are recovering because we could not record a checksum.
* This means previous checksums may be invalid. So drop them all.
*/
while (TRUE){
PDEFERRED_CHECKSUM_ENTRY defCheckEntry;
KIRQL oldIrql;
KeAcquireSpinLock(&DeviceExtension->SpinLock, &oldIrql);
if (IsListEmpty(&DeviceExtension->DeferredCheckSumList)){
defCheckEntry = NULL;
}
else {
PLIST_ENTRY listEntry = RemoveHeadList(&DeviceExtension->DeferredCheckSumList);
InitializeListHead(listEntry);
defCheckEntry = CONTAINING_RECORD(listEntry, DEFERRED_CHECKSUM_ENTRY, ListEntry);
}
KeReleaseSpinLock(&DeviceExtension->SpinLock, oldIrql);
if (defCheckEntry){
FreeDeferredCheckSumEntry(DeviceExtension, defCheckEntry);
}
else {
break;
}
}
DeviceExtension->DbgNumCriticalRecoveries++;
KeQueryTickCount(&DeviceExtension->DbgLastRecoveryTime);
DeviceExtension->NeedCriticalRecovery = FALSE;
DBGWARN(("< Critical recovery complete (devObj=%ph)", DeviceExtension->DeviceObject));
}