/*++

Copyright (c) 1989  Microsoft Corporation
Copyright (c) 1992  Digital Equipment Corporation

Module Name:

   physsect.c

Abstract:

    This module contains the routine for mapping physical sections for
    ALPHA machines.

Author:

    Lou Perazzoli (loup) 22-May-1989
    Joe Notarangelo      21-Sep-1992

Revision History:

    Landy Wang (landyw) 08-April-1998 : Modifications for 3-level 64-bit NT.

--*/

#include "mi.h"

//#define FIRSTDBG 1
//#define AGGREGATE_DBG FIRSTDBG


static
ULONG
MaximumAlignment( ULONG );

static
ULONG
AggregatePages( PMMPTE, ULONG, ULONG, PULONG );



NTSTATUS
MiMapViewOfPhysicalSection (
    IN PCONTROL_AREA ControlArea,
    IN PEPROCESS Process,
    IN PVOID *CapturedBase,
    IN PLARGE_INTEGER SectionOffset,
    IN PSIZE_T CapturedViewSize,
    IN ULONG ProtectionMask,
    IN ULONG_PTR ZeroBits,
    IN ULONG AllocationType,
    IN BOOLEAN WriteCombined,
    OUT PBOOLEAN ReleasedWsMutex
    )

/*++

Routine Description:

    This routine maps the specified physical section into the
    specified process's address space.

Arguments:

    see MmMapViewOfSection above...

    ControlArea - Supplies the control area for the section.

    Process - Supplies the process pointer which is receiving the section.

    ProtectionMask - Supplies the initial page protection-mask.

    ReleasedWsMutex - Supplies FALSE, receives TRUE if the working set
                      mutex is released.

Return Value:

    Status of the map view operation.

Environment:

    Kernel Mode, working set mutex and address creation mutex held.

--*/

{
    PMMVAD Vad;
    PVOID StartingAddress;
    PVOID EndingAddress;
    KIRQL OldIrql;
    PMMPTE PointerPpe;
    PMMPTE PointerPde;
    PMMPTE PointerPte;
    PMMPTE LastPte;
    MMPTE TempPte;
    PMMPFN Pfn2;
    ULONG PhysicalViewSize;
    ULONG Alignment;
    ULONG PagesToMap;
    ULONG NextPfn;
    PVOID UsedPageTableHandle;
    PVOID UsedPageDirectoryHandle;
    PMI_PHYSICAL_VIEW PhysicalView;

    //
    // Physical memory section.
    //

#ifdef FIRSTDBG

    DbgPrint( "MM: Physsect CaptureBase = %x  SectionOffset = %x\n",
              CapturedBase, SectionOffset->LowPart );
    DbgPrint( "MM: Physsect Allocation Type = %x, MEM_LARGE_PAGES = %x\n",
              AllocationType, MEM_LARGE_PAGES );

#endif //FIRSTDBG

    //
    // Compute the alignment we require for the virtual mapping.
    // The default is 64K to match protection boundaries.
    // Larger page sizes are used if MEM_LARGE_PAGES is requested.
    // The Alpha AXP architecture supports granularity hints so that
    // larger pages can be defined in the following multiples of
    // PAGE_SIZE:
    //            8**(GH) * PAGE_SIZE, where GH element of {0,1,2,3}
    //

    Alignment = X64K;

    if( AllocationType & MEM_LARGE_PAGES ){

        //
        // MaxAlignment is the maximum boundary alignment of the
        // SectionOffset (where the maximum boundary is one of the possible
        //                granularity hints boundaries)
        //

        ULONG MaxAlignment = MaximumAlignment( SectionOffset->LowPart );

        Alignment = (MaxAlignment > Alignment) ? MaxAlignment : Alignment;

#ifdef FIRSTDBG

        DbgPrint( "MM: Alignment = %x, SectionOffset = %x\n",
                       Alignment, SectionOffset->LowPart );

#endif //FIRSTDBG

    }


    LOCK_WS_UNSAFE (Process);

    if (*CapturedBase == NULL) {

        //
        // Attempt to locate address space.  This could raise an
        // exception.
        //

        try {

            //
            // Find a starting address on an alignment boundary.
            //


            PhysicalViewSize = (SectionOffset->LowPart + *CapturedViewSize) -
                               (ULONG)MI_64K_ALIGN(SectionOffset->LowPart);
            StartingAddress = MiFindEmptyAddressRange (PhysicalViewSize,
                                                       Alignment,
                                                       (ULONG)ZeroBits);

        } except (EXCEPTION_EXECUTE_HANDLER) {

            return GetExceptionCode();
        }

        EndingAddress = (PVOID)(((ULONG)StartingAddress +
                                PhysicalViewSize - 1L) | (PAGE_SIZE - 1L));
        StartingAddress = (PVOID)((ULONG)StartingAddress +
                                     (SectionOffset->LowPart & (X64K - 1)));

        if (ZeroBits > 0) {
            if (EndingAddress > (PVOID)((ULONG)0xFFFFFFFF >> ZeroBits)) {
                return STATUS_NO_MEMORY;
            }
        }

    } else {

        //
        // Check to make sure the specified base address to ending address
        // is currently unused.
        //

        PhysicalViewSize = (SectionOffset->LowPart + *CapturedViewSize) -
                                (ULONG)MI_64K_ALIGN(SectionOffset->LowPart);
        StartingAddress = (PVOID)((ULONG)MI_64K_ALIGN(*CapturedBase) +
                                    (SectionOffset->LowPart & (X64K - 1)));
        EndingAddress = (PVOID)(((ULONG)StartingAddress +
                                *CapturedViewSize - 1L) | (PAGE_SIZE - 1L));

        Vad = MiCheckForConflictingVad (StartingAddress, EndingAddress);
        if (Vad != (PMMVAD)NULL) {
#if 0
            MiDumpConflictingVad (StartingAddress, EndingAddress, Vad);
#endif

            return STATUS_CONFLICTING_ADDRESSES;
        }
    }

    //
    // An unoccuppied address range has been found, build the virtual
    // address descriptor to describe this range.
    //

    //
    // Establish an exception handler and attempt to allocate
    // the pool and charge quota.  Note that the InsertVad routine
    // will also charge quota which could raise an exception.
    //

    try  {

        PhysicalView = (PMI_PHYSICAL_VIEW)ExAllocatePoolWithTag (NonPagedPool,
                                                                 sizeof(MI_PHYSICAL_VIEW),
                                                                 MI_PHYSICAL_VIEW_KEY);
        if (PhysicalView == NULL) {
            ExRaiseStatus (STATUS_INSUFFICIENT_RESOURCES);
        }

        Vad = (PMMVAD)ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD), ' daV');
        if (Vad == NULL) {
            ExRaiseStatus (STATUS_INSUFFICIENT_RESOURCES);
        }

        PhysicalView->Vad = Vad;
        PhysicalView->StartVa = StartingAddress;
        PhysicalView->EndVa = EndingAddress;

        Vad->StartingVpn = MI_VA_TO_VPN (StartingAddress);
        Vad->EndingVpn = MI_VA_TO_VPN (EndingAddress);
        Vad->ControlArea = ControlArea;
        Vad->u.LongFlags = 0;
        Vad->u2.VadFlags2.Inherit = ViewUnmap;
        Vad->u.VadFlags.PhysicalMapping = 1;
        Vad->u4.Banked = NULL;
        // Vad->u.VadFlags.ImageMap = 0;
        Vad->u.VadFlags.Protection = ProtectionMask;
        Vad->u2.VadFlags2.CopyOnWrite = 0;
        // Vad->u.VadFlags.LargePages = 0;
        Vad->FirstPrototypePte =
                       (PMMPTE)(MI_CONVERT_PHYSICAL_BUS_TO_PFN(*SectionOffset));

        //
        // Set the first prototype PTE field in the Vad.
        //

        Vad->LastContiguousPte =
                       (PMMPTE)(MI_CONVERT_PHYSICAL_BUS_TO_PFN(*SectionOffset));

        //
        // Insert the VAD.  This could get an exception.
        //

        MiInsertVad (Vad);

    } except (EXCEPTION_EXECUTE_HANDLER) {

        if (PhysicalView != NULL) {
            ExFreePool (PhysicalView);
        }

        if (Vad != (PMMVAD)NULL) {

            //
            // The pool allocation suceeded, but the quota charge
            // in InsertVad failed, deallocate the pool and return
            // an error.
            //

            ExFreePool (Vad);
            return GetExceptionCode();
        }
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    // Increment the count of the number of views for the
    // section object.  This requires the PFN mutex to be held.
    //

    LOCK_AWE (Process, OldIrql);
    LOCK_PFN_AT_DPC ();

    if (PhysicalView->Vad->u.VadFlags.PhysicalMapping == 1) {
        Process->HasPhysicalVad = 1;
    }

    InsertHeadList (&Process->PhysicalVadList, &PhysicalView->ListEntry);

    ControlArea->NumberOfMappedViews += 1;
    ControlArea->NumberOfUserReferences += 1;
    ASSERT (ControlArea->NumberOfSectionReferences != 0);

    UNLOCK_PFN_FROM_DPC ();
    UNLOCK_AWE (Process, OldIrql);

    //
    // Build the PTEs in the address space.
    //

    PointerPpe = MiGetPpeAddress (StartingAddress);
    PointerPde = MiGetPdeAddress (StartingAddress);
    PointerPte = MiGetPteAddress (StartingAddress);
    LastPte = MiGetPteAddress (EndingAddress);

#if defined (_WIN64)
    MiMakePpeExistAndMakeValid (PointerPpe, Process, FALSE);
    if (PointerPde->u.Long == 0) {
        UsedPageDirectoryHandle = MI_GET_USED_PTES_HANDLE (PointerPte);
        ASSERT (MI_GET_USED_PTES_FROM_HANDLE (UsedPageDirectoryHandle) == 0); 
        MI_INCREMENT_USED_PTES_BY_HANDLE (UsedPageDirectoryHandle);
    }
#endif

    MiMakePdeExistAndMakeValid(PointerPde, Process, FALSE);

    Pfn2 = MI_PFN_ELEMENT(PointerPde->u.Hard.PageFrameNumber);

    PagesToMap = ( ((ULONG)EndingAddress - (ULONG)StartingAddress)
                   + (PAGE_SIZE-1) ) >> PAGE_SHIFT;

    NextPfn = MI_CONVERT_PHYSICAL_BUS_TO_PFN(*SectionOffset);

#ifdef FIRSTDBG

    DbgPrint( "MM: Physsect, PagesToMap = %x  NextPfn = %x\n",
                   PagesToMap, NextPfn );

#endif //FIRSTDBG

    MI_MAKE_VALID_PTE (TempPte,
                       NextPfn,
                       ProtectionMask,
                       PointerPte);

    if (WriteCombined == TRUE) {
        MI_SET_PTE_WRITE_COMBINE (TempPte);
    }

    if (TempPte.u.Hard.Write) {
            TempPte.u.Hard.Dirty = 1;
    }
    
    while (PointerPte <= LastPte) {

        ULONG PagesTogether;
        ULONG GranularityHint;

        //
        // Compute the number of pages that can be mapped together
        //

        if (AllocationType & MEM_LARGE_PAGES) {
            PagesTogether = AggregatePages (PointerPte,
                                            NextPfn,
                                            PagesToMap,
                                            &GranularityHint);
        } else {
            PagesTogether = 1;
            GranularityHint = 0;
        }

#ifdef FIRSTDBG

        DbgPrint( "MM: Physsect  PointerPte = %x, NextPfn = %x\n",
                       PointerPte, NextPfn );
        DbgPrint( "MM: Va = %x  TempPte.Pfn = %x\n",
                       MiGetVirtualAddressMappedByPte( PointerPte ),
                       TempPte.u.Hard.PageFrameNumber );
        DbgPrint( "MM: PagesToMap = %x\n", PagesToMap );
        DbgPrint( "MM: PagesTogether = %x, GH = %x\n",
                       PagesTogether, GranularityHint );

#endif //FIRSTDBG

        TempPte.u.Hard.GranularityHint = GranularityHint;

        NextPfn += PagesTogether;
        PagesToMap -= PagesTogether;

        UsedPageTableHandle = MI_GET_USED_PTES_HANDLE (MiGetVirtualAddressMappedByPte (PointerPte));

        while (PagesTogether--) {

            if (MiIsPteOnPdeBoundary (PointerPte)) {

                PointerPde = MiGetPteAddress (PointerPte);

                if (MiIsPteOnPpeBoundary (PointerPte)) {
                    PointerPpe = MiGetPteAddress (PointerPde);
                    MiMakePpeExistAndMakeValid (PointerPpe, Process, FALSE);
                    if (PointerPde->u.Long == 0) {
                        UsedPageDirectoryHandle = MI_GET_USED_PTES_HANDLE (PointerPte);
                        ASSERT (MI_GET_USED_PTES_FROM_HANDLE (UsedPageDirectoryHandle) == 0); 
                
                        MI_INCREMENT_USED_PTES_BY_HANDLE (UsedPageDirectoryHandle);
                    }
                }

                MiMakePdeExistAndMakeValid (PointerPde, Process, FALSE);
                Pfn2 = MI_PFN_ELEMENT (PointerPde->u.Hard.PageFrameNumber);
                UsedPageTableHandle = MI_GET_USED_PTES_HANDLE (MiGetVirtualAddressMappedByPte (PointerPte));
            }

            ASSERT( PointerPte->u.Long == 0 );

            *PointerPte = TempPte;
#if PFN_CONSISTENCY
            LOCK_PFN (OldIrql);
#endif
            Pfn2->u2.ShareCount += 1;
#if PFN_CONSISTENCY
            UNLOCK_PFN (OldIrql);
#endif

            //
            // Increment the count of non-zero page table entries for this
            // page table and the number of private pages for the process.
            //

            MI_INCREMENT_USED_PTES_BY_HANDLE (UsedPageTableHandle);

            PointerPte += 1;

            TempPte.u.Hard.PageFrameNumber += 1;

        } // while (PagesTogether-- )

    }  // while (PointerPte <= LastPte)

    UNLOCK_WS_UNSAFE (Process);
    *ReleasedWsMutex = TRUE;

    //
    // Update the current virtual size in the process header.
    //

    *CapturedViewSize = (ULONG)EndingAddress - (ULONG)StartingAddress + 1L;
    Process->VirtualSize += *CapturedViewSize;

    if (Process->VirtualSize > Process->PeakVirtualSize) {
        Process->PeakVirtualSize = Process->VirtualSize;
    }

    //
    // Translate the virtual address to a quasi-virtual address for
    // use by drivers that touch mapped devices.  Note: the routine
    // HalCreateQva will not translate the StartingAddress if the
    // StartingAddress is within system memory address space.
    //
    // N.B. - It will not work to attempt map addresses that begin in
    // system memory and extend through i/o space.
    //

    *CapturedBase = HalCreateQva( *SectionOffset, StartingAddress );

    return STATUS_SUCCESS;
}


ULONG
MaximumAlignment(
    IN ULONG Offset
    )
/*++

Routine Description:

    This routine returns the maximum granularity hint alignment boundary
    to which Offset is naturally aligned.

Arguments:

    Offset - Supplies the address offset to check for alignment.

Return Value:

    The number which represents the largest natural alignment of Offset.

Environment:

--*/
{

    if( (Offset & (GH3_PAGE_SIZE - 1)) == 0 ){
        return GH3_PAGE_SIZE;
    }

    if( (Offset & (GH2_PAGE_SIZE - 1)) == 0 ){
        return GH2_PAGE_SIZE;
    }

    if( (Offset & (GH1_PAGE_SIZE - 1)) == 0 ){
        return GH1_PAGE_SIZE;
    }

    if( (Offset & (PAGE_SIZE - 1)) == 0 ){
        return PAGE_SIZE;
    }

    return 0;
}


ULONG
AggregatePages(
    IN PMMPTE  PointerPte,
    IN ULONG   Pfn,
    IN ULONG   Pages,
    OUT PULONG GranularityHint
    )
/*++

Routine Description:

    This routine computes the number of standard size pages that can be
    aggregated into a single large page and returns the granularity hint
    for that size large page.

Arguments:

    PointerPte - Supplies the PTE pointer for the starting virtual address
                     of the mapping.
    Pfn - Supplies the starting page frame number of the memory to be
                     mapped.
    Pages - Supplies the number of pages to map.

    GranularityHint - Receives the granularity hint for the large page used
                         to aggregate the standard pages.

Return Value:

    The number of pages that can be aggregated together.

Environment:

--*/
{

    ULONG MaxVirtualAlignment;
    ULONG MaxPhysicalAlignment;
    ULONG MaxPageAlignment;
    ULONG MaxAlignment;

    //
    // Determine the largest page that will map a maximum of Pages.
    // The largest page must be both virtually and physically aligned
    // to the large page size boundary.
    // Determine the largest common alignment for the virtual and
    // physical addresses, factor in Pages, and then match to the
    // largest page size possible via the granularity hints.
    //

    MaxVirtualAlignment = MaximumAlignment((ULONG)
                              MiGetVirtualAddressMappedByPte( PointerPte ) );
    MaxPhysicalAlignment = MaximumAlignment( (ULONG)(Pfn << PAGE_SHIFT) );

    MaxPageAlignment = (ULONG)(Pages << PAGE_SHIFT);

#ifdef AGGREGATE_DBG

    DbgPrint( "MM: Aggregate MaxVirtualAlign = %x\n", MaxVirtualAlignment );
    DbgPrint( "MM: Aggregate MaxPhysicalAlign = %x\n", MaxPhysicalAlignment );
    DbgPrint( "MM: Aggregate MaxPageAlign = %x\n", MaxPageAlignment );

#endif //AGGREGATE_DBG
    //
    // Maximum alignment is the minimum of the virtual and physical alignments.
    //

    MaxAlignment =  (MaxVirtualAlignment > MaxPhysicalAlignment) ?
                        MaxPhysicalAlignment : MaxVirtualAlignment;
    MaxAlignment = (MaxAlignment > MaxPageAlignment) ?
                        MaxPageAlignment : MaxAlignment;

    //
    // Convert MaxAlignment to granularity hint value
    //

    if( (MaxAlignment & (GH3_PAGE_SIZE - 1)) == 0 ){

        *GranularityHint = GH3;

    } else if( (MaxAlignment & (GH2_PAGE_SIZE - 1)) == 0 ){

        *GranularityHint = GH2;

    } else if( (MaxAlignment & (GH1_PAGE_SIZE - 1)) == 0 ){

        *GranularityHint = GH1;

    } else if( (MaxAlignment & (PAGE_SIZE - 1)) == 0 ){

        *GranularityHint = GH0;

    } else {

        *GranularityHint = GH0;

#if DBG

        DbgPrint( "MM: Aggregate Physical pages - not page aligned\n" );

#endif //DBG

    } // end, if then elseif

    //
    // Return number of pages aggregated.
    //

    return( MaxAlignment >> PAGE_SHIFT );

}