windows-nt/Source/XPSP1/NT/drivers/storage/ide/atapi/crashdmp.c
2020-09-26 16:20:57 +08:00

1695 lines
46 KiB
C

//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1997 - 1999
//
// File: crashdmp.c
//
//--------------------------------------------------------------------------
//#include "stddef.h"
//#include "stdarg.h"
//#include "stdio.h"
//#include <excpt.h>
//#include <ntdef.h>
//#include <ntstatus.h>
//#include <bugcodes.h>
//#include <exlevels.h>
//#include <ntiologc.h>
//#include <ntos.h>
//#include <pci.h>
//#include "io.h"
//#include "scsi.h"
//#include <ntddscsi.h>
//#include <ntdddisk.h>
//#include <string.h>
//#include "stdio.h"
//
//#ifdef ACPI_CONTROL_METHOD_SUPPORT
////
//// for ACPI
////
//#include "acpiioct.h"
//#endif // ACPI_CONTROL_METHOD_SUPPORT
//
//#include "ide.h"
//
////
//// predefine structure pointer type to prevent
//// constant re-ordering of include files
////
//typedef struct _FDO_EXTENSION * PFDO_EXTENSION;
//typedef struct _PDO_EXTENSION * PPDO_EXTENSION;
//typedef struct _DEVICE_SETTINGS * PDEVICE_SETTINGS;
//
//#include "acpiutil.h"
//#include "port.h"
//#include "chanfdo.h"
//#include "detect.h"
//#include "atapi.h"
//#include "devpdo.h"
//#include "regutils.h"
//#include "atapinit.h"
//#include "luext.h"
//#include "fdopower.h"
//#include "pdopower.h"
//#include "hack.h"
//#include "crashdmp.h"
//#include "idedata.h"
//
//#include "ntddstor.h"
#include <ntosp.h>
#include "io.h"
#include "ideport.h"
NTSTATUS
AtapiCrashDumpIdeWriteDMA (
IN LONG Action,
IN PLARGE_INTEGER DiskByteOffset,
IN PMDL Mdl,
IN PVOID LocalData
);
VOID
AtapiCrashDumpBmCallback (
IN PVOID Context
);
#ifdef ALLOC_PRAGMA
//
// All the crash dump code can be marked "INIT".
// during crash dump or hibernate dump, a new copy
// of this driver gets loaded and its INIT code
// doesn't get thrown away when DriverEnry returns
//
#pragma alloc_text(INIT, AtapiCrashDumpDriverEntry)
#pragma alloc_text(INIT, AtapiCrashDumpOpen)
#pragma alloc_text(INIT, AtapiCrashDumpIdeWrite)
#pragma alloc_text(INIT, AtapiCrashDumpFinish)
#pragma alloc_text(INIT, AtapiCrashDumpBmCallback)
#pragma alloc_text(INIT, AtapiCrashDumpIdeWriteDMA)
#endif // ALLOC_PRAGMA
ULONG
AtapiCrashDumpDriverEntry (
PVOID Context
)
/*++
Routine Description:
dump driver entry point
Arguments:
Context - PCRASHDUMP_INIT_DATA
Return Value:
NT Status
--*/
{
PDUMP_INITIALIZATION_CONTEXT context = Context;
DebugPrint ((DBG_CRASHDUMP, "ATAPI: Entering AtapiCrashDumpDriverEntry...\n"));
//
// Put away what we need later
//
DumpData.CrashInitData = (PCRASHDUMP_INIT_DATA) context->PortConfiguration;
DumpData.StallRoutine = context->StallRoutine;
//
// return our dump interface
//
context->OpenRoutine = AtapiCrashDumpOpen;
context->WriteRoutine = AtapiCrashDumpIdeWrite;
context->FinishRoutine = AtapiCrashDumpFinish;
context->WritePendingRoutine = AtapiCrashDumpIdeWriteDMA;
DebugPrint ((DBG_CRASHDUMP, "ATAPI: Leaving AtapiCrashDumpDriverEntry...\n"));
return STATUS_SUCCESS;
}
BOOLEAN
AtapiCrashDumpOpen (
IN LARGE_INTEGER PartitionOffset
)
{
ULONG i;
PHW_DEVICE_EXTENSION hwExtension;
PPCIIDE_BUSMASTER_INTERFACE bmInterface;
DebugPrint ((DBG_CRASHDUMP, "ATAPI: Entering AtapiCrashDumpOpen...PartitionOffset = 0x%x%08x...\n", PartitionOffset.HighPart, PartitionOffset.LowPart));
// if we are crashdumping, reset the cotroller - Not necessary
//
// ISSUE 08/26/2000: Check for disk signature - Why?
//
DumpData.PartitionOffset = PartitionOffset;
RtlMoveMemory (
&DumpData.HwDeviceExtension,
DumpData.CrashInitData->LiveHwDeviceExtension,
sizeof (HW_DEVICE_EXTENSION)
);
// for (i=0; i<DumpData.HwDeviceExtension.MaxIdeDevice; i++) {
//
// AKadatch: we may use DMA and will use DMA if its available
// Do it in AtapiCrashDumpIdeWrite instead
// CLRMASK (DumpData.HwDeviceExtension.DeviceFlags[i], DFLAGS_USE_DMA);
DumpData.HwDeviceExtension.CurrentSrb = NULL;
DumpData.HwDeviceExtension.DataBuffer = NULL;
DumpData.HwDeviceExtension.BytesLeft = 0;
DumpData.HwDeviceExtension.ExpectingInterrupt = FALSE;
DumpData.HwDeviceExtension.DMAInProgress = FALSE;
DumpData.HwDeviceExtension.DriverMustPoll = TRUE;
// }
DumpData.BytesPerSector = 512;
DumpData.MaxBlockSize = DumpData.BytesPerSector * 256;
hwExtension = &DumpData.HwDeviceExtension;
bmInterface = &(hwExtension->BusMasterInterface);
if (bmInterface->BmCrashDumpInitialize) {
bmInterface->BmCrashDumpInitialize(bmInterface->Context);
} else {
// Don't use DMA
for (i=0; i<DumpData.HwDeviceExtension.MaxIdeDevice; i++) {
CLRMASK (DumpData.HwDeviceExtension.DeviceFlags[i], DFLAGS_USE_DMA);
CLRMASK (DumpData.HwDeviceExtension.DeviceFlags[i], DFLAGS_USE_UDMA);
}
}
DebugPrint ((DBG_CRASHDUMP, "ATAPI: Leaving AtapiCrashDumpOpen...\n"));
return TRUE;
}
NTSTATUS
AtapiCrashDumpIdeWrite (
IN PLARGE_INTEGER DiskByteOffset,
IN PMDL Mdl
)
{
SCSI_REQUEST_BLOCK SrbData; // Store Srb on stack, don't modify memory!
ULONG retryCount;
ULONG srbStatus;
NTSTATUS status;
PSCSI_REQUEST_BLOCK srb;
PCDB cdb;
ULONG blockOffset;
ULONG blockCount;
ULONG blockSize;
ULONG bytesWritten;
UCHAR ideStatus;
ULONG i;
ULONG writeMulitpleBlockSize;
for (i=0; i<DumpData.HwDeviceExtension.MaxIdeDevice; i++) {
CLRMASK (DumpData.HwDeviceExtension.DeviceFlags[i], DFLAGS_USE_DMA);
}
DebugPrint((DBG_CRASHDUMP,
"AtapiCrashDumpWrite: Write memory at 0x%x for 0x%x bytes\n",
Mdl->MappedSystemVa,
Mdl->ByteCount));
if (Mdl->ByteCount % DumpData.BytesPerSector) {
//
// must be complete sectors
//
DebugPrint ((DBG_ALWAYS, "AtapiCrashDumpWrite ERROR: not writing full sectors\n"));
return STATUS_INVALID_PARAMETER;
}
if ((Mdl->ByteCount / DumpData.BytesPerSector) > 256) {
//
// need code to split request up
//
DebugPrint ((DBG_ALWAYS, "AtapiCrashDumpWrite ERROR: can't handle large write\n"));
return STATUS_INVALID_PARAMETER;
}
//
// get the WRITE MULTIPLE blocksize per interrupt for later use
//
if (DumpData.HwDeviceExtension.MaximumBlockXfer[DumpData.CrashInitData->TargetId]) {
writeMulitpleBlockSize =
DumpData.HwDeviceExtension.
MaximumBlockXfer[DumpData.CrashInitData->TargetId] *
DumpData.BytesPerSector;
} else {
writeMulitpleBlockSize = 1 * DumpData.BytesPerSector;
}
srb = &SrbData;
cdb = (PCDB)srb->Cdb;
//
// Zero SRB.
//
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
//
// Initialize SRB.
//
srb->Length = sizeof(SCSI_REQUEST_BLOCK);
srb->PathId = DumpData.CrashInitData->PathId;
srb->TargetId = DumpData.CrashInitData->TargetId;
srb->Lun = DumpData.CrashInitData->Lun;
srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
srb->SrbFlags = SRB_FLAGS_DATA_OUT |
SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
SRB_FLAGS_DISABLE_DISCONNECT |
SRB_FLAGS_DISABLE_AUTOSENSE;
srb->SrbStatus = srb->ScsiStatus = 0;
srb->NextSrb = 0;
srb->TimeOutValue = 10;
srb->CdbLength = 10;
//
// Initialize CDB for write command.
//
cdb->CDB10.OperationCode = SCSIOP_WRITE;
MARK_SRB_FOR_PIO(srb);
bytesWritten = 0;
do {
if ((Mdl->ByteCount - bytesWritten) > DumpData.MaxBlockSize) {
blockSize = DumpData.MaxBlockSize;
DebugPrint ((DBG_CRASHDUMP, "ATAPI: AtapiCrashDumpWrite: can't do a single write...\n"));
} else {
blockSize = Mdl->ByteCount - bytesWritten;
}
blockCount = blockSize / DumpData.BytesPerSector;
status = STATUS_UNSUCCESSFUL;
for (retryCount=0; (retryCount<2) && !NT_SUCCESS(status); retryCount++) {
srb->DataTransferLength = blockSize;
srb->DataBuffer = ((PUCHAR) Mdl->MappedSystemVa) + bytesWritten;
//
// Convert disk byte offset to block offset.
//
blockOffset = (ULONG)((DumpData.PartitionOffset.QuadPart +
(*DiskByteOffset).QuadPart +
(ULONGLONG) bytesWritten) / DumpData.BytesPerSector);
//
// Fill in CDB block address.
//
cdb->CDB10.LogicalBlockByte0 = ((PFOUR_BYTE)&blockOffset)->Byte3;
cdb->CDB10.LogicalBlockByte1 = ((PFOUR_BYTE)&blockOffset)->Byte2;
cdb->CDB10.LogicalBlockByte2 = ((PFOUR_BYTE)&blockOffset)->Byte1;
cdb->CDB10.LogicalBlockByte3 = ((PFOUR_BYTE)&blockOffset)->Byte0;
cdb->CDB10.TransferBlocksMsb = ((PFOUR_BYTE)&blockCount)->Byte1;
cdb->CDB10.TransferBlocksLsb = ((PFOUR_BYTE)&blockCount)->Byte0;
status = AtapiCrashDumpIdeWritePio(srb);
if (NT_SUCCESS(status)) {
bytesWritten += blockSize;
}
}
if (!NT_SUCCESS(status)) {
IdeHardReset (
&DumpData.HwDeviceExtension.BaseIoAddress1,
&DumpData.HwDeviceExtension.BaseIoAddress2,
TRUE,
TRUE
);
//
// model: WDC AC31000H
// version: 19.19E22
// serial #: DWW-2T27518018 6
//
// found out this device can't handle WRITE with more sectors than 16,
// the blocks per interrupt setting in ID data word 59.
//
// Therefore, it we see an error, we will change to blocksize to it.
// If it still fails, we will keep shrinking the blocksize by half
// until it gets to zero. Then, we will return an error
//
//
// last write fail, try a smaller block size
//
if (blockSize > writeMulitpleBlockSize) {
blockSize = writeMulitpleBlockSize;
} else {
blockSize /= 2;
}
if (blockSize) {
DebugPrint ((DBG_ALWAYS, "ATAPI: AtapiCrashDumpWrite max write block size is down to 0x%x\n", blockSize));
DumpData.MaxBlockSize = blockSize;
} else {
break;
}
}
} while (bytesWritten < Mdl->ByteCount);
DebugPrint ((DBG_CRASHDUMP, "ATAPI: Leaving AtapiCrashDumpWrite...\n"));
return status;
}
VOID
AtapiCrashDumpFinish (
VOID
)
{
SCSI_REQUEST_BLOCK SrbData; // Store Srb on stack, don't modify memory!
PSCSI_REQUEST_BLOCK srb = &SrbData;
PCDB cdb;
ULONG srbStatus;
ATA_PASS_THROUGH ataPassThroughData;
UCHAR flushCommand;
UCHAR ideStatus = 0;
DebugPrint ((DBG_CRASHDUMP, "ATAPI: Entering AtapiCrashDumpFinish...\n"));
flushCommand =
DumpData.HwDeviceExtension.DeviceParameters[DumpData.CrashInitData->TargetId].IdeFlushCommand;
if (flushCommand != IDE_COMMAND_NO_FLUSH) {
WaitOnBusy(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
//
// Zero SRB and ATA_PASS_THROUGH
//
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
RtlZeroMemory(&ataPassThroughData, sizeof(ATA_PASS_THROUGH));
//
// Initialize SRB.
//
srb->Length = sizeof(SCSI_REQUEST_BLOCK);
srb->PathId = DumpData.CrashInitData->PathId;
srb->TargetId = DumpData.CrashInitData->TargetId;
srb->Lun = DumpData.CrashInitData->Lun;
srb->Function = SRB_FUNCTION_ATA_PASS_THROUGH;
srb->SrbFlags = SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
SRB_FLAGS_DISABLE_DISCONNECT;
srb->SrbStatus = srb->ScsiStatus = 0;
srb->NextSrb = 0;
srb->TimeOutValue = 10;
srb->CdbLength = 10;
srb->DataTransferLength = sizeof (ataPassThroughData);
srb->DataBuffer = &ataPassThroughData;
ataPassThroughData.IdeReg.bCommandReg = flushCommand;
ataPassThroughData.IdeReg.bReserved = ATA_PTFLAGS_STATUS_DRDY_REQUIRED;
srbStatus = IdeSendPassThroughCommand(
&DumpData.HwDeviceExtension,
srb
);
if (srbStatus == SRB_STATUS_PENDING) {
UCHAR ideStatus;
WaitOnBusy(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
DebugPrint ((DBG_ALWAYS, "flush = 0x%x, status = 0x%x...\n", flushCommand, ideStatus));
} else if (srbStatus != SRB_STATUS_SUCCESS) {
DebugPrint ((DBG_ALWAYS, "AtapiCrashDumpFinish: flush failed...\n"));
}
}
//
// issue an standby to park the drive head
//
srb = &DumpData.Srb;
//
// Zero SRB and ATA_PASS_THROUGH
//
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
RtlZeroMemory(&ataPassThroughData, sizeof(ATA_PASS_THROUGH));
//
// Initialize SRB.
//
srb->Length = sizeof(SCSI_REQUEST_BLOCK);
srb->PathId = DumpData.CrashInitData->PathId;
srb->TargetId = DumpData.CrashInitData->TargetId;
srb->Lun = DumpData.CrashInitData->Lun;
srb->Function = SRB_FUNCTION_ATA_PASS_THROUGH;
srb->SrbFlags = SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
SRB_FLAGS_DISABLE_DISCONNECT;
srb->SrbStatus = srb->ScsiStatus = 0;
srb->NextSrb = 0;
srb->TimeOutValue = 10;
srb->CdbLength = 10;
srb->DataTransferLength = sizeof (ataPassThroughData);
srb->DataBuffer = &ataPassThroughData;
ataPassThroughData.IdeReg.bCommandReg = IDE_COMMAND_STANDBY_IMMEDIATE;
ataPassThroughData.IdeReg.bReserved = ATA_PTFLAGS_STATUS_DRDY_REQUIRED;
srbStatus = IdeSendPassThroughCommand(
&DumpData.HwDeviceExtension,
srb
);
if (srbStatus == SRB_STATUS_PENDING) {
WaitOnBusy(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
WaitOnBusy(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
WaitOnBusy(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
} else if (srbStatus != SRB_STATUS_SUCCESS) {
DebugPrint ((DBG_ALWAYS, "AtapiCrashDumpFinish: flush failed...\n"));
}
//
//the disk will be powered off.
//
DebugPrint ((DBG_CRASHDUMP, "ATAPI: Leaving AtapiCrashDumpFinish...\n"));
return;
}
/* ---------------------------- DMA --------------------------- */
/* --- */
VOID
AtapiCrashDumpBmCallback (
IN PVOID Context
)
{
// Just to make BmSetup happy -- it must be supplied
}
// Local variables that needs to be preserved across the calls
#define ENUM_DUMP_LOCALS(X) \
X(LARGE_INTEGER, DiskByteOffset) \
X(PSCSI_REQUEST_BLOCK, srb) \
X(PCDB, cdb) \
X(PMDL, Mdl) \
X(ULONG, blockSize) \
X(ULONG, bytesWritten)
// States
#define STATE_READY 0
#define STATE_WAIT_DMA 1
#define STATE_WAIT_IDE 2
#define STATE_BAD_DMA 3
#define STATE_IO_FAIL 4
#define DMA_MAGIC 'XDma'
typedef struct
{
UCHAR RegionDescriptorTablePage[PAGE_SIZE];
LONG State;
LONG Magic;
SCSI_REQUEST_BLOCK Srb;
PMDL Mdl;
LARGE_INTEGER DiskByteOffset;
ULONG BytesWritten;
ULONG RetryCount;
// Keep contents of BusMasterInterface.Context in safe place because
// originally it's stored in memory that's saved by hibernation.
// Unfortunately, BmSetup saves its arguments in PdoContext thus
// constantly modifying memory. Special troubleshooting code in
// po\hiber.c detects and reports such memory changes, and despite in this
// case it's absolutely harmless it's better be avoided.
PVOID BmContext;
UCHAR BmContextBuffer[1024];
}
DUMP_LOCALS;
BOOLEAN
AtapiCrashDumpInterrupt(
PVOID DeviceExtension
)
/*++
Routine Description:
This is the ISR for crashdump. Should be called in a polling mode and works
only for DMA requests. Doesn't need any of the flags, since we get called
in a synchronized manner.
Arguments:
DeviceExtension : The hardware device extension.
Return Value:
TRUE : if it is our interrupt.
FALSE : if it is not our interrupt or if there are no pending requests.
--*/
{
PHW_DEVICE_EXTENSION deviceExtension = DeviceExtension;
PPCIIDE_BUSMASTER_INTERFACE bmInterface = &deviceExtension->BusMasterInterface;
PIDE_REGISTERS_1 baseIoAddress1 = &DumpData.HwDeviceExtension.BaseIoAddress1;
PIDE_REGISTERS_2 baseIoAddress2 = &DumpData.HwDeviceExtension.BaseIoAddress2;
PSCSI_REQUEST_BLOCK srb;
BMSTATUS bmStatus;
UCHAR statusByte;
ULONG i;
ULONG status;
//
// This interface should exist
//
ASSERT(bmInterface->BmStatus);
//
// poll the bus master status register
//
bmStatus = bmInterface->BmStatus(bmInterface->Context);
//
// return false if it is not our interrupt
//
if (!(bmStatus & BMSTATUS_INTERRUPT)) {
DebugPrint((DBG_CRASHDUMP,
"Not our interrupt\n"
));
return FALSE;
}
//
// Some VIA motherboards do not work without it
//
KeStallExecutionProcessor (5);
//
// disarm DMA and clear bus master interrupt
//
bmInterface->BmDisarm(bmInterface->Context);
//
// Get the current request
//
srb = deviceExtension->CurrentSrb;
//
// we will return false if there are no pending requests
//
if (srb == NULL) {
DebugPrint((DBG_CRASHDUMP,
"No pending request\n"
));
return FALSE;
}
//
// ignore the dma active bit
//
if (bmInterface->IgnoreActiveBitForAtaDevice) {
CLRMASK (bmStatus, BMSTATUS_NOT_REACH_END_OF_TRANSFER);
}
//
// Select IDE line(Primary or Secondary).
//
SelectIdeLine(baseIoAddress1, srb->TargetId >> 1);
//
// Clear interrupt by reading status.
//
GetBaseStatus(baseIoAddress1, statusByte);
//
// should be an ATA device
//
ASSERT(!(deviceExtension->DeviceFlags[srb->TargetId] & DFLAGS_ATAPI_DEVICE));
//
// Check for error conditions.
//
if (statusByte & IDE_STATUS_ERROR) {
//
// Fail this request.
//
status = SRB_STATUS_ERROR;
goto CompleteRequest;
}
WaitOnBusyUntil(baseIoAddress1, statusByte, 500);
ASSERT(!(statusByte & IDE_STATUS_BUSY));
//
// interrupt indicates that the dma engine has finished the transfer
//
deviceExtension->BytesLeft = 0;
//
// bmStatus is initalized eariler.
//
if (!BMSTATUS_SUCCESS(bmStatus)) {
if (bmStatus & BMSTATUS_ERROR_TRANSFER) {
status = SRB_STATUS_ERROR;
}
if (bmStatus & BMSTATUS_NOT_REACH_END_OF_TRANSFER){
status = SRB_STATUS_DATA_OVERRUN;
}
} else {
status = SRB_STATUS_SUCCESS;
}
CompleteRequest:
//
// should we translate the srb error
// and a complicated retry mechanism.
//
//
// check if drq is still up
//
i=0;
while (statusByte & IDE_STATUS_DRQ) {
GetStatus(baseIoAddress1,statusByte);
i++;
if (i > 5) {
status = SRB_STATUS_BUSY;
}
KeStallExecutionProcessor(100);
}
//
// check if the device is busy
//
if (statusByte & IDE_STATUS_BUSY) {
status = SRB_STATUS_BUSY;
}
//
// Set the srb status
//
srb->SrbStatus = (UCHAR)status;
//
// request is done.
//
deviceExtension->CurrentSrb = NULL;
return TRUE;
}
NTSTATUS
AtapiCrashDumpIdeWriteDMA (
IN LONG Action,
IN PLARGE_INTEGER ArgDiskByteOffset,
IN PMDL ArgMdl,
IN PVOID LocalData
)
/*++
Routine Description:
Asynchronous DMA write routine.
Arguments:
Action - one of following:
IO_DUMP_WRITE_INIT - Initialize LocalData (must be first call)
IO_DUMP_WRITE_FULFILL - Perform IO and wait until completion
IO_DUMP_WRITE_START - Start IO and return ASAP
IO_DUMP_WRITE_RESUME - Resume previousely started IO
IO_DUMP_WRITE_FINISH - Complete previous IO request (wait if necessary)
Attention! It is caller's responsibility to make sure that
a) WriteDMA is always called with absolutely the same value of LocalData
b) Contents of *ArgMdl will be preserved between Start/Resume/Finish
c) Memory given by ArgMdl is not modified until the end of operation
ArgDiskByteOffset - Offset on hard disk in bytes
ArgMdl - MDL giving output memory layout
Attn: for DMA the best IO size is 4 KB; for PIO the more the better
LocalData - Memory region where WriteDMA will keep all the data
that need to be preserved between Start/Resume/Finish.
Attn: this region shall be of at least IO_DUMP_WRITE_DATA_SIZE bytes,
and it must be page-aligned
Return Value:
STATUS_SUCCESS - operation completed successfully
STATUS_PENDING - operation started but not completed yet
STATUS_UNSUCCESSFUL - operation failed; use of WriteRoutine (PIO-based IO) adviced
(however if user will keep on using WriteDMA it will redirect
all requests to PIO itself)
STATUS_INVALID_PARAMETER - previously started operation wasn't finished, or
incorrect parameter indeed
--*/
{
DUMP_LOCALS *Locals = LocalData;
PHW_DEVICE_EXTENSION hwExtension = &DumpData.HwDeviceExtension;
PPCIIDE_BUSMASTER_INTERFACE bmInterface = &hwExtension->BusMasterInterface;
LONG targetId = DumpData.CrashInitData->TargetId;
PSCSI_REQUEST_BLOCK srb;
NTSTATUS status;
ULONG srbStatus;
BMSTATUS bmStatus;
ULONG i;
PCDB cdb;
UCHAR statusByte;
PMDL mdl;
BOOLEAN interruptCleared;
BOOLEAN usePio;
if (IO_DUMP_WRITE_DATA_SIZE < sizeof (*Locals)) {
DebugPrint ((DBG_CRASHDUMP, "AtapiCrashDumpIdeWriteDMA: IO_DUMP_WRITE_DATA_SIZE = %d, sizeof (*Locals) == %d\n",
IO_DUMP_WRITE_DATA_SIZE, sizeof (*Locals)));
return STATUS_INVALID_PARAMETER;
}
switch (Action) {
case IO_DUMP_WRITE_INIT:
//
// initalize the state to bad_dma
//
Locals->State = STATE_BAD_DMA;
Locals->Magic = 0;
//
// Check alignment
//
if (((ULONG_PTR) Locals) & (PAGE_SIZE-1)) {
DebugPrint ((DBG_CRASHDUMP, "AtapiCrashDumpIdeWriteDMA: misaligned Locals = %p\n", Locals));
return STATUS_UNSUCCESSFUL;
}
//
// Make sure we may use UDMA; do not try to use pure DMA --
// it won't work on some machines (e.g. Compaq Armada 7800)
//
if (!(hwExtension->DeviceFlags[targetId] & DFLAGS_DEVICE_PRESENT) ||
!(hwExtension->DeviceParameters[targetId].TransferModeSupported & UDMA_SUPPORT) ||
!(hwExtension->DeviceParameters[targetId].TransferModeSelected & UDMA_SUPPORT) ||
!(hwExtension->DeviceFlags[targetId] & DFLAGS_USE_UDMA) ||
bmInterface->MaxTransferByteSize <= 0
) {
DebugPrint ((DBG_CRASHDUMP, "AtapiCrashDumpIdeWriteDMA: UDMA is not available\n"));
return STATUS_UNSUCCESSFUL;
}
//
// Copy contents of BusMasterInterface.Context to safe place and
// substitute the pointer. Bm* functions change its contents
//
ASSERT(bmInterface->ContextSize > 0);
ASSERT(bmInterface->ContextSize < sizeof(Locals->BmContextBuffer));
//
// make sure we can copy the context to the local buffer
//
if ((bmInterface->ContextSize <=0) ||
(bmInterface->ContextSize > sizeof(Locals->BmContextBuffer))) {
return STATUS_UNSUCCESSFUL;
}
//
// Save BM context in modifyable memory:
// Bm* functions change its contents
//
Locals->BmContext = bmInterface->Context;
RtlCopyMemory (&Locals->BmContextBuffer, Locals->BmContext, bmInterface->ContextSize);
//
// Check version of PCIIDEX.SYS
//
ASSERT(bmInterface->BmSetupOnePage);
//
// OK, now we are ready to use DMA
//
Locals->Magic = DMA_MAGIC;
Locals->State = STATE_READY;
return STATUS_SUCCESS;
case IO_DUMP_WRITE_START:
case IO_DUMP_WRITE_FULFILL:
//
// Make sure it was properly initialized
//
if (Locals->Magic != DMA_MAGIC) {
return STATUS_INVALID_PARAMETER;
}
//
// Do not call DMA if it failed once -- use PIO
//
if (Locals->State == STATE_BAD_DMA) {
return AtapiCrashDumpIdeWrite (ArgDiskByteOffset, ArgMdl);
}
//
// Caller did not complete prev IO
//
if (Locals->State != STATE_READY) {
return STATUS_INVALID_PARAMETER;
}
//
// Copy arguments into local variables
//
Locals->DiskByteOffset = *ArgDiskByteOffset;
Locals->Mdl = ArgMdl;
Locals->RetryCount = 0;
srb = &Locals->Srb;
mdl = Locals->Mdl;
//
// must be complete sectors
//
if (mdl->ByteCount % DumpData.BytesPerSector) {
DebugPrint ((DBG_CRASHDUMP,
"AtapiCrashDumpWriteDMA ERROR: not writing full sectors\n"
));
return STATUS_INVALID_PARAMETER;
}
//
// need code to split request up
//
if ((mdl->ByteCount / DumpData.BytesPerSector) > 256) {
DebugPrint ((DBG_CRASHDUMP,
"AtapiCrashDumpWriteDMA ERROR: can't handle large write\n"
));
return STATUS_INVALID_PARAMETER;
}
//
// use modifiable memory
//
bmInterface->Context = &Locals->BmContextBuffer;
//
// Zero SRB.
//
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
//
// Initialize SRB.
//
srb->Length = sizeof(SCSI_REQUEST_BLOCK);
srb->PathId = DumpData.CrashInitData->PathId;
srb->TargetId = (UCHAR) targetId;
srb->Lun = DumpData.CrashInitData->Lun;
srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
srb->SrbFlags = SRB_FLAGS_DATA_OUT |
SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
SRB_FLAGS_DISABLE_DISCONNECT |
SRB_FLAGS_DISABLE_AUTOSENSE;
srb->SrbStatus = srb->ScsiStatus = 0;
srb->NextSrb = 0;
srb->TimeOutValue = 10;
srb->CdbLength = 10;
cdb = (PCDB)srb->Cdb;
//
// Initialize CDB for write command.
//
cdb->CDB10.OperationCode = SCSIOP_WRITE;
//
// Mark it for DMA
//
MARK_SRB_FOR_DMA (srb);
hwExtension->CurrentSrb = srb;
break;
case IO_DUMP_WRITE_RESUME:
case IO_DUMP_WRITE_FINISH:
//
// Make sure it was properly initialized
//
if (Locals->Magic != DMA_MAGIC) {
return STATUS_INVALID_PARAMETER;
}
//
// restore the local variables from scratch memory
//
srb = &Locals->Srb;
mdl = Locals->Mdl;
//
// Resume/finish operation
//
if (Locals->State == STATE_READY) {
//
// we are done. return success
//
return(STATUS_SUCCESS);
}
if (Locals->State == STATE_WAIT_DMA) {
//
// Restore CurrentSrb
// (it should be reset back to NULL on return)
//
hwExtension->CurrentSrb = srb;
bmInterface->Context = &Locals->BmContextBuffer;
goto WaitDma;
}
//
// if any of the DMA operations failed, we would have used
// PIO. PIO would have completed the transfer, so just return
// status success.
//
if (Locals->State == STATE_BAD_DMA) {
return STATUS_SUCCESS;
}
//
// wrong state
//
DebugPrint((DBG_ALWAYS,
"Wrong local state 0x%x\n",
Locals->State
));
ASSERT(FALSE);
return(STATUS_INVALID_PARAMETER);
default:
DebugPrint ((DBG_CRASHDUMP, "AtapiCrashDumpIdeWriteDMA: Wrong Action = %d\n", Action));
return STATUS_INVALID_PARAMETER;
}
DebugPrint((DBG_CRASHDUMP,
"AtapiCrashDumpWriteDMA: Write memory at 0x%x for 0x%x bytes\n",
mdl->MappedSystemVa,
mdl->ByteCount));
Locals->BytesWritten = 0;
usePio = FALSE;
do {
ULONG blockSize;
ULONG blockCount;
ULONG blockOffset;
ULONG bytesWritten = Locals->BytesWritten;
//
// determine the block size
//
//
// cannot be greater than the max block size
//
if ((mdl->ByteCount - bytesWritten) > DumpData.MaxBlockSize) {
blockSize = DumpData.MaxBlockSize;
DebugPrint ((DBG_CRASHDUMP, "AtapiCrashDumpWriteDMA: can't do a single write...\n"));
} else {
blockSize = mdl->ByteCount - bytesWritten;
}
//
// Write page by page in order to avoid extra memory allocations in HAL
//
{
ULONG Size = PAGE_SIZE - (((ULONG) ((ULONG_PTR) mdl->MappedSystemVa + bytesWritten)) & (PAGE_SIZE - 1));
if (blockSize > Size) {
blockSize = Size;
}
}
//
// Don't do more than DMA can
//
if (blockSize > bmInterface->MaxTransferByteSize) {
blockSize = bmInterface->MaxTransferByteSize;
}
blockCount = blockSize / DumpData.BytesPerSector;
//
// initialize status
//
status = STATUS_UNSUCCESSFUL;
//
// fill in the fields in the srb
//
srb->SrbStatus = srb->ScsiStatus = 0;
srb->DataTransferLength = blockSize;
srb->DataBuffer = ((PUCHAR) mdl->MappedSystemVa) + bytesWritten;
//
// Convert disk byte offset to block offset.
//
blockOffset = (ULONG)((DumpData.PartitionOffset.QuadPart +
(Locals->DiskByteOffset).QuadPart +
(ULONGLONG) bytesWritten) / DumpData.BytesPerSector);
cdb = (PCDB)srb->Cdb;
//
// Fill in CDB block address.
//
cdb->CDB10.LogicalBlockByte0 = ((PFOUR_BYTE)&blockOffset)->Byte3;
cdb->CDB10.LogicalBlockByte1 = ((PFOUR_BYTE)&blockOffset)->Byte2;
cdb->CDB10.LogicalBlockByte2 = ((PFOUR_BYTE)&blockOffset)->Byte1;
cdb->CDB10.LogicalBlockByte3 = ((PFOUR_BYTE)&blockOffset)->Byte0;
cdb->CDB10.TransferBlocksMsb = ((PFOUR_BYTE)&blockCount)->Byte1;
cdb->CDB10.TransferBlocksLsb = ((PFOUR_BYTE)&blockCount)->Byte0;
//
// make sure device is not busy
//
WaitOnBusy(&hwExtension->BaseIoAddress1, statusByte);
//
// HACK: do PIO.
// Complete this request with PIO. Further requests will not
// use DMA.
//
if (usePio) {
status = AtapiCrashDumpIdeWritePio(srb);
goto CompleteIde;
}
//
// Make sure DMA is not busy
//
bmStatus = bmInterface->BmStatus (bmInterface->Context);
if (bmStatus & BMSTATUS_INTERRUPT) {
//
// Well, in absense of interrupts it means that DMA is ready
// However extra disarming won't hurt
//
bmInterface->BmDisarm (bmInterface->Context);
} else if (bmStatus != BMSTATUS_NO_ERROR) {
ASSERT(bmStatus == BMSTATUS_NO_ERROR);
status = STATUS_UNSUCCESSFUL;
goto Return;
}
//
// Flush cached data buffers
//
KeFlushIoBuffers(mdl, FALSE, TRUE);
//
// Start new DMA operation
//
if (bmInterface->BmSetupOnePage == NULL) {
status = bmInterface->BmSetup (
bmInterface->Context,
srb->DataBuffer,
srb->DataTransferLength,
mdl,
FALSE,
AtapiCrashDumpBmCallback,
NULL
);
} else {
status = bmInterface->BmSetupOnePage (
bmInterface->Context,
srb->DataBuffer,
srb->DataTransferLength,
mdl,
FALSE,
Locals
);
}
if (!NT_SUCCESS(status)) {
ASSERT(NT_SUCCESS(status));
goto Return;
}
//
// make sure the device is not busy
//
WaitOnBusy(&DumpData.HwDeviceExtension.BaseIoAddress1, statusByte);
//
// srb should be marked for DMA
//
ASSERT(SRB_USES_DMA(srb));
//
// Start new IO
//
srbStatus = IdeReadWrite (hwExtension, srb);
if (srbStatus != SRB_STATUS_PENDING) {
DebugPrint ((DBG_CRASHDUMP,
"AtapiCrashDumpWriteDMA: Wrong srbStatus = 0x%x\n",
srbStatus
));
//
// reset and retry
//
srb->SrbStatus = (UCHAR)srbStatus;
goto CompleteIde;
}
WaitDma:
//
// wait for the dma to finish and the controller to
// interrupt. we will keep polling the bus master status
// register
//
bmStatus = bmInterface->BmStatus(bmInterface->Context);
//
// if we have an interrupt or there is an error
// we are done
//
if (!((bmStatus & BMSTATUS_INTERRUPT) ||
(bmStatus & BMSTATUS_ERROR_TRANSFER))) {
//
// if we don't have to fulfill the request, just
// return pending. we will be called again.
//
if ((Action == IO_DUMP_WRITE_START) ||
(Action == IO_DUMP_WRITE_RESUME)) {
Locals->State = STATE_WAIT_DMA;
status = STATUS_PENDING;
goto Return;
}
//
// we have to finish the request. wait until the interrupt
// is set
//
i=0;
while (i++ < 10000) {
bmStatus = bmInterface->BmStatus(bmInterface->Context);
if ((bmStatus & BMSTATUS_INTERRUPT) ||
(bmStatus & BMSTATUS_ERROR_TRANSFER)) {
break;
}
KeStallExecutionProcessor (100);
}
//
// check if we received an interrupt.
//
if (i >= 10000) {
//
// reset and retry
//
ASSERT(FALSE);
//
// disarm the dma controller
//
bmInterface->BmDisarm (bmInterface->Context);
srb->SrbStatus = SRB_STATUS_ERROR;
goto CompleteIde;
}
}
if (bmStatus & BMSTATUS_ERROR_TRANSFER){
//
// Transfer Error. fail the transfer.
//
status = STATUS_UNSUCCESSFUL;
goto Return;
}
//
// wait for our ISR to finish its job
//
interruptCleared = AtapiCrashDumpInterrupt(hwExtension);
//
// it should be our interrupt
//
ASSERT(interruptCleared);
if (!interruptCleared) {
status = STATUS_DEVICE_BUSY;
goto Return;
}
//
// clear any spurious interrupts
//
i=0;
while (AtapiCrashDumpInterrupt(hwExtension)) {
i++;
if (i>=100) {
DebugPrint((0,
"AtapiCrashDump: InterruptStorm\n"
));
status = STATUS_DEVICE_BUSY;
goto Return;
}
KeStallExecutionProcessor (100);
}
CompleteIde:
//
// Flush the adapter buffers
//
if (usePio) {
//
// don't do anything
//
} else if (bmInterface->BmSetupOnePage == NULL) {
bmInterface->BmFlush (bmInterface->Context);
} else {
status = bmInterface->BmFlushAdapterBuffers (
bmInterface->Context,
srb->DataBuffer,
srb->DataTransferLength,
mdl,
FALSE
);
}
//
// update the bytesWritten
//
if (srb->SrbStatus == SRB_STATUS_SUCCESS) {
//
// status success
//
status = STATUS_SUCCESS;
//
// update byteswritten
//
Locals->BytesWritten += srb->DataTransferLength;
//
// reset retry count
//
Locals->RetryCount = 0;
} else {
ASSERT(FALSE);
//
// reset the bus and retry the request
//
IdeHardReset (
&DumpData.HwDeviceExtension.BaseIoAddress1,
&DumpData.HwDeviceExtension.BaseIoAddress2,
TRUE,
TRUE
);
//
// we should probably look at the error code and
// decide on the retry appropriately. However, to
// minimize complexity, we would just blindly retry
// 4 times and then use PIO
//
Locals->RetryCount++;
//
// retry with PIO (dma timeout)
// Give dma a fair shot. Once we switch to PIO
// we would not use DMA for the rest of hibernation.
//
if (Locals->RetryCount == 5) {
usePio = TRUE;
}
//
// PIO failed. Return error.
//
if (Locals->RetryCount > 5) {
status = STATUS_IO_DEVICE_ERROR;
goto Return;
}
}
} while (Locals->BytesWritten < mdl->ByteCount);
Locals->State = STATE_READY;
status = STATUS_SUCCESS;
Return:
//
// if we used PIO this time, disable dma
// for the rest of hibernation
//
if (usePio) {
Locals->State = STATE_BAD_DMA;
}
if (!NT_SUCCESS(status)) {
ASSERT(FALSE);
Locals->State = STATE_IO_FAIL;
}
hwExtension->CurrentSrb = NULL;
bmInterface->Context = Locals->BmContext;
return status;
}
NTSTATUS
AtapiCrashDumpIdeWritePio (
IN PSCSI_REQUEST_BLOCK Srb
)
{
NTSTATUS status;
ULONG srbStatus;
UCHAR ideStatus;
ULONG i;
MARK_SRB_FOR_PIO(Srb);
//
// make sure it is not busy
//
WaitOnBusy(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
//
// Send the srb to the device
//
srbStatus = IdeReadWrite(
&DumpData.HwDeviceExtension,
Srb
);
if (srbStatus == SRB_STATUS_PENDING) {
while (DumpData.HwDeviceExtension.BytesLeft) {
//
// ATA-2 spec requires a minimum of 400 ns stall here
//
KeStallExecutionProcessor (1);
//
// a quick wait
//
for (i=0; i<100; i++) {
GetStatus(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
if (!(ideStatus & IDE_STATUS_BUSY)) {
break;
}
}
if (ideStatus & IDE_STATUS_BUSY) {
//
// go to a slower wait
//
WaitOnBusy(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
}
if (ideStatus & (IDE_STATUS_BUSY | IDE_STATUS_ERROR)) {
status = STATUS_UNSUCCESSFUL;
DebugPrint ((DBG_ALWAYS, "AtapiCrashDumpIdeWrite: unexpected status 0x%x\n", ideStatus));
break;
} else {
ULONG byteCount;
//
// a quick wait On DRQ
//
for (i=0; i<100; i++) {
GetStatus(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
if (ideStatus & IDE_STATUS_DRQ) {
break;
}
}
if (!(ideStatus & IDE_STATUS_DRQ)) {
WaitForDrq(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
}
if (!(ideStatus & IDE_STATUS_DRQ)) {
status = STATUS_UNSUCCESSFUL;
DebugPrint ((DBG_ALWAYS, "AtapiCrashDumpIdeWrite: drq fails to assert, 0x%x\n", ideStatus));
break;
}
if (DumpData.HwDeviceExtension.BytesLeft <
DumpData.HwDeviceExtension.DeviceParameters[Srb->TargetId].MaxBytePerPioInterrupt) {
byteCount = DumpData.HwDeviceExtension.BytesLeft;
} else {
byteCount = DumpData.HwDeviceExtension.DeviceParameters[Srb->TargetId].MaxBytePerPioInterrupt;
}
WriteBuffer(&DumpData.HwDeviceExtension.BaseIoAddress1,
(PUSHORT)DumpData.HwDeviceExtension.DataBuffer,
byteCount / sizeof(USHORT));
DumpData.HwDeviceExtension.BytesLeft -= byteCount;
DumpData.HwDeviceExtension.DataBuffer += byteCount;
}
}
if (!DumpData.HwDeviceExtension.BytesLeft) {
//
// ATA-2 spec requires a minimum of 400 ns stall here
//
KeStallExecutionProcessor (1);
//
// a quick wait
//
for (i=0; i<100; i++) {
GetStatus(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
if (!(ideStatus & IDE_STATUS_BUSY)) {
break;
}
}
if (ideStatus & IDE_STATUS_BUSY) {
//
// go to a slower wait
//
WaitOnBusy(&DumpData.HwDeviceExtension.BaseIoAddress1, ideStatus);
}
}
if (DumpData.HwDeviceExtension.BytesLeft) {
status = STATUS_UNSUCCESSFUL;
DebugPrint ((DBG_ALWAYS, "AtapiCrashDumpIdeWrite: write failed. idestatus = 0x%x\n", ideStatus));
} else {
Srb->SrbStatus = SRB_STATUS_SUCCESS;
status = STATUS_SUCCESS;
}
} else {
DebugPrint ((DBG_ALWAYS,
"atapi crash dump: IdeReadWrite failed with stautus = 0x%x\n",
srbStatus
));
status = STATUS_UNSUCCESSFUL;
}
if (!NT_SUCCESS(status)) {
Srb->SrbStatus = SRB_STATUS_ERROR;
} else {
ASSERT(Srb->SrbStatus == SRB_STATUS_SUCCESS);
}
DumpData.HwDeviceExtension.BytesLeft = 0;
DumpData.HwDeviceExtension.DataBuffer = 0;
return status;
}