// Copyright (c) 1999-2010 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:
//
#include "hostmbufpool.h"
#include <bluetooth/hcicommandqueue.h>
#include <bluetooth/hci/hostnumberofcompletedpacketscommand.h>
#include <bluetooth/hci/hcievents.h>
#include <bluetooth/hci/commandcompleteevent.h>
#include <bluetooth/hci/hciutil.h>
#include "linkconsts.h"
#include "linkutil.h"
#include <bluetooth/logger.h>
#ifdef HOSTCONTROLLER_TO_HOST_FLOW_CONTROL
#ifdef __FLOG_ACTIVE
_LIT8(KLogComponent, LOG_COMPONENT_LINKMGR);
#endif
__DEBUG_ONLY(PANICCATEGORY("mbufpool");)
CHostMBufPool* CHostMBufPool::NewL(MHCICommandQueue& aCommandQueue)
{
LOG_STATIC_FUNC
CHostMBufPool* self = new (ELeave) CHostMBufPool(aCommandQueue);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
return self;
}
void CHostMBufPool::DeletePool(TSglQue<TPoolBuffer>& aQueue)
{
LOG_FUNC
TPoolBuffer* buffer = NULL;
TSglQueIter<TPoolBuffer> iter(aQueue);
while(buffer=iter++, buffer)
{
aQueue.Remove(*buffer);
DeleteBuffer(buffer);
}
}
void CHostMBufPool::DeleteBuffer(TPoolBuffer*& aBuffer)
{
LOG_FUNC
if(aBuffer)
{
aBuffer->iMBufChain.Free();
delete aBuffer;
aBuffer = NULL;
}
}
CHostMBufPool::~CHostMBufPool()
{
LOG_FUNC
Cancel();
// iMBufRequester is cleaned in it's destructor (not much of an R-class...but it is what it is)
DeletePool(iBufferPool);
DeletePool(iWaitingAllocPool);
DeleteBuffer(iBufferBeingAllocd);
}
CHostMBufPool::CHostMBufPool(MHCICommandQueue& aCommandQueue)
: CActive(EPriorityHigh)
// High priority so that buffers are allocated occur before more data is read, this prevents
// the cases the data floods the device and exhausts the buffers before any more can be allocated.
// This maximises throughput since we will ensure we send packet completion notifications in a
// timely manner.
, iCmdQ(aCommandQueue)
, iBufferPool(_FOFF(TPoolBuffer,iLink))
, iWaitingAllocPool(_FOFF(TPoolBuffer,iLink))
, iCurrAckHandle(KErrNotFound)
{
LOG_FUNC
}
void CHostMBufPool::ConstructL()
/**
2nd phase constructor for the Host MBuf Pool.
This method will attempt to reserve enough MBufs from the global pool
for bluetooth use.
@leave KErrNoMemory If the required number of MBufs couldn't be reserved
*/
{
LOG_FUNC
LOG2(_L("CHostMBufPool: now reserving %d size %d MBufChains"), KStackACLBuffersNum, KLinkMgrIncomingBufferSize);
for (TInt i=0; i<=KStackACLBuffersNum-1; i++)
{
TPoolBuffer* thisBuffer = CreatePoolBufferL();
AddToBufferPool(*thisBuffer);
}
CActiveScheduler::Add(this);
}
CHostMBufPool::TPoolBuffer* CHostMBufPool::CreatePoolBufferL()
{
LOG_FUNC
TPoolBuffer* newBuffer = new(ELeave) TPoolBuffer();
CleanupStack::PushL(newBuffer);
newBuffer->iCurrentHandle = KInvalidConnectionHandle; // we assert on this later
newBuffer->iMBufChain.AllocL(KLinkMgrIncomingBufferSize);
CleanupStack::Pop(newBuffer);
return newBuffer;
}
void CHostMBufPool::DoCancel()
{
LOG_FUNC
iMBufRequester.Cancel();
}
RMBufChain CHostMBufPool::TakeBufferL(THCIConnHandle aConnHandle)
/**
Takes a buffer from the pool and schedules an asynchronous allocation
of the next buffer. Only when that allocation has succeeded will the host
controller be signalled with a host_number_of_completed_packets. Hence,
if we cannot allocate a buffer from the global MBuf pool, the host controller
will be flowed off and no data will be lost.
*/
{
LOG_FUNC
ASSERT_DEBUG(aConnHandle != KInvalidConnectionHandle);
// Speculatively attempt to allocate any queued allocations that may have previously failed.
TryToAllocQueuedBuffer();
TPoolBuffer* ready = iBufferPool.First();
if(!ready)
{
// Whoops run out of buffers - even though we were trying to prevent this with
// flow control, in the case of disconnection the controller will assume all the
// data for a connection handle will be flushed and therefore the buffers associated
// with that connection handle will be free. Unfortunately for us we don't have
// that much control with the MBuf pool (since flow control is for asynchronous
// buffer allocation rather than waiting for the given MBufs to be relinquished
// by a higher layer).
// So the controller could think we have more buffers than we actually have...
LOG(_L8("CHostMBufPool: Ran out of buffers!!!!"));
LEAVEL(KErrOverflow);
}
// If here then we should have a valid pool buffer to use
__ASSERT_DEBUG(!ready->iMBufChain.IsEmpty(), Panic(ELinkMgrHostControllerHasOverflowedHost));
__ASSERT_DEBUG(ready->iCurrentHandle == KInvalidConnectionHandle, Panic(ELinkMgrHostControllerHasOverflowedHost));
RemoveFromBufferPool(*ready);
ready->iCurrentHandle = aConnHandle;
RMBufChain retChain;
retChain.Assign(ready->iMBufChain);
if (IsActive())
{
//This buffer will be reclaimed from the global pool
//after the one(s) we're currently trying to reclaim
LOG(_L8("CHostMBufPool: TakeBuffer, buffer taken while alloc outstanding: queued alloc"));
iWaitingAllocPool.AddLast(*ready);
}
else
{
LOG(_L8("CHostMBufPool: TakeBuffer, buffer taken"));
AllocNewBuffer(*ready);
}
return retChain;
}
void CHostMBufPool::InvalidateByConnH(THCIConnHandle aConnHandle)
{
LOG_FUNC
ASSERT_DEBUG(aConnHandle != KInvalidConnectionHandle);
// We need to scan through the two pending "lists" to remove the
// connection handle from record so we don't try to provide a
// packet completion notification (the controller already assumes
// the buffers are free as they are entitled to by the spec).
// The current buffer being allocated
if(iBufferBeingAllocd && iBufferBeingAllocd->iCurrentHandle == aConnHandle)
{
iBufferBeingAllocd->iCurrentHandle = KInvalidConnectionHandle;
}
// The list of buffers waiting to be allocted
TPoolBuffer* buffer = NULL;
TSglQueIter<TPoolBuffer> iter(iWaitingAllocPool);
while(buffer=iter++, buffer)
{
if(buffer->iCurrentHandle == aConnHandle)
{
buffer->iCurrentHandle = KInvalidConnectionHandle;
}
}
// Finally we need to purge any batched up completions if they
// are for this connection handle
if(iCurrAckHandle == aConnHandle)
{
iCurrAckHandle = KErrNotFound;
iCurrCompletedPackets = 0;
}
}
void CHostMBufPool::RunL()
{
LOG_FUNC
LEAVEIFERRORL(iStatus.Int());
// We've successfully allocated a new MBufChain
TPoolBuffer* justAllocd = iBufferBeingAllocd;
iBufferBeingAllocd = NULL;
THCIConnHandle justAllocdHandle = justAllocd->iCurrentHandle;
// Return buffer to pool for re-use
AddToBufferPool(*justAllocd);
justAllocd->iCurrentHandle = KInvalidConnectionHandle;
// If connection handle is still valid then we need to provide a completion
// notification for the packet to the connection handle it was from.
if(justAllocdHandle != KInvalidConnectionHandle)
{
if (iCurrAckHandle == KErrNotFound)
{
// This is the first completion we have seen
iCurrAckHandle = justAllocdHandle;
}
ASSERT_DEBUG(iCurrAckHandle != KInvalidConnectionHandle); // just to be sure
TBool ackNow = (justAllocdHandle != iCurrAckHandle);
if (!ackNow)
{
iCurrCompletedPackets++;
LOG2(_L8("CHostMBufPool: CompletedPackets++ for conn: %d [->%d]"), justAllocdHandle, iCurrCompletedPackets);
ackNow = (iCurrCompletedPackets >= KStackACLBuffersTideMarkNum);
}
if (ackNow)
{
TInt err = KErrNone;
if (iCurrCompletedPackets > 0)
{
LOG2(_L8("CHostMBufPool: Sending HostNumberOfCompletedPackets for conn: %d [%d completed]"), iCurrAckHandle, iCurrCompletedPackets);
//Acknowledge the completed packets
TRAP(err, HostNumberOfCompletedPacketsL(iCurrAckHandle, iCurrCompletedPackets));
//if this failed we probably couldn't alloc the memory for the command frame,
//the HC is still flowed off.
__ASSERT_DEBUG(err == KErrNone, Panic(ELinkMgrCouldNotSendHostNumberOfCompletedPackets));
LEAVEIFERRORL(err);
}
iCurrCompletedPackets = (justAllocdHandle != iCurrAckHandle) ? 1 : 0;
iCurrAckHandle = justAllocdHandle;
}
}
TryToAllocQueuedBuffer();
}
void CHostMBufPool::TryToAllocQueuedBuffer()
{
LOG_FUNC
if (!iWaitingAllocPool.IsEmpty() && !IsActive())
{
TPoolBuffer* needsAlloc = iWaitingAllocPool.First();
iWaitingAllocPool.Remove(*needsAlloc);
AllocNewBuffer(*needsAlloc);
}
}
void CHostMBufPool::AllocNewBuffer(TPoolBuffer& aBuffer)
{
LOG_FUNC
ASSERT_DEBUG(!iBufferBeingAllocd);
iBufferBeingAllocd = &aBuffer;
iMBufRequester.Alloc(aBuffer.iMBufChain, KLinkMgrIncomingBufferSize, iStatus);
SetActive();
}
void CHostMBufPool::HostNumberOfCompletedPacketsL(THCIConnHandle aConnH, TUint16 aNumPackets)
{
RArray<THCIConnectionHandle> connHandles;
connHandles.AppendL(aConnH);
CleanupClosePushL(connHandles);
RArray<THCINumOfCompletedPackets> numPackets;
numPackets.AppendL(aNumPackets);
CleanupClosePushL(numPackets);
CHostNumberOfCompletedPacketsCommand* cmd = CHostNumberOfCompletedPacketsCommand::NewL(1, connHandles, numPackets);
// Ownership of the arrays is taken by the command object.
CleanupStack::Pop(2, &connHandles); // &numPackets, &connHandles
// This is a priority command as we want to try to get this out as soon as possible (and not wait
// for all normal control aspects to be processed). This command shouldn't normally consume any credits
// so as a priority command it has little impact.
// Ownership of cmd transfered even if MhcqAddPriorityCommandL leaves
iCmdQ.MhcqAddPriorityCommandL(cmd, *this);
}
void CHostMBufPool::MhcqcCommandEventReceived(const THCIEventBase& aEvent, const CHCICommandBase* /*aRelatedCommand*/)
{
LOG_FUNC
// We don't expect a non-error event back because we're only sending Host_Number_of_Completed_Packet commands
if(aEvent.EventCode() == ECommandCompleteEvent)
{
const THCICommandCompleteEvent& completeEvent = THCICommandCompleteEvent::Cast(aEvent);
if(completeEvent.CommandOpcode() == KHostNumberOfCompletedPacketsOpcode)
{
// a regular error for a Host_Number_Of_Completed_Packets command
TInt err = CHciUtil::SymbianErrorCode(completeEvent.ErrorCode());
if(err != KErrNone) // we shouldn't get a non-erroring event back, but just in case
{
Error(err);
}
}
else // an unexpected command complete event
{
LOG1(_L8("CHostMBufPool: Unexpected HCI command complete event; opcode = 0x%04x"), completeEvent.CommandOpcode());
DEBUG_PANIC_LINENUM;
}
}
else // some unexpected event
{
LOG1(_L8("CHostMBufPool: Unexpected HCI event received; code = 0x%02x"), aEvent.EventCode());
DEBUG_PANIC_LINENUM;
}
}
void CHostMBufPool::MhcqcCommandErrored(TInt aErrorCode, const CHCICommandBase* /*aCommand*/)
{
LOG_FUNC
Error(aErrorCode);
}
TInt CHostMBufPool::RunError(TInt aError)
{
LOG_FUNC
if(iBufferBeingAllocd)
{
TPoolBuffer* justFailedToAlloc = iBufferBeingAllocd;
iBufferBeingAllocd = NULL;
// Add to wait for alloc queue - we may get another chance
iWaitingAllocPool.AddFirst(*justFailedToAlloc);
}
Error(aError);
return KErrNone;
}
void CHostMBufPool::Error(TInt IF_FLOGGING(aError))
{
LOG_FUNC
// So there has been some internal error when handling controller to host
// flow control. Tough, we've done our best for now - the only real thing
// that might be worth doing is a hard reset to start-up clean.
LOG1(_L8("CHostMBufPool: ERROR (%d) - inbound data to the stack may stall soon"), aError);
}
#endif