Sample Code

Windows Driver Samples/ StorAhci StorPort Miniport Driver/ C++/ src/ hbastat.c/

/*++

Copyright (C) Microsoft Corporation, 2009

Module Name:

    hbastat.c

Abstract:
    This file contains the core logic for managing AHCI controller state transistions.
    Primarily it focuses on starting and stopping the channel and performing resets.

Notes:

Revision History:

--*/

#if _MSC_VER >= 1200
#pragma warning(push)
#endif

#pragma warning(disable:4214) // bit field types other than int
#pragma warning(disable:4201) // nameless struct/union

#include "generic.h"


__inline
VOID
ForceGenxSpeed (
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ ULONG GenNumber
    )
{
    AHCI_SERIAL_ATA_CONTROL sctl;
    AHCI_SERIAL_ATA_ERROR   serr;
    UCHAR                   spd;

    if (GenNumber == 0) {
        return;
    }

    spd = (UCHAR)min(GenNumber, ChannelExtension->AdapterExtension->CAP.ISS);

    sctl.AsUlong = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &ChannelExtension->Px->SCTL.AsUlong);
    if (sctl.SPD == spd) {
        return;
    }

    //Set speed limitation.
    sctl.SPD = spd;
    StorPortWriteRegisterUlong(ChannelExtension->AdapterExtension, &ChannelExtension->Px->SCTL.AsUlong, sctl.AsUlong);
    //Reset the port
    AhciCOMRESET(ChannelExtension, ChannelExtension->Px);
    //and clear out SERR
    serr.AsUlong = (ULONG)~0;
    StorPortWriteRegisterUlong(ChannelExtension->AdapterExtension, &ChannelExtension->Px->SERR.AsUlong, serr.AsUlong);

    return;
}


BOOLEAN
AhciAdapterReset(
    _In_ PAHCI_ADAPTER_EXTENSION AdapterExtension
    )
/*
This function brings the HBA and all ports to the H:Idle and P:Idle states by way of HBA Reset and P:Reset

Called By:
    AhciHwFindAdapter if needed.

It assumes:
    AdapterExtension->ABAR_Address is valid.

It performs:
    //10.4.3    HBA Reset
    //5.3.2.1    P:Reset
    //5.3.2.2    P:Init

Return value:
    BOOLEAN - success or not.  note the many controller registers that hold reset values.
*/
{
    AHCI_Global_HBA_CONTROL ghc;
    PAHCI_MEMORY_REGISTERS  abar = AdapterExtension->ABAR_Address;
    int i;

    if (abar == NULL) {
        return FALSE;
    }

//10.4.3    HBA Reset
  //If the HBA becomes unusable for multiple ports, and a software reset or port reset does not correct the problem, software may reset the entire HBA by setting GHC.HR to �1�.  When software sets the GHC.HR bit to �1�, the HBA shall perform an internal reset action.
    ghc.HR = 1;
    StorPortWriteRegisterUlong(AdapterExtension, &abar->GHC.AsUlong, ghc.AsUlong);

  //The bit shall be cleared to �0� by the HBA when the reset is complete.  A software write of �0� to GHC.HR shall have no effect.  To perform the HBA reset, software sets GHC.HR to �1� and may poll until this bit is read to be �0�, at which point software knows that the HBA reset has completed.
    ghc.AsUlong = StorPortReadRegisterUlong(AdapterExtension, &abar->GHC.AsUlong);
    //5.2.2.1    H:Init
    //5.2.2.2    H:WaitForAhciEnable
    for (i = 0;(i < 50) && (ghc.HR == 1); i++) {
        StorPortStallExecution(20000);  //20 milliseconds
        ghc.AsUlong = StorPortReadRegisterUlong(AdapterExtension, &abar->GHC.AsUlong);
    }

    //If the HBA has not cleared GHC.HR to �0� within 1 second of software setting GHC.HR to �1�, the HBA is in a hung or locked state.
    if(i == 50) {
        AdapterExtension->ErrorFlags = (1 << 29);
        return FALSE;
    }

    //When GHC.HR is set to �1�, GHC.AE, GHC.IE, the IS register, and all port register fields (except PxFB/PxFBU/PxCLB/PxCLBU) that are not HwInit in the HBA�s register memory space are reset.  The HBA�s configuration space and all other global registers/bits are not affected by setting GHC.HR to �1�.  Any HwInit bits in the port specific registers are not affected by setting GHC.HR to �1�.  The port specific registers PxFB, PxFBU, PxCLB, and PxCLBU are not affected by setting GHC.HR to �1�.  If the HBA supports staggered spin-up, the PxCMD.SUD bit will be reset to �0�; software is responsible for setting the PxCMD.SUD and PxSCTL.DET fields appropriately such that communication can be established on the Serial ATA link.  If the HBA does not support staggered spin-up, the HBA reset shall cause a COMRESET to be sent on the port

    return TRUE;
}


VOID
AhciCOMRESET(
    PAHCI_CHANNEL_EXTENSION ChannelExtension,
    PAHCI_PORT Px
    )
/*
PHY Reset:COMRESET
    SCTL.DET Controls the HBA�s device detection and interface initialization.
    DET=1 Performs interface communication initialization sequence to establish communication. This is functionally equivalent to a hard reset and results in the interface being reset and communications reinitialized.  While this field is 1h, COMRESET is transmitted on the interface.
    Software should leave the DET field set to 1h for a minimum of 1 millisecond to ensure that a COMRESET is sent on the interface.
    since we are in 5.3.2.3    P:NotRunning and PxCMD.SUD = �0� does this still take us to P:StartComm?
Called By:
    AhciPortReset,AhciNonQueuedErrorRecovery,P_Running_WaitWhileDET1,AhciHwControlIdeStart
It assumes:
    nothing
It performs:
    5.3.2.11    P:StartComm
    (overview)
    1 Prepare for COMRSET
    2 Perform COMRESET
    3 Clean up after COMRESET
    (details)
    1.1 make sure ST is 0.  DET cannot be altered while ST == 1 as per AHCI 1.1 section 5.3.2.3.
    1.2 Don't allow a comm init to trigger a hotplug
    1.3 Ignore Hotplug events until the channel is started again
    2.1 Perform COMRESET
    2.2 Wait for DET to be set
    3.1 Clear SERR

Affected Variables,Registers:
    CMD.ST, SCTL.DET, CI, SACT, SERR

Return Values:
    none
*/
{
    AHCI_SERIAL_ATA_CONTROL sctl;
    AHCI_COMMAND            cmd;
    AHCI_INTERRUPT_ENABLE   ieOrig;
    AHCI_INTERRUPT_ENABLE   ieTemp;
    AHCI_SERIAL_ATA_STATUS  ssts;
    UCHAR                   i;

    RecordExecutionHistory(ChannelExtension, 0x00000010);//AhciCOMRESET

  //1.1 make sure ST is 0.  DET cannot be altered while ST == 1 as per AHCI 1.1 section 5.3.2.3.
    cmd.AsUlong = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &Px->CMD.AsUlong);
    if(cmd.ST == 1) {
        RecordExecutionHistory(ChannelExtension, 0x10fa0010);   //AhciCOMRESET, PxCMD.ST is 1. Abort
        return;
    }

  //1.2 Don't allow a COMINIT to trigger a hotplug
    ieTemp.AsUlong = ieOrig.AsUlong = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &Px->IE.AsUlong);
    ieTemp.PRCE = 0;        // "PhyRdy Change Interrupt Enable", no generate interrupt with PxIS.PRCS
    ieTemp.PCE = 0;         // "Port Change Interrupt Enable", no generate interrupt with PxIS.PCS
    StorPortWriteRegisterUlong(ChannelExtension->AdapterExtension, &Px->IE.AsUlong, ieTemp.AsUlong);

  //1.3 Ignore Hotplug events until the channel is started again
    ChannelExtension->StateFlags.IgnoreHotplugInterrupt = TRUE;

  //2.1 Perform COMRESET
    sctl.AsUlong = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &Px->SCTL.AsUlong);
    sctl.DET = 1;
    StorPortWriteRegisterUlong(ChannelExtension->AdapterExtension, &Px->SCTL.AsUlong, sctl.AsUlong);
    //DET=1 Performs interface communication initialization sequence to establish communication. This is functionally equivalent to a hard reset and results in the interface being reset and communications reinitialized.  While this field is 1h, COMRESET is transmitted on the interface.
    //Software should leave the DET field set to 1h for a minimum of 1 millisecond to ensure that a COMRESET is sent on the interface.
    StorPortStallExecution(1000);

    sctl.AsUlong = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &Px->SCTL.AsUlong);
    sctl.DET = 0;
    StorPortWriteRegisterUlong(ChannelExtension->AdapterExtension, &Px->SCTL.AsUlong, sctl.AsUlong);

  //2.2 Wait for DET to be set
    // AHCI 10.4.2 After clearing PxSCTL.DET to 0h, software should wait for communication to be re-established as indicated by bit 0 of PxSSTS.DET being set to �1�.
    // typically, it will be done in 10ms. max wait 30ms to be safe
    StorPortStallExecution(50);
    ssts.AsUlong = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &Px->SSTS.AsUlong);

    if (ssts.DET == 0) {
        for (i = 0; i < (AHCI_PORT_WAIT_ON_DET_COUNT * 10); i++) {
            StorPortStallExecution(1000);
            ssts.AsUlong = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &Px->SSTS.AsUlong);
            if (ssts.DET != 0) {
                break;
            }
        }
    }

  //2.3 Enable Hotplug again if it was enabled before
    StorPortWriteRegisterUlong(ChannelExtension->AdapterExtension, &Px->IE.AsUlong, ieOrig.AsUlong);

  //3.1 Clear SERR
    // AHCI 10.4.2 software should write all 1s to the PxSERR register to clear any bits that were set as part of the port reset.
    StorPortWriteRegisterUlong(ChannelExtension->AdapterExtension, &Px->SERR.AsUlong, (ULONG)~0);

  //3.2 record the count of reset (both interal or asked by port driver)
    ChannelExtension->DeviceExtension[0].IoRecord.TotalResetCount++;

    RecordExecutionHistory(ChannelExtension, 0x10000010);//Exit AhciCOMRESET
}


BOOLEAN
P_NotRunning(
    PAHCI_CHANNEL_EXTENSION ChannelExtension,
    PAHCI_PORT Px
    )
/*
Called By:

It assumes:
    nothing

It performs:
    5.3.2.3    P:NotRunning
    (overview)
    1 Clear CMD.ST
    2 Clear CMD.FRE
    3 Update the Channel Start State

    (details)
    1.1 Clear CMD.ST
    1.2 Verify CR cleared
    1.3 Verify CI cleared
    2.1 Clear CMD.FRE
    2.2 Verify FR cleared
    3.1 Update the Channel Start State

Affected Variables,Registers:
    cmd.ST cmd.CR cmd.FRE cmd.FR are 0 and the controller is in P:NotRunning

Return value:
    FALSE if the ST,CR,FRE, and FRE could not be cleared.  TRUE if success.
    If FALSE is returned, the caller may issue RESET to recover
*/
{
    PAHCI_ADAPTER_EXTENSION adapterExtension;
    BOOLEAN                 supportsCLO;
    AHCI_COMMAND            cmd;
    AHCI_TASK_FILE_DATA     tfd;
    ULONG                   ci;
    UCHAR                   i;

    adapterExtension = ChannelExtension->AdapterExtension;
    supportsCLO = CloResetEnabled(adapterExtension);     //3.1.1 make sure HBA supports Command List Override.

    if (ChannelExtension->StartState.ChannelNextStartState == Stopped) {
        RecordExecutionHistory(ChannelExtension, 0x00030011);   //P_NotRunning, already stopped, nothing to do.
        return TRUE;
    }

    // record this function is called.
    if (supportsCLO) {
        RecordExecutionHistory(ChannelExtension, 0x10810011);   //P_NotRunning, CLO is enabled.
    } else {
        RecordExecutionHistory(ChannelExtension, 0x00000011);   //P_NotRunning, CLO isn't enabled.
    }

  //1.1 Clear CMD.ST
    cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &Px->CMD.AsUlong);
    //AHCI 10.3.2 on FRE it says:
    //Software shall not clear this [FRE] bit while PxCMD.ST remains set to �1�."
    //System software places a port into the idle state by clearing PxCMD.ST and waiting for PxCMD.CR to return �0� when read.
    cmd.ST = 0;
    StorPortWriteRegisterUlong(adapterExtension, &Px->CMD.AsUlong, cmd.AsUlong);

  //1.1.1 Update the Channel Start State after we ask the channel to stop
    ChannelExtension->StartState.ChannelNextStartState = Stopped;

    StorPortDebugPrint(3, "StorAHCI - LPM: Port %02d - Port Stopped\n", ChannelExtension->PortNumber);

    if (supportsCLO) {
        // AHCI 3.3.7 make sure PxCMD.ST is 0.
        for (i = 1; i < 101; i++) {
            cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &Px->CMD.AsUlong);
            if (cmd.ST == 0) {
                break;
            }
            StorPortStallExecution(5000);  //5 milliseconds
        }
        if (i == 101) {
            RecordExecutionHistory(ChannelExtension, 0x10820011); //P_NotRunning, After 500ms of writing 0 to PxCMD.ST, it's still 1.
        }

        tfd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &ChannelExtension->Px->TFD.AsUlong);
        if (tfd.STS.BSY) {
          // AHCI 3.3.7 Command List Override (CLO):  Setting this bit to �1� causes PxTFD.STS.BSY and PxTFD.STS.DRQ to be cleared to �0�.
          // This allows a software reset to be transmitted to the device regardless of whether the BSY and DRQ bits are still set in the PxTFD.STS register.

          // Do this to make sure the port can stop
            cmd.CLO = 1;
            StorPortWriteRegisterUlong(adapterExtension, &Px->CMD.AsUlong, cmd.AsUlong);
        }
    }

  //1.2 Verify CR cleared
    //AHCI 10.1.2 - 3: wait cmd.CR to be 0. Software should wait at least 500 milliseconds for this to occur.
    for (i = 1; i < 101; i++) {
        cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &Px->CMD.AsUlong);
        if ( (cmd.CR == 0) && (cmd.ST == 0) ) {
            break;
        }
        StorPortStallExecution(5000);  //5 milliseconds
    }

    //AHCI 10.4.2 If PxCMD.CR or PxCMD.FR do not clear to �0� correctly, then software may attempt a port reset or a full HBA reset to recover.
    if (i == 101) {
        RecordExecutionHistory(ChannelExtension, 0x10350011); // P_NotRunning, PxCMD.CR or PxCMD.FR do not clear to �0� correctly
        return FALSE;
    }

  //1.3 Verify CI cleared
    //This must have the effect of clearling CI as per AHCI section 3.3.14
    for (i = 1; i < 101; i++) {
        ci = StorPortReadRegisterUlong(adapterExtension, &Px->CI);
        if (ci == 0) {
            break;
        }
        StorPortStallExecution(50);  //50 microseconds
    }

    //If CI does not clear to �0� correctly abort the stop
    if (i == 101) {
        RecordExecutionHistory(ChannelExtension, 0x10360011); // P_NotRunning, CI does not clear to �0�
        return FALSE;
    }

  //2.1 Clear CMD.FRE
    cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &Px->CMD.AsUlong);
    if( (cmd.FRE | cmd.FR) != 0 ) {
        //If PxCMD.FRE is set to �1�, software should clear it to �0� and wait at least 500 milliseconds for PxCMD.FR to return �0� when read.
        //AHCI 10.3.2 Software shall not clear this bit while PxCMD.ST or PxCMD.CR is set to �1�.
        cmd.FRE = 0;
        StorPortWriteRegisterUlong(adapterExtension, &Px->CMD.AsUlong, cmd.AsUlong);

        if (supportsCLO) {
            tfd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &ChannelExtension->Px->TFD.AsUlong);
            if (tfd.STS.BSY) {
                NT_ASSERT(FALSE);     //don't expect BSY is set again
                cmd.CLO = 1;
                StorPortWriteRegisterUlong(adapterExtension, &Px->CMD.AsUlong, cmd.AsUlong);
            }
        }

        //Software should wait at least 500 milliseconds for this to occur.
        for (i = 1; i < 101; i++) {
            cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &Px->CMD.AsUlong);
            if( (cmd.CR == 0) && (cmd.FR == 0) && (cmd.ST == 0) && (cmd.FRE == 0) ) {
                break;
            }
            StorPortStallExecution(5000);    //  5 milliseconds
        }

        if ( i == 101 ) {  //If PxCMD.CR or PxCMD.FR do not clear to �0� correctly, then software may attempt a port reset or a full HBA reset to recover.
            if (supportsCLO) {
                RecordExecutionHistory(ChannelExtension, 0x10380011); // P_NotRunning, PxCMD.CR or PxCMD.FR do not clear to �0�, CLO enabled
            } else {
                RecordExecutionHistory(ChannelExtension, 0x10370011); // P_NotRunning, PxCMD.CR or PxCMD.FR do not clear to �0�, CLO not enabled
            }
            return FALSE;
        }
    }

  //2.2 wait for CLO to clear
    // AHCI 3.3.7 Software must wait for CLO to be cleared to �0� before setting PxCMD.ST to �1�.
    // register bit CLO might be set in this function, and it should have been cleared before function exit.
    if ( supportsCLO && (cmd.CLO == 0) ) {
        for (i = 1; i < 101; i++) {
            cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &Px->CMD.AsUlong);
            if( (cmd.CLO == 0) ) {
                break;
            }
            StorPortStallExecution(5000);    //  5 milliseconds
        }

        if ( i == 101 ) {
            RecordExecutionHistory(ChannelExtension, 0x10390011); // P_NotRunning, PxCMD.CLO not clear to �0�, CLO enabled
            return FALSE;
        }
    }

    RecordExecutionHistory(ChannelExtension, 0x10000011); //Exit P_NotRunning, port stopped

    return TRUE;
}


VOID
AhciAdapterRunAllPorts(
    _In_ PAHCI_ADAPTER_EXTENSION AdapterExtension
    )
{
    ULONG i;

    //only start with the first one. callback routine will go over all ports.
    for (i = 0; i <= AdapterExtension->HighestPort; i++) {
        if (AdapterExtension->PortExtension[i] != NULL) {
            AdapterExtension->InRunningPortsProcess = TRUE;
            P_Running_StartAttempt(AdapterExtension->PortExtension[i], FALSE);
            return;
        }
    }

    return;
}

VOID
RunNextPort(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN AtDIRQL
    )
{
    PAHCI_ADAPTER_EXTENSION adapterExtension;
    PAHCI_CHANNEL_EXTENSION nextPortExtension;
    ULONG i;

    adapterExtension = ChannelExtension->AdapterExtension;
    nextPortExtension = NULL;

    //starting all ports process either not started or already completed.
    if (!adapterExtension->InRunningPortsProcess) {
        return;
    }

    //get next port extension
    for (i = (ChannelExtension->PortNumber + 1); i <= adapterExtension->HighestPort; i++) {
        if (adapterExtension->PortExtension[i] != NULL) {
            nextPortExtension = adapterExtension->PortExtension[i];
            break;
        }
    }

    // run next port or declare the ports running process completed.
    if (nextPortExtension != NULL) {
        if (nextPortExtension->StartState.ChannelNextStartState == 0) {
            P_Running_StartAttempt(nextPortExtension, AtDIRQL);
        } else {
            // return, do nothing here as the Port Start effort has been made.
            // RunNextPort() is only used in AhciAdapterRunAllPorts() which is one time operation from AhciHwFindAdapter.
            // In other cases, P_Running_StartAttempt() will be called directly for each port.
            return;
        }
    } else {
        //starting all ports process is completed.
        adapterExtension->InRunningPortsProcess = FALSE;
    }

    return;
}

VOID
P_Running_StartAttempt(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN AtDIRQL
    )
/*

Called By:
It assumes:
    Channel Start may already be in progress (not function reentrant)
It performs:
    1 Initializes the Channel Start state machine
    2 Starts the Channel Start state machine

Affected Variables/Registers:
    none
*/
{
    STOR_LOCK_HANDLE    lockhandle;
    AhciZeroMemory((PCHAR)&lockhandle, sizeof(STOR_LOCK_HANDLE));

    if (!AtDIRQL) {
        StorPortAcquireSpinLock(ChannelExtension->AdapterExtension, InterruptLock, NULL, &lockhandle);
    }

  //1 Initializes the Channel Start state machine
    ChannelExtension->StartState.ChannelNextStartState = WaitOnDET;
    ChannelExtension->StartState.ChannelStateDETCount = 0;
    ChannelExtension->StartState.ChannelStateDET1Count = 0;
    ChannelExtension->StartState.ChannelStateDET3Count = 0;
    ChannelExtension->StartState.ChannelStateFRECount = 0;
    ChannelExtension->StartState.ChannelStateBSYDRQCount = 0;
    ChannelExtension->StartState.AtDIRQL = AtDIRQL ? 1 : 0;
    ChannelExtension->StartState.DirectStartInProcess = 1;

    if (!AtDIRQL) {
        StorPortReleaseSpinLock(ChannelExtension->AdapterExtension, &lockhandle);
    }

  //2 Starts the Channel Start state machine
    P_Running(ChannelExtension, FALSE);
    return;
}

VOID
P_Running_Callback(
    _In_ PVOID AdapterExtension,
    _In_opt_ PVOID ChannelExtension
    )
{
    PAHCI_CHANNEL_EXTENSION channelExtension = (PAHCI_CHANNEL_EXTENSION)ChannelExtension;
    ULONG                   callbackIndex = 0;

    if (channelExtension == NULL) {
        NT_ASSERT(FALSE);
        return;
    }

    NT_ASSERT(AdapterExtension == (PVOID)(channelExtension->AdapterExtension));

    UNREFERENCED_PARAMETER(AdapterExtension);

    callbackIndex = channelExtension->StartState.ChannelStateDETCount +
                    channelExtension->StartState.ChannelStateDET1Count +
                    channelExtension->StartState.ChannelStateDET3Count +
                    channelExtension->StartState.ChannelStateFRECount +
                    channelExtension->StartState.ChannelStateBSYDRQCount;

    // only clear the bit if this is the first timer callback in Port Start process
    if (callbackIndex == 1) {
        STOR_LOCK_HANDLE    lockhandle;
        AhciZeroMemory((PCHAR)&lockhandle, sizeof(STOR_LOCK_HANDLE));

        StorPortAcquireSpinLock(channelExtension->AdapterExtension, InterruptLock, NULL, &lockhandle);

        if (channelExtension->StartState.AtDIRQL == 1) {
            channelExtension->StartState.AtDIRQL = 0;
        }
        if (channelExtension->StartState.DirectStartInProcess == 1) {
            channelExtension->StartState.DirectStartInProcess = 0;
        }

        StorPortReleaseSpinLock(channelExtension->AdapterExtension, &lockhandle);
    }

    P_Running(channelExtension, TRUE);

    return;
}


BOOLEAN
P_Running(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN TimerCallbackProcess
    )
/*
    The purpose of this function is to verify and drive the Start Channel state machine

Called By:
    P_Running_StartAttempt

    Through StorPortRequestTimer which is set up by each of the Start Channel state functions:
            P_Running_WaitOnDET
            P_Running_WaitWhileDET1
            P_Running_WaitOnDET3
            P_Running_WaitOnFRE
            P_Running_WaitOnBSYDRQ
It assumes:
    Nothing

It performs:
    (overview)
    1 Verify Start can/needs to be done
    2 Run the Start Channel state machine

    (details)
    1.1 First, is the channel initialized.  Would turning start on blow up the machine?
    1.2 Next, is the port somehow already running?
    1.3 Make sure the device knows it is supposed to be spun up
    1.4 Check to make sure that FR and CR are both 0.  Attempt to bring the controller into a consistent state by stopping the controller.
    1.5 CMD.ST has to be set, when that happens check to see that PxSSTS.DET is not �4h�

    2.1 Dispatch to the current state (states are responsible for selecting the next state)

Affected Variables/Registers:
    CMD

Return Values:
    TRUE if the state machine can be run
    FALSE if the state machine can't
*/
{

    AHCI_COMMAND            cmd;
    AHCI_SERIAL_ATA_STATUS  ssts;
    PAHCI_PORT              px = ChannelExtension->Px;

    PAHCI_ADAPTER_EXTENSION adapterExtension = ChannelExtension->AdapterExtension;

    if (LogExecuteFullDetail(adapterExtension->LogFlags)) {
        RecordExecutionHistory(ChannelExtension, 0x00000015);//P_Running
    }

  //1.1 First, is the channel initialized.  Would turning start on blow up the machine?
    if( !IsPortStartCapable(ChannelExtension) ) {
        RecordExecutionHistory(ChannelExtension, 0x10120015);//No Channel Resources
        RunNextPort(ChannelExtension, (!TimerCallbackProcess && ChannelExtension->StartState.AtDIRQL));
        return FALSE;
    }

    if ( TimerCallbackProcess && (ChannelExtension->StartState.DirectStartInProcess == 1) ) {
        RecordExecutionHistory(ChannelExtension, 0x10130015);//This is timer callback and a direct port start process has been started seperately, bail out this one.
        RunNextPort(ChannelExtension, (!TimerCallbackProcess && ChannelExtension->StartState.AtDIRQL));
        return FALSE;
    }

  //1.2 Next, is the port somehow already running?
    cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &px->CMD.AsUlong);
    if( (cmd.ST == 1) && (cmd.CR == 1) && (cmd.FRE == 1) && (cmd.FR == 1) ) {
        ChannelExtension->StartState.ChannelNextStartState = StartComplete;
        RecordExecutionHistory(ChannelExtension, 0x30000015);//Channel Already Running
        RunNextPort(ChannelExtension, (!TimerCallbackProcess && ChannelExtension->StartState.AtDIRQL));
        return TRUE;
    }


  //1.3 Make sure the device knows it is supposed to be spun up
    cmd.SUD = 1;
    StorPortWriteRegisterUlong(adapterExtension, &px->CMD.AsUlong, cmd.AsUlong);

  //1.4 Check to make sure that FR and CR are both 0.  If not then ST and/or FRE are 0 which a bad scenario.
    if ( ( (cmd.FR == 1) && (cmd.FRE == 0) ) ||
         ( (cmd.CR == 1) && (cmd.ST  == 0) ) ) {
        //Attempt to bring the controller into a consistent state by stopping the controller.
        if ( !P_NotRunning(ChannelExtension, ChannelExtension->Px) ) {
            RecordExecutionHistory(ChannelExtension, 0x10880015);   //CR or FR 1 when ST or FRE 0
            AhciCOMRESET(ChannelExtension, ChannelExtension->Px);   //Issue COMRESET to recover
        }
    }

  //1.5 CMD.ST has to be set, when that happens check to see that PxSSTS.DET is not �4h�
    ssts.AsUlong = StorPortReadRegisterUlong(adapterExtension, &px->SSTS.AsUlong);
    if( ssts.DET == 0x4) {
        P_Running_StartFailed(ChannelExtension, TimerCallbackProcess);
        RecordExecutionHistory(ChannelExtension, 0x10150015);   //Channel Disabled
        return FALSE;
    }

  //2.1 Ok, run the Start Channel State Machine
    switch(ChannelExtension->StartState.ChannelNextStartState) {

        case WaitOnDET:
            P_Running_WaitOnDET(ChannelExtension, TimerCallbackProcess);
            break;
        case WaitWhileDET1:
            P_Running_WaitWhileDET1(ChannelExtension, TimerCallbackProcess);
            break;
        case WaitOnDET3:
            P_Running_WaitOnDET3(ChannelExtension, TimerCallbackProcess);
            break;
        case WaitOnFRE:
            P_Running_WaitOnFRE(ChannelExtension, TimerCallbackProcess);
            break;
        case WaitOnBSYDRQ:
            P_Running_WaitOnBSYDRQ(ChannelExtension, TimerCallbackProcess);
            break;
        default:
            // Above cases cover all intermediate states. If a port start process has been completed, just exit.
            break;
    }

    // record this function is called.
    RecordExecutionHistory(ChannelExtension, 0x10000015);   //Exit P_Running
    return TRUE;
}

VOID
P_Running_WaitOnDET(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN TimerCallbackProcess
    )
/*
    Search for Device Activity phase.  Use DET and IPM for any signs of life as defined in AHCI 1.2 section 10.1.2 and 10.3.1.
    Polled 3 times in 30ms

Called By:
    P_Running
It assumes:
    Controller is start capable
It performs:
    (overview)
    1 Look for signs of life while DET == 0
    2 Look for signs of life while DET == 1
    3 Look for signs of life which is DET == 3
    (details)
    1.1 When looking for device presence, seeing link level power managment shall count as device present as per 10.3.1
    1.2 Wait for 1 second for DET to show signs of life
    1.3 After 30ms give up
    2.1 When looking for device presence, seeing link level power managment shall count as device present as per 10.3.1
    2.2 Otherwise continue on to WaitOnDET1
    3.1 If DET==3 we are ready for WaitOnFRE already.

Affected Variables/Registers:
    none
*/
{
    AHCI_SERIAL_ATA_STATUS ssts;

    PAHCI_ADAPTER_EXTENSION adapterExtension = ChannelExtension->AdapterExtension;
    UCHAR                   waitMaxCount = AHCI_PORT_WAIT_ON_DET_COUNT;

    if (IsPortD3ColdEnabled(ChannelExtension)) {
        // if D3 Cold is enabled, a device was there; Add addtional wait time to make sure device is able to get ready after a possible power on.
        waitMaxCount += 30;     // wait timer is 10ms, add 300ms for safe
    } else if (IsDumpMode(adapterExtension)) {
        //
        // If we're on the dump stack we may have crashed while the device was
        // in an indeterminant state so give it some more time to get ready.
        //
        waitMaxCount += 30;
    }

WaitOnDET_Start:
    if (LogExecuteFullDetail(adapterExtension->LogFlags)) {
        RecordExecutionHistory(ChannelExtension, 0x00000016);//P_Running_WaitOnDET
    }

  //1 Look for signs of life while DET == 0
    ssts.AsUlong = StorPortReadRegisterUlong(adapterExtension, &ChannelExtension->Px->SSTS.AsUlong);

    if (ssts.DET == 0)  { //When a COMRESET is sent to the device the PxSSTS.DET field shall be cleared to 0h.

      //1.1 When looking for device presence, seeing link level power managment shall count as device present as per 10.3.1
        if ((ssts.IPM == 0x2) || (ssts.IPM == 0x6) || (ssts.IPM == 0x8)) {
            ChannelExtension->StartState.ChannelNextStartState = WaitOnFRE;
            RecordExecutionHistory(ChannelExtension, 0x10800016);//P_Running_WaitOnDET is 0 w/ LPM activity, goto FRE
            P_Running_WaitOnFRE(ChannelExtension, TimerCallbackProcess);
            return;
      //1.3 After 30ms give up (spec requires 10ms, use 30ms for safety). There is no device connected.
        //from SATA-IO spec 3.0 - 15.2.2.2:
        //If a device is present, the Phy shall detect device presence within 10 ms of a power-on reset (i.e. COMINIT shall be returned within 10 ms of an issued COMRESET).
        } else if (ChannelExtension->StartState.ChannelStateDETCount > waitMaxCount) {
            P_Running_StartFailed(ChannelExtension, TimerCallbackProcess);
            RecordExecutionHistory(ChannelExtension, 0x10ff0016);//P_Running_WaitOnDET Timed out, No Device
            return;
      //1.2 Wait for 30ms for DET to show signs of life
        } else {
            ChannelExtension->StartState.ChannelStateDETCount++;
            RecordExecutionHistory(ChannelExtension, 0x10810016);//P_Running_WaitOnDET is 0, still waiting

            if (IsDumpMode(adapterExtension)) {
                StorPortStallExecution(10000); //10 milliseconds
                goto WaitOnDET_Start;
            } else {
                ULONG status;
                status = StorPortRequestTimer(adapterExtension, ChannelExtension->StartPortTimer, P_Running_Callback, ChannelExtension, 10000, 0);     //10 milliseconds
                if ((status != STOR_STATUS_SUCCESS) && (status != STOR_STATUS_BUSY)) {
                    StorPortStallExecution(10000); //10 milliseconds
                    goto WaitOnDET_Start;
                }
            }
            return;
        }

    } else {
      //2 Look for signs of life while DET == 1
        if (ssts.DET == 1)  {
          //2.1 When looking for device presence, seeing link level power managment shall count as device present as per 10.3.1
            if ((ssts.IPM == 0x2) || (ssts.IPM == 0x6) || (ssts.IPM == 0x8)) {
                ChannelExtension->StartState.ChannelNextStartState = WaitOnFRE;
                RecordExecutionHistory(ChannelExtension, 0x10a00016);//P_Running_WaitOnDET is 1 w/ LPM activity, goto FRE
                P_Running_WaitOnFRE(ChannelExtension, TimerCallbackProcess);
                return;
          //2.2 Otherwise continue on to WaitOnDET1
            } else {
                ChannelExtension->StartState.ChannelNextStartState = WaitWhileDET1;
                RecordExecutionHistory(ChannelExtension, 0x10a10016);//P_Running_WaitOnDET is 1
                P_Running_WaitWhileDET1(ChannelExtension, TimerCallbackProcess);
                return;
            }
      //3.1 If DET==3 we are ready for WaitOnFRE already.
        } else if (ssts.DET == 3)  {
            ChannelExtension->StartState.ChannelNextStartState = WaitOnFRE;
            RecordExecutionHistory(ChannelExtension, 0x10a20016);//P_Running_WaitOnDET is 3
            P_Running_WaitOnFRE(ChannelExtension, TimerCallbackProcess);
            return;

        } else {
            P_Running_StartFailed(ChannelExtension, TimerCallbackProcess);
            RecordExecutionHistory(ChannelExtension, 0x10a30016);//P_Running_WaitOnDET is Bogus, aborting
            return;
        }
    }
}

VOID
P_Running_WaitWhileDET1(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN TimerCallbackProcess
    )
/*
    Waiting on establishment of link level communications phase.
    A '1' should become a '3'.  If it doesn't, help it along.
    NOTE:   When a COMINIT is received, the PxSSTS.DET field shall be set to 1h.
            That means a device is detected, but communications has not finished
    Polled 100 times in 1 second

Called By:
    P_Running
It assumes:
    Channel is start capable
    DET was previously the value of 1
It performs:
    (overview)
    1 Look for signs of life which is DET == 3
    2 Look for signs of live while DET == 1
    (details)
    1.1 If DET moves from 1 to 3, go to WaitOnFRE
    2.1 Wait for 1 second for DET to become 3
    2.2 After 1 second of waiting, force the controller to 150MB/s speeds and go to WaitOnDET3
Affected Variables/Registers:
    SCTL, SERR
*/
{
    AHCI_SERIAL_ATA_STATUS  ssts;

    PAHCI_ADAPTER_EXTENSION adapterExtension = ChannelExtension->AdapterExtension;

WaitWhileDET1_Start:
    if (LogExecuteFullDetail(adapterExtension->LogFlags)) {
        RecordExecutionHistory(ChannelExtension, 0x00000017);//P_Running_WaitWhileDET1
    }

  //1.1 If DET moves from 1 to 3, go to WaitOnFRE
    ssts.AsUlong = StorPortReadRegisterUlong(adapterExtension, &ChannelExtension->Px->SSTS.AsUlong);

    if (ssts.DET == 3) {
        ChannelExtension->StartState.ChannelNextStartState = WaitOnFRE;
        RecordExecutionHistory(ChannelExtension, 0x10a20017);//P_Running_WaitWhileDET1 done
        P_Running_WaitOnFRE(ChannelExtension, TimerCallbackProcess);
        return;

    } else {
      //2.2 After 1 second of waiting, force the controller to 150MB/s speeds and go to WaitOnDET3
        if (ChannelExtension->StartState.ChannelStateDET1Count > 100) {
            //A very wise woman once taught me this trick
            //it is possible that the device is not handling speed negotation very well
            //help it out by allowing only 150MB/s
            ForceGenxSpeed(ChannelExtension, 1);

            ChannelExtension->StartState.ChannelNextStartState = WaitOnDET3;
            RecordExecutionHistory(ChannelExtension, 0x100a0017);//P_Running_WaitWhileDET1 timed out, speed stepping down
            if (IsDumpMode(adapterExtension)) {
                StorPortStallExecution(10000); //10 milliseconds
                P_Running_WaitOnDET3(ChannelExtension, TimerCallbackProcess);
            } else {
                ULONG status;
                status = StorPortRequestTimer(adapterExtension, ChannelExtension->StartPortTimer, P_Running_Callback, ChannelExtension, 10000, 0);     //10 milliseconds
                if ((status != STOR_STATUS_SUCCESS) && (status != STOR_STATUS_BUSY)) {
                    StorPortStallExecution(10000); //10 milliseconds
                    P_Running_WaitOnDET3(ChannelExtension, TimerCallbackProcess);
                }
            }
            return;
      //2.1 Wait for DET to become 3 for 1 second
        } else {
            ChannelExtension->StartState.ChannelStateDET1Count++;
            RecordExecutionHistory(ChannelExtension, 0x10600017);//P_Running_WaitWhileDET1 still waiting
            if (IsDumpMode(adapterExtension)) {
                StorPortStallExecution(10000); //10 milliseconds
                goto WaitWhileDET1_Start;
            } else {
                ULONG status;
                status = StorPortRequestTimer(adapterExtension, ChannelExtension->StartPortTimer, P_Running_Callback, ChannelExtension, 10000, 0);     //10 milliseconds
                if ((status != STOR_STATUS_SUCCESS) && (status != STOR_STATUS_BUSY)) {
                    StorPortStallExecution(10000); //10 milliseconds
                    goto WaitWhileDET1_Start;
                }
            }
            return;
        }
    }
}

VOID
P_Running_WaitOnDET3(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN TimerCallbackProcess
    )
/*
    Done monkeying phase.
    From here on out only a DET of 3 will do

Called By:
    P_Running

It assumes:
    Channel is start capable

It performs:
    (overview)
    1 Look for signs of life which are DET == 3
    (details)
    1.1 Wait for 1 second until DET becomes 3
    1.2 After waiting for 1 second, give up on starting the channel
    1.3 If DET==3 we are ready for WaitOnFRE

Affected Variables/Registers:
    none
*/
{
    AHCI_SERIAL_ATA_STATUS  ssts;
    PAHCI_ADAPTER_EXTENSION adapterExtension = ChannelExtension->AdapterExtension;

WaitOnDET3_Start:
    if (LogExecuteFullDetail(adapterExtension->LogFlags)) {
        RecordExecutionHistory(ChannelExtension, 0x00000018);//P_Running_WaitOnDET3
    }

  //1.2 After waiting for 1 second, give up on starting the channel
    ssts.AsUlong = StorPortReadRegisterUlong(adapterExtension, &ChannelExtension->Px->SSTS.AsUlong);

    if (ssts.DET != 3) {

        if (ChannelExtension->StartState.ChannelStateDET3Count > 100) {
            P_Running_StartFailed(ChannelExtension, TimerCallbackProcess);
            RecordExecutionHistory(ChannelExtension, 0x10ff0018);//P_Running_WaitOnDET3 timed out
            return;
      //1.1 Wait for 1 second until DET becomes 3
        } else {
            ChannelExtension->StartState.ChannelStateDET3Count++;
            RecordExecutionHistory(ChannelExtension, 0x10080018);//P_Running_WaitOnDET3 still waiting
            if (IsDumpMode(adapterExtension)) {
                StorPortStallExecution(10000); //10 milliseconds
                goto WaitOnDET3_Start;
            } else {
                ULONG status;
                status = StorPortRequestTimer(adapterExtension, ChannelExtension->StartPortTimer, P_Running_Callback, ChannelExtension, 10000, 0);     //10 milliseconds
                if ((status != STOR_STATUS_SUCCESS) && (status != STOR_STATUS_BUSY)) {
                    StorPortStallExecution(10000); //10 milliseconds
                    goto WaitOnDET3_Start;
                }
            }
            return;
        }
  //1.3 If DET==3 we are ready for WaitOnFRE
    } else {
        ChannelExtension->StartState.ChannelNextStartState = WaitOnFRE;
        RecordExecutionHistory(ChannelExtension, 0x100a0018);//P_Running_WaitOnDET3 Success
        P_Running_WaitOnFRE(ChannelExtension, TimerCallbackProcess);
        return;
    }
}

VOID
P_Running_WaitOnFRE(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN TimerCallbackProcess
    )
/*
    Start the Receive buffer phase
    Wait for confirmation is a 'nice to have' but isn't necessary.

Called By:
    P_Running

It assumes:
    DET == 3 or IPM == 02 or 06

It performs:
    (overview)
    1 Set FRE
    2 Wait for 50ms for FR to reflect running status
    3 Move to WaitOnBSYDRQ
    (details)
    1.1 Set FRE
    2.1 Wait for 50ms for FR to reflect running status
    3.1 Move to WaitOnBSYDRQ
Affected Variables/Registers:
    CMD
*/
{
    AHCI_COMMAND cmd;

    PAHCI_ADAPTER_EXTENSION adapterExtension = ChannelExtension->AdapterExtension;

WaitOnFRE_Start:
    if (LogExecuteFullDetail(adapterExtension->LogFlags)) {
        RecordExecutionHistory(ChannelExtension, 0x00000019);//P_Running_FRE
    }

    // There is a device connected when we get here. Trace how long the port need to set DET from 0 to 1.
    if (ChannelExtension->StartState.ChannelStateDETCount > 1) {
        DebugPrint((1, "STORAHCI: Channel %u: waited more than %u ms for DET 0 -> 1\n",
                      ChannelExtension->PortNumber,
                      10 * ChannelExtension->StartState.ChannelStateDETCount));
    }

  //1.1 Set FRE
    cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &ChannelExtension->Px->CMD.AsUlong);
    cmd.FRE = 1;
    StorPortWriteRegisterUlong(adapterExtension, &ChannelExtension->Px->CMD.AsUlong, cmd.AsUlong);

    cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &ChannelExtension->Px->CMD.AsUlong);

    if (cmd.FR == 1) {
      //3.1 Move to WaitOnBSYDRQ
        ChannelExtension->StartState.ChannelNextStartState = WaitOnBSYDRQ;
        RecordExecutionHistory(ChannelExtension, 0x000a0019);//P_Running_WaitOnFRE Success
        P_Running_WaitOnBSYDRQ(ChannelExtension, TimerCallbackProcess);
        return;

    } else {
      //3.1 Move to WaitOnBSYDRQ
        if (ChannelExtension->StartState.ChannelStateFRECount > 5) {
            ChannelExtension->StartState.ChannelNextStartState = WaitOnBSYDRQ;
            RecordExecutionHistory(ChannelExtension, 0x00ff0019);//P_Running_WaitOnFRE timed out
            P_Running_WaitOnBSYDRQ(ChannelExtension, TimerCallbackProcess);
            return;
      //2.1 Wait for 50ms for FR to reflect running status
        } else {
            ChannelExtension->StartState.ChannelStateFRECount++;
            RecordExecutionHistory(ChannelExtension, 0x00080019);//P_Running_WaitOnFRE still waiting
            if (IsDumpMode(adapterExtension)) {
                StorPortStallExecution(10000); //10 milliseconds
                goto WaitOnFRE_Start;
            } else {
                ULONG status;
                status = StorPortRequestTimer(adapterExtension, ChannelExtension->StartPortTimer, P_Running_Callback, ChannelExtension, 10000, 0);     //10 milliseconds
                if ((status != STOR_STATUS_SUCCESS) && (status != STOR_STATUS_BUSY)) {
                    StorPortStallExecution(10000); //10 milliseconds
                    goto WaitOnFRE_Start;
                }
            }
            return;
        }
    }
}

VOID
P_Running_WaitOnBSYDRQ(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN TimerCallbackProcess
    )
/*
    Home stretch

Called By:
    P_Running
It assumes:
    DET == 3 or IPM == 02 or 06
It performs:
    (overview)
    1 Enable BSY and DRQ to go to 0
    2 Wait for BSY and DRQ to go to 0
    3 Set ST to 1
    (details)
    1.1 Clear serr.DIAG.X
    2.1 Wait for the rest of the 10 seconds for BSY and DRQ to clear
    2.2 After a total amount of 3 seconds working on the start COMRESET
    2.3 After waiting for the rest of the 4 seconds for BSY and DRQ to clear, give up
    3.1 Set ST to 1
Affected Variables/Registers:

*/
{
    AHCI_COMMAND            cmd;
    AHCI_TASK_FILE_DATA     tfd;
    AHCI_SERIAL_ATA_ERROR   serr;
    USHORT                  totalstarttime;

    PAHCI_ADAPTER_EXTENSION adapterExtension = ChannelExtension->AdapterExtension;

WaitOnBSYDRQ_Start:
    RecordExecutionHistory(ChannelExtension, 0x0000001a);//P_Running_WaitOnBSYDRQ
    tfd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &ChannelExtension->Px->TFD.AsUlong);

  //1.1 Enable BSY and DRQ to go to 0
    if ( (tfd.STS.BSY) || (tfd.STS.DRQ) ) {
        //When [serr.DIAG.X is] set to one this bit indicates a COMINIT signal was received.  This bit is reflected in the P0IS.PCS bit.
        //to allow the TFD to be updated serr.DIAG.X must be cleared.
        serr.AsUlong = 0;
        serr.DIAG.X = 1;
        StorPortWriteRegisterUlong(adapterExtension, &ChannelExtension->Px->SERR.AsUlong, serr.AsUlong);
    }
  //3.1 Set ST to 1
    if ( ( tfd.STS.BSY == 0) && ( tfd.STS.DRQ == 0) ) {
        STOR_LOCK_HANDLE    lockhandle;
        BOOLEAN             needSpinLock;

        if ( TimerCallbackProcess && (ChannelExtension->StartState.DirectStartInProcess == 1) ) {
            //This is timer callback and a direct port start process has been started separately, bail out this one.
            RunNextPort(ChannelExtension, (!TimerCallbackProcess && ChannelExtension->StartState.AtDIRQL));
            return;
        }

        AhciZeroMemory((PCHAR)&lockhandle, sizeof(STOR_LOCK_HANDLE));
        needSpinLock = TimerCallbackProcess || (ChannelExtension->StartState.AtDIRQL == 0);

        if (needSpinLock) {
            StorPortAcquireSpinLock(ChannelExtension->AdapterExtension, InterruptLock, NULL, &lockhandle);
        }

      //3.2 Enable Interrupts on the channel (AHCI 1.1 Section 10.1.2 - 7)
        PortClearPendingInterrupt(ChannelExtension);
        Set_PxIE(ChannelExtension, &ChannelExtension->Px->IE);

      //We made it!  Set ST and start the IO we have collected!
        cmd.AsUlong = StorPortReadRegisterUlong(adapterExtension, &ChannelExtension->Px->CMD.AsUlong);
        cmd.ST = 1;
        StorPortWriteRegisterUlong(adapterExtension, &ChannelExtension->Px->CMD.AsUlong, cmd.AsUlong);

        ChannelExtension->StartState.ChannelNextStartState = StartComplete;
        ChannelExtension->StateFlags.IgnoreHotplugInterrupt = FALSE;

        StorPortDebugPrint(3, "StorAHCI - LPM: Port %02d - Port Started\n", ChannelExtension->PortNumber);

      //Start requests on this Port
        AhciGetNextIos(ChannelExtension, TRUE);

        ChannelExtension->StartState.DirectStartInProcess = 0;

        RecordExecutionHistory(ChannelExtension, 0x100a001a);//Exit P_Running_WaitOnBSYDRQ Succeeded

        if (needSpinLock) {
            StorPortReleaseSpinLock(ChannelExtension->AdapterExtension, &lockhandle);
        }

        RunNextPort(ChannelExtension, (!TimerCallbackProcess && ChannelExtension->StartState.AtDIRQL));

        return;

    } else {
      //2.3 After waiting for the remainder of the 60 second maximum Channel Start time for BSY and DRQ to clear, give up
      //Some big HDDs takes close to 20 second to spin up
        ULONG portStartTimeoutIn10MS = (AHCI_PORT_START_TIMEOUT_IN_SECONDS * 100);


        // calculate the total time in unit of 10 ms.
        totalstarttime = ChannelExtension->StartState.ChannelStateDETCount  +
                         ChannelExtension->StartState.ChannelStateDET1Count +
                         ChannelExtension->StartState.ChannelStateDET3Count +
                         ChannelExtension->StartState.ChannelStateFRECount  +
                        (ChannelExtension->StartState.ChannelStateBSYDRQCount * 2);

        if ( totalstarttime > portStartTimeoutIn10MS ) {
            P_Running_StartFailed(ChannelExtension, TimerCallbackProcess);
            RecordExecutionHistory(ChannelExtension, 0x00ff001a);//P_Running_WaitOnBSYDRQ timed out
           return;
      //2.2 After a total amount of 1 second working on the start COMRESET
        } else if ( ChannelExtension->StartState.ChannelStateBSYDRQCount == 50 ){
            //Stop FRE,FR in preperation for RESET
            if ( !P_NotRunning(ChannelExtension, ChannelExtension->Px) ){
                //It takes 1/2 second for stop to fail.
                //This is taking way too long and the controller is not responding properly. Abort the start.
                P_Running_StartFailed(ChannelExtension, TimerCallbackProcess);
                RecordExecutionHistory(ChannelExtension, 0x00fc001a);//P_Running_WaitOnBSYDRQ Stop Failed on COMRESET
                return;
            }

            //All set, bring down the hammer
            AhciCOMRESET(ChannelExtension, ChannelExtension->Px);

            //Set the timers for time remaining.  This is a best case scenario and the best that can be offered.
            ChannelExtension->StartState.ChannelStateDETCount = 0;
            ChannelExtension->StartState.ChannelStateDET1Count = 0;
            ChannelExtension->StartState.ChannelStateDET3Count = 0;     // won't exceed 100 * 10ms for 1 second
            ChannelExtension->StartState.ChannelStateFRECount = 0;      // won't exceed 5 * 10ms for 50 ms
            ChannelExtension->StartState.ChannelStateBSYDRQCount = 51;  // won't exceed 3000 * 20ms for 60 seconds minus what DET3 and FRE use.

            //Go back to WaitOnDet3
            ChannelExtension->StartState.ChannelNextStartState = WaitOnDET3;
            RecordExecutionHistory(ChannelExtension, 0x00fd001a);//P_Running_WaitOnBSYDRQ crossing 1 second.  COMRESET done, going to WaitOnDET3
            P_Running_WaitOnDET3(ChannelExtension, TimerCallbackProcess);
            return;
      //2.1 Wait for the rest of the 4 seconds for BSY and DRQ to clear
        } else {
            ChannelExtension->StartState.ChannelStateBSYDRQCount++;
            RecordExecutionHistory(ChannelExtension, 0x0008001a);//P_Running_WaitOnBSYDRQ still waiting
            if (IsDumpMode(adapterExtension)) {
                StorPortStallExecution(20000); //20 milliseconds
                goto WaitOnBSYDRQ_Start;
            } else {
                ULONG status;
                status = StorPortRequestTimer(adapterExtension, ChannelExtension->StartPortTimer, P_Running_Callback, ChannelExtension, 20000, 0);     //10 milliseconds
                if ((status != STOR_STATUS_SUCCESS) && (status != STOR_STATUS_BUSY)) {
                    StorPortStallExecution(20000); //20 milliseconds
                    goto WaitOnBSYDRQ_Start;
                }
            }
            return;
        }
    }
}

VOID
P_Running_StartFailed(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN TimerCallbackProcess
    )
/*

Called By:
    P_Running_WaitOnDET
    P_Running_WaitOnDET3
    P_Running_WaitOnBSYDRQ

It assumes:
    nothing

It performs:
    (overview)
    1 clean up internal state structures
    2 complete all the commands that showed up while the start channel failed
    (details)
    1.1 update state machines
    1.2 clear out the programmed slots
    2.1 complete all outstanding commands

Affected Variables/Registers:
    none
*/
{
    STOR_LOCK_HANDLE    lockhandle;
    BOOLEAN             needSpinLock;

    if ( TimerCallbackProcess && (ChannelExtension->StartState.DirectStartInProcess == 1) ) {
        //This is timer callback and a direct port start process has been started separately, bail out this one.
        RunNextPort(ChannelExtension, (!TimerCallbackProcess && ChannelExtension->StartState.AtDIRQL));
        return;
    }

    AhciZeroMemory((PCHAR)&lockhandle, sizeof(STOR_LOCK_HANDLE));
    needSpinLock = TimerCallbackProcess || (ChannelExtension->StartState.AtDIRQL == 0);

    if (needSpinLock) {
        StorPortAcquireSpinLock(ChannelExtension->AdapterExtension, InterruptLock, NULL, &lockhandle);
    }

  //1.1 update state machines
    ChannelExtension->StartState.ChannelNextStartState = StartFailed;
    ChannelExtension->StateFlags.IgnoreHotplugInterrupt = FALSE;
    ChannelExtension->StateFlags.D3ColdEnabled = FALSE;     //this flag may be set to "TRUE" for last connected device, clear it when no device connected.

    //1.1.1 release initial commands for previous device.
    if (ChannelExtension->DeviceInitCommands.CommandTaskFile != NULL) {
        StorPortFreePool(ChannelExtension->AdapterExtension, (PVOID)ChannelExtension->DeviceInitCommands.CommandTaskFile);
        ChannelExtension->DeviceInitCommands.CommandTaskFile = NULL;
        ChannelExtension->DeviceInitCommands.CommandCount = 0;
        ChannelExtension->DeviceInitCommands.ValidCommandCount = 0;
        ChannelExtension->DeviceInitCommands.CommandToSend = 0;
    }

    if (ChannelExtension->PersistentSettings.Slots > 0) {
        AhciZeroMemory((PCHAR)&ChannelExtension->PersistentSettings, sizeof(PERSISTENT_SETTINGS));
    }

    //1.1.2 clean up cached device information
    AhciZeroMemory((PCHAR)&ChannelExtension->DeviceExtension->SupportedGPLPages, sizeof(ATA_SUPPORTED_GPL_PAGES));
    AhciZeroMemory((PCHAR)&ChannelExtension->DeviceExtension->SupportedCommands, sizeof(ATA_COMMAND_SUPPORTED));

  //1.2 clear out the programmed slots
    ChannelExtension->SlotManager.CommandsToComplete = GetOccupiedSlots(ChannelExtension);
    ChannelExtension->SlotManager.CommandsIssued = 0;
    ChannelExtension->SlotManager.NCQueueSlice = 0;
    ChannelExtension->SlotManager.NormalQueueSlice = 0;
    ChannelExtension->SlotManager.SingleIoSlice = 0;
    ChannelExtension->SlotManager.HighPriorityAttribute = 0;

  //1.3 Enable Interrupts on the Channel (AHCI 1.1 Section 10.1.2 - 7)
    PortClearPendingInterrupt(ChannelExtension);
    Set_PxIE(ChannelExtension, &ChannelExtension->Px->IE);

  //2.1 Call AhciGetNextIos to complete all outstanding commands now that ChannelNextStartState is StartFailed
    AhciPortFailAllIos(ChannelExtension, SRB_STATUS_NO_DEVICE, TRUE);

    ChannelExtension->StartState.DirectStartInProcess = 0;

    if (needSpinLock) {
        StorPortReleaseSpinLock(ChannelExtension->AdapterExtension, &lockhandle);
    }

    RunNextPort(ChannelExtension, (!TimerCallbackProcess && ChannelExtension->StartState.AtDIRQL));

    return;
}

VOID
AhciNonQueuedErrorRecovery(
    PAHCI_CHANNEL_EXTENSION ChannelExtension
  )
{
/*
AHCI 1.1 Section 6.2.2.1
"The flow for system software to recover from an error when non-queued commands are issued is as follows:
    Reads PxCI to see which commands are still outstanding
    Reads PxCMD.CCS to determine the slot that the HBA was processing when the error occurred
    Clears PxCMD.ST to �0� to reset the PxCI register, waits for PxCMD.CR to clear to �0�
    Clears any error bits in PxSERR to enable capturing new errors.
    Clears status bits in PxIS as appropriate
    If PxTFD.STS.BSY or PxTFD.STS.DRQ is set to �1�, issue a COMRESET to the device to put it in an idle state
    Sets PxCMD.ST to �1� to enable issuing new commands

It assumes:
    Called asynchronously

Called by:
    WorkerDispatch <-- AhciHwInterrupt


It performs:
    (overview)
    1 Recover from an error
    2 Complete the Failed Command
    3 Complete Succeeded Commands

    (details)
    1.1 Initialize
    1.2 Reads PxCI to see which commands are still outstanding
    1.3 Reads PxCMD.CCS to determine the slot that the HBA was processing when the error occurred
    1.4.1 Clears PxCMD.ST to �0� to reset the PxCI register, waits for PxCMD.CR to clear to �0�
     1.5 Clears any error bits in PxSERR to enable capturing new errors.
     1.6 Clears status bits in PxIS as appropriate
    1.4.2 If PxTFD.STS.BSY or PxTFD.STS.DRQ is set to �1�, issue a COMRESET to the device to put it in an idle state
    1.4.3 If a COMRESET was issued, restore Preserved Settings
    1.4.4 Start the channel

    2.1 Complete the command being issued if there was one
    2.2 Restore the unsent programmed commands for careful reprocessing
    2.3 If a request sense SRB was created due to this error

    3.1 If there were commands that are ready to complete, complete them now

Affected Variables/Registers:
    CI, CMD, TFD
    Channel Extension
*/
    BOOLEAN performedCOMRESET;
    ULONG   ci;
    ULONG   localCommandsIssued;
    UCHAR   numberCommandsOutstanding;
    ULONG   failingCommand;

    AHCI_COMMAND        cmd;
    AHCI_TASK_FILE_DATA tfd;
    PSCSI_REQUEST_BLOCK senseSrb;
    PAHCI_SRB_EXTENSION srbExtension;

  //1.1 Initialize variables
    RecordExecutionHistory(ChannelExtension, 0x00000013);//AhciNonQueuedErrorRecovery

    senseSrb = NULL;
    performedCOMRESET = FALSE;

  //1.2 Reads PxCI to see which commands are still outstanding
    ci = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &ChannelExtension->Px->CI);

  //1.3 Reads PxCMD.CCS to determine the slot that the HBA was processing when the error occurred
    cmd.AsUlong = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &ChannelExtension->Px->CMD.AsUlong);

  //1.4 Clears PxCMD.ST to �0� to reset the PxCI register, waits for PxCMD.CR to clear to �0�
    if ( !P_NotRunning(ChannelExtension, ChannelExtension->Px) ){ //This clears PxCI
        RecordExecutionHistory(ChannelExtension, 0x10160013);//AhciNonQueuedErrorRecovery, Port Stop Failed
        AhciPortReset(ChannelExtension, FALSE);
        return;
    }

  //2.1 Complete the command being issued if there was one

    //Determine how many commands are outstanding
    localCommandsIssued = ChannelExtension->SlotManager.CommandsIssued;
    numberCommandsOutstanding = 0;
    failingCommand = 0;
    while (localCommandsIssued) {
        if (localCommandsIssued & 1) {
            numberCommandsOutstanding++;
        }
        //To keep from having to walk through the localCommandsIssued mask again,
        //if there is only 1 command, failing commands will be 1 greater than that slot after this while loop.
        failingCommand++;
        localCommandsIssued >>= 1;
    }

    //If there is only one command outstanding, then we know which one to complete.
    if (numberCommandsOutstanding == 1) {
        failingCommand--;
        //As long as there is an SRB in that command slot.
        if (ChannelExtension->Slot[failingCommand].Srb == NULL) {
            RecordExecutionHistory(ChannelExtension, 0x10170013);//AhciNonQueuedErrorRecovery, didn't find the expected command from slot.
            AhciPortReset(ChannelExtension, FALSE);
            return;
        }
    //If NonQueuedErrorRecovery should not be done for more than 1 command stop here.
    } else if ( (cmd.ST == 1) && AdapterNoNonQueuedErrorRecovery(ChannelExtension->AdapterExtension) ) {
        RecordExecutionHistory(ChannelExtension, 0x10180013);//AhciNonQueuedErrorRecovery, NCQ_NeverNonQueuedErrorRecovery
        AhciPortReset(ChannelExtension, FALSE);
        return;
    } else {
        //otherwise the failing command is in CCS, as long as there is an SRB in that command slot.
        if( ((ChannelExtension->SlotManager.CommandsIssued & (1 << cmd.CCS)) > 0) &&
            (ChannelExtension->Slot[cmd.CCS].Srb) ) {
            failingCommand = cmd.CCS;
        } else {
            RecordExecutionHistory(ChannelExtension, 0x10190013);//AhciNonQueuedErrorRecovery, command to be failed is not indicated by PxCMD.CCS
            AhciPortReset(ChannelExtension, FALSE);
            return;
        }
    }

    // Keep in mind although Px.CMD.ST was just shut off, we are working with the previous snapshot of ST.
    if (cmd.ST == 1) {
        srbExtension = GetSrbExtension(ChannelExtension->Slot[failingCommand].Srb);
        // Remove the errant command from the Commands Issued.
        ci &= ~(1 << failingCommand);
        // Move the errant command from Issued list to the 'to complete' list
        ChannelExtension->SlotManager.CommandsIssued &= ~(1 << failingCommand);
        ChannelExtension->SlotManager.HighPriorityAttribute &= ~(1 << failingCommand);
        ChannelExtension->SlotManager.CommandsToComplete |= (1 << failingCommand);

        // Fill in the status of the command
        srbExtension->AtaStatus = ChannelExtension->TaskFileData.STS.AsUchar;
        srbExtension->AtaError = ChannelExtension->TaskFileData.ERR;
        ChannelExtension->Slot[failingCommand].Srb->SrbStatus = SRB_STATUS_ERROR;
        // Handle Request Sense if needed
        if ( NeedRequestSense(ChannelExtension->Slot[failingCommand].Srb) ) {
            // This is for ATAPI device only
            senseSrb = BuildRequestSenseSrb(ChannelExtension, ChannelExtension->Slot[failingCommand].Srb); // it will be programmed to slot later in this routine.
            if (senseSrb != NULL) {
                //Make the slot available again now, its slot content will not be cleaned up later.
                ChannelExtension->Slot[failingCommand].CmdHeader = NULL;
                ChannelExtension->Slot[failingCommand].CommandHistoryIndex = 0;
                ChannelExtension->Slot[failingCommand].Srb = NULL;
                //Do not complete this command normally, it will be completed when it's request sense is completed
                ChannelExtension->SlotManager.CommandsToComplete &= ~(1 << failingCommand);
                RecordExecutionHistory(ChannelExtension, 0x10730013);//NonNCQ Error Received RequestSense
            }
        }

        if (senseSrb == NULL) {
            // if not need Request Sense or cannot get Reqeust Sense Srb, Complete the command manually
            ReleaseSlottedCommand(ChannelExtension, (UCHAR)failingCommand, TRUE); //&ChannelExtension->Slot[cmd.CCS] );
            RecordExecutionHistory(ChannelExtension, 0x10630013);//NonNCQ Error Completed failed command
        }
    }

    //1.5 Clears any error bits in PxSERR to enable capturing new errors.
        //Handled in the Interrupt routine
    //1.6 Clears status bits in PxIS as appropriate
        //Handled in the Interrupt routine

    //1.4.2 If PxTFD.STS.BSY or PxTFD.STS.DRQ is set to �1�, issue a COMRESET to the device to put it in an idle state
    tfd.AsUlong = StorPortReadRegisterUlong(ChannelExtension->AdapterExtension, &ChannelExtension->Px->TFD.AsUlong);

    if(tfd.STS.BSY || tfd.STS.DRQ) {
        AhciCOMRESET(ChannelExtension, ChannelExtension->Px);
        performedCOMRESET = TRUE;
    }

    //2.2 Restore the unsent programmed commands for careful reprocessing
    ChannelExtension->SlotManager.NormalQueueSlice |= ci;   //put the commands that didn't get a chance to finish back into the normal queue
    ChannelExtension->SlotManager.CommandsIssued &= ~ci;    //Remove the unfinished commands from the 'issued' list

    //2.3 If there were commands that are ready to complete, complete them
    if (ChannelExtension->SlotManager.CommandsToComplete) {
        AhciCompleteIssuedSRBs(ChannelExtension, SRB_STATUS_SUCCESS, TRUE); //complete the successful commands to start the RS SRB.
    }

    //3.1 If a request sense SRB was created due to this error
    if (senseSrb != NULL) {
        ChannelExtension->StateFlags.QueuePaused = FALSE;
        AhciProcessIo(ChannelExtension, (PSTORAGE_REQUEST_BLOCK)senseSrb, TRUE);  //program Sense Srb into Slot
    }

    //3.2 If a COMRESET was issued, restore Preserved Settings
    if (performedCOMRESET == TRUE) {
        RestorePreservedSettings(ChannelExtension, TRUE);
    }

    //3.3 Start the channel, IO will be resumed after port start complete.
    P_Running_StartAttempt(ChannelExtension, TRUE);

    RecordExecutionHistory(ChannelExtension, 0x10000013);//Exit AhciNonQueuedErrorRecovery
}

VOID
AhciPortErrorRecovery(
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension
  )
/*
    Error recovery routine for Port
    AHCI: 6.2.2.1 Non-Queued Error Recovery
          6.2.2.2 Native Command Queuing Error Recovery
          6.2.2.3 Recovery of Unsolicited COMINIT

It assumes:
    nothing

Called by:
    AhciHwInterrupt

Return Values:
    None
*/
{
    if (ChannelExtension->StateFlags.CallAhciReportBusChange) {
        // Handle AHCI 6.2.2.3 Recovery of Unsolicited COMINIT and hot plug
        // AhciPortBusChangeDpcRoutine() will issue RESET, ignore other error recovery marks.
        ChannelExtension->StateFlags.CallAhciReportBusChange = FALSE;
        ChannelExtension->StateFlags.CallAhciNonQueuedErrorRecovery = FALSE;
        ChannelExtension->StateFlags.CallAhciReset = FALSE;

        if (!IsDumpMode(ChannelExtension->AdapterExtension)) {
            StorPortIssueDpc(ChannelExtension->AdapterExtension, &ChannelExtension->BusChangeDpc, ChannelExtension, NULL);
        } else {
            AhciPortBusChangeDpcRoutine(&ChannelExtension->BusChangeDpc, ChannelExtension->AdapterExtension, ChannelExtension, NULL);
        }
    }

    if (ChannelExtension->StateFlags.CallAhciReset) {
        // Handle AHCI 6.2.2.2 Native Command Queuing Error Recovery and other events require RESET.
        ChannelExtension->StateFlags.CallAhciNonQueuedErrorRecovery = FALSE;
        ChannelExtension->StateFlags.CallAhciReset = FALSE;

        AhciPortReset(ChannelExtension, FALSE);
    }

    if (ChannelExtension->StateFlags.CallAhciNonQueuedErrorRecovery) {
        // Handle AHCI 6.2.2.1 Non-Queued Error Recovery
        ChannelExtension->StateFlags.CallAhciNonQueuedErrorRecovery = FALSE;

        AhciNonQueuedErrorRecovery(ChannelExtension);
    }

    return;
}

BOOLEAN
AhciPortReset (
    _In_ PAHCI_CHANNEL_EXTENSION ChannelExtension,
    _In_ BOOLEAN CompleteAllRequests
    )
/*++
AhciPortReset can be called even if the miniport driver is not ready for another request.
The miniport driver should complete all pending requests and must reset the given channel.

It assumes:
    nothing

Called by:

It performs:
    COMRESET along with completing commands to be retried and restoring settings that may be be persistent across a COMRESET

    (overview)
    1 Initialize
    2 Perform COMReset
    3 Complete all outstanding commands
    4 Restore device configuration

Affected Variables/Registers:
    SCTL, CI, SACT
    Channel Extension

Return Values:
    AhciPortReset return TRUE if the reset operation succeeded.
    If the reset failed the routine must return FALSE.
--*/
{
    ULONG commandsToCompleteCount;

  //1.1 Initialize Variables
    RecordExecutionHistory(ChannelExtension, 0x00000050);//AhciPortReset

  //2.1 Stop the channel
    P_NotRunning(ChannelExtension, ChannelExtension->Px);

  //2.2 Perform the COMRESET
    // AHCI 10.1.2 - 3: If PxCMD.CR or PxCMD.FR do not clear to �0� correctly, then software may attempt a port reset or a full HBA reset to recover.
    AhciCOMRESET(ChannelExtension, ChannelExtension->Px);

  //2.3 If either Init Command or PreservedSettings Command is being processed, reset the command index to start from the first one again.
  //    The process will be continued by the Srb's completion routine.
    if (ChannelExtension->StateFlags.ReservedSlotInUse == 1) {
        ChannelExtension->DeviceInitCommands.CommandToSend = 0;
        ChannelExtension->PersistentSettings.SlotsToSend = ChannelExtension->PersistentSettings.Slots;
    }

  //3.1 Complete all issued commands
    ChannelExtension->SlotManager.CommandsToComplete = ChannelExtension->SlotManager.CommandsIssued;
    ChannelExtension->SlotManager.CommandsIssued = 0;
    ChannelExtension->SlotManager.HighPriorityAttribute &= ~ChannelExtension->SlotManager.CommandsToComplete;

    commandsToCompleteCount = NumberOfSetBits(ChannelExtension->SlotManager.CommandsToComplete);

    if (commandsToCompleteCount > 0) {
        UCHAR completeStatus;

        if (commandsToCompleteCount == 1) {
            // we know the error is for this command as it's the only one programmed to adapter,
            // set status to be SRB_STATUS_ERROR to make sure the function - AtaMapError() assigns the real error to Srb
            completeStatus = SRB_STATUS_ERROR;
        } else {
            // more than one command were programmed, we don't know which one triggered the error.
            // set status to be SRB_STATUS_BUS_RESET to reflect the action StorAHCI is doing.
            completeStatus = SRB_STATUS_BUS_RESET;
        }

        AhciCompleteIssuedSRBs(ChannelExtension, completeStatus, TRUE); //AhciPortReset is under Interrupt spinlock
    }

  //3.2 Complete all other commands miniport own for this device, when it's necessary or when the Reset is requested by Storport
    if (CompleteAllRequests) {
        AhciPortFailAllIos(ChannelExtension, SRB_STATUS_BUS_RESET, TRUE);
    }

  //4.1 Restore device configuration
    if (ChannelExtension->StateFlags.ReservedSlotInUse == 0) {
        RestorePreservedSettings(ChannelExtension, TRUE);
    }

  //2.3 Start the channel
    P_Running_StartAttempt(ChannelExtension, TRUE); //AhciPortReset is under Interrupt spinlock

    // record that the channel is reset.
    RecordExecutionHistory(ChannelExtension, 0x10000050);//Exit AhciPortReset

    return TRUE;
}


#if _MSC_VER >= 1200
#pragma warning(pop)
#else
#pragma warning(default:4152)
#pragma warning(default:4214)
#pragma warning(default:4201)
#endif

Our Services

  • What our customers say about us?

© 2011-2024 All Rights Reserved. Joya Systems. 4425 South Mopac Building II Suite 101 Austin, TX 78735 Tel: 800-DEV-KERNEL

Privacy Policy. Terms of use. Valid XHTML & CSS