--- /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 <bluetooth/hcicmdqcontroller.h>
+#include <bluetooth/hci/hciconsts.h>
+
+#include "HciCmdQTimer.h"
+#include "HciCmdQUtil.h"
+
+#include <bluetooth/hardresetinitiator.h>
+#include <bluetooth/hcicommandqitem.h>
+#include <bluetooth/hcicommandqueueclient.h>
+#include <bluetooth/linkmuxnotifier.h>
+#include <bluetooth/hci/commandstatusevent.h>
+#include <bluetooth/hci/event.h>
+#include <bluetooth/hci/commandcompleteevent.h>
+#include <bluetooth/hci/hcicmdqueuedecisionplugin.h>
+#include <bluetooth/hci/hcicmdqueuedecisioninterface.h>
+#include <bluetooth/hci/hciframe.h>
+#include <bluetooth/hci/hctlinterface.h>
+#include <bluetooth/hci/hciutil.h>
+#include <bluetooth/hci/command.h>
+#include <bluetooth/hci/hcievents.h>
+#include <bluetooth/hci/completingeventquery.h>
+
+#include <bluetooth/logger.h>
+
+#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<CHCICommandQItem> sentIter(iSentCommandQ);
+ CleanUpQueue(sentIter);
+ TDblQueIter<CHCICommandQItem> 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<TAny*&>(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<CHCICommandQItem>* 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<CHCICommandQItem> 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<CHCICommandQItem> initIter(iInitCommandQ);
+ RemoveAllCommands(initIter, aCmdOriginator, ETrue);
+
+ TDblQueIter<CHCICommandQItem> normalIter(iNormalCommandQ);
+ RemoveAllCommands(normalIter, aCmdOriginator, ETrue);
+
+ TDblQueIter<CHCICommandQItem> priorityIter(iPriorityCommandQ);
+ RemoveAllCommands(priorityIter, aCmdOriginator, ETrue);
+
+ TDblQueIter<CHCICommandQItem> workaroundIter(iWorkaroundCommandQ);
+ RemoveAllCommands(workaroundIter, aCmdOriginator, ETrue);
+
+ TDblQueIter<CHCICommandQItem> sentIter(iSentCommandQ);
+ RemoveAllCommands(sentIter, aCmdOriginator, EFalse);
+
+ TDblQueIter<CHCICommandQItem> resendIter(iResendCommandQ);
+ RemoveAllCommands(resendIter, aCmdOriginator, EFalse);
+
+ if(iSendingCommand)
+ {
+ if(iSendingCommand->Client() == &aCmdOriginator)
+ {
+ HandleCommandRemoval(iSendingCommand, ETrue);
+ }
+ }
+ }
+
+void CHCICmdQController::CleanUpQueue(TDblQueIter<CHCICommandQItem> 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<CHCICommandQItem> 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<THCIEventBase&>(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<MHCICmdQueueDecisionInterface*>(iQdpPlugin->Interface(TUid::Uid(KHCICmdQueueDecisionInterfaceUid)));
+ iQdpEventModifier = reinterpret_cast<MHCICmdQueueEventModifierInterface*>(iQdpPlugin->Interface(TUid::Uid(KHCICmdQueueDecisionEventModifierInterfaceUid)));
+
+ MHCICmdQueueUtilityUser* qdpUtilityUser = reinterpret_cast<MHCICmdQueueUtilityUser*>(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<CHCICommandQItem>* 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<CHCICommandQItem>* 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<CHCICommandQItem>& 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<CHCICommandQItem>& 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<CHCICommandQItem>& aQueue,
+ CHCICommandQItem* aMark)
+ {
+ LOG_FUNC
+
+ // A null aMark implies that there are no commands to purge.
+ if(aMark)
+ {
+ TDblQueIter<CHCICommandQItem> 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<CHCICommandQItem>& aQueue, THCIOpcode aOpcode)
+ {
+ LOG_FUNC
+
+ CHCICommandQItem* item = NULL;
+ TDblQueIter<CHCICommandQItem> 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<CHCICommandQItem>& aQueue, TUint aCmdId)
+ {
+ LOG_FUNC
+
+ CHCICommandQItem* item = NULL;
+ TDblQueIter<CHCICommandQItem> 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<CHCICommandQItem>* 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<CHCICommandQItem>* 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<CHCICommandQItem>* 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<CHCICommandQItem> 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<CHCICommandQItem> 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<CHCICommandQItem>& 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<CHCICommandQItem>* 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<CHCICommandQItem> &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<CHCICommandQItem> &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<const CHCICommandQItem>* sentQ = (reinterpret_cast<const TDblQue<const CHCICommandQItem>*>(&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<THCIEventBase&>(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<const CHCICommandQItem>* sentQ = (reinterpret_cast<const TDblQue<const CHCICommandQItem>*>(&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<CHCICmdQController*>(aCmdQController);
+ cmdQController->DoReset();
+ return EFalse; // Don't call back any more.
+ }
+
+/*static*/ TInt CHCICmdQController::AsyncCallBackForSend(TAny* aCmdQController)
+ {
+ CHCICmdQController* cmdQController = static_cast<CHCICmdQController*>(aCmdQController);
+ cmdQController->TryToSend();
+ return EFalse; // Don't call back any more.
+ }
+
+#ifdef _DEBUG
+void CHCICmdQController::LogQueue(TDblQue<CHCICommandQItem>& aQueue)
+ {
+ TDblQueIter<CHCICommandQItem> 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
+
+