/***************************************************************************

Copyright (c) 1999  Microsoft Corporation

Module Name:

    RNDISMP.C

Abstract:

    Remote NDIS Miniport driver. Sits on top of Remote NDIS bus specific
    layers.

Environment:

    kernel mode only

Notes:

    THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
    KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
    PURPOSE.

    Copyright (c) 1999 Microsoft Corporation.  All Rights Reserved.


Revision History:

    5/6/99 : created

Author:

    Tom Green

    
****************************************************************************/

#include "precomp.h"



//
// miniport driver block list (miniport layer may support several microports)
//
DRIVER_BLOCK        RndismpMiniportBlockListHead = {0};

UINT                RndismpNumMicroports = 0;

NDIS_SPIN_LOCK      RndismpGlobalLock;

ULONG               RndisForceReset = FALSE;

#ifdef TESTING
UCHAR               OffloadBuffer[sizeof(NDIS_TASK_OFFLOAD_HEADER) +
                                  sizeof(NDIS_TASK_OFFLOAD) +
                                  sizeof(NDIS_TASK_TCP_IP_CHECKSUM)];
PUCHAR              pOffloadBuffer = OffloadBuffer;
ULONG               OffloadSize = sizeof(OffloadBuffer);
#endif

#ifdef RAW_ENCAP
ULONG               gRawEncap = TRUE;
#else
ULONG               gRawEncap = FALSE;
#endif

//
//  A list of NDIS versions we cycle through, trying to register the
//  highest version we can with NDIS. This is so that we can run on
//  earlier platforms.
//
//  To support a newer version, add an entry at the TOP of the list.
//
struct _RNDISMP_NDIS_VERSION_TABLE
{
    UCHAR           MajorVersion;
    UCHAR           MinorVersion;
    ULONG           CharsSize;
} RndismpNdisVersionTable[] =

{
#ifdef NDIS51_MINIPORT
    {5, 1, sizeof(NDIS51_MINIPORT_CHARACTERISTICS)},
#endif

    {5, 0, sizeof(NDIS50_MINIPORT_CHARACTERISTICS)}
};

ULONG   RndismpNdisVersions = sizeof(RndismpNdisVersionTable) /
                              sizeof(struct _RNDISMP_NDIS_VERSION_TABLE);


/****************************************************************************/
/*                          DriverEntry                                     */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*   Driver entry routine. Never called, Microport driver entry is used     */
/*                                                                          */
/****************************************************************************/
NTSTATUS
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    // this is never called. Driver entry in Microport is entry.
    TRACE1(("DriverEntry\n"));

    return NDIS_STATUS_SUCCESS;
} // DriverEntry

/****************************************************************************/
/*                          RndisMInitializeWrapper                         */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*   RndisMInitializeWrapper is called from the microport to init driver    */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*   pNdisWrapperHandle    - Pass NDIS wrapper handle back to microport     */
/*   MicroportContext      - Microport "Global" context                     */
/*   DriverObject          - Driver object                                  */
/*   RegistryPath          - Registry path                                  */
/*   pCharacteristics      - Characteristics of RNDIS microport             */
/*                                                                          */
/* Return Value:                                                            */
/*                                                                          */
/*   NDIS_STATUS_SUCCESS                                                    */
/*   NDIS_STATUS_PENDING                                                    */
/*                                                                          */
/****************************************************************************/
NDIS_STATUS
RndisMInitializeWrapper(OUT PNDIS_HANDLE                      pNdisWrapperHandle,
                        IN  PVOID                             MicroportContext,
                        IN  PVOID                             DriverObject,
                        IN  PVOID                             RegistryPath,
                        IN  PRNDIS_MICROPORT_CHARACTERISTICS  pCharacteristics)
{
    // Receives the status of the NdisMRegisterMiniport operation.
    NDIS_STATUS                         Status;

    // Characteristics table for this driver
    NDIS_MINIPORT_CHARACTERISTICS       RndismpChar;

    // Pointer to the global information for this driver
    PDRIVER_BLOCK                       NewDriver;

    // Handle for referring to the wrapper about this driver.
    NDIS_HANDLE                         NdisWrapperHandle;

    ULONG                               i;

    TRACE3(("RndisMInitializeWrapper\n"));

    // allocate the driver block object, exit if error occurs
    Status = MemAlloc(&NewDriver, sizeof(DRIVER_BLOCK));

    if(Status != NDIS_STATUS_SUCCESS)
    {
        TRACE2(("Block Allocate Memory failed (%08X)\n", Status));
        return Status;
    }

    // Initialize the wrapper.
    NdisMInitializeWrapper(&NdisWrapperHandle,
                           (PDRIVER_OBJECT)DriverObject,
                           RegistryPath,
                           NULL);

    // Save the global information about this driver.
    NewDriver->NdisWrapperHandle        = NdisWrapperHandle;
    NewDriver->AdapterList              = (PRNDISMP_ADAPTER) NULL;
    NewDriver->DriverObject             = DriverObject;
    NewDriver->Signature                = BLOCK_SIGNATURE;

    // get handlers passed in from microport
    NewDriver->RmInitializeHandler      = pCharacteristics->RmInitializeHandler;
    NewDriver->RmInitCompleteNotifyHandler  = pCharacteristics->RmInitCompleteNotifyHandler;
    NewDriver->RmHaltHandler            = pCharacteristics->RmHaltHandler;
    NewDriver->RmShutdownHandler        = pCharacteristics->RmShutdownHandler;
    NewDriver->RmUnloadHandler          = pCharacteristics->RmUnloadHandler;
    NewDriver->RmSendMessageHandler     = pCharacteristics->RmSendMessageHandler;
    NewDriver->RmReturnMessageHandler   = pCharacteristics->RmReturnMessageHandler;

    // save microport "Global" context
    NewDriver->MicroportContext         = MicroportContext;

    // pass the microport the wrapper handle
    *pNdisWrapperHandle                 = (NDIS_HANDLE) NdisWrapperHandle;

    // initialize the Miniport characteristics for the call to NdisMRegisterMiniport.
    NdisZeroMemory(&RndismpChar, sizeof(RndismpChar));
    
    RndismpChar.HaltHandler             = RndismpHalt;
    RndismpChar.InitializeHandler       = RndismpInitialize;
    RndismpChar.QueryInformationHandler = RndismpQueryInformation;
    RndismpChar.ReconfigureHandler      = RndismpReconfigure;
    RndismpChar.ResetHandler            = RndismpReset;
    RndismpChar.SendPacketsHandler      = RndismpMultipleSend;
    RndismpChar.SetInformationHandler   = RndismpSetInformation;
    RndismpChar.ReturnPacketHandler     = RndismpReturnPacket;
    RndismpChar.CheckForHangHandler     = RndismpCheckForHang;
    RndismpChar.DisableInterruptHandler = NULL;
    RndismpChar.EnableInterruptHandler  = NULL;
    RndismpChar.HandleInterruptHandler  = NULL;
    RndismpChar.ISRHandler              = NULL;
    RndismpChar.SendHandler             = NULL;
    RndismpChar.TransferDataHandler     = NULL;
#if CO_RNDIS
    RndismpChar.CoSendPacketsHandler    = RndismpCoSendPackets;
    RndismpChar.CoCreateVcHandler       = RndismpCoCreateVc;
    RndismpChar.CoDeleteVcHandler       = RndismpCoDeleteVc;
    RndismpChar.CoActivateVcHandler     = RndismpCoActivateVc;
    RndismpChar.CoDeactivateVcHandler   = RndismpCoDeactivateVc;
    RndismpChar.CoRequestHandler        = RndismpCoRequest;
#endif // CO_RNDIS

#ifdef NDIS51_MINIPORT
    RndismpChar.PnPEventNotifyHandler   = RndismpPnPEventNotify;
    RndismpChar.AdapterShutdownHandler  = RndismpShutdownHandler;
#endif

    for (i = 0; i < RndismpNdisVersions; i++)
    {
        RndismpChar.MajorNdisVersion = RndismpNdisVersionTable[i].MajorVersion;
        RndismpChar.MinorNdisVersion = RndismpNdisVersionTable[i].MinorVersion;

        Status = NdisMRegisterMiniport(NdisWrapperHandle,
                                       &RndismpChar,
                                       RndismpNdisVersionTable[i].CharsSize);

        if (Status == NDIS_STATUS_SUCCESS)
        {
            TRACE1(("InitializeWrapper: successfully registered as a %d.%d miniport\n",
                RndismpNdisVersionTable[i].MajorVersion,
                RndismpNdisVersionTable[i].MinorVersion));

            NewDriver->MajorNdisVersion = RndismpNdisVersionTable[i].MajorVersion;
            NewDriver->MinorNdisVersion = RndismpNdisVersionTable[i].MinorVersion;

            break;
        }
    }

    if(Status != NDIS_STATUS_SUCCESS)
    {
        Status = STATUS_UNSUCCESSFUL;

        // free up memory allocated for block
        MemFree(NewDriver, sizeof(DRIVER_BLOCK));
    }
    else
    {
        // everything went fine, so add the driver block to the list
        AddDriverBlock(&RndismpMiniportBlockListHead, NewDriver);

#ifndef BUILD_WIN9X
        // if we are running on a platform < NDIS 5.1, attempt to support
        // surprise removal.
        HookPnpDispatchRoutine(NewDriver);
#endif

#ifndef BUILD_WIN9X
        // Not supported on Win98 Gold:
        NdisMRegisterUnloadHandler(NdisWrapperHandle, RndismpUnload);
#endif
    }

    return (NDIS_STATUS) Status;

} // RndisMInitializeWrapper


/****************************************************************************/
/*                          RndismpUnload                                   */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Called by NDIS when this driver is unloaded.                            */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pDriverObject - Pointer to driver object.                               */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   VOID                                                                   */
/*                                                                          */
/****************************************************************************/
VOID
RndismpUnload(IN PDRIVER_OBJECT pDriverObject)
{
    PDRIVER_BLOCK       DriverBlock;

    // Find our Driver block for this driver object.
    DriverBlock = DriverObjectToDriverBlock(&RndismpMiniportBlockListHead, pDriverObject);

    TRACE1(("RndismpUnload: DriverObj %x, DriverBlock %x\n", pDriverObject, DriverBlock));

    if (DriverBlock)
    {
        if (DriverBlock->RmUnloadHandler)
        {
            (DriverBlock->RmUnloadHandler)(DriverBlock->MicroportContext);
        }

        RemoveDriverBlock(&RndismpMiniportBlockListHead, DriverBlock);

        MemFree(DriverBlock, sizeof(*DriverBlock));
    }

    TRACE1(("RndismpUnload: Done\n"));

}

#ifndef BUILD_WIN9X

/****************************************************************************/
/*                          DllInitialize                                   */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Called by the system when this driver is loaded.                        */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pRegistryPath - Pointer to registry path for this service.              */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   NTSTATUS - success always                                              */
/*                                                                          */
/****************************************************************************/
NTSTATUS
DllInitialize(IN PUNICODE_STRING    pRegistryPath)
{
#if DBG
    DbgPrint("RNDISMP: RndismpDebugFlags set to %x, &RndismpDebugFlags is %p\n",
                RndismpDebugFlags, &RndismpDebugFlags);
#endif

    TRACE1(("DllInitialize\n"));
#ifdef TESTING
    {
        PNDIS_TASK_OFFLOAD_HEADER   pOffloadHdr = (PNDIS_TASK_OFFLOAD_HEADER)pOffloadBuffer;
        PNDIS_TASK_OFFLOAD  pTask;
        PNDIS_TASK_TCP_IP_CHECKSUM pChksum;

        pOffloadHdr->Version = NDIS_TASK_OFFLOAD_VERSION;
        pOffloadHdr->Size = sizeof(NDIS_TASK_OFFLOAD_HEADER);
        pOffloadHdr->EncapsulationFormat.Encapsulation = IEEE_802_3_Encapsulation;
        pOffloadHdr->EncapsulationFormat.EncapsulationHeaderSize = 0; // ?
        pOffloadHdr->EncapsulationFormat.Flags.FixedHeaderSize = 0;
        pOffloadHdr->OffsetFirstTask = sizeof(NDIS_TASK_OFFLOAD_HEADER);

        pTask = (PNDIS_TASK_OFFLOAD)(pOffloadHdr + 1);
        pTask->Version = NDIS_TASK_OFFLOAD_VERSION;
        pTask->Size = sizeof(NDIS_TASK_OFFLOAD);
        pTask->Task = TcpIpChecksumNdisTask;
        pTask->OffsetNextTask = 0;
        pTask->TaskBufferLength = sizeof(NDIS_TASK_TCP_IP_CHECKSUM);

        pChksum = (PNDIS_TASK_TCP_IP_CHECKSUM)&pTask->TaskBuffer[0];
        *(PULONG)pChksum = 0;
        pChksum->V4Transmit.TcpChecksum = 1;
        pChksum->V4Transmit.UdpChecksum = 1;
    }
#endif

    return STATUS_SUCCESS;
}

#endif // !BUILD_WIN9X

/****************************************************************************/
/*                          DllUnload                                       */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Called by the system when this driver is unloaded.                      */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  None                                                                    */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   NTSTATUS - success always                                              */
/*                                                                          */
/****************************************************************************/
NTSTATUS
DllUnload(VOID)
{
#if DBG
    DbgPrint("RNDISMP: DllUnload called!\n");
#endif

    return STATUS_SUCCESS;
}

/****************************************************************************/
/*                          RndismpHalt                                     */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Stop the adapter and release resources                                  */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   VOID                                                                   */
/*                                                                          */
/****************************************************************************/
VOID
RndismpHalt(IN NDIS_HANDLE MiniportAdapterContext)
{

#ifdef BUILD_WIN9X
    //
    //  On Win98/SE, we would have intercepted the config mgr handler.
    //  Put it back the way it was.
    //
    UnHookNtKernCMHandler((PRNDISMP_ADAPTER)MiniportAdapterContext);
#endif

    RndismpInternalHalt(MiniportAdapterContext, TRUE);
}

/****************************************************************************/
/*                          RndismpInternalHalt                             */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Internal Halt routine. This is usually called from the MiniportHalt     */
/*  entry point, but it may also be called when we are notified of surprise */
/*  removal by NDIS. Do all work atmost once.                               */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*  bCalledFromHalt - Is this called from the MiniportHalt entry point?     */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*  VOID                                                                    */
/*                                                                          */
/****************************************************************************/
VOID
RndismpInternalHalt(IN NDIS_HANDLE MiniportAdapterContext,
                    IN BOOLEAN bCalledFromHalt)
{
    PRNDISMP_ADAPTER            Adapter;
    PDRIVER_BLOCK               DriverBlock;
    PRNDISMP_MESSAGE_FRAME      pMsgFrame;
    BOOLEAN                     bWokenUp;
    UINT                        Count, LoopCount;

    // get adapter context
    Adapter = PRNDISMP_ADAPTER_FROM_CONTEXT_HANDLE(MiniportAdapterContext);

    CHECK_VALID_ADAPTER(Adapter);

    TRACE1(("RndismpInternalHalt: Adapter %x, Halting %d, CalledFromHalt %d\n", Adapter, Adapter->Halting, bCalledFromHalt));

    FlushPendingMessages(Adapter);

    if (!Adapter->Halting)
    {
        pMsgFrame = BuildRndisMessageCommon(Adapter, 
                                            NULL,
                                            REMOTE_NDIS_HALT_MSG,
                                            0,
                                            NULL,
                                            0);

        if(pMsgFrame)
        {
            RNDISMP_ACQUIRE_ADAPTER_LOCK(Adapter);

            Adapter->Halting = TRUE;
            NdisInitializeEvent(&Adapter->HaltWaitEvent);

            RNDISMP_RELEASE_ADAPTER_LOCK(Adapter);

            // send the message to the microport
            RNDISMP_SEND_TO_MICROPORT(Adapter, pMsgFrame, FALSE, CompleteSendHalt);

            // wait for the -Send- to complete
            bWokenUp = NdisWaitEvent(&Adapter->HaltWaitEvent, MINIPORT_HALT_TIMEOUT);
        }
        else
        {
            ASSERT(FALSE);
            // Consider allocating the Halt message during Init time.
        }

        //
        // Wait for any outstanding receives to finish before halting
        // the microport.
        //
        LoopCount = 0;
        while ((Count = NdisPacketPoolUsage(Adapter->ReceivePacketPool)) != 0)
        {
            TRACE1(("RndismpInternalHalt: Adapter %p, Pkt pool %x has "
                "%d outstanding\n",
                Adapter, Adapter->ReceivePacketPool, Count));

            NdisMSleep(200);

            if (LoopCount++ > 30)
            {
                TRACE1(("RndismpInternalHalt: Adapter %p, cant reclaim packet pool %x\n",
                        Adapter, Adapter->ReceivePacketPool));
                break;
            }
        }

        //
        // Wait for send-messages pending at the microport to finish.
        // Since we have set Halting to TRUE, no -new- messages will
        // be sent down, however there may be running threads that
        // have gone past the check for Halting - allow those
        // threads to finish now.
        //
        LoopCount = 0;
        while (Adapter->CurPendedMessages)
        {
            TRACE1(("RndismpInternalHalt: Adapter %p, %d msgs at microport\n",
                Adapter, Adapter->CurPendedMessages));

            NdisMSleep(200);

            if (LoopCount++ > 30)
            {
                TRACE1(("RndismpInternalHalt: Adapter %p, %d messages not send-completed!\n",
                        Adapter, Adapter->CurPendedMessages));
                break;
            }
        }

        // cancel our keep alive timer
        NdisCancelTimer(&Adapter->KeepAliveTimer, &Adapter->TimerCancelled);

        // call the microport halt handler
        Adapter->RmHaltHandler(Adapter->MicroportAdapterContext);
    }

    if (bCalledFromHalt)
    {
        // free lists associated with OID support
        FreeOIDLists(Adapter);

        // free the adapter spinlock
        NdisFreeSpinLock(&Adapter->Lock);

        // save driver block pointer
        DriverBlock = Adapter->DriverBlock;

        // remove adapter from list
        RemoveAdapter(Adapter);

        // Free the Adapter and associated memory resources
        FreeAdapter(Adapter);
    }

} // RndismpInternalHalt


/****************************************************************************/
/*                          RndismpReconfigure                              */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  NDIS calls this when the device is pulled. Note: only on WinMe!         */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*  VOID                                                                    */
/*                                                                          */
/****************************************************************************/
NDIS_STATUS
RndismpReconfigure(OUT PNDIS_STATUS pStatus,
                   IN NDIS_HANDLE MiniportAdapterContext,
                   IN NDIS_HANDLE ConfigContext)
{
    PRNDISMP_ADAPTER        pAdapter;

    // get adapter context
    pAdapter = PRNDISMP_ADAPTER_FROM_CONTEXT_HANDLE(MiniportAdapterContext);

    CHECK_VALID_ADAPTER(pAdapter);

    TRACE1(("Reconfig: Adapter %x\n", pAdapter));

    RndismpInternalHalt(pAdapter, FALSE);

    *pStatus = NDIS_STATUS_SUCCESS;

    return (NDIS_STATUS_SUCCESS);
}
    

/****************************************************************************/
/*                          RndismpReset                                    */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  The RndismpReset request instructs the Miniport to issue a hardware     */
/*  reset to the network adapter.  The driver also resets its software      */
/*  state.  See the description of NdisMReset for a detailed description    */
/*  of this request.                                                        */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  AddressingReset - Does the adapter need the addressing information      */
/*   reloaded.                                                              */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*    NDIS_STATUS                                                           */
/*                                                                          */
/****************************************************************************/
NDIS_STATUS
RndismpReset(OUT PBOOLEAN    AddressingReset,
             IN  NDIS_HANDLE MiniportAdapterContext)
{
    PRNDISMP_ADAPTER        Adapter;
    PRNDISMP_MESSAGE_FRAME  pMsgFrame;
    NDIS_STATUS             Status;

    // get adapter context
    Adapter = PRNDISMP_ADAPTER_FROM_CONTEXT_HANDLE(MiniportAdapterContext);

    CHECK_VALID_ADAPTER(Adapter);
    ASSERT(Adapter->ResetPending == FALSE);

    TRACE1(("RndismpReset: Adapter %x\n", Adapter));

    Adapter->ResetPending = TRUE;

    FlushPendingMessages(Adapter);

    pMsgFrame = BuildRndisMessageCommon(Adapter, 
                                        NULL,
                                        REMOTE_NDIS_RESET_MSG,
                                        0,
                                        NULL,
                                        0);

    if (pMsgFrame)
    {
        RNDISMP_ACQUIRE_ADAPTER_LOCK(Adapter);

        Adapter->NeedReset = FALSE;

        //
        // Fix water mark so that the reset gets sent down.
        //
        Adapter->HiWatPendedMessages = RNDISMP_PENDED_SEND_HIWAT + 1;

        RNDISMP_RELEASE_ADAPTER_LOCK(Adapter);

        // send the message to the microport
        RNDISMP_SEND_TO_MICROPORT(Adapter, pMsgFrame, FALSE, CompleteSendReset);
        Status = NDIS_STATUS_PENDING;

        RNDISMP_ACQUIRE_ADAPTER_LOCK(Adapter);

        Adapter->HiWatPendedMessages--;

        RNDISMP_RELEASE_ADAPTER_LOCK(Adapter);
    }
    else
    {
        CompleteMiniportReset(Adapter, NDIS_STATUS_RESOURCES, FALSE);
        Status = NDIS_STATUS_PENDING;
    }

    return Status;
} // RndismpReset

/****************************************************************************/
/*                          RndismpCheckForHang                             */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Check and see if device is "hung"                                       */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   BOOLEAN                                                                */
/*                                                                          */
/****************************************************************************/
BOOLEAN
RndismpCheckForHang(IN NDIS_HANDLE MiniportAdapterContext)
{
    PRNDISMP_ADAPTER        Adapter;
    BOOLEAN                 bReturnHung;
    PRNDISMP_MESSAGE_FRAME  pMsgFrame;
    PLIST_ENTRY             pEnt;

    Adapter = PRNDISMP_ADAPTER_FROM_CONTEXT_HANDLE(MiniportAdapterContext);

    TRACE2(("RndismpCheckForHang: Adapter %x\n", Adapter));

    CHECK_VALID_ADAPTER(Adapter);

    RNDISMP_ACQUIRE_ADAPTER_LOCK(Adapter);

    bReturnHung = (Adapter->NeedReset && !Adapter->ResetPending);

#if THROTTLE_MESSAGES
    // Try to grow the pending send window, if we can.
    //
    if (!Adapter->SendInProgress)
    {
        if (Adapter->CurPendedMessages == 0)
        {
            Adapter->HiWatPendedMessages = RNDISMP_PENDED_SEND_HIWAT;
            Adapter->LoWatPendedMessages = RNDISMP_PENDED_SEND_LOWAT;
        }
    }

    if (!bReturnHung && !Adapter->ResetPending)
    {
        //
        //  Check if the microport isn't completing messages.
        //
        if (!IsListEmpty(&Adapter->PendingAtMicroportList))
        {
            pEnt = Adapter->PendingAtMicroportList.Flink;
            pMsgFrame = CONTAINING_RECORD(pEnt, RNDISMP_MESSAGE_FRAME, PendLink);

            if (pMsgFrame->TicksOnQueue > 4)
            {
                TRACE1(("CheckForHang: Adapter %x, Msg %x has timed out!\n",
                        Adapter, pMsgFrame));
                bReturnHung = TRUE;
            }
            else
            {
                pMsgFrame->TicksOnQueue++;
            }
        }
    }

#endif // THROTTLE_MESSAGES

    if (RndisForceReset)
    {
        RndisForceReset = FALSE;
        Adapter->NeedReset = TRUE;
        Adapter->ResetPending = FALSE;
        bReturnHung = TRUE;
    }

    RNDISMP_RELEASE_ADAPTER_LOCK(Adapter);

    return (bReturnHung);


} // RndismpCheckForHang


/****************************************************************************/
/*                          RndismpInitialize                               */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*   RndismpInitialize starts an adapter and registers resources with the   */
/*   wrapper.                                                               */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*    OpenErrorStatus - Extra status bytes for opening token ring adapters. */
/*    SelectedMediumIndex - Index of the media type chosen by the driver.   */
/*    MediumArray - Array of media types for the driver to chose from.      */
/*    MediumArraySize - Number of entries in the array.                     */
/*    MiniportAdapterHandle - Handle for passing to the wrapper when        */
/*       referring to this adapter.                                         */
/*    ConfigurationHandle - A handle to pass to NdisOpenConfiguration.      */
/*                                                                          */
/* Return Value:                                                            */
/*                                                                          */
/*    NDIS_STATUS_SUCCESS                                                   */
/*    NDIS_STATUS_PENDING                                                   */
/*                                                                          */
/****************************************************************************/

NDIS_STATUS
RndismpInitialize(OUT PNDIS_STATUS  OpenErrorStatus,
                  OUT PUINT         SelectedMediumIndex,
                  IN  PNDIS_MEDIUM  MediumArray,
                  IN  UINT          MediumArraySize,
                  IN  NDIS_HANDLE   MiniportAdapterHandle,
                  IN  NDIS_HANDLE   ConfigurationHandle)
{
    ULONG                       Index;
    NDIS_STATUS                 Status;
    PRNDISMP_ADAPTER            Adapter;
    NDIS_INTERFACE_TYPE         IfType;
    PDEVICE_OBJECT              Pdo, Fdo, Ndo;
    PDRIVER_BLOCK               DriverBlock;
    PRNDIS_INITIALIZE_COMPLETE  pInitCompleteMessage;
    PRNDISMP_MESSAGE_FRAME      pMsgFrame = NULL;
    PRNDISMP_MESSAGE_FRAME      pPendingMsgFrame;
    PRNDISMP_REQUEST_CONTEXT    pReqContext = NULL;
    RNDIS_REQUEST_ID            RequestId;
    ULONG                       PacketAlignmentFactor;
    NDIS_EVENT                  Event;
    BOOLEAN                     bWokenUp;
    BOOLEAN                     bLinkedAdapter;
    BOOLEAN                     bMicroportInitialized;

    TRACE2(("RndismpInitialize\n"));
    Adapter = NULL;
    Status = NDIS_STATUS_SUCCESS;
    bLinkedAdapter = FALSE;
    bMicroportInitialized = FALSE;

    do
    {
        // allocate the adapter object, exit if error occurs
        Status = MemAlloc(&Adapter, sizeof(RNDISMP_ADAPTER));

        if (Status != NDIS_STATUS_SUCCESS)
        {
            TRACE2(("Adapter Allocate Memory failed (%08X)\n", Status));
            break;
        }

        // allocate space for list of driver-supported OIDs
        Status = MemAlloc(&Adapter->DriverOIDList,
                           RndismpSupportedOidsNum*sizeof(NDIS_OID));

        if (Status != NDIS_STATUS_SUCCESS)
        {
            break;
        }
            
        RNDISMP_MOVE_MEM(Adapter->DriverOIDList, RndismpSupportedOids, RndismpSupportedOidsNum*sizeof(NDIS_OID));

        Adapter->NumDriverOIDs = RndismpSupportedOidsNum;

        Adapter->MiniportAdapterHandle  = MiniportAdapterHandle;

        InitializeListHead(&Adapter->PendingFrameList);
        Adapter->Initing = TRUE;
        Adapter->MacOptions = RNDIS_DRIVER_MAC_OPTIONS;

#if THROTTLE_MESSAGES
        Adapter->HiWatPendedMessages = RNDISMP_PENDED_SEND_HIWAT;
        Adapter->LoWatPendedMessages = RNDISMP_PENDED_SEND_LOWAT;
        Adapter->CurPendedMessages = 0;
        Adapter->SendInProgress = FALSE;
        InitializeListHead(&Adapter->WaitingMessageList);
#endif
        InitializeListHead(&Adapter->PendingAtMicroportList);

        Adapter->IndicatingReceives = FALSE;
        InitializeListHead(&Adapter->PendingRcvMessageList);
        NdisInitializeTimer(&Adapter->IndicateTimer, IndicateTimeout, (PVOID)Adapter);

        Adapter->SendProcessInProgress = FALSE;
        InitializeListHead(&Adapter->PendingSendProcessList);
        NdisInitializeTimer(&Adapter->SendProcessTimer, SendProcessTimeout, (PVOID)Adapter);


        TRACE2(("Adapter structure pointer is (%08X)\n", Adapter));

        NdisAllocateSpinLock(&Adapter->Lock);

        // get PDO to pass to microport
        NdisMGetDeviceProperty(MiniportAdapterHandle,
                               &Pdo,
                               &Fdo,
                               &Ndo,
                               NULL,
                               NULL);

#if NEW_NDIS_API_IN_MILLENNIUM
        {
            NDIS_STRING        UnicodeString;
            Status = NdisMQueryAdapterInstanceName(&UnicodeString,
                                                   Adapter->MiniportAdapterHandle);
            if (Status == NDIS_STATUS_SUCCESS)
            {
                TRACE1(("Init: NDIS returned len %d [%ws]\n",
                        UnicodeString.Length, UnicodeString.Buffer));
                NdisFreeString(UnicodeString);
            }
        }
#endif

        Adapter->pDeviceObject = Fdo;
        Adapter->pPhysDeviceObject = Pdo;

        Status = GetDeviceFriendlyName(Pdo,
                                       &Adapter->FriendlyNameAnsi,
                                       &Adapter->FriendlyNameUnicode);

        if (Status == NDIS_STATUS_SUCCESS)
        {
            TRACE1(("Init: Pdo %x, Ndo %x: Adapter %x: [%s]\n",
                Pdo, Ndo, Adapter, Adapter->FriendlyNameAnsi.Buffer));
        }
        else
        {
            Status = NDIS_STATUS_SUCCESS;
        }

        // Determine the platform we are running on. Ideally we would
        // like to do this from DriverEntry, but the NDIS API isn't
        // available until MiniportInit time.
        {
            NDIS_STATUS                     NdisStatus;
            PNDIS_CONFIGURATION_PARAMETER   pParameter;
            NDIS_STRING                     VersionKey = NDIS_STRING_CONST("Environment");

            NdisReadConfiguration(
                &NdisStatus,
                &pParameter,
                ConfigurationHandle,
                &VersionKey,
                NdisParameterInteger);
           
            if ((NdisStatus == NDIS_STATUS_SUCCESS) &&
                ((pParameter->ParameterType == NdisParameterInteger) ||
                 (pParameter->ParameterType == NdisParameterHexInteger)))
            {
                Adapter->bRunningOnWin9x =
                    (pParameter->ParameterData.IntegerData == NdisEnvironmentWindows);

                TRACE1(("Init: Adapter %p, running on %s\n",
                        Adapter,
                        ((Adapter->bRunningOnWin9x)? "Win9X": "NT")));
            }
            else
            {
                TRACE1(("Init: ReadConfig: NdisStatus %x\n", NdisStatus));
#if DBG
                if (NdisStatus == NDIS_STATUS_SUCCESS)
                {
                    TRACE1(("Init: ReadConfig: parametertype %x\n",
                        pParameter->ParameterType));
                }
#endif // DBG
                Adapter->bRunningOnWin9x = TRUE;
            }
        }

        // find the driver block associated with this adapter
        DriverBlock = DeviceObjectToDriverBlock(&RndismpMiniportBlockListHead, Fdo);

        if (DriverBlock == NULL)
        {
            TRACE1(("Init: Can't find driver block for FDO %x!\n", Fdo));
            Status = NDIS_STATUS_ADAPTER_NOT_FOUND;
            break;
        }

        // save the associated driver block in the adapter
        Adapter->DriverBlock            = DriverBlock;

        Adapter->Signature              = ADAPTER_SIGNATURE;

        // get handlers passed in from microport
        Adapter->RmInitializeHandler    = DriverBlock->RmInitializeHandler;
        Adapter->RmInitCompleteNotifyHandler = DriverBlock->RmInitCompleteNotifyHandler;
        Adapter->RmHaltHandler          = DriverBlock->RmHaltHandler;
        Adapter->RmShutdownHandler      = DriverBlock->RmShutdownHandler;
        Adapter->RmSendMessageHandler   = DriverBlock->RmSendMessageHandler;
        Adapter->RmReturnMessageHandler = DriverBlock->RmReturnMessageHandler;

        // call microport initialize handler
        //
        // Microport returns context
        // Pass in Miniport context
        // Pass in NDIS adapter handle
        // Pass in NDIS configuration handle
        // Pass in PDO for this adapter
        Status = Adapter->RmInitializeHandler(&Adapter->MicroportAdapterContext,
                                              &Adapter->MaxReceiveSize,
                                              (NDIS_HANDLE) Adapter,
                                              (NDIS_HANDLE) MiniportAdapterHandle,
                                              (NDIS_HANDLE) ConfigurationHandle,
                                              Ndo);


        if (Status != NDIS_STATUS_SUCCESS)
        {
            TRACE2(("Microport initialize handler failed (%08X)\n", Status));
            break;
        }

        bMicroportInitialized = TRUE;

        // everything looks good, so finish up
        Status = AllocateTransportResources(Adapter);

        if (Status != NDIS_STATUS_SUCCESS)
        {
            Status = NDIS_STATUS_RESOURCES; 
            break;
        }

        // allocate space to receive a copy of the Initialize complete message in
        Status = MemAlloc(&Adapter->pInitCompleteMessage, sizeof(RNDIS_INITIALIZE_COMPLETE));
        if (Status != NDIS_STATUS_SUCCESS)
        {
            Status = NDIS_STATUS_RESOURCES;
            break;
        }
    

        // now we send down a RNDIS initialize message to the device
        pMsgFrame = BuildRndisMessageCommon(Adapter, 
                                            NULL,
                                            REMOTE_NDIS_INITIALIZE_MSG,
                                            0,
                                            (PVOID) NULL,
                                            0);

        if (pMsgFrame == NULL)
        {
            Status = NDIS_STATUS_RESOURCES;
            break;
        }

        RequestId = pMsgFrame->RequestId;

        pReqContext = AllocateRequestContext(Adapter);
        if (pReqContext == NULL)
        {
            Status = NDIS_STATUS_RESOURCES;
            break;
        }

        pReqContext->pNdisRequest = NULL;

        NdisInitializeEvent(&Event);
        pReqContext->pEvent = &Event;

        pMsgFrame->pVc = NULL;
        pMsgFrame->pReqContext = pReqContext;

        RNDISMP_ASSERT_AT_PASSIVE();

        // send the message to the microport.
        RNDISMP_SEND_TO_MICROPORT(Adapter, pMsgFrame, TRUE, NULL);

        RNDISMP_ASSERT_AT_PASSIVE();
        // wait for message to complete
        bWokenUp = NdisWaitEvent(&Event, MINIPORT_INIT_TIMEOUT);

        // remove the message from the pending queue - it may or may not be there.
        RNDISMP_LOOKUP_PENDING_MESSAGE(pPendingMsgFrame, Adapter, RequestId);

        DereferenceMsgFrame(pMsgFrame);

        if (!bWokenUp)
        {
            // Failed to receive an Init complete within a reasonable time.
            TRACE1(("Init: Adapter %x, failed to receive Init complete\n", Adapter));
            Status = NDIS_STATUS_DEVICE_FAILED; 
            break;
        }

        //
        // the init complete message from the device is now
        // copied over to our local structure
        //
        pInitCompleteMessage = Adapter->pInitCompleteMessage;

        if (pInitCompleteMessage->Status != NDIS_STATUS_SUCCESS)
        {
            Status = pInitCompleteMessage->Status;
            break;
        }

        // make sure this is a supported device.
        if (!(pInitCompleteMessage->DeviceFlags & (RNDIS_DF_CONNECTIONLESS | RNDIS_DF_RAW_DATA)) ||
             (pInitCompleteMessage->Medium != RNdisMedium802_3))
        {
            TRACE1(("Init: Complete: unknown DeviceFlags %x or Medium %d\n",
                    pInitCompleteMessage->DeviceFlags,
                    pInitCompleteMessage->Medium));
            Status = NDIS_STATUS_NOT_SUPPORTED;
            break;
        }

        if ((pInitCompleteMessage->DeviceFlags & RNDIS_DF_RAW_DATA)
            || (gRawEncap))
        {
            Adapter->MultipleSendFunc = DoMultipleSendRaw;
        } else
        {
            Adapter->MultipleSendFunc = DoMultipleSend;
        }

        Adapter->Medium = RNDIS_TO_NDIS_MEDIUM(pInitCompleteMessage->Medium);

        // get device parameters.
        Adapter->MaxPacketsPerMessage = pInitCompleteMessage->MaxPacketsPerMessage;
        if (Adapter->MaxPacketsPerMessage == 0)
        {
            Adapter->MaxPacketsPerMessage = 1;
        }

#if HACK
        if (Adapter->MaxPacketsPerMessage > 1)
        {
            Adapter->MaxPacketsPerMessage = 2;
        }
#endif // HACK

        Adapter->bMultiPacketSupported = (Adapter->MaxPacketsPerMessage > 1);

        Adapter->MaxTransferSize = pInitCompleteMessage->MaxTransferSize;

        PacketAlignmentFactor = pInitCompleteMessage->PacketAlignmentFactor;

        if (PacketAlignmentFactor > 7)
        {
            PacketAlignmentFactor = 7;
        }

        Adapter->AlignmentIncr = (1 << PacketAlignmentFactor);
        Adapter->AlignmentMask = ~((1 << PacketAlignmentFactor) - 1);

#if DBG
        DbgPrint("RNDISMP: InitComp: Adapter %x, Version %d.%d, MaxPkt %d, AlignIncr %d, AlignMask %x, MaxXferSize %d\n",
                Adapter,
                pInitCompleteMessage->MajorVersion,
                pInitCompleteMessage->MinorVersion,
                Adapter->MaxPacketsPerMessage,
                Adapter->AlignmentIncr,
                Adapter->AlignmentMask,
                Adapter->MaxTransferSize);
#endif // DBG

        // Get the medium type.
        for (Index = 0; Index < MediumArraySize; Index++)
        {
            if (MediumArray[Index] == Adapter->Medium)
            {
                break;
            }
        }

        if (Index == MediumArraySize)
        {
            TRACE1(("InitComp: Adapter %x, device returned unsupported medium %d\n",
                Adapter, pInitCompleteMessage->Medium));
            Status = NDIS_STATUS_UNSUPPORTED_MEDIA;
            break;
        }

        *SelectedMediumIndex = Index;

        Adapter->DeviceFlags = pInitCompleteMessage->DeviceFlags;

        // call NdisMSetAttributesEx in order to let NDIS know
        // what kind of driver and features we support

        // interface type
        IfType = NdisInterfaceInternal;

        if (Adapter->bRunningOnWin9x)
        {
            //
            //  NOTE! The 0x80000000 bit is set to let NDIS know
            //  (Millennium only!) that our reconfig handler should
            //  be called when the device is surprise-removed.
            //
            NdisMSetAttributesEx(Adapter->MiniportAdapterHandle,
                                (NDIS_HANDLE) Adapter,
                                4,
                                (ULONG) NDIS_ATTRIBUTE_DESERIALIZE | 0x80000000,
                                IfType);
        }
        else
        {
            ULONG       AttrFlags;

            AttrFlags = NDIS_ATTRIBUTE_DESERIALIZE |
                        NDIS_ATTRIBUTE_SURPRISE_REMOVE_OK;

            if (Adapter->DeviceFlags & RNDIS_DF_CONNECTIONLESS)
            {
                AttrFlags |= NDIS_ATTRIBUTE_NOT_CO_NDIS;
            }

            NdisMSetAttributesEx(Adapter->MiniportAdapterHandle,
                                (NDIS_HANDLE) Adapter,
                                4,
                                AttrFlags,
                                IfType);
        }

        // Tell the microport that the device completed Init
        // successfully:
        if (Adapter->RmInitCompleteNotifyHandler)
        {
            Status = Adapter->RmInitCompleteNotifyHandler(
                                Adapter->MicroportAdapterContext,
                                Adapter->DeviceFlags,
                                &Adapter->MaxTransferSize);
            if (Status != NDIS_STATUS_SUCCESS)
            {
                break;
            }
        }

        // get the list of supported OIDs from the device
        pMsgFrame = BuildRndisMessageCommon(Adapter, 
                                            NULL,
                                            REMOTE_NDIS_QUERY_MSG,
                                            OID_GEN_SUPPORTED_LIST,
                                            (PVOID) NULL,
                                            0);

        if (pMsgFrame == NULL)
        {
            Status = NDIS_STATUS_RESOURCES;
            break;
        }

        // link us on to the list of adapters for this driver block
        AddAdapter(Adapter);
        bLinkedAdapter = TRUE;

        pReqContext->pNdisRequest = NULL;
        pReqContext->Oid = OID_GEN_SUPPORTED_LIST;
        pReqContext->CompletionStatus = NDIS_STATUS_SUCCESS;

        NdisInitializeEvent(&Event);
        pReqContext->pEvent = &Event;

        pMsgFrame->pVc = NULL;
        pMsgFrame->pReqContext = pReqContext;

        // send the message to the microport.
        RNDISMP_SEND_TO_MICROPORT(Adapter, pMsgFrame, TRUE, NULL);

        RNDISMP_ASSERT_AT_PASSIVE();
        bWokenUp = NdisWaitEvent(&Event, MINIPORT_INIT_TIMEOUT);

        // remove the message from the pending queue - it may or may not be there.
        RNDISMP_LOOKUP_PENDING_MESSAGE(pPendingMsgFrame, Adapter, RequestId);

        DereferenceMsgFrame(pMsgFrame);

        if (!bWokenUp || (Adapter->DriverOIDList == NULL))
        {
            // Failed to receive a response within a reasonable time,
            // or the device failed this query.
            //
            TRACE1(("Init: Adapter %x, failed to receive response to OID_GEN_SUPPORTED_LIST\n", Adapter));
            Status = NDIS_STATUS_DEVICE_FAILED; 
            ASSERT(FALSE);
            break;
        }

        // Successfully queried the supported OID list.

#ifdef BUILD_WIN9X
        //
        // Attempt to support surprise removal of this device (Win98/SE)
        // by intercepting config mgr messages forwarded by NDIS.
        //
        HookNtKernCMHandler(Adapter);
#endif

        // send any registry parameters down to the device, if it supports them.

        if (GetOIDSupport(Adapter, OID_GEN_RNDIS_CONFIG_PARAMETER) == DEVICE_SUPPORTED_OID)
        {
            Status = ReadAndSetRegistryParameters(Adapter, ConfigurationHandle);
            if (Status != NDIS_STATUS_SUCCESS)
            {
                break;
            }
        }

        // register a shutdown handler
        NdisMRegisterAdapterShutdownHandler(Adapter->MiniportAdapterHandle,
                                            (PVOID) Adapter,
                                            RndismpShutdownHandler);

        Adapter->TimerCancelled = FALSE;

        Adapter->Initing = FALSE;

        // initialize "KeepAlive" timer
        NdisInitializeTimer(&Adapter->KeepAliveTimer,
                            KeepAliveTimerHandler,
                            (PVOID) Adapter);

        NdisSetTimer(&Adapter->KeepAliveTimer, KEEP_ALIVE_TIMER / 2);

        Status = NDIS_STATUS_SUCCESS;
    }
    while (FALSE);

    if (Adapter)
    {
        if (Adapter->pInitCompleteMessage)
        {
            MemFree(Adapter->pInitCompleteMessage, sizeof(*Adapter->pInitCompleteMessage));
        }
    }

    if (Status != NDIS_STATUS_SUCCESS)
    {
        TRACE1(("Failed to init adapter %x, status %x\n", Adapter, Status));

        if (bMicroportInitialized)
        {
            ASSERT(Adapter);

            Adapter->RmHaltHandler(Adapter->MicroportAdapterContext);
        }

        if (Adapter)
        {
            if (bLinkedAdapter)
            {
                RemoveAdapter(Adapter);
            }

            FreeAdapter(Adapter);
        }
    }
        
    return Status;
} // RndismpInitialize

/****************************************************************************/
/*                          RndisMSendComplete                              */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Called by microport to indicate a message miniport sent is completed    */
/*  by microport                                                            */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*  RndisMessageHandle - context used by miniport                           */
/*  SendStatus - indicate status of send message                            */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*    VOID                                                                  */
/*                                                                          */
/****************************************************************************/
VOID
RndisMSendComplete(IN  NDIS_HANDLE     MiniportAdapterContext,
                   IN  NDIS_HANDLE     RndisMessageHandle,
                   IN  NDIS_STATUS     SendStatus)
{
    PRNDISMP_ADAPTER        Adapter;
    PRNDISMP_MESSAGE_FRAME  pMsgFrame;

    // get adapter context
    Adapter = PRNDISMP_ADAPTER_FROM_CONTEXT_HANDLE(MiniportAdapterContext);

    CHECK_VALID_ADAPTER(Adapter);

    pMsgFrame = MESSAGE_FRAME_FROM_HANDLE(RndisMessageHandle);

    CHECK_VALID_FRAME(pMsgFrame);

    ASSERT(pMsgFrame->pAdapter == Adapter);

    TRACE2(("RndisMSendComplete: Adapter %x, MsgFrame %x, MDL %x\n", Adapter, pMsgFrame, pMsgFrame->pMessageMdl));

    if ((SendStatus != NDIS_STATUS_SUCCESS) &&
        (SendStatus != NDIS_STATUS_RESOURCES))
    {
        RNDISMP_INCR_STAT(Adapter, MicroportSendError);
        TRACE0(("RndisMSendComplete: Adapter %x, MsgFrame %x, MDL %x, ERROR %x\n",
                    Adapter,
                    pMsgFrame,
                    pMsgFrame->pMessageMdl,
                    SendStatus));
    }

#if THROTTLE_MESSAGES
    RNDISMP_ACQUIRE_ADAPTER_LOCK(Adapter);

    Adapter->CurPendedMessages--;
    RemoveEntryList(&pMsgFrame->PendLink);

    if (SendStatus == NDIS_STATUS_RESOURCES)
    {
        RNDISMP_INCR_STAT(Adapter, SendMsgLowRes);
    }

    if ((SendStatus != NDIS_STATUS_RESOURCES) ||
        (Adapter->CurPendedMessages < 2))
    {
        if (Adapter->CurPendedMessages == Adapter->LoWatPendedMessages)
        {
            RNDISMP_RELEASE_ADAPTER_LOCK(Adapter);
            QueueMessageToMicroport(Adapter, NULL, FALSE);
        }
        else
        {
            RNDISMP_RELEASE_ADAPTER_LOCK(Adapter);
        }

        if (SendStatus == NDIS_STATUS_RESOURCES)
        {
            TRACE1(("RndisMSendComplete: Adapter %x, got resources\n", Adapter));
            SendStatus = NDIS_STATUS_SUCCESS;
        }

        if (pMsgFrame->pCallback)
        {
            (*pMsgFrame->pCallback)(pMsgFrame, SendStatus);
        }
        else
        {
            //
            //  Do nothing. The sender will take care of freeing
            //  this.
            //
        }
    }
    else
    {
        //
        //  The microport is out of send resources. Requeue this
        //  and adjust water marks.
        //
        InsertHeadList(&Adapter->WaitingMessageList, &pMsgFrame->PendLink);

        Adapter->HiWatPendedMessages = Adapter->CurPendedMessages;
        Adapter->LoWatPendedMessages = Adapter->CurPendedMessages / 2;

        TRACE1(("RndisMSendComplete: Adapter %x, new Hiwat %d, Lowat %d\n",
                Adapter, Adapter->HiWatPendedMessages, Adapter->LoWatPendedMessages));
        RNDISMP_RELEASE_ADAPTER_LOCK(Adapter);
    }
#else
    if (pMsgFrame->pCallback)
    {
        (*pMsgFrame->pCallback)(pMsgFrame, SendStatus);
    }
    else
    {
        //
        //  Do nothing. The sender will take care of freeing
        //  this.
        //
    }
#endif // THROTTLE_MESSAGES

} // RndisMSendComplete

/****************************************************************************/
/*                          InitCompletionMessage                           */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Completion message from microport in response to init message miniport  */
/*  sent. The init message was sent  from the adapter init routine which is */
/*  waiting for this event to unblock                                       */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pAdapter - Pointer to our adapter structure                             */
/*  pMessage - Pointer to RNDIS message                                     */
/*  pMdl - Pointer to MDL received from microport                           */
/*  TotalLength - length of complete message                                */
/*  MicroportMessageContext - context for message from micorport            */
/*  ReceiveStatus - used by microport to indicate it is low on resource     */
/*  bMessageCopied - is this a copy of the original message?                */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*  BOOLEAN - should the message be returned to the microport?              */
/*                                                                          */
/****************************************************************************/
BOOLEAN
InitCompletionMessage(IN PRNDISMP_ADAPTER   pAdapter,
                      IN PRNDIS_MESSAGE     pMessage,
                      IN PMDL               pMdl,
                      IN ULONG              TotalLength,
                      IN NDIS_HANDLE        MicroportMessageContext,
                      IN NDIS_STATUS        ReceiveStatus,
                      IN BOOLEAN            bMessageCopied)
{
    PRNDIS_INITIALIZE_COMPLETE  pInitCompleteMessage;
    PRNDISMP_MESSAGE_FRAME      pMsgFrame;
    PRNDISMP_REQUEST_CONTEXT    pReqContext;
    BOOLEAN                     bDiscardMsg = TRUE;

    TRACE2(("InitCompletionMessage\n"));

    do
    {
        if (pMessage->MessageLength < RNDISMP_MIN_MESSAGE_LENGTH(InitializeComplete))
        {
            TRACE1(("InitCompletion: Message length (%d) too short, expect at least (%d)\n",
                    pMessage->MessageLength,
                    RNDISMP_MIN_MESSAGE_LENGTH(InitializeComplete)));
            break;
        }

        if (pAdapter->pInitCompleteMessage == NULL)
        {
            TRACE1(("InitCompletion: multiple InitComplete from device, ignored\n"));
            break;
        }

        pInitCompleteMessage = RNDIS_MESSAGE_PTR_TO_MESSAGE_PTR(pMessage);

        // get request frame from request ID in message
        RNDISMP_LOOKUP_PENDING_MESSAGE(pMsgFrame, pAdapter, pInitCompleteMessage->RequestId);

        if (pMsgFrame == NULL)
        {
            // invalid request ID or aborted request.
            TRACE1(("Invalid request ID %d in Init Complete\n",
                    pInitCompleteMessage->RequestId));
            break;
        }

        pReqContext = pMsgFrame->pReqContext;

        RNDISMP_MOVE_MEM(pAdapter->pInitCompleteMessage,
                         pInitCompleteMessage,
                         sizeof(*pInitCompleteMessage));

        // signal the adapter init routine we are done
        NdisSetEvent(pReqContext->pEvent);

    }
    while (FALSE);

    return (bDiscardMsg);

} // InitCompletionMessage

/****************************************************************************/
/*                               HaltMessage                                */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Process a HALT message from the device.                                 */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pAdapter - Pointer to our Adapter structure                             */
/*  pMessage - pointer to RNDIS message                                     */
/*  pMdl - Pointer to MDL from microport                                    */
/*  TotalLength - length of complete message                                */
/*  MicroportMessageContext - context for message from microport            */
/*  ReceiveStatus - used by microport to indicate it is low on resource     */
/*  bMessageCopied - is this a copy of the original message?                */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*  BOOLEAN - should the message be returned to the microport?              */
/*                                                                          */
/****************************************************************************/
BOOLEAN
HaltMessage(IN PRNDISMP_ADAPTER   pAdapter,
            IN PRNDIS_MESSAGE     pMessage,
            IN PMDL               pMdl,
            IN ULONG              TotalLength,
            IN NDIS_HANDLE        MicroportMessageContext,
            IN NDIS_STATUS        ReceiveStatus,
            IN BOOLEAN            bMessageCopied)
{
    TRACE1(("HaltMessage: Adapter %x\n", pAdapter));

#ifndef BUILD_WIN9X
	// Not supported on Win98 Gold:
    NdisMRemoveMiniport(pAdapter->MiniportAdapterHandle);
#endif

    return TRUE;

} // HaltMessage

/****************************************************************************/
/*                          ResetCompletionMessage                          */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Completion message from microport in response to reset message miniport */
/*  sent. Indicate this completion message to the upper layers since        */
/*  the miniport reset routine indicated STATUS_PENDING to the upper layers */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pAdapter - Pointer to our Adapter structure                             */
/*  pMessage - pointer to RNDIS message                                     */
/*  pMdl - Pointer to MDL from microport                                    */
/*  TotalLength - length of complete message                                */
/*  MicroportMessageContext - context for message from microport            */
/*  ReceiveStatus - used by microport to indicate it is low on resource     */
/*  bMessageCopied - is this a copy of the original message?                */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*  BOOLEAN - should the message be returned to the microport?              */
/*                                                                          */
/****************************************************************************/
BOOLEAN
ResetCompletionMessage(IN PRNDISMP_ADAPTER   pAdapter,
                       IN PRNDIS_MESSAGE     pMessage,
                       IN PMDL               pMdl,
                       IN ULONG              TotalLength,
                       IN NDIS_HANDLE        MicroportMessageContext,
                       IN NDIS_STATUS        ReceiveStatus,
                       IN BOOLEAN            bMessageCopied)
{
    PRNDIS_RESET_COMPLETE   pResetMessage;
    BOOLEAN                 AddressingReset;
    NDIS_STATUS             Status;
    
    TRACE2(("ResetCompletionMessage\n"));

    pResetMessage = RNDIS_MESSAGE_PTR_TO_MESSAGE_PTR(pMessage);

    // save these parameters to call to upper layers
    Status = pResetMessage->Status;
    AddressingReset = (BOOLEAN)pResetMessage->AddressingReset;

    CompleteMiniportReset(pAdapter, Status, AddressingReset);

    return TRUE;

} // ResetCompletionMessage


/****************************************************************************/
/*                          KeepAliveCompletionMessage                      */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Completion message for a keep alive request send down by miniport       */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pAdapter - Pointer to our Adapter structure                             */
/*  pMessage - pointer to RNDIS message                                     */
/*  pMdl - Pointer to MDL from microport                                    */
/*  TotalLength - length of complete message                                */
/*  MicroportMessageContext - context for message from microport            */
/*  ReceiveStatus - used by microport to indicate it is low on resource     */
/*  bMessageCopied - is this a copy of the original message?                */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*  BOOLEAN - should the message be returned to the microport?              */
/*                                                                          */
/****************************************************************************/
BOOLEAN
KeepAliveCompletionMessage(IN PRNDISMP_ADAPTER   pAdapter,
                           IN PRNDIS_MESSAGE     pMessage,
                           IN PMDL               pMdl,
                           IN ULONG              TotalLength,
                           IN NDIS_HANDLE        MicroportMessageContext,
                           IN NDIS_STATUS        ReceiveStatus,
                           IN BOOLEAN            bMessageCopied)
{
    PRNDIS_KEEPALIVE_COMPLETE   pKeepaliveComplete;
    NDIS_STATUS                 Status;

    TRACE2(("KeepAliveCompletionMessage\n"));

    pKeepaliveComplete = RNDIS_MESSAGE_PTR_TO_MESSAGE_PTR(pMessage);

    // save off status
    Status = pKeepaliveComplete->Status;

    // grab the spinlock
    NdisAcquireSpinLock(&pAdapter->Lock);

    if (pKeepaliveComplete->RequestId != pAdapter->KeepAliveMessagePendingId)
    {
        TRACE0(("KeepAliveCompletion: Adapter %x, expected ID %x, got %x\n",
                pAdapter,
                pAdapter->KeepAliveMessagePendingId,
                pKeepaliveComplete->RequestId));
        //
        // TBD - should we set NeedReset?
    }

    pAdapter->KeepAliveMessagePending = FALSE;

    // if there are problems, tell the check for hang handler we need a reset
    if (Status != NDIS_STATUS_SUCCESS)
    {
        TRACE0(("KeepAliveCompletion: Adapter %x, err status %x from device\n",
                   pAdapter, Status));

        // indicate later from check for hang handler
        pAdapter->NeedReset = TRUE;
    }

    // release spinlock
    NdisReleaseSpinLock(&pAdapter->Lock);

    return TRUE;

} // KeepAliveCompletionMessage


/****************************************************************************/
/*                          KeepAliveMessage                                */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Process a keepalive message sent by the device. Send back a completion. */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pAdapter - Pointer to our Adapter structure                             */
/*  pMessage - pointer to RNDIS message                                     */
/*  pMdl - Pointer to MDL from microport                                    */
/*  TotalLength - length of complete message                                */
/*  MicroportMessageContext - context for message from microport            */
/*  ReceiveStatus - used by microport to indicate it is low on resource     */
/*  bMessageCopied - is this a copy of the original message?                */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*  BOOLEAN - should the message be returned to the microport?              */
/*                                                                          */
/****************************************************************************/
BOOLEAN
KeepAliveMessage(IN PRNDISMP_ADAPTER   pAdapter,
                 IN PRNDIS_MESSAGE     pMessage,
                 IN PMDL               pMdl,
                 IN ULONG              TotalLength,
                 IN NDIS_HANDLE        MicroportMessageContext,
                 IN NDIS_STATUS        ReceiveStatus,
                 IN BOOLEAN            bMessageCopied)
{
    PRNDIS_KEEPALIVE_REQUEST    pKeepalive;
    PRNDISMP_MESSAGE_FRAME      pMsgFrame;

    TRACE2(("KeepAliveMessage\n"));

    pKeepalive = RNDIS_MESSAGE_PTR_TO_MESSAGE_PTR(pMessage);

    //
    //  Send a response if we can.
    //
    pMsgFrame = BuildRndisMessageCommon(pAdapter,
                                        NULL,
                                        REMOTE_NDIS_KEEPALIVE_CMPLT,
                                        0,
                                        &pKeepalive->RequestId,
                                        sizeof(pKeepalive->RequestId));
    if (pMsgFrame != NULL)
    {
        // send the message to the microport.
        RNDISMP_SEND_TO_MICROPORT(pAdapter, pMsgFrame, FALSE, NULL);
    }
    else
    {
        TRACE1(("KeepAlive: Adapter %x: failed to alloc response!\n", pAdapter));
    }

    return TRUE;

} // KeepAliveMessage
                      

/****************************************************************************/
/*                          RndismpShutdownHandler                          */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Removes an adapter instance that was previously initialized. Since the  */
/*  system is shutting down there is no need to release resources, just     */
/*  shutdown receive.                                                       */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*    VOID                                                                  */
/*                                                                          */
/****************************************************************************/
VOID
RndismpShutdownHandler(IN NDIS_HANDLE MiniportAdapterContext)
{
    PRNDISMP_ADAPTER            Adapter;

    // get adapter context
    Adapter = PRNDISMP_ADAPTER_FROM_CONTEXT_HANDLE(MiniportAdapterContext);

    TRACE1(("RndismpShutdownHandler\n"));
} // RndismpShutdownHandler


//
// Interrupt routines, stubbed up for now, we don't need them
//

/****************************************************************************/
/*                          RndismpDisableInterrupt                         */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   VOID                                                                   */
/*                                                                          */
/****************************************************************************/
VOID
RndismpDisableInterrupt(IN NDIS_HANDLE MiniportAdapterContext)
{

    // NOP

} // RndismpDisableInterrupt


/****************************************************************************/
/*                          RndismpEnableInterrupt                          */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   VOID                                                                   */
/*                                                                          */
/****************************************************************************/
VOID
RndismpEnableInterrupt(IN NDIS_HANDLE MiniportAdapterContext)
{

    // NOP

} // RndismpEnableInterrupt

/****************************************************************************/
/*                          RndismpHandleInterrupt                          */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  MiniportAdapterContext - a context version of our Adapter pointer       */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   VOID                                                                   */
/*                                                                          */
/****************************************************************************/
VOID
RndismpHandleInterrupt(IN NDIS_HANDLE MiniportAdapterContext)
{

    // NOP

} // RndismpHandleInterrupt

/****************************************************************************/
/*                          RndismpIsr                                      */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  This is the interrupt handler which is registered with the operating    */
/*  system. If several are pending (i.e. transmit complete and receive),    */
/*  handle them all.  Block new interrupts until all pending interrupts     */
/*  are handled.                                                            */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  InterruptRecognized - Boolean value which returns TRUE if the           */
/*      ISR recognizes the interrupt as coming from this adapter.           */
/*                                                                          */
/*  QueueDpc - TRUE if a DPC should be queued.                              */
/*                                                                          */
/*  Context - pointer to the adapter object                                 */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   VOID                                                                   */
/*                                                                          */
/****************************************************************************/
VOID
RndismpIsr(OUT PBOOLEAN InterruptRecognized,
           OUT PBOOLEAN QueueDpc,
           IN  PVOID    Context)
{

    ASSERT(FALSE); // don't expect to be called here.

} // RndismpIsr

/****************************************************************************/
/*                          CompleteSendHalt                                */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Utility function to handle completion of sending of a HALT message.     */
/*  We simply wake up the thread waiting for this.                          */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pMsgFrame - Frame structure describing the HALT message                 */
/*  SendStatus - outcome of sending this message.                           */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   VOID                                                                   */
/*                                                                          */
/****************************************************************************/
VOID
CompleteSendHalt(IN PRNDISMP_MESSAGE_FRAME pMsgFrame,
                 IN NDIS_STATUS SendStatus)
{
    PRNDISMP_ADAPTER        pAdapter;

    pAdapter = pMsgFrame->pAdapter;

    TRACE1(("CompleteSendHalt: Adapter %x, SendStatus %x\n", pAdapter, SendStatus));

    ASSERT(pAdapter->Halting);

    DereferenceMsgFrame(pMsgFrame);

    NdisSetEvent(&pAdapter->HaltWaitEvent);
} // CompleteSendHalt


/****************************************************************************/
/*                          CompleteSendReset                               */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Callback routine to handle send-completion of a reset message by the    */
/*  microport.                                                              */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pMsgFrame - Pointer to message frame for the Reset.                     */
/*  SendStatus - Status of send                                             */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*  VOID                                                                    */
/*                                                                          */
/****************************************************************************/
VOID
CompleteSendReset(IN PRNDISMP_MESSAGE_FRAME pMsgFrame,
                  IN NDIS_STATUS SendStatus)
{
    PRNDISMP_ADAPTER        pAdapter;

    pAdapter = pMsgFrame->pAdapter;

    TRACE1(("CompleteSendReset: Adapter %x, SendStatus %x\n",
            pAdapter, SendStatus));

    DereferenceMsgFrame(pMsgFrame);

    if (SendStatus != NDIS_STATUS_SUCCESS)
    {
        CompleteMiniportReset(pAdapter, SendStatus, FALSE);
    }
}


/****************************************************************************/
/*                          CompleteMiniportReset                           */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Utility function to complete a pending NDIS Reset. We complete any      */
/*  pending requests/sets before indicating reset complete to NDIS.         */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pAdapter - Pointer to our adapter structure                             */
/*  ResetStatus - to be used for completing reset                           */
/*  AddressingReset - Do we need filters to be resent to us?                */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   VOID                                                                   */
/*                                                                          */
/****************************************************************************/
VOID
CompleteMiniportReset(IN PRNDISMP_ADAPTER pAdapter,
                      IN NDIS_STATUS ResetStatus,
                      IN BOOLEAN AddressingReset)
{
    LIST_ENTRY              PendingRequests;
    PLIST_ENTRY             pEntry, pNext;
    PRNDISMP_MESSAGE_FRAME  pMsgFrame;

    do
    {
        if (!pAdapter->ResetPending)
        {
            break;
        }

        pAdapter->ResetPending = FALSE;
        
        //
        //  Take out all pending requests/sets queued on the adapter.
        //
        InitializeListHead(&PendingRequests);

        RNDISMP_ACQUIRE_ADAPTER_LOCK(pAdapter);

        for (pEntry = pAdapter->PendingFrameList.Flink;
             pEntry != &pAdapter->PendingFrameList;
             pEntry = pNext)
        {
            pNext = pEntry->Flink;
            pMsgFrame = CONTAINING_RECORD(pEntry, RNDISMP_MESSAGE_FRAME, Link);
            if (pMsgFrame->NdisMessageType == REMOTE_NDIS_QUERY_MSG ||
                pMsgFrame->NdisMessageType == REMOTE_NDIS_SET_MSG)
            {
                RemoveEntryList(pEntry);
                InsertTailList(&PendingRequests, pEntry);

                TRACE0(("RNDISMP: ResetComplete: taking out MsgFrame %x, msg type %x\n",
                        pMsgFrame, pMsgFrame->NdisMessageType));

            }
        }

        RNDISMP_RELEASE_ADAPTER_LOCK(pAdapter);

        //
        //  Complete all these requests.
        //
        for (pEntry = PendingRequests.Flink;
             pEntry != &PendingRequests;
             pEntry = pNext)
        {
            pNext = pEntry->Flink;
            pMsgFrame = CONTAINING_RECORD(pEntry, RNDISMP_MESSAGE_FRAME, Link);

            TRACE0(("RNDISMP: ResetComplete: completing MsgFrame %x, msg type %x\n",
                    pMsgFrame, pMsgFrame->NdisMessageType));

            ASSERT(pMsgFrame->pReqContext != NULL);

            if (pMsgFrame->pReqContext->pNdisRequest != NULL)
            {
                //
                //  This request came down thru our MiniportCoRequest handler.
                //
                NdisMCoRequestComplete(NDIS_STATUS_REQUEST_ABORTED,
                                       pAdapter->MiniportAdapterHandle,
                                       pMsgFrame->pReqContext->pNdisRequest);
            }
            else
            {
                //
                //  This request came thru our connectionless query/set handler.
                //
                if (pMsgFrame->NdisMessageType == REMOTE_NDIS_QUERY_MSG)
                {
                    NdisMQueryInformationComplete(pAdapter->MiniportAdapterHandle,
                                                  NDIS_STATUS_REQUEST_ABORTED);
                }
                else
                {
                    ASSERT(pMsgFrame->NdisMessageType == REMOTE_NDIS_SET_MSG);
                    NdisMSetInformationComplete(pAdapter->MiniportAdapterHandle,
                                                NDIS_STATUS_REQUEST_ABORTED);
                }
            }

            FreeRequestContext(pAdapter, pMsgFrame->pReqContext);
            pMsgFrame->pReqContext = (PRNDISMP_REQUEST_CONTEXT)UlongToPtr(0xabababab);
            DereferenceMsgFrame(pMsgFrame);
        }

        TRACE0(("Completing reset on Adapter %x, Status %x, AddressingReset %d\n",
                    pAdapter, ResetStatus, AddressingReset));

        RNDISMP_INCR_STAT(pAdapter, Resets);

        //
        //  Complete the reset now.
        //
        NdisMResetComplete(pAdapter->MiniportAdapterHandle,
                           ResetStatus,
                           AddressingReset);
    }
    while (FALSE);
}



/****************************************************************************/
/*                     ReadAndSetRegistryParameters                         */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  This is called when initializing a device, to read and send any         */
/*  registry parameters applicable to this device.                          */
/*                                                                          */
/*  We go through the entire list of configurable parameters by walking     */
/*  subkeys under the "ndi\Params" key. Each subkey represents one          */
/*  parameter. Using information about this parameter (specifically, its    */
/*  name and type), we query its value, and send a SetRequest to the        */
/*  device.                                                                 */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pAdapter - Pointer to adapter structure for the device                  */
/*  ConfigurationContext - NDIS handle to access registry for this device   */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   NDIS_STATUS                                                            */
/*                                                                          */
/****************************************************************************/
NDIS_STATUS
ReadAndSetRegistryParameters(IN PRNDISMP_ADAPTER pAdapter,
                             IN NDIS_HANDLE ConfigurationContext)
{
    NDIS_STATUS                 Status;
    NDIS_HANDLE                 ConfigHandle;
    NDIS_STRING                 NdiKeyName = NDIS_STRING_CONST("Ndi");
    NDIS_HANDLE                 NdiKeyHandle = NULL;

    Status = NDIS_STATUS_SUCCESS;
    ConfigHandle = NULL;

    do
    {
        NdisOpenConfiguration(&Status,
                              &ConfigHandle,
                              ConfigurationContext);

        if (Status != NDIS_STATUS_SUCCESS)
        {
            break;
        }

        NdisOpenConfigurationKeyByName(
            &Status,
            ConfigHandle,
            &NdiKeyName,
            &NdiKeyHandle);
        
        if (Status == NDIS_STATUS_SUCCESS)
        {
            NDIS_STRING     ParamsKeyName = NDIS_STRING_CONST("Params");
            NDIS_HANDLE     ParamsKeyHandle = NULL;

            NdisOpenConfigurationKeyByName(
                &Status,
                NdiKeyHandle,
                &ParamsKeyName,
                &ParamsKeyHandle);
            
            if (Status == NDIS_STATUS_SUCCESS)
            {
                ULONG   i;
                BOOLEAN bDone = FALSE;

                //
                //  Iterate through all subkeys under ndi\Params:
                //
                for (i = 0; !bDone; i++)
                {
                    NDIS_STRING     ParamSubKeyName;
                    NDIS_HANDLE     ParamSubKeyHandle;
                    NDIS_STRING     ParamTypeName = NDIS_STRING_CONST("type");
                    PNDIS_CONFIGURATION_PARAMETER    pConfigParameter;

                    ParamSubKeyName.Length =
                    ParamSubKeyName.MaximumLength = 0;
                    ParamSubKeyName.Buffer = NULL;

                    NdisOpenConfigurationKeyByIndex(
                        &Status,
                        ParamsKeyHandle,
                        i,
                        &ParamSubKeyName,
                        &ParamSubKeyHandle);
                   
                    if (Status != NDIS_STATUS_SUCCESS)
                    {
                        //
                        //  Done with parameters. Cook return value.
                        //
                        Status = NDIS_STATUS_SUCCESS;
                        break;
                    }

                    //
                    //  Got the handle to a subkey under ndi\Params,
                    //  now read the type information for this parameter.
                    //

#ifndef BUILD_WIN9X
                    TRACE3(("ReadAndSetRegParams: subkey %d under ndi\\params: %ws\n",
                        i, ParamSubKeyName.Buffer));
#else
                    //
                    //  Handle Win98Gold behavior.
                    //
                    if (ParamSubKeyName.Buffer == NULL)
                    {
                        PNDIS_STRING    pNdisString;

                        pNdisString = *(PNDIS_STRING *)&ParamSubKeyName;
                        ParamSubKeyName = *pNdisString;
                    }

                    TRACE2(("ReadAndSetRegParams: subkey %d under ndi\\params: %ws\n",
                        i, ParamSubKeyName.Buffer));
#endif

                    //
                    //  We have a parameter name now, in ParamSubKeyName.
                    //  Get its type information.
                    //
                    NdisReadConfiguration(
                        &Status,
                        &pConfigParameter,
                        ParamSubKeyHandle,
                        &ParamTypeName,
                        NdisParameterString);
                    
                    if (Status == NDIS_STATUS_SUCCESS)
                    {
                        TRACE2(("ReadAndSetRegParams: Adapter %p, type is %ws\n",
                            pAdapter,
                            pConfigParameter->ParameterData.StringData.Buffer));

                        //
                        //  Send off a Set Request for this
                        //  parameter to the device.
                        //

                        Status = SendConfiguredParameter(
                                        pAdapter,
                                        ConfigHandle,
                                        &ParamSubKeyName,
                                        &pConfigParameter->ParameterData.StringData);

                        if (Status != NDIS_STATUS_SUCCESS)
                        {
                            TRACE0(("ReadAndSetRegParams: Adapter %p, failed %x\n",
                                pAdapter, Status));
                            bDone = TRUE;
                        }
                        else
                        {
                            NDIS_STRING     NetworkAddressName =
                                        NDIS_STRING_CONST("NetworkAddress");

                            //
                            //  Special case for the "NetworkAddress"
                            //  parameter - if we just set this successfully,
                            //  make note of the fact.
                            //
                            if (NdisEqualString(&ParamSubKeyName,
                                                &NetworkAddressName,
                                                TRUE))
                            {
                                TRACE1(("ReadAndSetRegParams: Adapter %p,"
                                        " supports MAC address overwrite\n",
                                        pAdapter));

                                pAdapter->MacOptions |=
                                    NDIS_MAC_OPTION_SUPPORTS_MAC_ADDRESS_OVERWRITE;
                            }
                        }
    
                    }

                    //
                    //  Done with this subkey under ndi\Params.
                    //
                    NdisCloseConfiguration(ParamSubKeyHandle);

                } // for each subkey under ndi\Params

                //
                //  Done with "ndi\Params"
                //
                NdisCloseConfiguration(ParamsKeyHandle);
            }

            //
            //  Done with "ndi"
            //
            NdisCloseConfiguration(NdiKeyHandle);
        }

        //
        //  Done with configuration section for this device.
        //
        NdisCloseConfiguration(ConfigHandle);
    }
    while (FALSE);
   
    return (Status);
}


/****************************************************************************/
/*                     SendConfiguredParameter                              */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Read the value of the specified config parameter, format a SetRequest,  */
/*  send it to the device, and wait for a response.                         */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pAdapter - Pointer to adapter structure for the device                  */
/*  ConfigHandle - handle to configuration section for this device          */
/*  pParameterName - parameter key name                                     */
/*  pParameterType - parameter type                                         */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*   NDIS_STATUS                                                            */
/*                                                                          */
/****************************************************************************/
NDIS_STATUS
SendConfiguredParameter(IN PRNDISMP_ADAPTER     pAdapter,
                        IN NDIS_HANDLE          ConfigHandle,
                        IN PNDIS_STRING         pParameterName,
                        IN PNDIS_STRING         pParameterType)
{
    PRNDISMP_MESSAGE_FRAME          pMsgFrame = NULL;
    PRNDISMP_MESSAGE_FRAME          pPendingMsgFrame = NULL;
    PRNDISMP_REQUEST_CONTEXT        pReqContext = NULL;
    NDIS_PARAMETER_TYPE             NdisParameterType;
    PNDIS_CONFIGURATION_PARAMETER   pConfigParameter;
    ULONG                           ParameterValueLength;
    PUCHAR                          pParameterValue;
    UINT32                          ParameterType;
    NDIS_EVENT                      Event;
    UINT                            BytesRead;
    BOOLEAN                         bWokenUp;
    RNDIS_REQUEST_ID                RequestId;

    PRNDIS_CONFIG_PARAMETER_INFO    pRndisConfigInfo = NULL;
    ULONG                           RndisConfigInfoLength;
    PUCHAR                          pConfigInfoBuf;
    NDIS_STATUS                     Status;

    struct {
        NDIS_STRING         TypeName;
        NDIS_PARAMETER_TYPE NdisType;
    } StringToNdisType[] =
        {
            {NDIS_STRING_CONST("int"), NdisParameterInteger},
            {NDIS_STRING_CONST("long"), NdisParameterInteger},
            {NDIS_STRING_CONST("word"), NdisParameterInteger},
            {NDIS_STRING_CONST("dword"), NdisParameterInteger},
            {NDIS_STRING_CONST("edit"), NdisParameterString},
            {NDIS_STRING_CONST("enum"), NdisParameterString}
        };

    ULONG                       NumTypes = sizeof(StringToNdisType);
    ULONG                       i;

    do
    {
        //
        //  Determine the parameter type.
        //
        for (i = 0; i < NumTypes; i++)
        {
            if (NdisEqualString(&StringToNdisType[i].TypeName,
                                pParameterType,
                                TRUE))
            {
                NdisParameterType = StringToNdisType[i].NdisType;
                break;
            }
        }

        if (i == NumTypes)
        {
            TRACE1(("SendConfiguredParam: Adapter %p, Param %ws, invalid type %ws\n",
                pAdapter,
                pParameterName->Buffer,
                pParameterType->Buffer));
            Status = NDIS_STATUS_INVALID_DATA;
            break;
        }

        NdisReadConfiguration(
            &Status,
            &pConfigParameter,
            ConfigHandle,
            pParameterName,
            NdisParameterType
            );

        if (Status != NDIS_STATUS_SUCCESS)
        {
            //
            //  It is okay for a parameter to not be configured.
            //
            Status = NDIS_STATUS_SUCCESS;
            break;
        }

        if (NdisParameterType == NdisParameterInteger)
        {
            ParameterValueLength = sizeof(UINT32);
            pParameterValue = (PUCHAR)&pConfigParameter->ParameterData.IntegerData;
            ParameterType = RNDIS_CONFIG_PARAM_TYPE_INTEGER;
        }
        else
        {
            ASSERT(NdisParameterType == NdisParameterString);
            ParameterValueLength = pConfigParameter->ParameterData.StringData.Length;
            pParameterValue = (PUCHAR)pConfigParameter->ParameterData.StringData.Buffer;
            ParameterType = RNDIS_CONFIG_PARAM_TYPE_STRING;
        }

        RndisConfigInfoLength = sizeof(RNDIS_CONFIG_PARAMETER_INFO) +
                                pParameterName->Length +
                                ParameterValueLength;

        Status = MemAlloc(&pRndisConfigInfo, RndisConfigInfoLength);

        if (Status != NDIS_STATUS_SUCCESS)
        {
            break;
        }

        pRndisConfigInfo->ParameterNameOffset = sizeof(RNDIS_CONFIG_PARAMETER_INFO);
        pRndisConfigInfo->ParameterNameLength = pParameterName->Length;
        pRndisConfigInfo->ParameterType = ParameterType;
        pRndisConfigInfo->ParameterValueOffset =
                    pRndisConfigInfo->ParameterNameOffset +
                    pRndisConfigInfo->ParameterNameLength;
        pRndisConfigInfo->ParameterValueLength = ParameterValueLength;

        //
        //  Copy in the parameter name.
        //
        pConfigInfoBuf = (PUCHAR)pRndisConfigInfo +
                          pRndisConfigInfo->ParameterNameOffset;
        
        RNDISMP_MOVE_MEM(pConfigInfoBuf, pParameterName->Buffer, pParameterName->Length);

        //
        //  Copy in the parameter value.
        //
        pConfigInfoBuf = (PUCHAR)pRndisConfigInfo +
                          pRndisConfigInfo->ParameterValueOffset;
        RNDISMP_MOVE_MEM(pConfigInfoBuf, pParameterValue, ParameterValueLength);

        //
        //  Build a Set Request
        //
        pMsgFrame = BuildRndisMessageCommon(pAdapter,
                                            NULL,
                                            REMOTE_NDIS_SET_MSG,
                                            OID_GEN_RNDIS_CONFIG_PARAMETER,
                                            pRndisConfigInfo,
                                            RndisConfigInfoLength);

        if (pMsgFrame == NULL)
        {
            Status = NDIS_STATUS_RESOURCES;
            break;
        }

#if DBG
        {
            PMDL    pTmpMdl = pMsgFrame->pMessageMdl;
            ULONG   Length;
            PUCHAR  pBuf;
            ULONG   OldDebugFlags = RndismpDebugFlags;

            Length = MmGetMdlByteCount(pTmpMdl);
            pBuf = MmGetSystemAddressForMdl(pTmpMdl);

            RndismpDebugFlags |= DBG_DUMP;
            TRACEDUMP(("SetRequest (OID_GEN_RNDIS_CONFIG_PARAMETER):"
                " Adapter %p, Param %ws\n", pAdapter, pParameterName->Buffer), pBuf, Length);
            RndismpDebugFlags = OldDebugFlags;
        }
#endif

        pReqContext = AllocateRequestContext(pAdapter);
        if (pReqContext == NULL)
        {
            Status = NDIS_STATUS_RESOURCES;
            break;
        }

        // Fill up the request context.

        pReqContext->pNdisRequest = NULL;
        pReqContext->Oid = OID_GEN_RNDIS_CONFIG_PARAMETER;

        NdisInitializeEvent(&Event);
        pReqContext->pEvent = &Event;
        pReqContext->bInternal = TRUE;
        pReqContext->pBytesRead = &BytesRead;
        pReqContext->InformationBufferLength = RndisConfigInfoLength;

        pMsgFrame->pVc = NULL;
        pMsgFrame->pReqContext = pReqContext;

        // save off the request Id.
        RequestId = pMsgFrame->RequestId;

        // send the message to the microport.
        RNDISMP_SEND_TO_MICROPORT(pAdapter, pMsgFrame, TRUE, NULL);

        RNDISMP_ASSERT_AT_PASSIVE();
        bWokenUp = NdisWaitEvent(&Event, MINIPORT_INIT_TIMEOUT);

        // remove the message from the pending queue - it may or may not be there.
        RNDISMP_LOOKUP_PENDING_MESSAGE(pPendingMsgFrame, pAdapter, RequestId);


        if (!bWokenUp)
        {
            TRACE1(("No response to set parameter, Adapter %x\n", pAdapter));
            Status = NDIS_STATUS_DEVICE_FAILED; 
        }
        else
        {
            Status = pReqContext->CompletionStatus;
            TRACE1(("Got response to set config param, Status %x, %d bytes read\n",
                        Status, BytesRead));
        }

    }
    while (FALSE);

    if (pRndisConfigInfo)
    {
        MemFree(pRndisConfigInfo, RndisConfigInfoLength);
    }

    if (pMsgFrame)
    {
        DereferenceMsgFrame(pMsgFrame);
    }

    if (pReqContext)
    {
        FreeRequestContext(pAdapter, pReqContext);
    }

    return (Status);
}


#ifdef NDIS51_MINIPORT
/****************************************************************************/
/*                          RndismpPnPEventNotify                           */
/****************************************************************************/
/*                                                                          */
/* Routine Description:                                                     */
/*                                                                          */
/*  Entry point called by NDIS to notify us of PnP events affecting our     */
/*  device. The main event of importance to us is surprise removal.         */
/*                                                                          */
/* Arguments:                                                               */
/*                                                                          */
/*  pAdapter - Pointer to adapter structure                                 */
/*                                                                          */
/* Return:                                                                  */
/*                                                                          */
/*  NDIS_STATUS                                                             */
/*                                                                          */
/****************************************************************************/
VOID
RndismpPnPEventNotify(IN NDIS_HANDLE MiniportAdapterContext,
                      IN NDIS_DEVICE_PNP_EVENT EventCode,
                      IN PVOID InformationBuffer,
                      IN ULONG InformationBufferLength)
{
    PRNDISMP_ADAPTER        pAdapter;

    // get adapter context
    pAdapter = PRNDISMP_ADAPTER_FROM_CONTEXT_HANDLE(MiniportAdapterContext);

    CHECK_VALID_ADAPTER(pAdapter);

    TRACE3(("PnPEventNotify: Adapter %x\n", pAdapter));


    switch (EventCode)
    {
        case NdisDevicePnPEventSurpriseRemoved:
            TRACE1(("PnPEventNotify: Adapter %p, surprise remove\n", pAdapter));
            RndismpInternalHalt(pAdapter, FALSE);
            break;

        default:
            break;
    }

} // RndismpPnPEventNotify

#endif // NDIS51_MINIPORT