/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    dumpctl.c

Abstract:

    This module contains the code to dump memory to disk after a crash.

Author:

    Darryl E. Havens (darrylh) 17-dec-1993

Environment:

    Kernel mode

Revision History:


--*/

#include "iop.h"

//
// Forward declarations
//

static
VOID
MapPhysicalMemory(
    IN OUT PMDL Mdl,
    IN ULONG MemoryAddress,
    IN PPHYSICAL_MEMORY_RUN PhysicalMemoryRun,
    IN ULONG Length
    );

extern PULONG MmPfnDatabase;

NTSTATUS IopFinalCrashDumpStatus = -1;

#if 0
#if DBG
ULONG BreakDiskByteOffset;
ULONG BreakPfn;
#endif // DBG
#endif // 0

BOOLEAN
IoWriteCrashDump(
    IN ULONG BugCheckCode,
    IN ULONG BugCheckParameter1,
    IN ULONG BugCheckParameter2,
    IN ULONG BugCheckParameter3,
    IN ULONG BugCheckParameter4,
    IN PVOID ContextSave
    )

/*++

Routine Description:

    This routine checks to see whether or not crash dumps are enabled and, if
    so, writes all of physical memory to the system disk's paging file.

Arguments:

    BugCheckCode/ParameterN - Code and parameters w/which BugCheck was called.

Return Value:

    None.

--*/

{
    PDUMP_CONTROL_BLOCK dcb;
    INITIALIZATION_CONTEXT initContext;
    PLIST_ENTRY nextEntry;
    PMINIPORT_NODE mpNode;
    PDUMP_DRIVER_OPEN open;
    PDUMP_DRIVER_WRITE write;
    PDUMP_DRIVER_FINISH finishUp;
    PDUMP_HEADER header;
    EXCEPTION_RECORD exception;
    PCONTEXT context = ContextSave;
    PULONG block;
    LARGE_INTEGER diskByteOffset;
    PULONG page;
    ULONG localMdl[(sizeof( MDL )/4) + 17];
    PMDL mdl;
    PLARGE_INTEGER mcb;
    ULONG memoryAddress;
    ULONG byteOffset;
    ULONG byteCount;
    ULONG bytesRemaining;
    NTSTATUS status;
    PMAPPED_ADDRESS addresses;
    ULONG addressCount;
    ULONG addressChecksum;
    UCHAR messageBuffer[128];

    //
    // Begin by determining whether or not crash dumps are enabled.  If not,
    // check to see whether or not auto-rebooting is enabled.  If not, return
    // immediately since there is nothing to do.
    //

    dcb = IopDumpControlBlock;
    if (!dcb) {
        return FALSE;
    }

    if (dcb->Flags & DCB_DUMP_ENABLED || dcb->Flags & DCB_SUMMARY_ENABLED) {

        IopFinalCrashDumpStatus = STATUS_PENDING;

        //
        // A dump is to be written to the paging file.  Ensure that all of the
        // descriptor data for what needs to be done is valid, otherwise it
        // could be that part of the reason for the bugcheck is that this data
        // was corrupted.  Or, it could be that no paging file was found yet,
        // or any number of other situations.
        //
        // The rules for determining this are as follows:
        //
        //     1)  The dump control block must be a dump control block.
        //
        //     2)  The dump control block structure checksum must match.
        //
        //     3)  The disk dump driver checksum must match.
        //
        //     4)  The miniport queue must be consistent and all driver
        //         checksums must match.
        //
        //     5)  The file descriptor pointer must be valid and its checksum
        //         must match.
        //
        //     6)  All buffers must have been allocated.
        //
        //     7)  The module list address must be valid.
        //

        if (dcb->Type != IO_TYPE_DCB ||

            dcb->Size < sizeof( DUMP_CONTROL_BLOCK ) ||

            IopChecksum( dcb, dcb->Size ) != IopDumpControlBlockChecksum ||

/////////   IopChecksum( dcb->DiskDumpDriver->DllBase, dcb->DiskDumpDriver->SizeOfImage ) != dcb->DiskDumpChecksum ||

            IsListEmpty( &dcb->MiniportQueue ) ||

            !dcb->FileDescriptorArray ||

            IopChecksum( dcb->FileDescriptorArray, dcb->FileDescriptorSize ) != dcb->FileDescriptorChecksum ||

            !dcb->NonCachedBufferVa1 ||

            !dcb->NonCachedBufferVa2 ||

            !dcb->Buffer ||

            !dcb->HeaderPage ||

            dcb->LoadedModuleList != &PsLoadedModuleList) {

#if DBG
            if (dcb->Type != IO_TYPE_DCB) {
                DbgPrint( "DCB Type field is invalid\n" );
            }
            if (dcb->Size < sizeof( DUMP_CONTROL_BLOCK )) {
                DbgPrint( "DCB Size field is invalid\n" );
            }
            if (IopChecksum( dcb, dcb->Size ) != IopDumpControlBlockChecksum) {
                DbgPrint( "DCB checksum is inconsistent\n" );
            }
/////////   if (IopChecksum( dcb->DiskDumpDriver, dcb->DiskDumpDriver->SizeOfImage) != dcb->DiskDumpChecksum) {
/////////       DbgPrint( "Disk dump driver checksum is inconsistent\n" );
/////////   }
            if (IsListEmpty( &dcb->MiniportQueue )) {
                DbgPrint( "Miniport queue is empty\n" );
            }
            if (!dcb->FileDescriptorArray) {
                DbgPrint( "No boot device paging file was found or was too small\n" );
            }
            if (IopChecksum( dcb->FileDescriptorArray, dcb->FileDescriptorSize ) != dcb->FileDescriptorChecksum) {
                DbgPrint( "Page file descriptor checksum is inconsistent\n" );
            }
            DbgPrint( "CRASHDUMP: Disk dump routine returning due to DCB integrity error\n" );
            DbgPrint( "           No dump will be created\n" );
#endif // DBG

            IopFinalCrashDumpStatus = STATUS_UNSUCCESSFUL;
            return FALSE;
        }

        //
        // Finally, check the mapped registers in the driver to guarantee that
        // they are consistent as well.
        //

        addressCount = 0;
        addressChecksum = 0;
        addresses = * (PMAPPED_ADDRESS *) dcb->MappedRegisterBase;

        while (addresses) {
            addressCount++;
            addressChecksum += IopChecksum( addresses, sizeof( MAPPED_ADDRESS ) );
            addresses = addresses->NextMappedAddress;
        }

        if (addressCount != dcb->MappedAddressCount ||
            addressChecksum != dcb->MappedAddressChecksum) {
#if DBG
            DbgPrint( "Mapped address count or checksum failed\n" );
#endif // DBG
            IopFinalCrashDumpStatus = STATUS_UNSUCCESSFUL;
            return FALSE;
        }

        //
        // The dump control block appears to be in good order.  Begin by
        // initializing the disk dump driver.
        //

        initContext.Length = sizeof( INITIALIZATION_CONTEXT );
        initContext.DiskSignature = dcb->DiskSignature;
        initContext.MemoryBlock = dcb->Buffer;
        initContext.CommonBuffer[0] = dcb->NonCachedBufferVa1;
        initContext.CommonBuffer[1] = dcb->NonCachedBufferVa2;
        initContext.PhysicalAddress[0] = dcb->NonCachedBufferPa1;
        initContext.PhysicalAddress[1] = dcb->NonCachedBufferPa2;
        initContext.StallRoutine = &KeStallExecutionProcessor;
        initContext.AdapterObject = dcb->AdapterObject;
        initContext.MappedRegisterBase = dcb->MappedRegisterBase;
        initContext.PortConfiguration = dcb->PortConfiguration;

        status = ((PDRIVER_INITIALIZE) (dcb->DiskDumpDriver->EntryPoint))( (PDRIVER_OBJECT) NULL,
                                                                           (PUNICODE_STRING) &initContext );
        if (!NT_SUCCESS( status )) {
#if DBG
            DbgPrint( "CRASHDUMP: Unable to initialize disk dump driver; error = %x\n", status );
#endif // DBG
            IopFinalCrashDumpStatus = STATUS_UNSUCCESSFUL;
            return FALSE;
        }

        //
        // Record the dump driver's two entry points.
        //

        open = initContext.OpenRoutine;
        write = initContext.WriteRoutine;
        finishUp = initContext.FinishRoutine;

        //
        // Display message on screen that we are starting the crashdump
        //

        sprintf( messageBuffer, "%Z\n", &dcb->PssInitMsg );
        HalDisplayString( messageBuffer );


        //
        // Now initialize each of the miniport drivers.
        //

        nextEntry = dcb->MiniportQueue.Flink;

        while (nextEntry != &dcb->MiniportQueue) {

            mpNode = CONTAINING_RECORD( nextEntry,
                                        MINIPORT_NODE,
                                        ListEntry );

            if (IopChecksum( mpNode->DriverEntry->DllBase, mpNode->DriverEntry->SizeOfImage ) != mpNode->DriverChecksum) {
                IopFinalCrashDumpStatus = STATUS_UNSUCCESSFUL;
                return FALSE;
            }

            status = ((PDRIVER_INITIALIZE) (mpNode->DriverEntry->EntryPoint))( NULL, NULL );

            if (!NT_SUCCESS( status )) {
#if DBG
                DbgPrint( "CRASHDUMP: Could not initialize miniport; error = %x\n", status );
#endif // DBG
                IopFinalCrashDumpStatus = STATUS_UNSUCCESSFUL;
                return FALSE;
            }

            nextEntry = nextEntry->Flink;
        }

        //
        // Now attempt to open the partition from which the system was booted.
        // This returns TRUE if the disk w/the appropriate signature was found,
        // otherwise a NULL, in which case there is no way to continue.
        //

        if (!open( dcb->PartitionOffset )) {
#if DBG
            DbgPrint( "CRASHDUMP: Could not find/open partition offset\n" );
#endif // DBG
            IopFinalCrashDumpStatus = STATUS_UNSUCCESSFUL;
            return FALSE;
        }

        //
        // The boot partition was found, so put together a dump file header
        // and write it to the disk.
        //

        block = dcb->HeaderPage;
        header = (PDUMP_HEADER) block;

        RtlFillMemoryUlong( header, PAGE_SIZE, 'EGAP' );
        header->ValidDump = 'PMUD';
        header->BugCheckCode = BugCheckCode;
        header->BugCheckParameter1 = BugCheckParameter1;
        header->BugCheckParameter2 = BugCheckParameter2;
        header->BugCheckParameter3 = BugCheckParameter3;
        header->BugCheckParameter4 = BugCheckParameter4;
        header->DirectoryTableBase = PsInitialSystemProcess->Pcb.DirectoryTableBase[0];
        header->PfnDataBase = MmPfnDatabase;
        header->PsLoadedModuleList = &PsLoadedModuleList;
        header->PsActiveProcessHead = &PsActiveProcessHead;
        header->NumberProcessors = dcb->NumberProcessors;
        header->MajorVersion = dcb->MajorVersion;
        header->MinorVersion = dcb->MinorVersion;

#ifdef i386
        header->MachineImageType = IMAGE_FILE_MACHINE_I386;
#endif // i386

#ifdef MIPS
        header->MachineImageType = IMAGE_FILE_MACHINE_R4000;
#endif // MIPS

#ifdef ALPHA
        header->MachineImageType = IMAGE_FILE_MACHINE_ALPHA;
#endif // ALPHA

#ifdef _PPC_
        header->MachineImageType = IMAGE_FILE_MACHINE_POWERPC;
#endif // PPC

        if (!(dcb->Flags & DCB_DUMP_ENABLED)) {
            dcb->MemoryDescriptor->NumberOfPages = 1;
        }

        strcpy( header->VersionUser, dcb->VersionUser );

        RtlCopyMemory( &block[DH_PHYSICAL_MEMORY_BLOCK],
                       dcb->MemoryDescriptor,
                       sizeof( PHYSICAL_MEMORY_DESCRIPTOR ) +
                       ((dcb->MemoryDescriptor->NumberOfRuns - 1) *
                       sizeof( PHYSICAL_MEMORY_RUN )) );

        RtlCopyMemory( &block[DH_CONTEXT_RECORD],
                       context,
                       sizeof( CONTEXT ) );

        exception.ExceptionCode = STATUS_BREAKPOINT;
        exception.ExceptionRecord = (PEXCEPTION_RECORD) NULL;
        exception.NumberParameters = 0;
        exception.ExceptionFlags = EXCEPTION_NONCONTINUABLE;

#if defined(i386)

        exception.ExceptionAddress = (PVOID) context->Eip;

#elif defined(MIPS)

        exception.ExceptionAddress = (PVOID) context->Fir;

#elif defined(ALPHA)

        exception.ExceptionAddress = (PVOID) context->Fir;

#elif defined(_PPC_)

        exception.ExceptionAddress = (PVOID) context->Iar;

#else

#error( "unknown processor type" )

#endif

        RtlCopyMemory( &block[DH_EXCEPTION_RECORD],
                       &exception,
                       sizeof( EXCEPTION_RECORD ) );

        //
        // All of the pieces of the header file have been generated.  Before
        // mapping or writing anything to the disk, the I- & D-stream caches
        // must be flushed so that page color coherency is kept.  Sweep both
        // caches now.
        //

        KeSweepCurrentDcache();
        KeSweepCurrentIcache();

        //
        // Create MDL for dump.
        //

        mdl = (PMDL) &localMdl[0];
        MmCreateMdl( mdl, NULL, PAGE_SIZE );
        mdl->MdlFlags |= MDL_PAGES_LOCKED;

        mcb = dcb->FileDescriptorArray;

        page = (PULONG) (mdl + 1);
        *page = dcb->HeaderPfn;
        mdl->MdlFlags |= MDL_MAPPED_TO_SYSTEM_VA;

        bytesRemaining = PAGE_SIZE;
        memoryAddress = (ULONG) dcb->HeaderPage;

        //
        // All of the pieces of the header file have been generated.  Write
        // the header page to the paging file, using the appropriate drivers,
        // etc.
        //

#if DBG
        DbgPrint( "IoWriteCrashDump: Writing dump header to disk\n" );
#endif
        while (bytesRemaining) {

            if (mcb[0].QuadPart <= bytesRemaining) {
                byteCount = mcb[0].LowPart;
            } else {
                byteCount = bytesRemaining;
            }

            mdl->ByteCount = byteCount;
            mdl->ByteOffset = memoryAddress & (PAGE_SIZE - 1);
            mdl->MappedSystemVa = (PVOID) memoryAddress;

            //
            // Write to disk.
            //

            if (!NT_SUCCESS( write( &mcb[1], mdl ) )) {
                IopFinalCrashDumpStatus = STATUS_UNSUCCESSFUL;
                return FALSE;
            }

            //
            // Adjust bytes remaining.
            //

            bytesRemaining -= byteCount;
            memoryAddress += byteCount;
            mcb[0].QuadPart = mcb[0].QuadPart - byteCount;
            mcb[1].QuadPart = mcb[1].QuadPart + byteCount;

            if (!mcb[0].QuadPart) {
                mcb += 2;
            }
        }

#if DBG
            DbgPrint( "IoWriteCrashDump: Header page written\n" );
#endif
        //
        // The header page has been written to the paging file.  If a full dump
        // of all of physical memory is to be written, write it now.
        //

        if (dcb->Flags & DCB_DUMP_ENABLED) {

            ULONG pagesDoneSoFar = 0;
            ULONG currentPercentage = 0;
            ULONG maximumPercentage = 0;

#if DBG
            DbgPrint( "IoWriteCrashDump: Writing memory dump\n" );
#endif
            //
            // Set the virtual file offset and initialize loop variables and
            // constants.
            //

          //mdl->MdlFlags &= ~MDL_MAPPED_TO_SYSTEM_VA;
            memoryAddress = dcb->MemoryDescriptor->Run[0].BasePage * PAGE_SIZE;

            //
            // Now loop, writing all of physical memory to the paging file.
            //

            while (mcb[0].QuadPart) {

                diskByteOffset = mcb[1];

                //
                // Calculate byte offset;
                //

                byteOffset = memoryAddress & (PAGE_SIZE - 1);

                if (32768 <= mcb[0].QuadPart) {
                    byteCount = 32768 - byteOffset;
                } else {
                    byteCount = mcb[0].LowPart;
                }

                pagesDoneSoFar += byteCount / PAGE_SIZE;

                currentPercentage =
                    (pagesDoneSoFar * 100) /
                    dcb->MemoryDescriptor->NumberOfPages;

                if (currentPercentage > maximumPercentage) {

                    maximumPercentage = currentPercentage;
                    //
                    // Update message on screen.
                    //

                    sprintf( messageBuffer, "%Z: %3d\r", &dcb->PssProgressMsg, maximumPercentage );
                    HalDisplayString( messageBuffer );

                }

                //
                // Map the physical memory and write it to the
                // current segment of the file.
                //

                MapPhysicalMemory( mdl,
                                   memoryAddress,
                                   &dcb->MemoryDescriptor->Run[0],
                                   byteCount );

                //
                // Write the next segment.
                //

                if (!NT_SUCCESS( write( &diskByteOffset, mdl ) )) {
                    IopFinalCrashDumpStatus = STATUS_UNSUCCESSFUL;
                    return FALSE;
                }

                //
                // Adjust pointers for next part.
                //

                memoryAddress += byteCount;
                mcb[0].QuadPart = mcb[0].QuadPart - byteCount;
                mcb[1].QuadPart = mcb[1].QuadPart + byteCount;

                if (!mcb[0].QuadPart) {
                    mcb += 2;
                }

                if (pagesDoneSoFar >= dcb->MemoryDescriptor->NumberOfPages) {

                    break;

                }

            }

#if DBG
            DbgPrint( "IoWriteCrashDump: Memory dump written\n" );
#endif
        }

        sprintf( messageBuffer, "%Z", &dcb->PssDoneMsg );
        HalDisplayString( messageBuffer );

        //
        // Sweep the cache so the debugger will work.
        //

        KeSweepCurrentDcache();
        KeSweepCurrentIcache();

        //
        // Have the dump flush the adapter and disk caches.
        //
        finishUp();

        //
        // Indicate to the debugger that the dump has been successfully
        // written.
        //

        IopFinalCrashDumpStatus = STATUS_SUCCESS;
    }

    //
    // Check to see whether or not auto-reboots are enabled and, if so,
    // reboot now.
    //

    if (dcb->Flags & DCB_AUTO_REBOOT) {

#if DBG
        DbgPrint( "IoWriteCrashDump: Autorebooting\n" );
#endif
        KeReturnToFirmware( HalRebootRoutine );
    }

    return TRUE;
}

static
VOID
MapPhysicalMemory(
    IN OUT PMDL Mdl,
    IN ULONG MemoryAddress,
    IN PPHYSICAL_MEMORY_RUN PhysicalMemoryRun,
    IN ULONG Length
    )

/*++

Routine Description:

    This routine is invoked to fill in the specified MDL (Memory Descriptor
    List) w/the appropriate information to map the specified memory address
    range.

Arguments:

    Mdl - Address of the MDL to be filled in.

    MemoryAddress - Pseudo-virtual address being mapped.

    PhysicalMemoryRun - Base address of the physical memory run list.

    Length - Length of transfer to be mapped.

Return Value:

    None.

--*/

{
    PPHYSICAL_MEMORY_RUN pmr = PhysicalMemoryRun;
    PULONG page;
    ULONG pages;
    ULONG base;
    ULONG currentBase;

    //
    // Begin by determining the base physical page of the start of the address
    // range and filling in the MDL appropriately.
    //

    Mdl->StartVa = (PVOID) (MemoryAddress);
    Mdl->ByteOffset = MemoryAddress & (PAGE_SIZE - 1);
    Mdl->ByteCount = Length;

    //
    // Get the page frame index for the base address.
    //

    base = (ULONG) Mdl->StartVa >> PAGE_SHIFT;
    pages = COMPUTE_PAGES_SPANNED( (ULONG) MemoryAddress, Length );
    currentBase = pmr->BasePage;
    page = (PULONG) (Mdl + 1);

    //
    // Map all of the pages for this transfer until there are no more remaining
    // to be mapped.
    //

    while (pages) {

        //
        // Find the memory run that maps the beginning of this transfer.
        //

        while (currentBase + pmr->PageCount <= base) {
            currentBase += pmr->PageCount;
            pmr++;
        }

        //
        // The current memory run maps the start of this transfer.  Capture
        // the base page for the start of the transfer.
        //

        *page++ = pmr->BasePage + (base++ - currentBase);
        pages--;
    }

    //
    // All of the PFNs for the address range have been filled in so map the
    // physical memory into virtual address space.
    //

    MmMapMemoryDumpMdl( Mdl );
}