diff -r 000000000000 -r 29b1cd4cb562 bthci/bthci2/hcicmdq/src/HciCmdQController.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bthci/bthci2/hcicmdq/src/HciCmdQController.cpp Fri Jan 15 08:13:17 2010 +0200 @@ -0,0 +1,2107 @@ +// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// + +/** + @file + @internalComponent +*/ + +#include +#include + +#include "HciCmdQTimer.h" +#include "HciCmdQUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef __FLOG_ACTIVE +_LIT8(KLogComponent, LOG_COMPONENT_HCICMDQ); +#endif + +#ifdef _DEBUG +PANICCATEGORY("hcicmdqcon"); +#endif + +_LIT(KHciCommandQueueComponentName, "cmdq"); + +EXPORT_C void MHCICmdQueueUtilities::InjectEvent(const THCIEventBase& aEvent) + { + MhcquiInjectEvent(aEvent); + } + +EXPORT_C CHCICommandQItem* MHCICmdQueueUtilities::FindOutstandingCommand(THCIOpcode aOpcode) + { + return MhcquiFindOutstandingCommand(aOpcode); + } + +EXPORT_C /*static*/ CHCICmdQController* CHCICmdQController::NewL() + { + LOG_STATIC_FUNC + + CHCICmdQController* self = new (ELeave) CHCICmdQController(); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + +/** +Destructor. +*/ +EXPORT_C CHCICmdQController::~CHCICmdQController() + { + LOG_FUNC + + // All (entry point) command queue clients should have removed any commands they added. +#if defined(_DEBUG) && defined(__FLOG_ACTIVE) + // In debug to help debugging by logging the commands left on the queues that should be empty. + if(!iInitCommandQ.IsEmpty()) + { + LOG(_L("Initialisation Queue is not empty...")) + LogQueue(iInitCommandQ); + } + if(!iNormalCommandQ.IsEmpty()) + { + LOG(_L("Normal Queue is not empty...")) + LogQueue(iNormalCommandQ); + } + if(!iPriorityCommandQ.IsEmpty()) + { + LOG(_L("Priority Queue is not empty...")) + LogQueue(iPriorityCommandQ); + } + if(!iWorkaroundCommandQ.IsEmpty()) + { + LOG(_L("Workaround Queue is not empty...")) + LogQueue(iWorkaroundCommandQ); + } +#endif // _DEBUG && __FLOG_ACTIVE + __ASSERT_ALWAYS(iInitCommandQ.IsEmpty(), PANIC(KHCICmdQPanic, EInitCommandQNotEmptyInDestructor)); + __ASSERT_ALWAYS(iNormalCommandQ.IsEmpty(), PANIC(KHCICmdQPanic, ENormalCommandQNotEmptyInDestructor)); + __ASSERT_ALWAYS(iPriorityCommandQ.IsEmpty(), PANIC(KHCICmdQPanic, EPriorityCommandQNotEmptyInDestructor)); + __ASSERT_ALWAYS(iWorkaroundCommandQ.IsEmpty(), PANIC(KHCICmdQPanic, EWorkaroundCommandQNotEmptyInDestructor)); + + // Clean up internally managed queues + TDblQueIter sentIter(iSentCommandQ); + CleanUpQueue(sentIter); + TDblQueIter resendIter(iResendCommandQ); + CleanUpQueue(resendIter); + + __ASSERT_ALWAYS(iSentCommandQ.IsEmpty(), PANIC(KHCICmdQPanic, ESentCommandQNotEmptyInDestructor)); + __ASSERT_ALWAYS(iResendCommandQ.IsEmpty(), PANIC(KHCICmdQPanic, EResendCommandQNotEmptyInDestructor)); + + delete iQStarvationTimer; + delete iSendingCommand; + delete iQdpPlugin; + + // Delete async CallBacks. If running, these should be cancelled by the + // d'tor of CAsyncOneShot. + delete iAsyncCallBackForReset; + delete iAsyncCallBackForSend; + + delete iHciUtil; + CLOSE_LOGGER + } + +/** +Sets the HCTL interface instance to be used by the Command Queue Controller to +send command frames over the HCTL. + +@param aHctlInterface An instance implementing the interface to the HCTL. +*/ +EXPORT_C void CHCICmdQController::SetHCTLInterface(MHCTLInterface& aHctlInterface) + { + LOG_FUNC + __ASSERT_DEBUG(!iHctl, PANIC(KHCICmdQPanic, EHctlInterfaceInitialisedTwice)); + iHctl = &aHctlInterface; + } + +/** +Sets the HCI Command Allocator interface instance to be used by the Command Queue +Controller to create HCI command frames. + +@param aCommandAllocator Implementation of command allocator interface. +*/ +EXPORT_C void CHCICmdQController::SetHCICommandAllocator(MHCICommandAllocator& aCommandAllocator) + { + LOG_FUNC + __ASSERT_DEBUG(!iCommandAllocator, PANIC(KHCICmdQPanic, EHciCommandAllocatorInterfaceInitialisedTwice)); + iCommandAllocator = &aCommandAllocator; + } + +/** +Sets the Link Mux Notifier interface instance to be used by the Command Queue +Controller to provide co-ordinated sending to the HCTL. + +@param aLinkMuxer An instance implementing the interface to the Link Muxer. +*/ +EXPORT_C void CHCICmdQController::SetLinkMuxNotifier(MLinkMuxNotifier& aLinkMuxer) + { + LOG_FUNC + __ASSERT_DEBUG(!iLinkMuxer, PANIC(KHCICmdQPanic, ELinkMuxNotifierInitialisedTwice)); + iLinkMuxer = &aLinkMuxer; + } + +/** +Sets the command queue client that will subsequently receive unmatched events. + +@param aUnmatchedEventObserver An instance implementing the command queue client interface. +*/ +EXPORT_C void CHCICmdQController::SetHCIUnmatchedEventObserver(MHCICommandQueueClient& aUnmatchedEventObserver) + { + LOG_FUNC + __ASSERT_DEBUG(!iUnmatchedEventObserver, PANIC(KHCICmdQPanic, EUnmatchedEventObserverInitialisedTwice)); + iUnmatchedEventObserver = &aUnmatchedEventObserver; + } + +/** +Sets the Hard Reset Initiator interface instance to be used by the QDP. +This will allow the QDP to request a Hard Reset. + +@param aHardResetInitiator An instance implementing the interface to the Hard Reset Initiator. +*/ +EXPORT_C void CHCICmdQController::SetHardResetInitiator(MHardResetInitiator& aHardResetInitiator) + { + LOG_FUNC + __ASSERT_DEBUG(!iHardResetInitiator, PANIC(KHCICmdQPanic, EHardResetInitiatorInitialisedTwice)); + iHardResetInitiator = &aHardResetInitiator; + iQdp->MhcqdiSetHardResetInitiator(aHardResetInitiator); + } + +/** +Sets the physical links state interface instance to be used by the QDP to query the state of +physical links in the stack.. + +@param aStackInfo An instance implementing the interface to the physical links state information. +*/ +EXPORT_C void CHCICmdQController::SetPhysicalLinksState(const MPhysicalLinksState& aStackInfo) + { + LOG_FUNC + iQdp->MhcqdiSetPhysicalLinksState(aStackInfo); + } + +/** +This allows the Command Queue to start accepting commands to be queued. Whilst +in the Initialise state only Initialisation commands will be sent but other +commands will be added to the Normal and Priority queues so they can be sent +when we transition to the Started state. +@see CHCICmdQController::Start +*/ +EXPORT_C void CHCICmdQController::Initialise() + { + LOG_FUNC + + // Can only enter the Initialising state from either the Uninitialised + // state or the Resetting state + switch(iCmdQControllerState) + { + case EUninitialised: + iCmdQControllerState = EInitialising; + break; + + case EResetting: + iCmdQControllerState = EResetInit; + break; + + default: + PANIC(KHCICmdQPanic, EInvalidStateTransition); + break; + }; + } + +/** +Initiates an asynchronous request for reset processing on the command queue. +Asynchronous reset processing is performed by CHCICmdQController::DoReset(), to +empty the command queue and perform any other tasks on the Command Queue when +the stack requests a sub-system reset. This will put the Command Queue into a +state where no commands can be sent until CHCICmdQController::Initialise() is +called. +@see CHCICmdQController::Initialise +*/ +EXPORT_C void CHCICmdQController::Reset() + { + LOG_FUNC + + // Mark the tails of the queues. + StorePurgeMarks(); + + // Stop the queue starvation timer and reset the priority insert point. + iQStarvationTimer->Cancel(); + + iCmdQControllerState = EResetting; + + // Cancel the sending callback if it is currently active. + iAsyncCallBackForSend->Cancel(); + + // Call async CallBack to initiate Reset processing. + iAsyncCallBackForReset->CallBack(); + } + +/** +Signals that the Stack has finished intialising the HCI, and that it is +safe to send normal (non-initialisation) commands. +Trying to add intialisation commands after this call will result in a panic. +@see CHCICmdQController::Reset +@panic EInvalidStateTransition if not in the initialising state. +@panic EStartCalledWhenInitQNotEmpty if the method is called when the initialisation queue is not empty. +@panic EObjectNotInitialised if not all inteface references have been populated. +*/ +EXPORT_C void CHCICmdQController::Start() + { + LOG_FUNC + + // Can only enter the Started state from the Initialising state + __ASSERT_ALWAYS((iCmdQControllerState == EInitialising), PANIC(KHCICmdQPanic, EInvalidStateTransition)); + __ASSERT_ALWAYS((iInitCommandQ.IsEmpty()), PANIC(KHCICmdQPanic, EStartCalledWhenInitQNotEmpty)); + __ASSERT_ALWAYS(iHctl && + iCommandAllocator && + iLinkMuxer && + iUnmatchedEventObserver && + iHardResetInitiator, PANIC(KHCICmdQPanic, EObjectNotInitialised)); + + iCmdQControllerState = EStarted; + if(AnyCmdsToSend()) + { + iAsyncCallBackForSend->CallBack(); + } + } + +/** +Called by CLinkMuxer only when the command sub-channel is open. +This shall be as a result of this class calling MLinkMuxNotifier::TryToSend. +Only one send should be sent during this function. If another command is suitable +for sending then MLinkMuxNotifier::TryToSend should be called again to get +permission to do the sending. +*/ +EXPORT_C void CHCICmdQController::DoSend() + { + LOG_FUNC; + + LOG1(_L("CHCICmdQController::DoSend() - iSendingCommand = 0x%08x"), iSendingCommand); + + if(iSendingCommand) + { + // Format and send the command frame. Operation cannot fail. + iSendingCommand->FormatFrame(); + TInt err = iHctl->MhiWriteCommand(iSendingCommand->Frame().HCTLPayload()); + LOG1(_L("CHCICmdQController::DoSend() - MhiWriteCommand, Err = %d"), err); + + if(err == KErrNone) + { + // Increment the commands sent count. + iSendingCommand->CommandSent(); + + TUint consumed = iSendingCommand->Command().CreditsConsumed(); + // Check if the vendor command has a completion event + MHCICompletingEventQuery* completingEventInterface = NULL; + err = iSendingCommand->Command().Extension_(KCompletingEventExpectUid, reinterpret_cast(completingEventInterface), NULL); + if( (err == KErrNone && !completingEventInterface->MhceqCompletingEventExpected()) || + (err == KErrNotSupported && consumed == 0)) + { + // Certain commands (e.g. the Number of Host Complete Packets + // command) use no credits and do not normally return any + // event. In this case the command is not moved to the sent + // queue to prevent it from continually growing. + // This call will set iSendingCommand to NULL. + DeleteCommand(iSendingCommand); + } + else + { + // Decrement command credits, move the command to the sent queue and clear the send buffer. + __ASSERT_ALWAYS((consumed <= iCommandCredits), + PANIC(KHCICmdQPanic, ECommandCreditsCountLessThanZero)); + LOG2(_L("CHCICmdQController::DoSend() - iCommandCredits = %d, consumed = %d"), iCommandCredits, consumed); + + // If necessary start completion timer. + TUint timeout(iQdp->MhcqdiTimeoutRequired(*iSendingCommand)); + __ASSERT_ALWAYS((timeout <= MhcqMaxHciCommandTimeout()), + PANIC(KHCICmdQPanic, ECommandTimeoutTooBig)); + + // If a timeout has been specified from the QDP start it. + if(timeout != MHCICmdQueueDecisionInterface::KNoTimeoutRequired) + { + iSendingCommand->StartCompletionTimer(timeout, *this); + } + + iCommandCredits -= consumed; + iSentCommandQ.AddLast(*iSendingCommand); + iSendingCommand = NULL; + } + } + else + { + // Failed to write the command. Error the client. + if(iSendingCommand->Client()) + { + iSendingCommand->Client()->MhcqcCommandErrored(err, &iSendingCommand->Command()); + } + DeleteCommand(iSendingCommand); // This will also NULL iSendingCommand + } + } + // Clear the TryToSendBlock and reschedule if required. + ClearBlock(ETryToSendBlock); + if(AnyCmdsToSend()) + { + iAsyncCallBackForSend->CallBack(); + } + } + +/** +This function is called when the timer for aUncompletedCmdId fires. +@panic ETimedOutCommandError if aUncompletedCmdId does not exist on +the sent queue. +@param aUncompletedCmdId the ID of the uncompleted command. +*/ +void CHCICmdQController::CompletionTimeoutFired(TUint aUncompletedCmdId) + { + LOG_FUNC + + CHCICommandQItem* cmd = ScanQueueByQId(iSentCommandQ, aUncompletedCmdId); + __ASSERT_ALWAYS(cmd != NULL, PANIC(KHCICmdQPanic, ETimedOutCommandError)); + + ProcessCommandCompletionTimeout(cmd); + + if (AnyCmdsToSend()) + { + iAsyncCallBackForSend->CallBack(); + } + } + +/** +@panic ECanSendDeadlock as a guard against a CanSend block deadlock in + UDEB builds. In UREL builds, + MHardResetInitiator::MhriStartHardReset will be called. +@panic EResendDeadlock as a guard against a Resend block deadlock in + UDEB builds. In UREL builds, + MHardResetInitiator::MhriStartHardReset will be called. +@panic EUnknownDeadlock as a guard against priority queue deadlock in + UDEB builds, i.e. the priority queue hasn't changed at all. + In UREL builds, MHardResetInitiator::MhriStartHardReset will be called. +@param aNextPendingCmdId this was the ID of the next pending + command when the timer was started. If this hasn't changed then + panic / ResetCycle as above. +*/ +void CHCICmdQController::StarvationTimeoutFired(TUint __DEBUG_ONLY(aNextPendingCmdId)) + { + LOG_FUNC + +#ifdef _DEBUG + // Check for possible dead-lock situation that can occur. + TDblQue* queue = QueueSendingNext(); + + if (queue) + { + if (queue != &iPriorityCommandQ) + { + CHCICommandQItem* cmd = queue->First(); + + if (cmd->CommandQId() == aNextPendingCmdId) + { + if (Blocked(ECanSendBlock, ENoBlocks)) + { + PANIC(KHCICmdQPanic, ECanSendDeadlock); + } + else if (Blocked(EResendBlock, ENoBlocks)) + { + PANIC(KHCICmdQPanic, EResendDeadlock); + } + } + } + else if (aNextPendingCmdId == KInvalidCommandQueueItemId) + { + // This is priority queue, so we can't point to a single culprit. + // Apparently none of the commands on the queue could be sent. + PANIC(KHCICmdQPanic, EUnknownDeadlock); + } + } + + // Always assert in debug builds for starvation timer conditions. + // This may be removed when more is known about these scenarios. + __ASSERT_DEBUG(EFalse, PANIC(KHCICmdQPanic, EStarvationTimerFired)); +#endif + + // Force possibly stale caching blocks to be recomputed. + ClearBlock(ECachingCommandBlocks); + + iHardResetInitiator->MhriStartHardReset(); + } + +/** +Virtuals from MHCICommandQueue +*/ + +/*virtual*/ TUint CHCICmdQController::MhcqAddCommandL(CHCICommandQItem* aQueItem) + { + LOG_FUNC + __ASSERT_ALWAYS(aQueItem != NULL, PANIC(KHCICmdQPanic, EAttemptToAddNullCommand)); + + return DoAddCommandL(*aQueItem, iNormalCommandQ, EStarted); + } + +/*virtual*/ TUint CHCICmdQController::MhcqAddCommandL(CHCICommandBase* aCommandData, MHCICommandQueueClient& aCmdProgressRecipient) + { + LOG_FUNC + __ASSERT_ALWAYS(aCommandData != NULL, PANIC(KHCICmdQPanic, EAttemptToAddNullCommand)); + __ASSERT_ALWAYS(iCommandAllocator != NULL, PANIC(KHCICmdQPanic, EObjectNotInitialised)); + + CHCICommandQItem* aQueItem = CHCICommandQItem::NewL(*iCommandAllocator, + aCmdProgressRecipient, + aCommandData); + return MhcqAddCommandL(aQueItem); + } + +/*virtual*/ TUint CHCICmdQController::MhcqAddPriorityCommandL(CHCICommandQItem* aQueItem) + { + LOG_FUNC + __ASSERT_ALWAYS(aQueItem != NULL, PANIC(KHCICmdQPanic, EAttemptToAddNullCommand)); + + return DoAddCommandL(*aQueItem, iPriorityCommandQ, EStarted); + } + +/*virtual*/ TUint CHCICmdQController::MhcqAddPriorityCommandL(CHCICommandBase* aCommandData, MHCICommandQueueClient& aCmdProgressRecipient) + { + LOG_FUNC + __ASSERT_ALWAYS(aCommandData != NULL, PANIC(KHCICmdQPanic, EAttemptToAddNullCommand)); + __ASSERT_ALWAYS(iCommandAllocator != NULL, PANIC(KHCICmdQPanic, EObjectNotInitialised)); + + CHCICommandQItem* aQueItem = CHCICommandQItem::NewL(*iCommandAllocator, + aCmdProgressRecipient, + aCommandData); + return MhcqAddPriorityCommandL(aQueItem); + } + +/*virtual*/ TUint CHCICmdQController::MhcqAddInitCommandL(CHCICommandQItem* aQueItem) + { + LOG_FUNC + __ASSERT_ALWAYS(aQueItem != NULL, PANIC(KHCICmdQPanic, EAttemptToAddNullCommand)); + __ASSERT_ALWAYS((iCmdQControllerState != EStarted), PANIC(KHCICmdQPanic, EInitCmdAfterStart)); + + TUint ret = DoAddCommandL(*aQueItem, iInitCommandQ, EInitialising); + // Mark as an Initialisation command. + aQueItem->SetInitialisationCmd(); + return ret; + } + +/*virtual*/ TUint CHCICmdQController::MhcqAddInitCommandL(CHCICommandBase* aCommandData, MHCICommandQueueClient& aCmdProgressRecipient) + { + LOG_FUNC + __ASSERT_ALWAYS(aCommandData != NULL, PANIC(KHCICmdQPanic, EAttemptToAddNullCommand)); + __ASSERT_ALWAYS(iCommandAllocator != NULL, PANIC(KHCICmdQPanic, EObjectNotInitialised)); + + CHCICommandQItem* aQueItem = CHCICommandQItem::NewL(*iCommandAllocator, + aCmdProgressRecipient, + aCommandData); + return MhcqAddInitCommandL(aQueItem); + } + +/*virtual*/ TInt CHCICmdQController::MhcqRemoveCommand(TUint aCommandId, + const MHCICommandQueueClient& aCmdOriginator) + { + LOG_FUNC + + CHCICommandQItem* pendingCmd = NULL; + CHCICommandQItem* lockedCmd = NULL; + TInt retVal = KErrNotFound; + + pendingCmd = ScanQueueByQId(iNormalCommandQ, aCommandId); + if(!pendingCmd) + { + pendingCmd = ScanQueueByQId(iInitCommandQ, aCommandId); + if (!pendingCmd) + { + pendingCmd = ScanQueueByQId(iPriorityCommandQ, aCommandId); + if (!pendingCmd) + { + pendingCmd = ScanQueueByQId(iWorkaroundCommandQ, aCommandId); + if(!pendingCmd) + { + lockedCmd = ScanQueueByQId(iSentCommandQ, aCommandId); + if(!lockedCmd) + { + lockedCmd = ScanQueueByQId(iResendCommandQ, aCommandId); + } + } + } + } + } + + if(lockedCmd) + { + __ASSERT_ALWAYS(lockedCmd->Client() == &aCmdOriginator, PANIC(KHCICmdQPanic, ETryingToDeleteCommandNotOwned)); + retVal = KErrLocked; + } + else if(pendingCmd && pendingCmd->IsParent()) + { + __ASSERT_ALWAYS(pendingCmd->Client() == &aCmdOriginator, PANIC(KHCICmdQPanic, ETryingToDeleteCommandNotOwned)); + retVal = KErrLocked; + } + else if(pendingCmd) + { + __ASSERT_ALWAYS(pendingCmd->Client() == &aCmdOriginator, PANIC(KHCICmdQPanic, ETryingToDeleteCommandNotOwned)); + __ASSERT_ALWAYS(!pendingCmd->IsPreChild(), PANIC(KHCICmdQPanic, ETryingToDeletePreChildCommandById)); + __ASSERT_ALWAYS(!pendingCmd->IsPostChild(), PANIC(KHCICmdQPanic, ETryingToDeletePostChildCommandById)); + DeleteCommand(pendingCmd); + retVal = KErrNone; + } + + return retVal; + } + +void CHCICmdQController::HandleCommandRemoval(CHCICommandQItem*& aCmd, TBool aCanDelete) + { + LOG_FUNC + ASSERT_DEBUG(aCmd); + if(aCanDelete && !aCmd->IsParent()) + { + // The client should never be the originator of child commands + __ASSERT_ALWAYS(!aCmd->IsPreChild(), PANIC(KHCICmdQPanic, ETryingToDeletePreChildCommand)); + __ASSERT_ALWAYS(!aCmd->IsPostChild(), PANIC(KHCICmdQPanic, ETryingToDeletePostChildCommand)); + DeleteCommand(aCmd); + } + else + { + aCmd->DetachClient(); + } + } + +void CHCICmdQController::HandleCommandRemoval(CHCICommandQItem& aCmd, TBool aCanDelete) + { + LOG_FUNC + CHCICommandQItem* cmd = &aCmd; + HandleCommandRemoval(cmd, aCanDelete); + } + +void CHCICmdQController::RemoveAllCommands(TDblQueIter aIter, + const MHCICommandQueueClient& aCmdOriginator, + TBool aCanDelete) + { + LOG_FUNC + + for (CHCICommandQItem* item(aIter++);item != NULL;item = aIter++) + { + if(item->Client() == &aCmdOriginator) + { + HandleCommandRemoval(*item, aCanDelete); + } + } + } + +/*virtual*/ void CHCICmdQController::MhcqRemoveAllCommands(const MHCICommandQueueClient& aCmdOriginator) + { + LOG_FUNC + + TDblQueIter initIter(iInitCommandQ); + RemoveAllCommands(initIter, aCmdOriginator, ETrue); + + TDblQueIter normalIter(iNormalCommandQ); + RemoveAllCommands(normalIter, aCmdOriginator, ETrue); + + TDblQueIter priorityIter(iPriorityCommandQ); + RemoveAllCommands(priorityIter, aCmdOriginator, ETrue); + + TDblQueIter workaroundIter(iWorkaroundCommandQ); + RemoveAllCommands(workaroundIter, aCmdOriginator, ETrue); + + TDblQueIter sentIter(iSentCommandQ); + RemoveAllCommands(sentIter, aCmdOriginator, EFalse); + + TDblQueIter resendIter(iResendCommandQ); + RemoveAllCommands(resendIter, aCmdOriginator, EFalse); + + if(iSendingCommand) + { + if(iSendingCommand->Client() == &aCmdOriginator) + { + HandleCommandRemoval(iSendingCommand, ETrue); + } + } + } + +void CHCICmdQController::CleanUpQueue(TDblQueIter aIter) + { + LOG_FUNC + + for (CHCICommandQItem* item(aIter++);item != NULL;item = aIter++) + { + DeleteCommand(item); + } + } + +/*virtual*/ TUint CHCICmdQController::MhcqMaxHciCommandTimeout() const + { + LOG_FUNC + + return iMaxHciCommandTimeout; + } + +/*virtual*/ TAny* CHCICmdQController::MhcqQdpPluginInterface(TUid aUid) const + { + LOG_FUNC + + __ASSERT_ALWAYS((aUid != TUid::Uid(KHCICmdQueueDecisionInterfaceUid)) + && (aUid != TUid::Uid(KHCICmdQueueDecisionEventModifierInterfaceUid)), + PANIC(KHCICmdQPanic, EIllegalRequestForQdpInterface)); + return (iQdpPlugin) ? iQdpPlugin->Interface(aUid) : NULL; + } + +/** +Virtuals from MHCICommandEventObserver +*/ + +/*virtual*/ void CHCICmdQController::MhceoEventNotification(const THCIEventBase& aEvent) + { + LOG_FUNC + + ProcessEvent(aEvent, ETrue); + } + +void CHCICmdQController::ProcessEvent(const THCIEventBase& aEvent, TBool aSendToQdp) + { + LOG_FUNC + + // Process command credits. + THCIEventCode eventCode(aEvent.EventCode()); + if (eventCode == ECommandCompleteEvent) + { + + const THCICommandCompleteEvent& event(THCICommandCompleteEvent::Cast(aEvent)); + UpdateCommandCredits(event.NumHCICommandPackets()); + + if (event.CommandOpcode() == KNopOpcode) + { + // This is a special command complete event that does not + // complete an event. Command_Opcode, 0x0000 is a NOP, and + // can be used to change the number of outstanding HCI + // command packets that the Host can send before waiting. + + // Nothing else to do for this event, so throw it away + return; + } + } + else if (eventCode == ECommandStatusEvent) + { + TCommandStatusEvent& event = TCommandStatusEvent::Cast(aEvent); + UpdateCommandCredits(event.NumHCICommandPackets()); + + if (event.CommandOpcode() == KNopOpcode) + { + // Same as above for NOP command complete event. + + // Nothing else to do for this event, so throw it away + return; + } + } + + // Iterate through sent queue to try and match the event to a sent command. + CHCICommandQItem* cmd(0); + TDblQueIter qIter(iSentCommandQ); + TBool matchesCmd(EFalse); + TBool concludesCmd(EFalse); + TBool continueMatching(EFalse); + TBool eventMatched(EFalse); + + while ((qIter != NULL) && (!eventMatched || continueMatching)) + { + cmd = qIter++; + cmd->Command().Match(aEvent, matchesCmd, concludesCmd, continueMatching); + if (matchesCmd) + { + eventMatched = ETrue; + + // If the event concludes the command then remove it from the sent queue + if (concludesCmd) + { + cmd->iLink.Deque(); + } + + if (aSendToQdp) + { + // Notify QDP. + if (iQdpEventModifier) + { + THCIEventCode origEventCode = aEvent.EventCode(); + iQdpEventModifier->MhcqemiMatchedEventReceived(const_cast(aEvent), *cmd); + __ASSERT_ALWAYS(origEventCode == aEvent.EventCode(), PANIC(KHCICmdQPanic, EQdpTryingToChangeEventCode)); + } + else + { + iQdp->MhcqdiMatchedEventReceived(aEvent, *cmd); + } + } + + // Process the matched event. + if (aEvent.ErrorCode() == EOK) + { + ProcessMatchedEvent(aEvent, cmd, concludesCmd); + } + else + { + ProcessMatchedErrorEvent(aEvent, cmd, concludesCmd, aSendToQdp); + } + } + } + + if (!eventMatched) + { + // Process the unmatched event. + ProcessUnmatchedEvent(aEvent, aSendToQdp); + } + + // Reschedule if there are commands queued. + if (AnyCmdsToSend()) + { + iAsyncCallBackForSend->CallBack(); + } + + } + +void CHCICmdQController::MhcquiInjectEvent(const THCIEventBase& aEvent) + { + LOG_FUNC + + ProcessEvent(aEvent, EFalse); + } + +CHCICommandQItem* CHCICmdQController::MhcquiFindOutstandingCommand(THCIOpcode aOpcode) + { + LOG_FUNC + + return ScanQueueByOpcode(iSentCommandQ, aOpcode); + } + + +/** +Constructor. +*/ +CHCICmdQController::CHCICmdQController() + : iMaxHciCommandTimeout(KDefaultMaxHciCommandTimeout), + iQueueStarvationTimeout(2 * iMaxHciCommandTimeout), + iInitCommandQ(_FOFF(CHCICommandQItem,iLink)), + iNormalCommandQ(_FOFF(CHCICommandQItem,iLink)), + iPriorityCommandQ(_FOFF(CHCICommandQItem,iLink)), + iWorkaroundCommandQ(_FOFF(CHCICommandQItem,iLink)), + iSentCommandQ(_FOFF(CHCICommandQItem,iLink)), + iResendCommandQ(_FOFF(CHCICommandQItem,iLink)), + iCmdQControllerState(EUninitialised), + iNextCommandQItemId(1) + + { + CONNECT_LOGGER + LOG_FUNC + } + +void CHCICmdQController::ConstructL() + { + LOG_FUNC + + // Add Async Callbacks + TCallBack resetCallBack(AsyncCallBackForReset, this); + + iAsyncCallBackForReset = + new (ELeave)CAsyncCallBack(resetCallBack, CActive::EPriorityStandard); + + TCallBack startCallBack(AsyncCallBackForSend, this); + + iAsyncCallBackForSend = + new (ELeave)CAsyncCallBack(startCallBack, CActive::EPriorityStandard); + + iQStarvationTimer = CHCICmdQStarvationTimer::NewL(); + + // Create HCI Utility library + iHciUtil = CHciUtil::NewL(KHciCommandQueueComponentName); + + // If we can't open the ini file then this will be treated in the same way + // as not reading a valid UID from the ini file. + TRAP_IGNORE(iHciUtil->OpenIniFileL()); + + _LIT(KSection, "QDP"); + _LIT(KQdpUidTag, "EcomUid"); + + TUid qdpImplUid = TUid::Null(); + TRAPD(err, qdpImplUid = iHciUtil->GetUidFromFileL(KSection, KQdpUidTag)); + + if (err == KErrNone) + { + // Valid UID found, load it + iQdpPlugin = CHCICmdQueueDecisionPlugin::NewL(qdpImplUid); + } + else + { + // No UID found in ini file, attempt to load single instance of + // implementation + iQdpPlugin = CHCICmdQueueDecisionPlugin::NewL(); + } + + iQdp = reinterpret_cast(iQdpPlugin->Interface(TUid::Uid(KHCICmdQueueDecisionInterfaceUid))); + iQdpEventModifier = reinterpret_cast(iQdpPlugin->Interface(TUid::Uid(KHCICmdQueueDecisionEventModifierInterfaceUid))); + + MHCICmdQueueUtilityUser* qdpUtilityUser = reinterpret_cast(iQdpPlugin->Interface(TUid::Uid(KHCICmdQueueUtilityUserUid))); + + if (qdpUtilityUser) + { + qdpUtilityUser->MhcquuSetUtilitiesProvider(*this); + } + + // Read timeout values + _LIT(KMaxHciCommandTimeoutTag, "MaxHciCommandTimeout"); + _LIT(KQueueStarvationTimeoutTag, "QueueStarvationTimeout"); + + // Don't worry if we cannot read timeouts from file, already initialised + // to default values + TRAP_IGNORE(iMaxHciCommandTimeout = iHciUtil->GetValueFromFileL(KSection, KMaxHciCommandTimeoutTag)); + TRAP_IGNORE(iQueueStarvationTimeout = iHciUtil->GetValueFromFileL(KSection, KQueueStarvationTimeoutTag)); + + __ASSERT_DEBUG(iMaxHciCommandTimeout < iQueueStarvationTimeout, PANIC(KHCICmdQPanic, EQStarvationTimerNotGreaterThanMaxCmdTimer)); + iQdp->MhcqdiSetTimeouts(iQueueStarvationTimeout, iMaxHciCommandTimeout); + iQdp->MhcqdiSetHCICommandQueue(*this); + + // Reset the QDP. + iCommandCredits = iQdp->MhcqdiReset(); + } + +/** +Tests for the specified TQueueStateBits block(s) being set. +*/ +inline TBool CHCICmdQController::Blocked(TUint aBlocksToCheck, TUint aBlocksToBypass) + { + return Blocked(iCommandQState, aBlocksToCheck, aBlocksToBypass); + } + +/** +Tests for the specified TQueueStateBits block(s) being set. +*/ +inline TBool CHCICmdQController::Blocked(TUint aBlockStatus, TUint aBlocksToCheck, TUint aBlocksToBypass) + { + return (aBlockStatus & (ValidBlocks(aBlocksToCheck) & ValidBlocks(~aBlocksToBypass))); + } + +/** +Clears the specified TQueueStateBits block(s). +*/ +inline void CHCICmdQController::ClearBlock(TUint aBlocks) + { + __ASSERT_DEBUG(!InvalidBlocks(aBlocks), PANIC(KHCICmdQPanic, EIllegalBlock)); + iCommandQState &= ValidBlocks(~aBlocks); + } + +/** +Sets the specified TQueueStateBits block(s). +*/ +inline void CHCICmdQController::SetBlock(TUint aBlocks) + { + __ASSERT_DEBUG(!InvalidBlocks(aBlocks), PANIC(KHCICmdQPanic, EIllegalBlock)); + iCommandQState |= ValidBlocks(aBlocks); + } + +/** +Masks out invalid TQueueStateBits block bit(s). +*/ +inline TUint CHCICmdQController::ValidBlocks(TUint aBlocks) + { + return (aBlocks & EAllBlocks); + } + +/** +Masks out valid TQueueStateBits block bit(s). +*/ +inline TUint CHCICmdQController::InvalidBlocks(TUint aBlocks) + { + return (aBlocks & ~EAllBlocks); + } + +/** +Check for queue block bypass conditions. Currently the only such condition +is to allow a non-credit consuming command (e.g. the Number of Host Complete +Packets command) to bypass the insufficient credits block. +*/ +inline TUint CHCICmdQController::CmdBypassBlocks(const CHCICommandQItem& aCmd) + { + TUint bypassBlocks = ENoBlocks; + if (aCmd.Command().CreditsConsumed() == 0) + { + bypassBlocks |= EInsufficientCreditBlock; + } + return bypassBlocks; + } + +/** +Updates the command credits counter and maintains the EInsufficientCreditBlock +*/ +inline void CHCICmdQController::UpdateCommandCredits(TUint8 aCommandCredits) + { + iCommandCredits = aCommandCredits; + + if (iCommandCredits > 0) + { + // We clear block flags when sending priority commands, so there may be some + // commands blocked on insufficient credits, but the block flag not set + // anymore. Hence we want to schedule the callback irrespectably of the status + // of EInsufficientCreditBlock. + if (AnyCmdsToSend()) + { + iAsyncCallBackForSend->CallBack(); + } + ClearBlock(EInsufficientCreditBlock); + } + else + { + SetBlock(EInsufficientCreditBlock); + } + } + +/** +Selects the active command queue (iNormalCommandQ or iInitCommandQ) based on +current state. +*/ +inline TDblQue* CHCICmdQController::ActiveRegularQueue() + { + if (iCmdQControllerState == EStarted) + { + return &iNormalCommandQ; + } + else if ((iCmdQControllerState == EResetInit) || (iCmdQControllerState == EInitialising)) + { + return &iInitCommandQ; + } + + return NULL; + } + +/** +Returns the pointer to the queue which contains the command that would be scheduled next. +Does not take the Resend queue into account. +*/ +TDblQue* CHCICmdQController::QueueSendingNext() + { + if (!iWorkaroundCommandQ.IsEmpty()) + { + // Workaround has highest priority (well, second to resend which we don't care about). + return &iWorkaroundCommandQ; + } + else if ((iCmdQControllerState == EStarted) && !iPriorityCommandQ.IsEmpty()) + { + // Priority comes after Workaround, but we look at it only in EStarted. + return &iPriorityCommandQ; + } + else + { + // Normal or Initialisation or NULL, depending on the state we're in. + return ActiveRegularQueue(); + } + } + +/** +Returns the item at the head of the specified queue, or NULL if the queue is empty. +*/ +inline CHCICommandQItem* CHCICmdQController::FirstQueueItem(TDblQue& aQueue) + { + return (aQueue.IsEmpty() ? NULL : aQueue.First()); + } + +/** +Returns the item at the tail of the specified queue, or NULL if the queue is empty. +*/ +inline CHCICommandQItem* CHCICmdQController::LastQueueItem(TDblQue& aQueue) + { + return (aQueue.IsEmpty() ? NULL : aQueue.Last()); + } + +/** +Saves the purge marks for the queues that need them. When a reset is performed, +the queues will be purged up to those marks. +*/ +void CHCICmdQController::StorePurgeMarks() + { + iInitQPurgeMark = LastQueueItem(iInitCommandQ); + iNormalQPurgeMark = LastQueueItem(iNormalCommandQ); + iPriorityQPurgeMark = LastQueueItem(iPriorityCommandQ); + iWorkaroundQPurgeMark = LastQueueItem(iWorkaroundCommandQ); + } + +/** +Purge the queues up to previously saved purge marks. +*/ +void CHCICmdQController::PurgeAllQueues() + { + // Purge the queues to the recorded tail markers. + PurgeQueue(iInitCommandQ, iInitQPurgeMark); + PurgeQueue(iNormalCommandQ, iNormalQPurgeMark); + PurgeQueue(iPriorityCommandQ, iPriorityQPurgeMark); + PurgeQueue(iWorkaroundCommandQ, iWorkaroundQPurgeMark); + + iInitQPurgeMark = iNormalQPurgeMark = iPriorityQPurgeMark = iWorkaroundQPurgeMark = NULL; + + // Purge the sent and resend queues entirely. + PurgeQueue(iSentCommandQ, LastQueueItem(iSentCommandQ)); + PurgeQueue(iResendCommandQ, LastQueueItem(iResendCommandQ)); + } + + +/** +Removes and deletes items from the head of the queue up to and including the marked +item. +*/ +void CHCICmdQController::PurgeQueue(TDblQue& aQueue, + CHCICommandQItem* aMark) + { + LOG_FUNC + + // A null aMark implies that there are no commands to purge. + if(aMark) + { + TDblQueIter qIter(aQueue); + TBool found = EFalse; + + // Ensure that the mark is in the queue. + while(qIter && !found) + { + if(aMark == qIter++) + { + found = ETrue; + } + } + + // If the mark was found remove comands upto and including + // the mark. + if(found) + { + // Reset the queue iterator. + qIter.SetToFirst(); + found = EFalse; + CHCICommandQItem* item = NULL; + + while(qIter && !found) + { + item = qIter++; + if(item == aMark) + { + // Mark found. Exit the loop after this element has been removed. + found = ETrue; + } + DeleteCommand(item); + } + } + } + } + +/** +Returns the first item with the specified opcode which is closest to the head of the +specified queue, or NULL if there is no such opcode on the queue. +*/ +inline CHCICommandQItem* CHCICmdQController::ScanQueueByOpcode(TDblQue& aQueue, THCIOpcode aOpcode) + { + LOG_FUNC + + CHCICommandQItem* item = NULL; + TDblQueIter qIter(aQueue); + + while (((item = qIter) != NULL) && (item->Command().Opcode() != aOpcode)) + { + qIter++; + } + + return item; + } + +/** +Returns the item with the specified command queue ID from the specified queue, or NULL +if there is no such command on the queue. +*/ +inline CHCICommandQItem* CHCICmdQController::ScanQueueByQId(TDblQue& aQueue, TUint aCmdId) + { + LOG_FUNC + + CHCICommandQItem* item = NULL; + TDblQueIter qIter(aQueue); + + while (((item = qIter) != NULL) && (item->CommandQId() != aCmdId)) + { + qIter++; + } + + return item; + } + +/** +Performs command queue EResetting state processing. EResetting state is +reached by a call to CHCICmdQController::Reset(). +*/ +void CHCICmdQController::DoReset() + { + LOG_FUNC + + // Should only be reached if Reset has been called + __ASSERT_ALWAYS((iCmdQControllerState == EResetInit) || + (iCmdQControllerState == EResetting), + PANIC(KHCICmdQPanic, EInvalidStateTransition)); + + // Delete the parent command of possibly ongoing workaround. + // Wee need to do it here separately, because the command + // is not reachable any other way if it's in post-workaround phase + // - it's already been removed from its queue. + DeleteCommand(iParentCommand); + + PurgeAllQueues(); + + // Reset the QDP. + iCommandCredits = iQdp->MhcqdiReset(); + + // Delete any command waiting to be sent. + DeleteCommand(iSendingCommand); + + // Clear the block flags and execute state transition. + iCommandQState = 0; + + // Initialise could have been called during async break so check which + // state we should move into + if (iCmdQControllerState == EResetInit) + { + iCmdQControllerState = EInitialising; + } + else + { + // Acording to the ASSERT this must be EResetting + iCmdQControllerState = EUninitialised; + } + + // Reschedule if there are commands queued and we're ready to send. + if ((iCmdQControllerState == EInitialising) && CmdsQueued(&iInitCommandQ)) + { + iAsyncCallBackForSend->CallBack(); + } + } + +/** +The main queue processing routine. Schedules the next command and asks +HCTL to send. +*/ +void CHCICmdQController::TryToSend() + { + LOG_FUNC + + __ASSERT_DEBUG((iCmdQControllerState == EInitialising) || (iCmdQControllerState == EStarted), + PANIC(KHCICmdQPanic, ETryToSendWhileUnoperational)); + + if ((iCmdQControllerState != EInitialising) && (iCmdQControllerState != EStarted)) + { + // This is just a safety net. We shouldn't have been scheduled to run + // if we're not ready. But even if we've been mistakenly scheduled or + // the state changed in the meantime, this return makes it harmless. + return; + } + + TBool stopProcessing(ProcessResendQueue()); + if (!stopProcessing) + { + stopProcessing = ProcessWorkaroundQueue(); + if (!stopProcessing) + { + if (iCmdQControllerState == EStarted) + { + stopProcessing = ProcessPriorityQueue(); + } + if (!stopProcessing) + { + TDblQue* queue = ActiveRegularQueue(); + if (queue != NULL) + { + ProcessRegularQueue(*queue); + } + } + } + } + } + +/** +Returns ETrue if our current state allows commands to be queued. +*/ +inline TBool CHCICmdQController::CanAddCommands() + { + switch (iCmdQControllerState) + { + case EStarted: + case EResetInit: + case EInitialising: + return ETrue; + default: + return EFalse; + } + } + +/** +Tests if there are items queued on the specified queue. +*/ +inline TBool CHCICmdQController::CmdsQueued(TDblQue* aQueue) + { + if (aQueue != NULL) + { + return (!aQueue->IsEmpty()); + } + return EFalse; + } + +/** +Tests if there are items queued on any of the queues active in current state. +*/ +inline TBool CHCICmdQController::AnyCmdsToSend() + { + TDblQue* activeQ = ActiveRegularQueue(); + + // Need to check those two no matter which state we're in. + TBool commonQueuesEmpty = iWorkaroundCommandQ.IsEmpty() && iResendCommandQ.IsEmpty(); + + if (activeQ == &iInitCommandQ) + { + return !iInitCommandQ.IsEmpty() || !commonQueuesEmpty; + } + else + { + return !iNormalCommandQ.IsEmpty() || !iPriorityCommandQ.IsEmpty() || !commonQueuesEmpty; + } + } + +/** +Returns the next Command Queue Item Id +*/ +inline TUint CHCICmdQController::NextCommandQueueItemId() + { + __ASSERT_DEBUG(iNextCommandQItemId != KInvalidCommandQueueItemId, + PANIC(KHCICmdQPanic, EInvalidCommandQueueItemId)); + iNextCommandQItemId++; + if(iNextCommandQItemId == KInvalidCommandQueueItemId) + { + iNextCommandQItemId++; + } + return iNextCommandQItemId; + } + +/** +Processes the resend queue. +Returns ETrue if the scheduling loop shouldn't process lower priority queues +(which is when it schedules a command for resend) and EFalse if the loop should +go on. +*/ +TBool CHCICmdQController::ProcessResendQueue() + { + LOG_FUNC + + if (!CmdsQueued(&iResendCommandQ)) + { + return EFalse; + } + + CHCICommandQItem* cmd = iResendCommandQ.First(); + TUint bypassBlocks = CmdBypassBlocks(*cmd); + if (Blocked(EResendQueueBlocks, bypassBlocks)) + { + // We're the highest priority queue, but allow the other queues to try + // and send if we have commands queued but are blocked - "Only one + // active resend at a time" is still fulfilled. + return EFalse; + } + + if (OkToSendCommand(cmd, bypassBlocks)) + { + // Process command. + SendCommand(*cmd); + SetBlock(EResendBlock); + return ETrue; + } + + return EFalse; + } + +/** +Processes the workaround queue. +Returns ETrue if the scheduling loop shouldn't process lower priority queues +(which is when the queue is non-empty) and EFalse if the loop should go on. +*/ +TBool CHCICmdQController::ProcessWorkaroundQueue() + { + LOG_FUNC + + if (!CmdsQueued(&iWorkaroundCommandQ)) + { + return EFalse; + } + + CHCICommandQItem* cmd = iWorkaroundCommandQ.First(); + switch (cmd->Type()) + { + case CHCICommandQItem::EIndeterminate: + // Start of workaround sequence, set up the first command. + cmd->SetType(CHCICommandQItem::EParent); + SetUpNextWorkaroundCmd(NULL, NULL); + cmd = iWorkaroundCommandQ.First(); + UpdateStarvationTimer(); + break; + + case CHCICommandQItem::EPreChild: + case CHCICommandQItem::EPostChild: + case CHCICommandQItem::EParent: + // In the middle of a workaround. + // Nothing to do here really, command setup has been done by calling + // SetUpNextWorkaroundCmd() in Process*Event on completion of the previous + // command in the workaround sequence. + break; + + default: + __ASSERT_DEBUG(EFalse, PANIC(KHCICmdQPanic, EInvalidCmdQueueItemType)); + break; + } + +#ifdef _DEBUG + // We should only ever have at maximum one parent and one child on the + // workaround queue at any time. + TDblQueIter iter(iWorkaroundCommandQ); + TUint count = 0; + while(iter++) + { + count++; + } + __ASSERT_DEBUG(count <= 2, PANIC(KHCICmdQPanic, ETooManyItemsOnWorkaroundQueue)); +#endif // _DEBUG + + // Now that we're sure we have pre-workaraound set up we can check blocks. + TUint bypassBlocks = CmdBypassBlocks(*cmd); + if (Blocked(ESendQueueBlocks, bypassBlocks)) + { + // Don't allow lower priority queues to send until we're empty. + return ETrue; + } + + if (OkToSendCommand(cmd, bypassBlocks)) + { + SendCommand(*cmd); + UpdateStarvationTimer(); + SetBlock(EWorkaroundBlock); + } + + // Don't allow lower priority queues to send until we're empty. + return ETrue; + } + +/** +Processes the priority queue. +Returns ETrue if the scheduling loop shouldn't process lower priority queues +(which is when the queue is non-empty) and EFalse if the loop should go on. +*/ +TBool CHCICmdQController::ProcessPriorityQueue() + { + LOG_FUNC + + if (!CmdsQueued(&iPriorityCommandQ)) + { + return EFalse; + } + else if (Blocked(ESendQueueBlocks, ECachingCommandBlocks)) + { + // Check blocks excluding ECachingCommandBlocks + // The blocks we're checking don't depend on particular commands, so we can + // safely return here. + // Don't allow lower priority queues to send until we're empty. + return ETrue; + } + + // Search the queue for the first non-blocking command. + // Note that this is still just heurisitics, because even if we find a non-blocking + // command, it may still yield a blocking workaround. + CHCICommandQItem* cmd = NULL; + TDblQueIter i(iPriorityCommandQ); + while ((cmd = i++) != NULL) + { + // For every command we clear the cached blocks as they apply to the last command + // we attempted to send, using OkToSendCommand. As the priority queue can send out + // of order, i.e. not just the head command, the cached blocks need to be + // re-evaluated for each command until we find the one we intend to send. + ClearBlock(ECachingCommandBlocks); + if (OkToSendCommand(cmd, CmdBypassBlocks(*cmd))) + { + // Found available command. + if (iQdp->MhcqdiDoesCommandRequireWorkaround(*cmd)) + { + // Start of workaround sequence. + // Move onto the workaround queue and kick the scheduling loop. + cmd->iLink.Deque(); + iWorkaroundCommandQ.AddLast(*cmd); + iAsyncCallBackForSend->CallBack(); + } + else + { + // Non-workaround. + cmd->SetType(CHCICommandQItem::ENormal); + SendCommand(*cmd); + UpdateStarvationTimer(); + } + break; + } + } + + // At this point we are in one of the following states: + // - All priority commands are either blocked or have errored and been deleted in + // OkToSendCommand. If any commands are errored then the scheduling loop will + // also have been kick. + // - A priority command has been added to the workaround queue and the scheduling + // loop has been kick. + // - A priority command has been sent. + // + // In any one of these conditions we want to stop processing further queues so can + // always return true. + return ETrue; + } + +/** +Processes a regular queue - normal or initialization command queue. +*/ +void CHCICmdQController::ProcessRegularQueue(TDblQue& aQueue) + { + LOG_FUNC + + if (!CmdsQueued(&aQueue)) + { + return; + } + + CHCICommandQItem* cmd = aQueue.First(); + TUint bypassBlocks = CmdBypassBlocks(*cmd); + if (Blocked(ESendQueueBlocks, bypassBlocks)) + { + return; + } + + if (cmd->Type() == CHCICommandQItem::EIndeterminate) + { + // It's the first time we're looking into cmd. See if it needs a workaround. + if (iQdp->MhcqdiDoesCommandRequireWorkaround(*cmd)) + { + // Start of workaround sequence. + // Move it onto the workaround queue and kick the scheduling loop. + cmd->iLink.Deque(); + iWorkaroundCommandQ.AddLast(*cmd); + iAsyncCallBackForSend->CallBack(); + + return; + } + else + { + // Non-workaround. + cmd->SetType(CHCICommandQItem::ENormal); + } + } + if (OkToSendCommand(cmd, bypassBlocks)) + { + SendCommand(*cmd); + UpdateStarvationTimer(); + } + } + +/** +Process workaround related commands, and is invoked every time a +workaround completes. As the QDP interface needs to be passed the +concluding event of each workaround command, and events are used +synchronously and have a limited lifetime, this means workaround +handling has to be done at the same time as the event processing. + +This processing ensures that the head of the workaround queue contains +the next command in the workaround. It also tidies up and deletes +commands as necessary. Child commands are deleted once their events +have been reported to the QDP. The parent command lives until the end +of the workaround. +*/ +void CHCICmdQController::SetUpNextWorkaroundCmd(CHCICommandQItem* aPreviousCmd, + const THCIEventBase* aPreviousCmdResult) + { + LOG_FUNC + + // Update blocks. + ClearBlock(EWorkaroundBlock); + + // Get parent + if (iParentCommand == NULL) + { + iParentCommand = iWorkaroundCommandQ.First(); + } + + __ASSERT_ALWAYS(iParentCommand, PANIC(KHCICmdQPanic, EObjectNotInitialised)); + + // Determine next command in the workaround sequence. + CHCICommandQItem* child = NULL; + CHCICommandQItem::TType type; + + if (iParentCommand->SentCount() == 0) + { + child = iQdp->MhcqdiGetPreChildCommand(*iParentCommand, aPreviousCmd, aPreviousCmdResult); + if (child != NULL) + { + // Pre-workaround. + type = CHCICommandQItem::EPreChild; + } + else + { + // Transitioning to post-workaround. + type = CHCICommandQItem::EParent; + } + } + else + { + __ASSERT_ALWAYS(aPreviousCmd, PANIC(KHCICmdQPanic, EObjectNotInitialised)); + if (aPreviousCmd->Type() == CHCICommandQItem::EParent) + { + // Transitioning to post-workaround. + child = iQdp->MhcqdiGetPostChildCommand(*iParentCommand, NULL, aPreviousCmdResult); + } + else + { + // Post-workaround. + child = iQdp->MhcqdiGetPostChildCommand(*iParentCommand, aPreviousCmd, aPreviousCmdResult); + } + + type = CHCICommandQItem::EPostChild; + } + + // Delete previous command (if not parent). + if ((aPreviousCmd != NULL) && + (aPreviousCmd->Type() != CHCICommandQItem::EParent)) + { + DeleteCommand(aPreviousCmd); + } + + if (child != NULL) + { + // Enqueue child to head of queue. + child->SetType(type); + iWorkaroundCommandQ.AddFirst(*child); + } + else if (type != CHCICommandQItem::EParent) + { + // Give the QDP a chance to update state within the stack if required + THCIEventBase* fakedEvent = NULL; + while ((fakedEvent = iQdp->MhcqdiGetFakedUnsolicitedEvent(*iParentCommand, fakedEvent)) != NULL) + { + iUnmatchedEventObserver->MhcqcCommandEventReceived(*fakedEvent, NULL); + } + + // End of workaround sequence, delete parent. + DeleteCommand(iParentCommand); + } + } + +/** +A helper for restarting the Starvation Timer during command addition/sendout. +Determines the next command that will be sent and restarts the timer with +the ID of that command. +Caveat: this always restarts (or cancels, hence Update rather than just Restart in the name) +the timer, so use it only when you know that the command that will be sent next has really changed. +*/ +void CHCICmdQController::UpdateStarvationTimer() + { + // Determine the id of the next command to be executed. + // Don't care about resend queue, the starvation timer shouldn't be restarted on resends. + + TUint cmdId = KInvalidCommandQueueItemId; + + if (!iWorkaroundCommandQ.IsEmpty()) + { + cmdId = iWorkaroundCommandQ.First()->CommandQId(); + } + else if ((iCmdQControllerState == EStarted) && !iPriorityCommandQ.IsEmpty()) + { + // Only send off priority queue in EStarted (i.e. not in EInitialising) + // Never know which priority command will be sent next. + cmdId = KInvalidCommandQueueItemId; + } + else + { + TDblQue* queue = ActiveRegularQueue(); + if (queue && !queue->IsEmpty()) + { + cmdId = queue->First()->CommandQId(); + } + else + { + // No command to send. + iQStarvationTimer->Cancel(); + return; + } + } + iQStarvationTimer->Restart(iQueueStarvationTimeout, cmdId, *this); + } + +/** +Dequeues and deletes aItem from its queue. +*/ +void CHCICmdQController::DeleteCommand(CHCICommandQItem* &aItem) + { + LOG_FUNC + + if (!aItem) + { + return; + } + aItem->iLink.Deque(); + iQdp->MhcqdiCommandAboutToBeDeleted(*aItem); + delete aItem; + aItem = NULL; + UpdateStarvationTimer(); + } + +/** +Helper to be called on command addition. +Returns ETrue if the command that will be sent next changed after we added a command to aQueue. +Ignores the Resend queue. +*/ +TBool CHCICmdQController::NextCommandChanged(const TDblQue &aQueue) + { + LOG_FUNC + + if (&aQueue != QueueSendingNext()) + { + // We added a command to aQueue, but it's still another queue + // that sends next, so we didn't change the next command to be sent. + return EFalse; + } + else // aQueue is sending next. + { + if (&aQueue == &iPriorityCommandQ) + { + // PriorityQ is not FIFO, so any added command can potentially + // be sent next irrespectably of whether the queue was empty or not. + return ETrue; + } + else + { + // Rest of queues is FIFO, so the next command to be sent changed + // if the queue had been empty when we added the command. + return aQueue.First() == aQueue.Last(); + } + } + } + +/** +The general command addition routine. Adds aQueItem to aQueue and schedules +the command processing loop if current state is equal to aActiveState. +*/ +TUint CHCICmdQController::DoAddCommandL(CHCICommandQItem& aQueItem, + TDblQue &aQueue, TCmdQControllerStates aActiveState) + { + LOG_FUNC + + // Ensure we are in a state that allows commands to be added. + if(!CanAddCommands()) + { + delete &aQueItem; + User::Leave(KErrHardwareNotAvailable); + } + + // Assign a unique CommandQId. + aQueItem.SetCommandQId(NextCommandQueueItemId()); + + aQueue.AddLast(aQueItem); + // Restart queue starvation timer if this is the next command to be sent. + if (NextCommandChanged(aQueue)) + { + // If this is priority queue, we don't really know which command should be executed + // next, so we feed the timer with KInvalidCommandQueueItemId in that case. + TUint cmdId = KInvalidCommandQueueItemId; + if (&aQueue != &iPriorityCommandQ) + { + cmdId = aQueItem.CommandQId(); + } + iQStarvationTimer->Restart(iQueueStarvationTimeout, cmdId, *this); + } + + // Only schedule if we're in the state in which given queue should be considered for scheduling. + if (iCmdQControllerState == aActiveState) + { + iAsyncCallBackForSend->CallBack(); + } + + return aQueItem.CommandQId(); + } + +/** +Evaluates various blocking conditions to determine if the command can be sent. +aCmdQItem is passed by reference to pointer as it can be deleted and NULLified in case of error. +@return whether the command can be sent. +*/ +TBool CHCICmdQController::OkToSendCommand(CHCICommandQItem*& aCmdQItem, TUint aBypassBlocks) + { + TInt result = KErrNone; + LOG_FUNC + + __ASSERT_ALWAYS(aCmdQItem, PANIC(KHCICmdQPanic, ENullCmdQItemPtr)); + + if((iCommandCredits < aCmdQItem->Command().CreditsConsumed()) && + (!(aBypassBlocks & EInsufficientCreditBlock))) + { + // Insufficient HCI credits. Block and suspend processing queue. + SetBlock(EInsufficientCreditBlock); + } + else + { + CHCICommandQItem* const duplicate = ScanQueueByOpcode(iSentCommandQ, + aCmdQItem->Command().Opcode()); + if (duplicate && !(aBypassBlocks & EDuplicatedOpcodeBlock)) + { + // Duplicate command pending. Block and suspend processing queue. + SetBlock(EDuplicatedOpcodeBlock); + + // Need to mark the duplicate so that EDuplicatedOpcodeBlock is cleared + // on its completion. + duplicate->SetDuplicatedOpcode(ETrue); + } + else + { + const TDblQue* sentQ = (reinterpret_cast*>(&iSentCommandQ)); + + result = iQdp->MhcqdiCanSend(*aCmdQItem, *sentQ); + // KErrNone if aCommand is to be sent, EBlock if Command + // is to be delayed. + if(result < 0) + { + if(aCmdQItem->Client()) + { + aCmdQItem->Client()->MhcqcCommandErrored(result, &aCmdQItem->Command()); + } + + // Dequeue, delete & set to NULL. + DeleteCommand(aCmdQItem); + + if(AnyCmdsToSend()) + { + iAsyncCallBackForSend->CallBack(); + } + } + else if(result == MHCICmdQueueDecisionInterface::EBlock && !(aBypassBlocks & ECanSendBlock)) + { + // QDP should never block when the sent queue is empty (would permanently block the queue. + __ASSERT_ALWAYS((!iSentCommandQ.IsEmpty()), PANIC(KHCICmdQPanic, ECanSendBlockWhenEmpty)); + SetBlock(ECanSendBlock); + } + } + } + + return ((!Blocked(ECachingCommandBlocks, aBypassBlocks))&&(result==KErrNone)); + } + +/** +Submits a request to the link muxer for permission to send a command. +*/ +void CHCICmdQController::SendCommand(CHCICommandQItem& aCmdQItem) + { + LOG_FUNC + + __ASSERT_DEBUG(!iSendingCommand, PANIC(KHCICmdQPanic, ETryToSendWhilstSending)); + + iSendingCommand = &aCmdQItem; + // Dequeue command. + iSendingCommand->iLink.Deque(); + + // Initiate send request to HCTL, and block the queue. + iLinkMuxer->TryToSend(); + SetBlock(ETryToSendBlock); + } + +/** +Process matched non-error events. +*/ +void CHCICmdQController::ProcessMatchedEvent(const THCIEventBase& aEvent, CHCICommandQItem* aCmd, TBool aConcludesCmd) + { + LOG_FUNC + + // If required by the command, a Command Status Event should always be received before any other event related to a command. + THCIEventCode eventCode(aEvent.EventCode()); + if (eventCode == ECommandStatusEvent) + { + if (aCmd->Command().ExpectsCommandStatusEvent()) + { + aCmd->SetReceivedCmdStatusEvent(ETrue); + } + else + { + // assert in debug that we don't get here. + } + } + else + { + // Non Command Status Event, clear possible CanSend block. + ClearBlock(ECanSendBlock); + } + + CHCICommandQItem::TType type = aCmd->Type(); + if ((type != CHCICommandQItem::EPreChild) && + (type != CHCICommandQItem::EPostChild)) + { + // Command client is notified of non-workaround command events. + if(aCmd->Client()) + { + aCmd->Client()->MhcqcCommandEventReceived(aEvent, &aCmd->Command()); + } + } + + if (aConcludesCmd) + { + // Check that if a command status is expected then it has been got before being concluded. + + // Cancel completion timer. + aCmd->CancelCompletionTimer(); + + // Update blocks, cleared blocks will be re-evaluated on next Run Queue + if (aCmd->SentCount() > 1) + { + ClearBlock(EResendBlock); + } + if (aCmd->DuplicatedOpcode()) + { + ClearBlock(EDuplicatedOpcodeBlock); + } + + // Process completed command. + if (type == CHCICommandQItem::ENormal) + { + // Non-workaround command. Delete processed command. + DeleteCommand(aCmd); + } + else + { + // Workaround in progress. Set up next workaround command. + SetUpNextWorkaroundCmd(aCmd, &aEvent); + } + } + } + +/** +Process matched error events. +*/ +void CHCICmdQController::ProcessMatchedErrorEvent(const THCIEventBase& aEvent, CHCICommandQItem* aCmd, TBool /* aConcludesCmd */, TBool aSendToQdp) + { + LOG_FUNC + + // Cancel completion timer. + aCmd->CancelCompletionTimer(); + + /* + + Update blocks. The following blocks are not cleared: + + 1. The Resend block untouched to avoid interfering with an in-progress resend and + to ensure that only a single resend is active at a time. + 2. The TryToSend block which is strictly managed by the HCTL flow control methods + (SendCommand and DoSend). + 3. The Workaround block to prevent possibly bypassing a valid workaround block + on completion of a resend. + + All other cleared blocks (the caching blocks) will be re-evaluated before sending + the next command. + */ + ClearBlock(ECachingCommandBlocks); + + // Remove item from sent queue. + aCmd->iLink.Deque(); + + if (aSendToQdp && iQdp->MhcqdiMatchedErrorEventReceived(aEvent, *aCmd) == MHCICmdQueueDecisionInterface::EResendErroredCommand) + { + // Clear the command status event flag and enqueue to the resend queue + aCmd->SetReceivedCmdStatusEvent(EFalse); + if (aCmd->SentCount() > 1) + { + // Command was previously resent, enqueue to head of resend queue and clear Resend block + iResendCommandQ.AddFirst(*aCmd); + ClearBlock(EResendBlock); + } + else + { + // Command has not previously been resent, enqueue to tail of resend queue. + iResendCommandQ.AddLast(*aCmd); + } + } + else + { + CHCICommandQItem::TType type = aCmd->Type(); + + if ((type != CHCICommandQItem::EPreChild) && + (type != CHCICommandQItem::EPostChild)) + { + // Command error event received, notify client if non-child command + if(aCmd->Client()) + { + aCmd->Client()->MhcqcCommandEventReceived(aEvent, &aCmd->Command()); + } + } + + // Process completed command. + if (type == CHCICommandQItem::ENormal) + { + // Non-workaround command. Delete processed command. + DeleteCommand(aCmd); + } + else + { + // Workaround in progress. Set up next workaround command. + SetUpNextWorkaroundCmd(aCmd, &aEvent); + } + } + } + +/** +Processes unmatched events. +*/ +void CHCICmdQController::ProcessUnmatchedEvent(const THCIEventBase& aEvent, TBool aSendToQdp) + { + LOG_FUNC + + if (aSendToQdp) + { + // Notify QDP. + if (iQdpEventModifier) + { + THCIEventCode origEventCode = aEvent.EventCode(); + iQdpEventModifier->MhcqemiUnmatchedEventReceived(const_cast(aEvent)); + __ASSERT_ALWAYS(origEventCode == aEvent.EventCode(), PANIC(KHCICmdQPanic, EQdpTryingToChangeEventCode)); + } + else + { + iQdp->MhcqdiUnmatchedEventReceived(aEvent); + } + } + + iUnmatchedEventObserver->MhcqcCommandEventReceived(aEvent, NULL); + } + +/** +Processes timed out commands. +*/ +void CHCICmdQController::ProcessCommandCompletionTimeout(CHCICommandQItem* aCmd) + { + LOG_FUNC + + /* + + Update blocks. The following blocks are not cleared: + + 1. The Resend block untouched to avoid interfering with an in-progress resend and + to ensure that only a single resend is active at a time. + 2. The TryToSend block which is strictly managed by the HCTL flow control methods + (SendCommand and DoSend). + 3. The Workaround block to prevent possibly bypassing a valid workaround block + on completion of a resend. + + All other cleared blocks (the caching blocks) will be re-evaluated before sending + the next command. + */ + ClearBlock(ECachingCommandBlocks); + + // Remove item from sent queue. + aCmd->iLink.Deque(); + + TUint refundedCredits = 0; + MHCICmdQueueDecisionInterface::TCommandTimedOutAction action; + const TDblQue* sentQ = (reinterpret_cast*>(&iSentCommandQ)); + + action = iQdp->MhcqdiCommandTimedOut(*aCmd, *sentQ, iCommandCredits, refundedCredits); + + // Take back any refunded credits + iCommandCredits += refundedCredits; + + if (action == MHCICmdQueueDecisionInterface::EContinueWithTimeoutEvent) + { + CHCICommandQItem::TType type = aCmd->Type(); + + if ((type != CHCICommandQItem::EPreChild) && + (type != CHCICommandQItem::EPostChild)) + { + // Command error event received, notify client if non-child command + if(aCmd->Client()) + { + aCmd->Client()->MhcqcCommandErrored(CHciUtil::SymbianErrorCode(EHardwareFail), &aCmd->Command()); + } + } + + // Process completed command. + if (type == CHCICommandQItem::ENormal) + { + // Non-workaround command. Delete processed command. + DeleteCommand(aCmd); + } + else + { + SetUpNextWorkaroundCmd(aCmd, NULL); + } + } + else if (action == MHCICmdQueueDecisionInterface::EResendTimedOutCommand) + { + // Clear the command status event flag and enqueue to the resend queue + aCmd->SetReceivedCmdStatusEvent(EFalse); + if (aCmd->SentCount() > 1) + { + // Command was previously resent, enqueue to head of resend queue and clear Resend block + iResendCommandQ.AddFirst(*aCmd); + ClearBlock(EResendBlock); + } + else + { + // Command has not previously been resent, enqueue to tail of resend queue. + iResendCommandQ.AddLast(*aCmd); + } + } + } + +// Static async CallBack methods. +/*static*/ TInt CHCICmdQController::AsyncCallBackForReset(TAny* aCmdQController) + { + CHCICmdQController* cmdQController = static_cast(aCmdQController); + cmdQController->DoReset(); + return EFalse; // Don't call back any more. + } + +/*static*/ TInt CHCICmdQController::AsyncCallBackForSend(TAny* aCmdQController) + { + CHCICmdQController* cmdQController = static_cast(aCmdQController); + cmdQController->TryToSend(); + return EFalse; // Don't call back any more. + } + +#ifdef _DEBUG +void CHCICmdQController::LogQueue(TDblQue& aQueue) + { + TDblQueIter iter(aQueue); + while(CHCICommandQItem* item = iter++) + { + THCIOpcode opcode = item->Command().Opcode(); + const TUint16 KOgfMask = 0xfc00; + TUint8 ogf = (opcode&KOgfMask) >> 10; // OGF starts at bit 10. + TUint16 ocf = (opcode&~KOgfMask); + LOG4(_L("Command OGF(0x%02x) OCF(0x%04x) item 0x%08x owned by 0x%08x"), ogf, ocf, item, item->Client()); + } + } +#endif // _DEBUG + +