2020-09-30 17:17:25 +02:00

2292 lines
76 KiB
C++

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
disk.c
Abstract:
MU disk and partition routines. Top level interface of MU driver
Environment:
kernel mode
Revision History:
06/12/2000 - started re-write - georgioc
03/08/20001 - arbitray media block size support, see comments - mitchd
--*/
/*++
03/08/2001 - MEDIA BLOCK SIZES. Flash ROM media in used in the Memory
Units has two relevant lengths: a media page size and a
media block size. The media page size is the smallest
unit of storage that can be read from or written to. The
catch is that write doesn't really write it just ORs.
The media block size is the smallest block unit of storage
that can be erased.
A typical media page size is 512 bytes, and a typical media
block size is 8k for an 8 MB media and larger for larger media.
On all products for Windows, the FW in the media drives report the
SCSI Logical Block Size as their page size. The devices manage
the media block internally using on-board RAM to buffer writes
as needed. In addition to supporting much smaller logical
block sizes, the extra RAM helps them improve through-put. An
erase operation takes significant time. Without on-board RAM
a write command stalls while blocks are being erased. With enough
on-board the write is buffered during the erase and transfer proceeds
at the USB bandwidth limit. Still the media write (and read) is
comparable and often slower than the USB bus. Again the RAM helps
by allowing multiple blocks to be read to and written from simultaneously.
With a well implemented media drive, the USB bus is definately the
bottle-neck.
On Xbox, COGS were a signficant issue since the media drive and the
media are both sold as a single integrated unit. Thus there is no
tolerance for added cost to the drive. RAM was reduced to a minimum
and the Xbox side Memory Unit driver (this code) manages the media block
size. The FW is designed to erase the relevant blocks with any write.
So all writes must be to full media blocks, or there adjacent media pages
will be erased. A simple solution is to increase the sector size reported
to the filesystem to the media block size. Unfortunately, the filesystem
only supports sector sizes up to one x86 page of 4 kilobytes, while the
media block size are typically larger than this. So writes to less than
a full block are supported by reading the remaining portion of the block
and writing the whole block out.
The initial implementation was designed around the 8 MB media with 8 kbyte
media block sizes. The simplifying assumption that the block size is
twice the 4k sector size of the filesystem was used extensively. However,
it has become clear that media prices are dropping and larger media (With
larger block sizes) are becoming available.
This revision addresses this issue by generalizing the scheme to support any
media block size that is a positive integral multiple of the 4k sector size.
Since the driver is not allowed to dynamically allocate memory according to
the MU size, for predictability reasons, a maximum buffer size and thus maximum
media block size must be set at compile time of XSS. That limit has been
chosen as of this date to be 16 kbyte. The code has been written so that this
limit is simply a #define and can be changed at any time. However, if units
with larger block sizes become available, the games compiled with the
smaller maximum block size will refuse to mount them. Unfortunately, it is
much more difficult to completely hide the units from these games.
All writes are broken up into up to three of four phases. An initial unaligned
portion, a whole portion that is completely aligned, and a final portion that
is unaligned on the end. The fourth type I have dubbed a "middle partial portion"
which is a small write that begins and ends within a single media block and is
not aligned to either end of the block.
Partial Middle Writes have their own state machine that reads the portion before,
then the portion after and then spits out the whole block.
Other writes, send the whole poriton first (if there is one), and then if there
was an initial or final portion enter a state machine that handles the initial
and final portions in sequence.
In order to minimize buffer memory there is one global buffer for partial writes.
Requests to either state machine are queued in a single global queue. Note that
since requests for a given device are also queued, there can never be more than
one request per device in the partial write queue. Both state machines, whenever
they complete a partial write, pull the next request off the partial write queue
and start in the proper state machine.
See the comments at the top mrb.cpp for discussion of changes to the mrb state
machine (that implements the MU Bulk-Only protocol) to better accomodate the
needs of the more generalized buffering.
--*/
//*******************************************************************************
// Includes
//*******************************************************************************
#include "mu.h"
//*******************************************************************************
// local function declarations
//*******************************************************************************
DEFINE_USB_DEBUG_FUNCTIONS("MU");
VOID
MU_fDiskDeviceControl(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN PIRP Irp
);
NTSTATUS
FASTCALL
MU_fDiskReadDriveCapacity(
IN PMU_DEVICE_EXTENSION DeviceExtension,
PIRP Irp
);
VOID
MU_DiskReadCapacityCompletion(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
);
VOID
FASTCALL
MU_fDiskVerify(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN PIRP Irp
);
VOID
MU_DiskReadComplete(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
);
VOID
MU_DiskIoComplete(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
);
VOID
FASTCALL
MU_fDiskReadWrite(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN PIRP Irp
);
VOID
MU_DiskStartPartialWrite(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
);
VOID
MU_DiskWriteBuildPartialRequest(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
);
VOID
MU_DiskWriteBuildMiddlePartialRequest(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
);
VOID
FASTCALL
MU_fMarkWriteBufferCorrupt(
IN PMU_REQUEST_BLOCK Mrb
);
VOID
MU_DiskWriteStartNextPartialRequest();
//*******************************************************************************
// Declarations for diagnostic IOCTL's
//*******************************************************************************
#ifdef MU_DIAGNOSTIC_IOCTLS
NTSTATUS
FASTCALL
MU_fVscCommand(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN PIRP Irp,
IN BYTE bRequest,
IN ULONG ulOutputSize
);
VOID
MU_VscComplete(
IN PURB Urb,
IN PVOID Context
);
#endif
//*******************************************************************************
// debug write log stuff
//*******************************************************************************
#if DBG
#define WRITE_LOG_LENGTH 2000
ULONG WriteLog[WRITE_LOG_LENGTH];
ULONG WriteBlock[WRITE_LOG_LENGTH];
LONG WriteCount = 0;
ULONG MU_GetWriteSize(LONG count) { return WriteLog[count]; }
ULONG MU_GetWriteBlock(LONG count) { return WriteBlock[count]; }
LONG MU_GetWriteCount() { return WriteCount; }
#endif
//*******************************************************************************
// Implementation
//*******************************************************************************
NTSTATUS
MU_InternalIo (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
Entry Point for:
IRP_MJ_READ
IRP_MJ_WRITE
IRP_MJ_DEVICE_CONTROL
IRP_MJ_INTERNAL_DEVICE
In other words, excepting IRP_MJ_CREATE and IRP_MJ_CLOSE, all
supported IRPs go through here.
Since all USB requests to a single device are serialized by the bulk-only
protocol anyway, we just serialize all the IRPs coming in with a device queue.
--*/
{
PMU_DEVICE_EXTENSION deviceExtension;
KIRQL oldIrql;
NTSTATUS ntStatus = STATUS_PENDING;
USB_DBG_ENTRY_PRINT(("MU_InternalIo(DeviceObject=0x%0.8x,Irp=0x%0.8x)", DeviceObject, Irp));
deviceExtension = (PMU_DEVICE_EXTENSION) DeviceObject->DeviceExtension;
oldIrql = KeRaiseIrqlToDpcLevel();
//
// Other parts of the system (XAPI, or intelligent private caller of MU_CreateDeviceObject
// and MU_CloseDeviceObject) are supposed to ensure that we do not have outstanding I/O
// before calling MU_CloseDeviceObject, and that no new I/O will be initiated after calling
// MU_CloseDeviceObject. We will just ASSERT that this is so.
//
ASSERT(!TEST_FLAG(deviceExtension->DeviceFlags, DF_PENDING_CLOSE));
//
// Ensure that device is connected.
//
if(TEST_FLAG(deviceExtension->DeviceFlags, DF_REMOVED|DF_PENDING_REMOVE))
{
ntStatus = STATUS_DEVICE_NOT_CONNECTED;
Irp->IoStatus.Status = ntStatus;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
USB_DBG_WARN_PRINT(("An I/O Request has been sent to an MU which is removed"));
} else
{
//
// Mark the Irp Pending and add it to the device queue.
//
IoMarkIrpPending(Irp);
IoStartPacket(DeviceObject,
Irp,
NULL);
}
KeLowerIrql(oldIrql);
USB_DBG_EXIT_PRINT(("MU_InternalIo returning 0x%0.8x", ntStatus));
return ntStatus;
}
VOID
MU_StartIo (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
Typical StartIo routine. Handles one request at a time
and dispatches it.
--*/
{
PMU_DEVICE_EXTENSION deviceExtension;
PIO_STACK_LOCATION irpStack;
USB_DBG_ENTRY_PRINT(("MU_StartIo(DeviceObject=0x%0.8x,Irp=0x%0.8x)", DeviceObject, Irp));
deviceExtension = (PMU_DEVICE_EXTENSION) DeviceObject->DeviceExtension;
irpStack = IoGetCurrentIrpStackLocation(Irp);
USB_DBG_ENTRY_PRINT(("MU_StartIo(0x%0.8x(0x%0.8x), 0x%0.8x)", DeviceObject, deviceExtension, Irp));
//
// Other parts of the system (XAPI, or intelligent private caller of MU_CreateDeviceObject
// and MU_CloseDeviceObject) are supposed to ensure that we do not have outstanding I/O
// before calling MU_CloseDeviceObject, and that no new I/O will be initiated after calling
// MU_CloseDeviceObject. We will just ASSERT that this is so.
//
ASSERT(!TEST_FLAG(deviceExtension->DeviceFlags, DF_PENDING_CLOSE));
//
// If the console is preparing to reset or shutdown, there's no reason to
// continue processing this request.
//
if (HalIsResetOrShutdownPending())
{
Irp->IoStatus.Status = STATUS_REQUEST_ABORTED;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
IoStartNextPacket(DeviceObject);
return;
}
//
// Store our device object in the
// our stack so we can get back at it.
// (This is used by the partial write queue
// to relate an Irp back to its device object.)
//
irpStack->DeviceObject = DeviceObject;
//
// Save the IRP we are working on
// so it doesn't need to be passed everywhere.
//
deviceExtension->PendingIrp = Irp;
//
// For debug builds only set a watchdog timer
// on the IRP.
//
MU_DEBUG_SET_WATCHDOG(deviceExtension);
//
// Handle\Dispatch the request
//
switch (irpStack->MajorFunction)
{
case IRP_MJ_DEVICE_CONTROL:
MU_fDiskDeviceControl(deviceExtension,Irp);
break;
case IRP_MJ_WRITE:
IoMarkIrpMustComplete(Irp);
// FALL THROUGH
case IRP_MJ_READ:
MU_fDiskReadWrite(deviceExtension,Irp);
break;
default:
ASSERT("MU called with unsupported I/O Request.");
}
USB_DBG_EXIT_PRINT(("MU_StartIo returning"));
return;
}
VOID
MU_fDiskDeviceControl(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN PIRP Irp
)
/*++
Routine Description:
This routine is called by MU_StartIo to handle
IRP_MJ_DEVICE_CONTROL sent to an MU.
The following IOCTLs are supported, others spew ERROR strings:
IOCTL_DISK_GET_DRIVE_GEOMETRY
IOCTL_DISK_GET_PARTITION_INFO
IOCTL_DISK_VERIFY
Whenever possible these are satisified with cached information.
Otherwise, they are dispatched to a routine which builds and
submits an Mrb to the MRB state machine.
Arguments:
DeviceObject - Specifies the device object that the I/O request is for.
Irp - Specifies the packet that describes the I/O request.
Return Value:
None
--*/
{
NTSTATUS status;
PDISK_GEOMETRY diskGeometry;
PPARTITION_INFORMATION partitionInformation;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
//
// Most of the time, the request
// will just be pending, so set it here,
// and change it if necessary when processing
// specific cases.
//
status = STATUS_PENDING;
switch (irpStack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_DISK_GET_DRIVE_GEOMETRY:
diskGeometry = (PDISK_GEOMETRY)Irp->UserBuffer;
//debug parameter check
ASSERT(sizeof(DISK_GEOMETRY) <= irpStack->Parameters.DeviceIoControl.OutputBufferLength);
//
// If the Partition Length is zero, we need
// to fetch the capacity from the device.
//
if (DeviceExtension->PartitionLength.QuadPart == 0)
{
//
// MU_fDiskReadDriveCapacity doesn't
// return a status, it always, pends
// the IRP.
//
USB_DBG_TRACE_PRINT(("GetDriveGeometry, issuing read capacity"));
status = MU_fDiskReadDriveCapacity(DeviceExtension,Irp);
} else
//
// We already know the geometry, just copy it.
//
{
RtlCopyMemory(diskGeometry, &DeviceExtension->DiskGeometry,
sizeof(DISK_GEOMETRY));
status = STATUS_SUCCESS;
Irp->IoStatus.Information = sizeof(DISK_GEOMETRY);
}
break;
case IOCTL_DISK_GET_PARTITION_INFO:
//debug parameter check
ASSERT(sizeof(PARTITION_INFORMATION) <= irpStack->Parameters.DeviceIoControl.OutputBufferLength);
//
// Fill in the output buffer and return.
//
partitionInformation = (PPARTITION_INFORMATION)Irp->UserBuffer;
RtlZeroMemory(partitionInformation, sizeof(PARTITION_INFORMATION));
//
// If the Partition Length is zero, we need
// to fetch the capacity from the device.
//
if (DeviceExtension->PartitionLength.QuadPart == 0) {
//
// MU_fDiskReadDriveCapacity doesn't
// return a status, it always pends
// the IRP.
//
USB_DBG_TRACE_PRINT(("GetPartitionInfo, issuing read capacity"));
status = MU_fDiskReadDriveCapacity(DeviceExtension,Irp);
} else
//
// We already know the partition length, just copy it.
//
{
partitionInformation->PartitionLength = DeviceExtension->PartitionLength;
partitionInformation->RecognizedPartition = TRUE;
status = STATUS_SUCCESS;
Irp->IoStatus.Information = sizeof(PARTITION_INFORMATION);
}
break;
case IOCTL_DISK_VERIFY:
//
// MU_fDiskVerify doesn't return status, it always
// pends the IRP.
//
MU_fDiskVerify(DeviceExtension, Irp);
break;
//
// The following requests are for running
// diagnostics on the memory unit and require
// a special build.
//
#ifdef MU_DIAGNOSTIC_IOCTLS
case MU_IOCTL_GET_BAD_BLOCK_TABLE:
ASSERT(Irp->UserBuffer && "MU_IOCTL_GET_BAD_BLOCK_TABLE");
ASSERT((MU_VSC_BAD_BLOCK_TABLE_SIZE <= irpStack->Parameters.DeviceIoControl.OutputBufferLength) &&
"MU_IOCTL_GET_BAD_BLOCK_TABLE");
//
// MU_VSC_GET_BAD_BLOCK_TABLE, must always be proceeded
// by a read capacity.
//
status = MU_fDiskReadDriveCapacity(DeviceExtension,Irp);
break;
case MU_IOCTL_MEMORY_TEST:
ASSERT(Irp->UserBuffer && "MU_IOCTL_MEMORY_TEST");
ASSERT((MU_VSC_BAD_BLOCK_COUNT_SIZE <= irpStack->Parameters.DeviceIoControl.OutputBufferLength) &&
MU_IOCTL_MEMORY_TEST);
status = MU_fVscCommand(
DeviceExtension,
Irp,
MU_VSC_MEMORY_TEST,
MU_VSC_BAD_BLOCK_COUNT_SIZE
);
break;
#endif
default:
USB_DBG_ERROR_PRINT((
"MuDiskDeviceControl: disk device doesn't handle IOCTL %08x\n",
irpStack->Parameters.DeviceIoControl.IoControlCode
));
Irp->IoStatus.Information = 0;
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
//
// If the status is not pending, complete
// the current IRP and return an error
//
if(status != STATUS_PENDING)
{
Irp->IoStatus.Status = status;
MU_COMPLETE_REQUEST(DeviceExtension, Irp, IO_NO_INCREMENT);
IoStartNextPacket(DeviceExtension->DeviceObject);
}
return;
}
NTSTATUS
FASTCALL
MU_fDiskReadDriveCapacity(
IN PMU_DEVICE_EXTENSION DeviceExtension,
PIRP Irp
)
/*++
Routine Description:
Builds an MRB for Read Capacity and submits it
to the MRB state machine
--*/
{
PREAD_CAPACITY_DATA readCapacityBuffer;
PMU_REQUEST_BLOCK mrb;
//
// Allocate read capacity buffer
//
readCapacityBuffer = (PREAD_CAPACITY_DATA) RTL_ALLOCATE_HEAP(sizeof(READ_CAPACITY_DATA));
if (!readCapacityBuffer)
{
DeviceExtension->PendingIrp->IoStatus.Information = 0;
USB_DBG_WARN_PRINT(("Insufficient Memory to allocate READ_CAPACITY_DATA buffer"));
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Fill out the mrb
//
mrb = &DeviceExtension->Mrb;
mrb->DataBuffer = (PUCHAR) readCapacityBuffer;
mrb->TransferLength = sizeof(READ_CAPACITY_DATA);
mrb->CompletionRoutine = MU_DiskReadCapacityCompletion;
mrb->TimeOutValue = MRB_READ_CAPACITY_TIMEOUT;
mrb->Retries = MRB_MAXIMUM_RETRIES;
mrb->Flags = MRB_FLAGS_DATA_IN;
//
// Fill out the CDB
//
RtlZeroMemory(&mrb->Cbw.Cdb, sizeof(CDB));
mrb->Cbw.Cdb.CDB10.OperationCode = SCSIOP_READ_CAPACITY;
//
// Submit the MRB
//
MU_fStartMrb(DeviceExtension);
return STATUS_PENDING;
} // end MuDiskReadDriveCapacity()
VOID
MU_DiskReadCapacityCompletion(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
)
/*++
Routine Description:
The MRB completion routine called when SCIOP_READ_CAPACITY
returns.
This was called either so we could complete an
IOCTL_DISK_GET_DRIVE_GEOMETRY request or to complete
IOCTL_DISK_GET_PARTITION_INFO.
So we calculate both and cache them, then figure
out which Irp is outstanding and complete it.
--*/
{
PDISK_GEOMETRY diskGeometry;
PPARTITION_INFORMATION partitionInformation;
PREAD_CAPACITY_DATA readCapacityBuffer = (PREAD_CAPACITY_DATA) DeviceExtension->Mrb.DataBuffer;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(DeviceExtension->PendingIrp);
//
// If the Mrb failed, then fail the Irp. The Mrb
//
if(NT_ERROR(Status))
{
DeviceExtension->PendingIrp->IoStatus.Information = 0;
DeviceExtension->PendingIrp->IoStatus.Status = Status;
USB_DBG_WARN_PRINT(("ReadCapacity Transfer Failed"));
goto MU_DiskReadCapacityCompletionCleanup;
}
ULONG bytesPerLogicalBlock = ReverseEndian(readCapacityBuffer->BytesPerLogicalBlock);
ULONG mediaBlockSize = bytesPerLogicalBlock *
ReverseEndian(readCapacityBuffer->LogicalBlocksPerMediaBlock);
ULONG logicalBlockCount = ReverseEndian(readCapacityBuffer->LogicalBlockAddress) + 1;
ULONGLONG totalCapacity = ((ULONGLONG)logicalBlockCount) * bytesPerLogicalBlock ;
// The LogicalBlocksPerMediaBlock was added after the original FW, so if it is zero
// use the default value.
if(0 == mediaBlockSize)
mediaBlockSize = DEFAULT_MEDIA_BLOCK_SIZE;
//
// Check the capacity data to ensure we can support the device.
// If any of our requirements are not met, we will fail the IRP.
// This effectively makes the MU unmountable.
// Requirements:
// 1) BytesPerLogicalBlock - Must be a power of 2 not greater than the EMULATED_SECTOR_SIZE(=4096).
// 2) MediaBlockSize - Must be an integer multiple of the EMULATED_SECTOR_SIZE, but not greater than
// the MAXIMUM_MEDIA_BLOCK_SIZE.
// 3) Total Capacity - must be not less than the mediaBlockSize and not greater than 2^32 = 4 GB.
// 4) logicalBlockCount - must be non-zero. This is an artifact of adding 1 to the LogicalBlockAddress
// really it is saying that the LogicalBlockAddress may not be the maximum
// value, which would exceed the capacity limit anyway.
if(
!IsPowerOf2(bytesPerLogicalBlock) ||
(bytesPerLogicalBlock > EMULATED_SECTOR_SIZE) ||
(mediaBlockSize > MAXIMUM_MEDIA_BLOCK_SIZE) ||
(mediaBlockSize%EMULATED_SECTOR_SIZE) ||
(0 == logicalBlockCount) ||
(totalCapacity < mediaBlockSize) ||
(totalCapacity > (((ULONGLONG)1)<<32))
)
{
DeviceExtension->PendingIrp->IoStatus.Information = 0;
DeviceExtension->PendingIrp->IoStatus.Status = STATUS_UNRECOGNIZED_VOLUME;
USB_DBG_WARN_PRINT(("The logical block configuration of an MU is outside supported parameter ranges."));
goto MU_DiskReadCapacityCompletionCleanup;
}
//
// Store media capacity and media block size
//
DeviceExtension->PartitionLength.QuadPart = totalCapacity;
DeviceExtension->MediaBlockSize = mediaBlockSize;
//
// Calculate logical block to byte shift.
//
DeviceExtension->LogicalBlockShift = WhichBit(bytesPerLogicalBlock);
//
// Fill out the geometry information (note that BytesPerSector
// is our fixed EMULATED_SECTOR_SIZE.)
USB_DBG_TRACE_PRINT(("Geometry Buffer @ 0x%0.8x", &DeviceExtension->DiskGeometry));
DeviceExtension->DiskGeometry.MediaType = FixedMedia;
DeviceExtension->DiskGeometry.Cylinders.QuadPart = totalCapacity/mediaBlockSize;
DeviceExtension->DiskGeometry.TracksPerCylinder = 1;
DeviceExtension->DiskGeometry.SectorsPerTrack = mediaBlockSize / EMULATED_SECTOR_SIZE;
DeviceExtension->DiskGeometry.BytesPerSector = EMULATED_SECTOR_SIZE;
//
// now fill the appropriate Irp buffer results
//
switch (irpStack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_DISK_GET_DRIVE_GEOMETRY:
diskGeometry = (PDISK_GEOMETRY)DeviceExtension->PendingIrp->UserBuffer;
RtlCopyMemory(diskGeometry, &DeviceExtension->DiskGeometry, sizeof(DISK_GEOMETRY));
DeviceExtension->PendingIrp->IoStatus.Information = sizeof(DISK_GEOMETRY);
DeviceExtension->PendingIrp->IoStatus.Status = STATUS_SUCCESS;
break;
case IOCTL_DISK_GET_PARTITION_INFO:
//
// Fill in the output buffer and return.
//
partitionInformation = (PPARTITION_INFORMATION)DeviceExtension->PendingIrp->UserBuffer;
RtlZeroMemory(partitionInformation, sizeof(PARTITION_INFORMATION));
partitionInformation->PartitionLength = DeviceExtension->PartitionLength;
partitionInformation->RecognizedPartition = TRUE;
DeviceExtension->PendingIrp->IoStatus.Information = sizeof(PARTITION_INFORMATION);
DeviceExtension->PendingIrp->IoStatus.Status = STATUS_SUCCESS;
break;
#ifdef MU_DIAGNOSTIC_IOCTLS
//
// MU_IOCTL_GET_BAD_BLOCK_TABLE requires a READ_CAPACITY to proceed it.
// This could be why we are here.
//
case MU_IOCTL_GET_BAD_BLOCK_TABLE:
{
NTSTATUS status = MU_fVscCommand(
DeviceExtension,
DeviceExtension->PendingIrp,
MU_VSC_GET_BAD_BLOCK_TABLE,
MU_VSC_BAD_BLOCK_TABLE_SIZE
);
if(STATUS_PENDING == status)
{
RTL_FREE_HEAP(readCapacityBuffer);
return;
}
}
break;
#endif
}
MU_DiskReadCapacityCompletionCleanup: //error paths rejoin here for cleanup
//
// Deallocate read capacity buffer.
//
RTL_FREE_HEAP(readCapacityBuffer);
//
// Complete the Irp
//
MU_COMPLETE_REQUEST(DeviceExtension, DeviceExtension->PendingIrp, IO_NO_INCREMENT);
//
// Start processing the next Irp.
//
IoStartNextPacket(DeviceExtension->DeviceObject);
return;
}
VOID
FASTCALL
MU_fDiskVerify(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN PIRP Irp
)
/*++
Routine Description:
This routine handles the IOCTL_DISK_VERIFY request.
Arguments:
DeviceObject - Specifies the device object that the I/O request is for.
Irp - Specifies the packet that describes the I/O request.
Return Value:
Status of operation.
--*/
{
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PVERIFY_INFORMATION verifyInfo = (PVERIFY_INFORMATION)irpStack->Parameters.DeviceIoControl.InputBuffer;
PMU_REQUEST_BLOCK mrb = &DeviceExtension->Mrb;
ULONG logicalBlockOffset;
USHORT logicalBlockCount;
//
// Verify sectors
//
mrb->Cbw.Cdb.CDB10.OperationCode = SCSIOP_VERIFY;
//
// Assert that the parameter to verify are reasonable.
//
ASSERT(sizeof(VERIFY_INFORMATION) <= irpStack->Parameters.DeviceIoControl.InputBufferLength);
ASSERT(verifyInfo);
ASSERT(0==verifyInfo->StartingOffset.QuadPart%EMULATED_SECTOR_SIZE);
ASSERT(verifyInfo->Length);
ASSERT(0==verifyInfo->Length%EMULATED_SECTOR_SIZE);
//
// Convert byte offset to logical block offset.
//
logicalBlockOffset = (ULONG)(verifyInfo->StartingOffset.QuadPart >> DeviceExtension->LogicalBlockShift);
//
// Convert ULONG byte count to USHORT sector count.
//
logicalBlockCount = (USHORT)(verifyInfo->Length >> DeviceExtension->LogicalBlockShift);
//
// Fill out the mrb
//
mrb->DataBuffer = NULL;
mrb->TransferLength = 0;
mrb->CompletionRoutine = MU_DiskIoComplete;
mrb->TimeOutValue = MRB_CALC_VERIFY_TIMEOUT(verifyInfo->Length);
mrb->Retries = MRB_MAXIMUM_RETRIES;
mrb->Flags = MRB_FLAGS_NO_DATA_TRANSFER;
//
// Fill out the CDB
//
RtlZeroMemory(&mrb->Cbw.Cdb, sizeof(CDB));
mrb->Cbw.Cdb.CDB10.OperationCode = SCSIOP_VERIFY;
//
// Move little endian values into CDB in big endian format.
//
mrb->Cbw.Cdb.CDB10.LogicalBlock = ReverseEndian(logicalBlockOffset);
mrb->Cbw.Cdb.CDB10.TransferBlocks = ReverseEndian(logicalBlockCount);
//
// Submit the MRB
//
MU_fStartMrb(DeviceExtension);
return;
}
VOID
MU_DiskReadComplete(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
)
/*++
Routine Description:
Completion routine for read requests. Added so we can check for
the corruption pattern at the start of each EMULATED_SECTOR.
--*/
{
PUCHAR buffer = DeviceExtension->Mrb.DataBuffer;
PUCHAR bufferEnd = DeviceExtension->Mrb.DataBuffer + DeviceExtension->Mrb.TransferLength;
//Loop over the buffer checking the beginning of each emulated sector
for(; buffer < bufferEnd; buffer += EMULATED_SECTOR_SIZE)
{
ULONG patternOffset = 0;
//
// Loop as long as the pattern matches
//
while( ((PULONG)buffer)[patternOffset] == MU_CORRUPT_SECTOR_PATTERN[patternOffset])
{
//If we have reach the end of the pattern, then there is a match
//we need to complete with an error
if(++patternOffset == MU_CORRUPT_SECTOR_PATTERN_ULONG_COUNT)
{
ULONG oldProtect = MmQueryAddressProtect(buffer);
ULONG newProtect = ((ULONG)-1);
USB_DBG_WARN_PRINT(("MU_DiskReadComplete Corrupt Sector Found"));
//Only change the status, if it was a success code.
if(NT_SUCCESS(Status))
{
Status = STATUS_DATA_ERROR;
}
//erase the pattern from the return buffer (if the user doesn't
//check the error and reads it, then they may write and this
//will make us return more problems. Just nip it in the bud
//here. Fill with "FAIL" a.k.a 'LIAF'.
//
// BTW, even though we read into it via DMA, if it is the filesystem
// cache it may be marked PAGE_READONLY. If so we need to
// switch that off first, then switch it back on.
if(PAGE_READONLY == ((PAGE_READONLY|PAGE_READWRITE)&oldProtect))
{
newProtect = oldProtect; //Use the old protection as the base.
newProtect &= ~PAGE_READONLY; //clear readonly
newProtect |= PAGE_READWRITE; //set readwrite
MmSetAddressProtect(buffer, MU_CORRUPT_SECTOR_PATTERN_SIZE, newProtect);
}
while(patternOffset--)
{
((PULONG)buffer)[patternOffset] = 'LIAF';
}
if(((ULONG)-1) != newProtect)
{
MmSetAddressProtect(buffer, MU_CORRUPT_SECTOR_PATTERN_SIZE, oldProtect);
}
break;
}
}
}
MU_DiskIoComplete(DeviceExtension, Status);
return;
}
VOID
MU_DiskIoComplete(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
)
/*++
Routine Description:
General compeltion routine for MRB requests that require no post processing.
The status is checked and the Irp is completed accordingly. On success,
Information is set to the length of the transfer, on failure it is set to
zero.
This is used as the completion for MU_fDiskVerify and MU_fDiskRead. Unfortunately,
it cannot be worked into the write state machine.
--*/
{
DeviceExtension->PendingIrp->IoStatus.Status = Status;
if(NT_ERROR(Status))
{
DeviceExtension->PendingIrp->IoStatus.Information = 0;
USB_DBG_WARN_PRINT(("MU_DiskIoComplete Transfer Failed"));
} else
{
DeviceExtension->PendingIrp->IoStatus.Information = DeviceExtension->Mrb.TransferLength;
}
//
// Complete the request
//
MU_COMPLETE_REQUEST(DeviceExtension, DeviceExtension->PendingIrp, IO_NO_INCREMENT);
//
// Start processing the next Irp.
//
IoStartNextPacket(DeviceExtension->DeviceObject);
}
VOID
FASTCALL
MU_fDiskReadWrite(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN PIRP Irp
)
/*++
Routine Description:
Does the basic work of setting up a Read or Write to determine if
a the operation may be performed, to calculate the logical block
offset and count. On read submits the Mrb and sets MU_DiskIoComplete
as the completion routine. On write, it needs to worry about
aligment issues and to submit the request to the MrbState machine.
--*/
{
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PUCHAR virtualAddress = NULL;
ULONG byteOffset = irpStack->Parameters.Read.ByteOffset.LowPart;
ASSERT(0==irpStack->Parameters.Read.ByteOffset.HighPart);
ULONG transferLength = irpStack->Parameters.Read.Length;
//
// Check the byteOffset and transferLength against the disk
// partition length, also double check that they are properly
// sector aligned.
//
if(
(0!=byteOffset%EMULATED_SECTOR_SIZE) ||
(0!=transferLength%EMULATED_SECTOR_SIZE) ||
(irpStack->Parameters.Read.ByteOffset.QuadPart + transferLength) >
DeviceExtension->PartitionLength.QuadPart)
{
USB_DBG_WARN_PRINT(("MU_fDiskReadWrite: failing transfer with illegal byte offset or length"));
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
}
//
// Check for Zero length transfer, just succeed these
//
else if (transferLength == 0)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
}
//
// Get the usable virtual address of the buffer to send,
// or receive data.
//
else
{
//
// SL_FSCACHE_REQUEST indicate that the transfer is to\from cached memory mapped
// by the buffer in the irpStack.
//
if(TEST_FLAG(irpStack->Flags,SL_FSCACHE_REQUEST))
{
virtualAddress = (PUCHAR) irpStack->Parameters.Read.CacheBuffer;
ASSERT(virtualAddress != NULL);
}
else
//
// SL_FSCACHE_REQUEST is not set. The transfer is to\from the UserBuffer.
//
{
virtualAddress = (PUCHAR) Irp->UserBuffer;
ASSERT(virtualAddress != NULL);
//
// Add the buffer offset to the address we were passed.
//
virtualAddress += irpStack->Parameters.Read.BufferOffset;
}
//
// For convience we reuse the BufferOffset to store
// the computed virtual address for later.
//
irpStack->Parameters.Read.BufferOffset = (ULONG) virtualAddress;
}
//
// If for some reason no transfer is required
// complete the Irp and start the next one.
//
if(NULL == virtualAddress)
{
Irp->IoStatus.Information = 0;
MU_COMPLETE_REQUEST(DeviceExtension, Irp, IO_NO_INCREMENT);
IoStartNextPacket(DeviceExtension->DeviceObject);
return;
}
//
// Zero out the Cdb, we will start filling it out.
//
RtlZeroMemory(&DeviceExtension->Mrb.Cbw.Cdb, sizeof(CDB));
//
// If this is a write then make then check for alignment,
// set flags and make appropriate adjustment.
//
if(IRP_MJ_WRITE == irpStack->MajorFunction)
{
//
// Clear the write state machine has flags
//
CLEAR_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_FLAGS);
//
// Adjust the starting offset alignment.
//
if(byteOffset%DeviceExtension->MediaBlockSize)
{
ULONG initialLength = DeviceExtension->MediaBlockSize - (byteOffset%DeviceExtension->MediaBlockSize);
DeviceExtension->InitialWriteByteCount = initialLength;
// Check for a Partial Middle Write (i.e. a write
// which starts in the middle of a media block and
// and ends before the end of the same media block).
if(initialLength > transferLength)
{
//Set the middle portion flag
SET_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_MIDDLE_PORTION);
//Fix up the FinalWriteByteCount the way the middle state machine likes it.
DeviceExtension->FinalWriteByteCount = initialLength - transferLength;
//Start the partial write state machine.
MU_DiskStartPartialWrite(DeviceExtension, STATUS_SUCCESS);
return;
}
byteOffset += initialLength;
virtualAddress += initialLength;
transferLength -= initialLength;
SET_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_INITIAL_PORTION);
USB_DBG_TRACE_PRINT(("Write has initial portion."));
}
//
// Adjust the ending alignment
//
if(transferLength%DeviceExtension->MediaBlockSize)
{
DeviceExtension->FinalWriteByteCount = transferLength%DeviceExtension->MediaBlockSize;
transferLength -= DeviceExtension->FinalWriteByteCount;
SET_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_FINAL_PORTION);
USB_DBG_TRACE_PRINT(("Write has final portion."));
}
//
// If there is no whole portion, then jump into partial
// transfer state machine.
//
if(0 == transferLength)
{
MU_DiskStartPartialWrite(DeviceExtension, STATUS_SUCCESS);
return;
}
USB_DBG_TRACE_PRINT(("Write has whole portion."));
//
// If there were alignment adjustments then set the completion
// routine to MU_DiskStartPartialWrite, otherwise
// it is a one step write and we can use MU_DiskIoComplete.
//
if(TEST_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_FLAGS))
{
DeviceExtension->Mrb.CompletionRoutine = MU_DiskStartPartialWrite;
} else
{
DeviceExtension->Mrb.CompletionRoutine = MU_DiskIoComplete;
}
//
// Setup the parts of the MRB that indicate a write request
//
DeviceExtension->Mrb.Flags = MRB_FLAGS_DATA_OUT;
DeviceExtension->Mrb.Cbw.Cdb.CDB10.OperationCode = SCSIOP_WRITE;
} else
//
// This is a read request, setup the parts of the MRB
// that indicate a read request
//
{
DeviceExtension->Mrb.CompletionRoutine = MU_DiskReadComplete;
DeviceExtension->Mrb.Flags = MRB_FLAGS_DATA_IN;
DeviceExtension->Mrb.Cbw.Cdb.CDB10.OperationCode = SCSIOP_READ;
}
//
// Setup the rest of the MRB
//
DeviceExtension->Mrb.DataBuffer = virtualAddress;
DeviceExtension->Mrb.TransferLength = transferLength;
DeviceExtension->Mrb.TimeOutValue = MRB_STANDARD_TIMEOUT;
DeviceExtension->Mrb.Retries = MRB_MAXIMUM_RETRIES;
//
// Set the block information in the CDB
//
DeviceExtension->Mrb.Cbw.Cdb.CDB10.LogicalBlock = ReverseEndian(byteOffset >> DeviceExtension->LogicalBlockShift);
DeviceExtension->Mrb.Cbw.Cdb.CDB10.TransferBlocks = ReverseEndian((USHORT)(transferLength >> DeviceExtension->LogicalBlockShift));
//
// Submit the MRB
//
MU_fStartMrb(DeviceExtension);
}
VOID
MU_DiskStartPartialWrite(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
)
/*++
Routine Description:
This routine is called to begin writes of partial media blocks. It can
be called directly by MU_fDiskReadWrite if there is no contiguous portion
of the write or it is called as the MRB completion routine for the
contiguous portion. Either way, it is called at DPC level.
Partial Write requests use a global write buffer. As such they must be
serialized system wide across all MUs. This routine either begins
the partial transfer, or it inserts the request into the queue.
--*/
{
PIRP irp = DeviceExtension->PendingIrp;
//
// Clear the write state machine flags
//
CLEAR_FLAG(DeviceExtension->DeviceFlags,MU_WRITE_STATE_BITS);
//
// Make sure that the previous stage succeeded, if not
// just fail the Irp here.
//
if(NT_ERROR(Status))
{
irp->IoStatus.Status = Status;
irp->IoStatus.Information = 0;
MU_COMPLETE_REQUEST(DeviceExtension, irp, IO_NO_INCREMENT);
IoStartNextPacket(DeviceExtension->DeviceObject);
return;
}
//
// We can setup the common section of the MRB here
//
PMU_REQUEST_BLOCK mrb = &DeviceExtension->Mrb;
RtlZeroMemory(&mrb->Cbw.Cdb, sizeof(CDB));
mrb->TimeOutValue = MRB_STANDARD_TIMEOUT;
mrb->Retries = MRB_MAXIMUM_RETRIES;
// Choose the correct state machine, and initialize to start.
if(TEST_FLAG(DeviceExtension->DeviceFlags,MU_WRITE_HAS_MIDDLE_PORTION))
{
mrb->CompletionRoutine = MU_DiskWriteBuildMiddlePartialRequest;
SET_FLAG(DeviceExtension->DeviceFlags,MU_WRITE_STATE_MIDDLE_START);
} else
{
mrb->CompletionRoutine = MU_DiskWriteBuildPartialRequest;
SET_FLAG(DeviceExtension->DeviceFlags,MU_WRITE_STATE_START);
}
//
// see if the list of pending partial requests is empty
//
if (IsListEmpty(&MU_DriverExtension.PartialRequestQueue))
{
//
// process this one immediately
//
InsertHeadList(&MU_DriverExtension.PartialRequestQueue,
&irp->Tail.Overlay.ListEntry);
USB_DBG_TRACE_PRINT(("StartPartialRequest: Queuing Irp at the head %x, flink %x, blink %x",
irp,
MU_DriverExtension.PartialRequestQueue.Flink,
MU_DriverExtension.PartialRequestQueue.Blink));
//
// Start the request, call through the mrb->CompletionRoutine
// pointer so as to start the proper state machine.
//
mrb->CompletionRoutine(DeviceExtension, STATUS_SUCCESS);
return;
} else {
ASSERT(MU_DriverExtension.PartialRequestQueue.Flink != (PLIST_ENTRY)&irp->Tail.Overlay.ListEntry);
//
// queue this request...
//
InsertTailList(&MU_DriverExtension.PartialRequestQueue, &irp->Tail.Overlay.ListEntry);
USB_DBG_TRACE_PRINT(("StartPartialRequest: Queuing Irp %x, f %x, b %x",
irp,
MU_DriverExtension.PartialRequestQueue.Flink,
MU_DriverExtension.PartialRequestQueue.Blink));
}
}
VOID
MU_DiskWriteBuildPartialRequest(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
)
/*++
Routine Description:
This routine is the heart of MU write state machine. It is its own completion routine
routine. It builds and submits MRBs. It works as follows:
1) Increment the write state to the next state needed based on the DF_WRITE_HAS_XXX
flags.
2) switch the new state, and build an appriopriate MRB.
3) submit the MRB.
When the last required stage completes:
4) Complete the Irp.
5) Call IoStartNextPacket for current device
5) Continue with the next pending partial write (for a different device)
Note:
There are fields of the MRB that are NOT setup here:
These are the same for every stage and MU_DiskStartPartialWrite before the partial portion of the request
is queued.
--*/
{
PIRP irp = DeviceExtension->PendingIrp;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(irp);
ULONG byteOffset;
ULONG writeState = DeviceExtension->DeviceFlags&MU_WRITE_STATE_BITS;
if(NT_SUCCESS(Status))
{
//
// Increment to the next state.
//
writeState += MU_WRITE_STATE_INCREMENT;
//
// If the current stage is an initial read, and an initial
// read is not required, skip to final read.
//
if(MU_WRITE_STATE_INITIAL_READ == writeState)
{
if(!TEST_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_INITIAL_PORTION))
{
writeState = MU_WRITE_STATE_FINAL_READ;
}
}
//
// If the current stage is a final read, and a final
// read is not required, skip to done.
//
if(MU_WRITE_STATE_FINAL_READ == writeState)
{
if(!TEST_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_FINAL_PORTION))
{
writeState = MU_WRITE_STATE_DONE;
}
}
} else
//
// On any read errors in the state machine
// we need to modify the buffer with a bad
// sector pattern pattern and proceed to
// the write stage anyway
//
{
//
if(
( (MU_WRITE_STATE_INITIAL_READ == writeState)
||(MU_WRITE_STATE_FINAL_READ == writeState) ) &&
(STATUS_DATA_ERROR == Status)
)
{
MU_fMarkWriteBufferCorrupt(&DeviceExtension->Mrb);
// Go on to the next phase
Status = STATUS_SUCCESS;
writeState += MU_WRITE_STATE_INCREMENT;
} else
//
// If the write failed during a write phase, or
// for a reason other than a STATUS_DATA_ERROR
// (failed CSW), then just keep the error go to
// the done state, which will complete the IRP
// with the error.
//
{
writeState = MU_WRITE_STATE_DONE;
}
}
//
// Write the writeState back
//
CLEAR_FLAG(DeviceExtension->DeviceFlags,MU_WRITE_STATE_BITS);
SET_FLAG(DeviceExtension->DeviceFlags,writeState);
//
// Start working on the mrb.
//
PMU_REQUEST_BLOCK mrb = &DeviceExtension->Mrb;
//
// switch on the stage we need to program
//
switch(writeState)
{
case MU_WRITE_STATE_INITIAL_READ:
USB_DBG_TRACE_PRINT(("Starting initial portion."));
//
// Read the portion of the first block prior to the start of the
// requested write.
//
mrb->DataBuffer = MU_DriverExtension.WriteBuffer;
mrb->TransferLength = DeviceExtension->MediaBlockSize - DeviceExtension->InitialWriteByteCount;
mrb->Flags = MRB_FLAGS_DATA_IN;
mrb->Cbw.Cdb.CDB10.OperationCode = SCSIOP_READ;
//
// The byteOffset is transfer length bytes behind the original byteOffset of the
// write request
//
byteOffset = irpStack->Parameters.Read.ByteOffset.LowPart-mrb->TransferLength;
break;
case MU_WRITE_STATE_INITIAL_WRITE:
//
// Write one media block size out. DataBuffer points to the user's
// data we are writing.
//
// Setup MRB_FLAGS_SPLIT_WRITE feature, and its parameters: UserStartOffset,
// UserEndOffset so that MRB state machine knows to get the remaining data
// from the WriteBuffer.
//
mrb->Flags = MRB_FLAGS_DATA_OUT | MRB_FLAGS_SPLIT_WRITE;
mrb->UserStartOffset = mrb->TransferLength; //the UserStartOffset is the length we just read.
mrb->UserEndOffset = DeviceExtension->MediaBlockSize; //The UserEndOffset is the end of the block
mrb->DataBuffer = (PUCHAR)irpStack->Parameters.Read.BufferOffset;
mrb->TransferLength = DeviceExtension->MediaBlockSize;
mrb->Cbw.Cdb.CDB10.OperationCode = SCSIOP_WRITE;
//
// The byteOffset is the UserStartOffset before the original write request byteOffset
//
byteOffset = irpStack->Parameters.Read.ByteOffset.LowPart-mrb->UserStartOffset;
break;
case MU_WRITE_STATE_FINAL_READ:
USB_DBG_TRACE_PRINT(("Starting final portion."));
//
// Read the the end of the block behind the final write byte count
//
mrb->DataBuffer = MU_DriverExtension.WriteBuffer;
mrb->TransferLength = DeviceExtension->MediaBlockSize - DeviceExtension->FinalWriteByteCount;
mrb->Flags = MRB_FLAGS_DATA_IN;
mrb->Cbw.Cdb.CDB10.OperationCode = SCSIOP_READ;
//
// The logicalBlockAddress should be the sector right after the length that
// we really want to write to.
//
//
// The byteOffset is the end of the original write request.
//
byteOffset = irpStack->Parameters.Read.ByteOffset.LowPart +
irpStack->Parameters.Read.Length;
break;
case MU_WRITE_STATE_FINAL_WRITE:
//
// Write one media block size out. DataBuffer points to the user's buffer.
//
//
// Setup MRB_FLAGS_SPLIT_WRITE feature, and its parameters: UserStartOffset,
// UserEndOffset so that MRB state machine knows to get the remaining data
// from the WriteBuffer.
//
mrb->Flags = MRB_FLAGS_DATA_OUT | MRB_FLAGS_SPLIT_WRITE;
mrb->UserStartOffset = 0; //The UserStartOffset is the beginning of the write.
mrb->UserEndOffset = DeviceExtension->FinalWriteByteCount; //The UserEndOffset is the size of the user data
mrb->Cbw.Cdb.CDB10.OperationCode = SCSIOP_WRITE;
//
// The user data comes from the end of the user buffer.
//
mrb->DataBuffer = (PUCHAR) (irpStack->Parameters.Read.BufferOffset +
irpStack->Parameters.Read.Length -
DeviceExtension->FinalWriteByteCount);
mrb->TransferLength = DeviceExtension->MediaBlockSize;
//
// The byte offset is the length of the user buffer, DeviceExtension->FinalWriteByteCount
// before the end of the of the original request
//
byteOffset = irpStack->Parameters.Read.ByteOffset.LowPart +
irpStack->Parameters.Read.Length -
DeviceExtension->FinalWriteByteCount;
break;
case MU_WRITE_STATE_DONE:
//*
//* This case can be reached due to an error or because
//* the transfer is over, either way the Status is correct
//* complete the Irp with that status.
//
//
// Clear the write state flags.
//
CLEAR_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_STATE_BITS);
//
// Remove the IRP we are working on from the global queue.
//
ASSERT(!IsListEmpty(&MU_DriverExtension.PartialRequestQueue));
RemoveHeadList(&MU_DriverExtension.PartialRequestQueue);
//
// Complete the write Irp
//
irp->IoStatus.Information = irpStack->Parameters.Read.Length;
irp->IoStatus.Status = Status;
MU_COMPLETE_REQUEST(DeviceExtension, irp, IO_NO_INCREMENT);
//
// Start the next partial request.
// NOTE: that this request could not be
// for the same device we just finished with.
//
MU_DiskWriteStartNextPartialRequest();
//
// Start the next I/O for the current device.
//
IoStartNextPacket(DeviceExtension->DeviceObject);
return;
default:
//
// Reaching here is a bug in the write state machine.
//
ASSERT(FALSE);
return; //To avoid a compiler warning, without extra code.
}
//
// Do the endian conversions to plug the LBA and Block Counts
// into the Cdb structure.
//
mrb->Cbw.Cdb.CDB10.LogicalBlock = ReverseEndian(byteOffset >> DeviceExtension->LogicalBlockShift);
mrb->Cbw.Cdb.CDB10.TransferBlocks = ReverseEndian((USHORT)(mrb->TransferLength >> DeviceExtension->LogicalBlockShift));
//
// Start the MRB
//
MU_fStartMrb(DeviceExtension);
return;
}
VOID
MU_DiskWriteBuildMiddlePartialRequest(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN NTSTATUS Status
)
/*++
This routine is an alternative MU write state machine. It is its own completion routine
routine. It handles the special case of a write to a "middle" partial request, that is a request
that starts unaligned on the media block boundary and ends unaligned on the same media block.
This state machine breaks a single media block up as follows:
1) Read the portion of the media block before user's buffer into the WriteBuffer.
2) Read the portion of the media block after the user's buffer into the WriteBuffer
after the data read in 1).
3) Write, use the MRB_FLAG_SPLIT_WRITE flag, to glue it all together properly.
Only one instance of this state machine or the MU_DiskWriteBuildPartialRequest state machine
can be running on the system globally. Thus these routine work in tandem.
NOTE: InitialWriteByteCount and FinalWriteByteCount were really named for the other
state machine. The interpretation here is as follows:
InitialWriteByteCount - The length from offset of the beginning of the user buffer to
the end of the media block.
FinalWriteByteCount - The length from offset of the end of the user buffer to
the end of the media block. (This is actually set after MU_fDiskReadWrite
knows the write will end up here).
--*/
{
PIRP irp = DeviceExtension->PendingIrp;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(irp);
ULONG byteOffset;
ULONG writeState = DeviceExtension->DeviceFlags&MU_WRITE_STATE_BITS;
if(NT_SUCCESS(Status))
{
//
// Increment to the next state.
//
writeState += MU_WRITE_STATE_INCREMENT;
} else
//
// Handle errors
//
{
//
// If the error occured during either of the read
// states and it was STATUS_DATA_ERROR, mark the
// sector as corrupt and continue
//
if(
( (MU_WRITE_STATE_MIDDLE_READ_BEFORE == writeState)
||(MU_WRITE_STATE_MIDDLE_READ_AFTER == writeState) ) &&
(STATUS_DATA_ERROR == Status)
)
{
MU_fMarkWriteBufferCorrupt(&DeviceExtension->Mrb);
// Go on to the next phase
Status = STATUS_SUCCESS;
writeState += MU_WRITE_STATE_INCREMENT;
} else
//
// If the write failed during a write phase, or
// for a reason other than a STATUS_DATA_ERROR
// (failed CSW), then just keep the error and go to
// the done state, which will complete the IRP
// with the error.
//
{
writeState = MU_WRITE_STATE_MIDDLE_DONE;
}
}
//
// Write the writeState back
//
CLEAR_FLAG(DeviceExtension->DeviceFlags,MU_WRITE_STATE_BITS);
SET_FLAG(DeviceExtension->DeviceFlags,writeState);
//
// Start working on the mrb.
//
PMU_REQUEST_BLOCK mrb = &DeviceExtension->Mrb;
//
// switch on the stage we need to program
//
switch(writeState)
{
case MU_WRITE_STATE_MIDDLE_READ_BEFORE:
//
// Read operation goes in
//
mrb->Flags = MRB_FLAGS_DATA_IN;
mrb->Cbw.Cdb.CDB10.OperationCode = SCSIOP_READ;
//
// Read the length of the media block before the requested read
// into the start of the WriteBuffer.
//
mrb->DataBuffer = MU_DriverExtension.WriteBuffer;
mrb->TransferLength = DeviceExtension->MediaBlockSize - DeviceExtension->InitialWriteByteCount;
//
// byteOffset start transferLength before the requested write.
//
byteOffset = irpStack->Parameters.Read.ByteOffset.LowPart-mrb->TransferLength;
break;
case MU_WRITE_STATE_MIDDLE_READ_AFTER:
// Still a read since the last step.
// Pickup read into the buffer where we left off in the last step, and
// read until the end of the block
mrb->DataBuffer = MU_DriverExtension.WriteBuffer + mrb->TransferLength;
mrb->TransferLength = DeviceExtension->FinalWriteByteCount;
//
// The byteOffset starts right after the original requested write.
//
byteOffset = irpStack->Parameters.Read.ByteOffset.LowPart+
irpStack->Parameters.Read.Length;
break;
case MU_WRITE_STATE_MIDDLE_WRITE:
//
// Switch to a write operation and use MRB_FLAGS_SPLIT_WRITE
// feature.
//
mrb->Cbw.Cdb.CDB10.OperationCode = SCSIOP_WRITE;
mrb->Flags = MRB_FLAGS_DATA_OUT | MRB_FLAGS_SPLIT_WRITE;
//Set the offsets for the split write
mrb->UserStartOffset = DeviceExtension->MediaBlockSize - DeviceExtension->InitialWriteByteCount;
mrb->UserEndOffset = DeviceExtension->MediaBlockSize - DeviceExtension->FinalWriteByteCount;
//The data buffer is the users data buffer, the transfer length
//is a full media block
mrb->DataBuffer = (PUCHAR)irpStack->Parameters.Read.BufferOffset;
mrb->TransferLength = DeviceExtension->MediaBlockSize;
//The byteOffste is StartOffset before the requested write.
byteOffset = irpStack->Parameters.Read.ByteOffset.LowPart-mrb->UserStartOffset;
break;
case MU_WRITE_STATE_MIDDLE_DONE:
//*
//* This case can be reached due to an error or because
//* the transfer is over, either way the Status is correct
//* complete the Irp with that status.
//
//
// Clear the write state flags.
//
CLEAR_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_STATE_BITS);
//
// Remove the IRP we are working on from the global queue.
//
ASSERT(!IsListEmpty(&MU_DriverExtension.PartialRequestQueue));
RemoveHeadList(&MU_DriverExtension.PartialRequestQueue);
//
// Complete the write Irp
//
irp->IoStatus.Information = irpStack->Parameters.Read.Length;
irp->IoStatus.Status = Status;
MU_COMPLETE_REQUEST(DeviceExtension, irp, IO_NO_INCREMENT);
//
// Start the next partial request.
// NOTE: that this request could not be
// for the same device we just finished with.
//
MU_DiskWriteStartNextPartialRequest();
//
// Start the next I/O for the current device.
//
IoStartNextPacket(DeviceExtension->DeviceObject);
return;
default:
//
// Reaching here is a bug in the write state machine.
//
ASSERT(FALSE);
return; //To avoid a compiler warning, without extra code.
}
//
// Do the endian conversions to plug the LBA and Block Counts
// into the Cdb structure.
//
mrb->Cbw.Cdb.CDB10.LogicalBlock = ReverseEndian(byteOffset >> DeviceExtension->LogicalBlockShift);
mrb->Cbw.Cdb.CDB10.TransferBlocks = ReverseEndian((USHORT)(mrb->TransferLength >> DeviceExtension->LogicalBlockShift));
//
// Start the MRB
//
MU_fStartMrb(DeviceExtension);
return;
}
VOID MU_DiskWriteStartNextPartialRequest()
/*++
Routine Description:
When either of the partial write state machines completes
it calls this function to start the next pending partial
on the correct state machine.
--*/
{
PIRP irp;
PIO_STACK_LOCATION irpStack;
PMU_DEVICE_EXTENSION deviceExtension;
if(!IsListEmpty(&MU_DriverExtension.PartialRequestQueue))
{
//
// Peek at head of list to get IRP, but don't
// dequeue it.
//
irp = CONTAINING_RECORD(
MU_DriverExtension.PartialRequestQueue.Flink,
IRP,
Tail.Overlay.ListEntry
);
//
// We can find the device object in the stack location
// for the irp. From their we get the device extension
// to start the irp on the proper partial write state machine
//
irpStack = IoGetCurrentIrpStackLocation(irp);
deviceExtension = (PMU_DEVICE_EXTENSION) irpStack->DeviceObject->DeviceExtension;
deviceExtension->Mrb.CompletionRoutine(deviceExtension,STATUS_SUCCESS);
}
};
VOID
FASTCALL
MU_fMarkWriteBufferCorrupt(
IN PMU_REQUEST_BLOCK Mrb
)
/*++
Routine Description:
Marks up the WriteBuffer (used for partial write
operations) with a pattern marking corruption at the
beginning of each emulated sector. This routine
is called when the read phase of a partial write fails
with a STATUS_DATA_ERROR (i.e. the device returned
an error in the CSW).
--*/
{
//
// Write the corrupt sector pattern at the beginning of each 4k of the
// the data buffer in the MRB (should point somewhere into global write
// buffer).
//
for(ULONG Offset = 0; Offset < Mrb->TransferLength; Offset += EMULATED_SECTOR_SIZE)
{
RtlCopyMemory(
Mrb->DataBuffer+Offset,
MU_CORRUPT_SECTOR_PATTERN,
MU_CORRUPT_SECTOR_PATTERN_SIZE
);
}
}
//*
//* These IOCTL are for running diagnostics on Memory Units. The production line tests application
//* is the intended client, but there may be others. A special compile switch is required
//* to build this.
//*
//*
#ifdef MU_DIAGNOSTIC_IOCTLS
NTSTATUS
FASTCALL
MU_fVscCommand(
IN PMU_DEVICE_EXTENSION DeviceExtension,
IN PIRP Irp,
IN BYTE bRequest,
IN ULONG ulOutputSize
)
/*++
Routine Description:
Sets up a request to get the Bad Block Table from an MU.
Arguments:
Irp->UserBuffer should have the output buffer for the
Bad Block Table
irpStack->DeviceIoControl.OutputBufferLength should be greater
than or equal to MU_VSD_BAD_BLOCK_TABLE_SIZE
--*/
{
NTSTATUS status = STATUS_PENDING;
if(DeviceExtension->MuInstance)
{
PURB urb = (PURB)RTL_ALLOCATE_HEAP(sizeof(URB));
if(urb)
{
//
// Build VSC request
//
USB_BUILD_CONTROL_TRANSFER(
&urb->ControlTransfer,
NULL,
Irp->UserBuffer,
ulOutputSize,
USB_TRANSFER_DIRECTION_IN,
MU_VscComplete,
DeviceExtension,
FALSE,
USB_DEVICE_TO_HOST|USB_CLASS_COMMAND|USB_COMMAND_TO_INTERFACE,
bRequest,
0,
DeviceExtension->MuInstance->InterfaceNumber,
ulOutputSize
);
//
// Submit the request
//
DeviceExtension->MuInstance->Device->SubmitRequest(urb);
} else
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
} else
{
status = STATUS_DEVICE_NOT_CONNECTED;
}
return status;
}
VOID
MU_VscComplete(
IN PURB Urb,
IN PVOID Context
)
/*++
Routine Description:
Completion routine for all VSC, just fills out
the Irp.IoStatus fields and completes the IRP.
--*/
{
PMU_DEVICE_EXTENSION deviceExtension = (PMU_DEVICE_EXTENSION)Context;
PIRP irp = deviceExtension->PendingIrp;
irp->IoStatus.Status = IUsbDevice::NtStatusFromUsbdStatus(Urb->Header.Status);
irp->IoStatus.Information = Urb->ControlTransfer.TransferBufferLength;
//
// Free the URB
//
RTL_FREE_HEAP(Urb);
//
// Complete the Irp
//
MU_COMPLETE_REQUEST(deviceExtension, irp, IO_NO_INCREMENT);
//
// Start processing the next Irp.
//
IoStartNextPacket(deviceExtension->DeviceObject);
return;
}
#endif
//*
//* These routines are called via macros that should only call them in debug
//* builds.
//*
#if DBG
VOID
MUDebugWatchdogDpcRoutine(
PKDPC,
PMU_DEVICE_EXTENSION DeviceExtension,
PVOID,
PVOID
)
{
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(DeviceExtension->PendingIrp);
//
// This gets hit when an IRP hangs.
// Spew as much useful information
// The call KeDebugBreak;
//
DbgPrint("MU DRIVER IRP WATCHDOG TIMER HAS EXPIRED. Please report this hang.\n");
DbgPrint("MU Device Extension: 0x%0.8x, Irp: 0x%0.8x\n", DeviceExtension, DeviceExtension->PendingIrp);
// Print Irp Information
switch (irpStack->MajorFunction)
{
case IRP_MJ_DEVICE_CONTROL:
switch(irpStack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_DISK_GET_DRIVE_GEOMETRY:
DbgPrint("IOCTL_DISK_GET_DRIVE_GEOMETRY\n");
break;
case IOCTL_DISK_GET_PARTITION_INFO:
DbgPrint("IOCTL_DISK_GET_PARTITION_INFO\n");
break;
case IOCTL_DISK_VERIFY:
DbgPrint("IOCTL_DISK_VERIFY: Offset = 0x%0.8x, Length = 0x%0.8x\n",
irpStack->Parameters.Read.ByteOffset.LowPart,irpStack->Parameters.Read.Length);
break;
#ifdef MU_DIAGNOSTIC_IOCTLS
case MU_IOCTL_GET_BAD_BLOCK_TABLE:
DbgPrint("MU_IOCTL_GET_BAD_BLOCK_TABLE\n");
break;
case MU_IOCTL_MEMORY_TEST:
DbgPrint("MU_IOCTL_MEMORY_TEST\n");
break;
#endif
default:
DbgPrint("IOCTL = 0x%0.8x\n", irpStack->Parameters.DeviceIoControl.IoControlCode);
break;
}
break;
case IRP_MJ_WRITE:
DbgPrint("Write: Buffer = 0x%0.8x, Offset = 0x%0.8x, Length = 0x%0.8x\n",
irpStack->Parameters.Read.BufferOffset,
irpStack->Parameters.Read.ByteOffset.LowPart,
irpStack->Parameters.Read.Length
);
if(TEST_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_MIDDLE_PORTION))
{
DbgPrint("Middle Partial Write:");
switch(DeviceExtension->DeviceFlags&MU_WRITE_STATE_BITS)
{
case MU_WRITE_STATE_MIDDLE_START:
DbgPrint("MU_WRITE_STATE_MIDDLE_START\n");
break;
case MU_WRITE_STATE_MIDDLE_READ_BEFORE:
DbgPrint("MU_WRITE_STATE_MIDDLE_READ_BEFORE\n");
break;
case MU_WRITE_STATE_MIDDLE_READ_AFTER:
DbgPrint("MU_WRITE_STATE_MIDDLE_READ_AFTER\n");
break;
case MU_WRITE_STATE_MIDDLE_WRITE:
DbgPrint("MU_WRITE_STATE_MIDDLE_WRITE\n");
break;
case MU_WRITE_STATE_MIDDLE_DONE:
DbgPrint("MU_WRITE_STATE_MIDDLE_DONE\n");
break;
default:
break;
}
} else
{
if(TEST_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_INITIAL_PORTION))
DbgPrint("Start on a block aligned.\n");
if(TEST_FLAG(DeviceExtension->DeviceFlags, MU_WRITE_HAS_FINAL_PORTION))
DbgPrint("End on a block aligned.\n");
DbgPrint("Partial Write State:");
switch(DeviceExtension->DeviceFlags&MU_WRITE_STATE_BITS)
{
case MU_WRITE_STATE_START:
DbgPrint("MU_WRITE_STATE_START\n");
break;
case MU_WRITE_STATE_INITIAL_READ:
DbgPrint("MU_WRITE_STATE_INITIAL_READ\n");
break;
case MU_WRITE_STATE_INITIAL_WRITE:
DbgPrint("MU_WRITE_STATE_INITIAL_WRITE\n");
break;
case MU_WRITE_STATE_FINAL_READ:
DbgPrint("MU_WRITE_STATE_FINAL_READ\n");
break;
case MU_WRITE_STATE_FINAL_WRITE:
DbgPrint("MU_WRITE_STATE_FINAL_WRITE\n");
case MU_WRITE_STATE_DONE:
DbgPrint("MU_WRITE_STATE_DONE\n");
default:
break;
}
}
break;
case IRP_MJ_READ:
DbgPrint("Read: Buffer = 0x%0.8x, Offset = 0x%0.8x, Length = 0x%0.8x\n",
irpStack->Parameters.Read.BufferOffset,
irpStack->Parameters.Read.ByteOffset.LowPart,
irpStack->Parameters.Read.Length
);
break;
default:
DbgPrint("Unknown Request: 0x%0.8x\n", irpStack->MajorFunction);
}
//Now print out MRB state machine information
DbgPrint("MU Request State machine flags:\n");
if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING))
DbgPrint(" DF_MRB_TIMER_RUNNING\n");
if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_PRIMARY_URB_PENDING))
DbgPrint(" DF_PRIMARY_URB_PENDING\n");
if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_SECONDARY_URB_PENDING))
DbgPrint(" DF_SECONDARY_URB_PENDING\n");
if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_ERROR_PENDING))
DbgPrint(" DF_ERROR_PENDING\n");
if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_RESET_STEP1))
DbgPrint(" DF_RESET_STEP1\n");
if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_RESET_STEP2))
DbgPrint(" DF_RESET_STEP2\n");
if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_RESET_STEP3))
DbgPrint(" DF_RESET_STEP3\n");
// Print out state machine info
DbgPrint("Primary Urb @0x%0.8x, Secondary Urb @0x%0.8x\n", &DeviceExtension->Urb, &DeviceExtension->BulkUrbSecondary);
//Break into debugger
DbgBreakPoint();
}
VOID
MUDebugInitWatchDogParameters(
PMU_DEVICE_EXTENSION DeviceExtension
)
{
KeInitializeTimer(&DeviceExtension->DbgIrpTimer);
KeInitializeDpc(
&DeviceExtension->DbgIrpTimeoutDpc,
(PKDEFERRED_ROUTINE)MUDebugWatchdogDpcRoutine,
(PVOID)DeviceExtension
);
}
VOID
MuDebugSetWatchDogTimer(
PMU_DEVICE_EXTENSION DeviceExtension
)
{
//If the timer is running cancel it, it should not have been
//running so ASSERT that it wasn't.
LARGE_INTEGER timeoutTime;
BOOL fWasSet = KeCancelTimer(&DeviceExtension->DbgIrpTimer);
ASSERT(!fWasSet);
//
// GEt the stack location
//
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(DeviceExtension->PendingIrp);
//
// Based on the IRP, guess how long this should take, in ms
// (we will multiple accordingly after the switch statements)
//
switch (irpStack->MajorFunction)
{
case IRP_MJ_DEVICE_CONTROL:
switch(irpStack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_DISK_GET_DRIVE_GEOMETRY:
case IOCTL_DISK_GET_PARTITION_INFO:
timeoutTime.QuadPart = 2000; // 2 second
break;
case IOCTL_DISK_VERIFY:
timeoutTime.QuadPart = irpStack->Parameters.Read.Length; //assume 25k verified per second
timeoutTime.QuadPart /= 25;
timeoutTime.QuadPart += 4000; //add 4 seconds for overhead
break;
#ifdef MU_DIAGNOSTIC_IOCTLS
case MU_IOCTL_GET_BAD_BLOCK_TABLE:
timeoutTime.QuadPart = 4000; // 4 seconds
break;
case MU_IOCTL_MEMORY_TEST:
timeoutTime.QuadPart = 60000; //1 minute
break;
#endif
default:
timeoutTime.QuadPart = 10000; //10 seconds for anything else (there are none at this time)
}
break;
case IRP_MJ_WRITE:
timeoutTime.QuadPart = irpStack->Parameters.Read.Length; //assume 25k written per second
timeoutTime.QuadPart /= 25;
timeoutTime.QuadPart += 4000; //add 4 seconds for overhead
break;
case IRP_MJ_READ:
timeoutTime.QuadPart = irpStack->Parameters.Read.Length; //assume 50k read per second
timeoutTime.QuadPart /= 50;
timeoutTime.QuadPart += 4000; //add 4 seconds for overhead
break;
default:
timeoutTime.QuadPart = 10000; // 10 seconds in all other cases, actually there shouldn't be any.
}
//Convert from milliseconds to relative .1 microsecond units.
timeoutTime.QuadPart *= -10000;
DeviceExtension->DbgIrpTimeoutTime.QuadPart = timeoutTime.QuadPart;
//Set the timer running
KeSetTimer(&DeviceExtension->DbgIrpTimer, timeoutTime, &DeviceExtension->DbgIrpTimeoutDpc);
}
VOID
MuDebugPetWatchDogTimer(
PMU_DEVICE_EXTENSION DeviceExtension
)
{
KeSetTimer(&DeviceExtension->DbgIrpTimer, DeviceExtension->DbgIrpTimeoutTime, &DeviceExtension->DbgIrpTimeoutDpc);
}
VOID
MuDebugCompleteRequest(
PMU_DEVICE_EXTENSION DeviceExtension,
PIRP Irp,
CCHAR PriorityBoost
)
{
BOOL fWasSet = KeCancelTimer(&DeviceExtension->DbgIrpTimer);
ASSERT(fWasSet);
IoCompleteRequest(Irp, PriorityBoost);
}
#endif