diff -r 000000000000 -r 29b1cd4cb562 bluetooth/btstack/eirman/eirmanager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bluetooth/btstack/eirman/eirmanager.cpp Fri Jan 15 08:13:17 2010 +0200 @@ -0,0 +1,756 @@ +// Copyright (c) 2007-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 "eirmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "eirmansession.h" +#include "linkmgr.h" +#include "eirmanpanics.h" + +#ifdef __FLOG_ACTIVE +_LIT8(KLogComponent, LOG_COMPONENT_EIRMANAGER); +#endif + +__ASSERT_COMPILE(KReservedNameBytes + KReservedUuid16Bytes + KReservedUuid128Bytes + KReservedManufacturerSpecificDataBytes + KReservedTxPowerLevelBytes == KEirPacketSize); + +CEirManager* CEirManager::NewL(MHCICommandQueue& aCommandQueue, CLinkMgrProtocol& aLinkMgrProtocol) + { + LOG_STATIC_FUNC + + CEirManager* self = new(ELeave) CEirManager(aCommandQueue, aLinkMgrProtocol); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + +CEirManager::~CEirManager() + { + LOG_FUNC + + // Delete all registered tags + for(TInt8 i = static_cast(EEirTagName); i < static_cast(EEirTagRESERVED); i++) + { + __ASSERT_DEBUG(!iNotifiees[i].iNotifiee, EIR_MANAGER_PANIC(EEirManagerNotifieeNotCleared)); + } + + iCommandQueue.MhcqRemoveAllCommands(*this); + delete iWriteEirTimer; + } + +CEirManager::CEirManager(MHCICommandQueue& aCommandQueue, CLinkMgrProtocol& aLinkMgrProtocol) + : iOffersStale(EFalse) + , iCmdId(KInvalidCommandQueueItemId) + , iCmdCount(0) + , iCommandQueue(aCommandQueue) + , iLinkMgrProtocol(aLinkMgrProtocol) + { + LOG_FUNC + } + +void CEirManager::ConstructL() + { + LOG_FUNC + iWriteEirTimer = CWriteEirTimer::NewL(*this); + } + +void CEirManager::UnhookClient(TEirTag aTag) + { + LOG_FUNC + if (iNotifiees[aTag].iNotifiee) + { + iNotifiees[aTag].iNotifiee = NULL; + if(iNotifiees[aTag].iTagData) + { + delete iNotifiees[aTag].iTagData; + iNotifiees[aTag].iTagData = NULL; + } + } + } + +// A client register aTag with EirManager +TInt CEirManager::RegisterTag(TEirTag aTag, MEirManagerNotifiee& aNotifiee) + { + LOG_FUNC + TInt ret = KErrNone; + LOG1(_L("CEirManager::RegisterTag tag = %d"), aTag); + + if(!TagValid(aTag)) + { + LOG1(_L("CEirManager::RegisterTag INVALID TAG: %d"), aTag); + ret = KErrArgument; + } + else if(!TagAllowed(aTag)) + { + LOG(_L("CEirManager::RegisterTag: IN USE")); + ret = KErrInUse; + } + else + { + iNotifiees[aTag].iNotifiee = &aNotifiee; + iNotifiees[aTag].iTagData = NULL; + iNotifiees[aTag].iOfferedBytes = 0; + iNotifiees[aTag].iEirDataMode = EEirDataComplete; + iNotifiees[aTag].iState = 0x00; + + if (!OffersPending() && NeedToOffer()) + { + // Do not consider this for insertion yet if there are offers pending. + // Instead, once the offers have been responded to, this client will be dealt with + NotifyClients(); + } + } + return ret; + } + +// A client deregister aTag with EirManager +void CEirManager::DeregisterTag(TEirTag aTag) + { + LOG_FUNC + LOG1(_L("CEirManager::DeregisterTag tag = %d"), aTag); + + if(!TagValid(aTag)) + { + LOG1(_L("CEirManager::DeregisterTag INVALID TAG: %d"), aTag); + return; + } + + if (iNotifiees[aTag].iNotifiee) + { + UnhookClient(aTag); + if(iNotifiees[aTag].iRequiredBytes > 0) + { + // It did require some bytes and now requires none, reevaluate offers + iNotifiees[aTag].iState &= ~KNeedOffer; + // A client could storm in a deregister before setting data - so we remove + // any pending offer as a SetData after deregistering will be errored - any + // previous offer has been voided. + iNotifiees[aTag].iState &= ~KOfferPending; + if(!OffersPending()) + { + NotifyClients(); + } + else if(iNotifiees[aTag].iOfferedBytes > 0) + { + iOffersStale = ETrue; + } + } + if(!NeedToOffer() && !OffersPending()) + { + TryToUpdateHostController(); + } + // Now tidy up offer and requested values (they are no longer valid). + iNotifiees[aTag].iRequiredBytes = 0; + iNotifiees[aTag].iOfferedBytes = 0; + } + } + +TInt CEirManager::SetData(TEirTag aTag, TDesC8& aData, TEirDataMode aEirDataMode) + { + LOG_FUNC + LOG1(_L("CEirManager::SetData tag = %d"), aTag); + + if(!iLinkMgrProtocol.IsExtendedInquiryResponseSupportedLocally()) + { + return KErrNotSupported; + } + + if(!TagValid(aTag)) + { + LOG1(_L("CEirManager::SetData INVALID TAG: %d"), aTag); + return KErrArgument; + } + + if(!iNotifiees[aTag].iNotifiee) + { + // No client registered with this tag + return KErrNotFound; + } + + LOG2(_L("CEirManager::SetData data len = %d offered bytes: %d"), aData.Length(), iNotifiees[aTag].iOfferedBytes); + + if (!(iNotifiees[aTag].iState & KOfferPending)) + { + // No offer pending for this tag + return KErrNotReady; + } + if(iNotifiees[aTag].iOfferedBytes < aData.Length()) + { + // Client wants to publish more data than allowed to + return KErrTooBig; + } + + TInt ret = KErrNone; + + HBufC8** tagdata; + + FindTagData(aTag, tagdata); + + // Free up whatever there was before + if (*tagdata) + { + delete *tagdata; + } + + HBufC8* data = NULL; + + // If the descriptor is empty, mark the heap buffer pointer as NULL, + // meaning that the client is registered for this tag, but there is no currently no data available for it + if(aData.Length() != 0x0) + { + data = aData.Alloc(); + if (!data) + { + LOG(_L("CEirManager::SetData: NO MEMORY ")); + ret = KErrNoMemory; + } + } + + *tagdata = data; + + // We no longer have a value pending. + iNotifiees[aTag].iState &= ~KOfferPending; + iNotifiees[aTag].iEirDataMode = aEirDataMode; + + if(NeedToOffer() && !OffersPending()) + { + NotifyClients(); + } + + if(!NeedToOffer() && !OffersPending()) + { + + // Regardless of whether the allocation failed or not, check whether all offers have been accepted + // and update the Host Controller accordingly + TryToUpdateHostController(); + } + + LOG1(_L("CEirManager::SetData returning = %d"), ret); + return ret; + } + +void CEirManager::MhcqcCommandEventReceived(const THCIEventBase& aEvent, + const CHCICommandBase* /*aRelatedCommand*/) + { + LOG_FUNC + if(aEvent.EventCode() == ECommandCompleteEvent) + { + const THCICommandCompleteEvent& completeEvent = static_cast(aEvent); + if(completeEvent.CommandOpcode() == KWriteExtendedInquiryResponseOpcode) + { + --iCmdCount; + } + else + { + LOG1(_L("Warning!! Unexpected Command Complete Event Received (command opcode: 0x%04x)"), completeEvent.CommandOpcode()); + __ASSERT_DEBUG(EFalse, EIR_MANAGER_PANIC(EEirManagerUnexpectedCompleteEvent)); + } + } + else + { + LOG1(_L("Warning!! Unexpected Event Received (event code: 0x%02x)"), aEvent.EventCode()); + __ASSERT_DEBUG(EFalse, EIR_MANAGER_PANIC(EEirManagerUnexpectedEvent)); + } + } + +void CEirManager::MhcqcCommandErrored(TInt aErrorCode, const CHCICommandBase* aCommand) + { + LOG_FUNC + aErrorCode = aErrorCode; // Avoid unused warning + LOG1(_L("\taErrorCode = %d"), aErrorCode); + if(aCommand && aCommand->Opcode() == KWriteExtendedInquiryResponseOpcode) + { + --iCmdCount; + } + else + { + LOG(_L("Warning!! Unexpected Error Received")); + if(aCommand) + { + LOG1(_L("\taCommand->Opcode() = 0x%04x"), aCommand->Opcode()); + } + } + } + +void CEirManager::WriteExtendedInquiryResponseL(TBool aFECRequired, const TDesC8& aEir) + { + LOG_FUNC + if(iCmdCount > 0) + { + // There is at least 1 outstanding command in the queue + TInt err = iCommandQueue.MhcqRemoveCommand(iCmdId, *this); + if(err == KErrNone) + { + --iCmdCount; + } + // else if there was an error it is because the command cannot be removed + // "at this time" so we just "leak" it. This isn't a problem, this was a + // best effort optimisation. + } + CWriteExtendedInquiryResponseCommand* cmd = CWriteExtendedInquiryResponseCommand::NewL(aFECRequired ? 1 : 0, aEir); + // Ownership of cmd transfered + iCmdId = iCommandQueue.MhcqAddCommandL(cmd, *this); + ++iCmdCount; + } + +// Check whether a specific tag is allowed to be registered +TBool CEirManager::TagAllowed(TEirTag aTag) const + { + LOG_FUNC + // We don't support 32 bit UUIDs + return (aTag!=EEirTagSdpUuid32); + } + +// Check whether aTag is present +TBool CEirManager::TagPresent(TEirTag aTag) const + { + LOG_FUNC + return (iNotifiees[aTag].iNotifiee != NULL); + } + +// Find the registered data for aTag +void CEirManager::FindTagData(TEirTag aTag, HBufC8**& aOutPointerToData) + { + LOG_FUNC + aOutPointerToData = &iNotifiees[aTag].iTagData; + } + +// Count the number of registered tags +TInt CEirManager::TagCount() const + { + LOG_FUNC + TInt count = 0; + for (TInt i=0; i 0) + { + requiredBytes = aRequiredBytes + KEirLengthTagLength; + } + else if(aTag == EEirTagName && aRequiredBytes == 0) + { + // This is a special case for device without local name + requiredBytes = KEirLengthTagLength; + } + else + { + requiredBytes = 0; + } + + iNotifiees[aTag].iRequiredBytes = requiredBytes; // always record what we last asked for. + + if(!(iNotifiees[aTag].iState & KNeedOffer)) + { + // We currently aren't marked as needing an offer so we will need to fix that... + iNotifiees[aTag].iState |= KNeedOffer; + if(requiredBytes == currentBytes && !(iNotifiees[aTag].iState & KOfferPending) && !iOffersStale) + { + // If the same then we handle locally, if we aren't already waiting. + DoNotify(aTag); + } + else if(!OffersPending()) + { + // If not waiting for anybody then lets kick off a round. + NotifyClients(); + } + } + return KErrNone; + } + +// Notify all registered clients about their allocated data length +void CEirManager::NotifyClients() + { + + LOG_FUNC + + // go around each notifiee and ask for values + // our policy at the moment is: + // after allocating reserved bytes for each tag, we will prioritize tag with big granularity + + ComputeOfferLengths(); + + for (TInt i = 0; i 0) + { + // Notify client about offered bytes + iNotifiees[aTag].iState |= KOfferPending; + iNotifiees[aTag].iNotifiee->MemnEirBlockAvailable(aTag, iNotifiees[aTag].iOfferedBytes); + } + else if(iNotifiees[aTag].iOfferedBytes == 0) + { + if(iNotifiees[aTag].iRequiredBytes > 0) + { + // Notify client about 0 offer and not expecting client to call back + iNotifiees[aTag].iState |= KOfferPending; + iNotifiees[aTag].iNotifiee->MemnEirBlockAvailable(aTag, iNotifiees[aTag].iOfferedBytes); + } + else if(iNotifiees[aTag].iRequiredBytes == 0) + { + // request 0 and offered 0, delete client + iNotifiees[aTag].iState &= ~KNeedOffer; + delete iNotifiees[aTag].iTagData; + iNotifiees[aTag].iTagData = NULL; + } + } + } + +void CEirManager::ComputeOfferLengths() + { + LOG_FUNC + // count bytes available for EIR data, including length and tag + // due to the low cost of Tx Power Level, it is always given a space in EIR + TInt availableEirSize = EIRPacketSize(); + TInt reservedByteForName = KReservedNameBytes - (KEirPacketSize - availableEirSize); + TInt totalSpare = 0; + + ComputeClientInitialOffer(EEirTagName, totalSpare, reservedByteForName); + ComputeClientInitialOffer(EEirTagSdpUuid16, totalSpare, KReservedUuid16Bytes); + ComputeClientInitialOffer(EEirTagSdpUuid128, totalSpare, KReservedUuid128Bytes); + ComputeClientInitialOffer(EEirTagManufacturerSpecific, totalSpare, KReservedManufacturerSpecificDataBytes, ETrue); + ComputeClientInitialOffer(EEirTagTxPowerLevel, totalSpare, KReservedTxPowerLevelBytes, ETrue); + + // if there is still bytes left after allocating the reserved bytes, + // we will try to allocate them by giving high priority to tag with bigger granularity + /* tag granularity (byte) + name 1 + uuid16 2 + uuid128 16 + manufacturer length + */ + LOG4(_L("RequiredBytes **** Name: %d, UUID16: %d, UUID128: %d, Manu: %d"), + iNotifiees[0].iRequiredBytes, iNotifiees[1].iRequiredBytes, iNotifiees[3].iRequiredBytes, iNotifiees[4].iRequiredBytes); + LOG4(_L("OfferedBytes ----- Name: %d, UUID16: %d, UUID128: %d, Manu: %d"), + iNotifiees[0].iOfferedBytes, iNotifiees[1].iOfferedBytes, iNotifiees[3].iOfferedBytes, iNotifiees[4].iOfferedBytes); + LOG1(_L("totalSpare: %d"), totalSpare); + ComputeClientFinalOffer(EEirTagManufacturerSpecific, totalSpare, KReservedManufacturerSpecificDataBytes, ETrue); + ComputeClientFinalOffer(EEirTagSdpUuid128, totalSpare, K128BitUuidGranularity); + ComputeClientFinalOffer(EEirTagSdpUuid16, totalSpare, K16BitUuidGranularity); + ComputeClientFinalOffer(EEirTagName, totalSpare, KDeviceNameGranularity); + // Tx Power Level should be after the allocating the reserved bytes + iOffersStale = EFalse; // We now know any "staleness" has been corrected. + + LOG(_L("After allocating reserved")); + LOG4(_L("RequiredBytes **** Name: %d, UUID16: %d, UUID128: %d, Manu: %d"), + iNotifiees[0].iRequiredBytes, iNotifiees[1].iRequiredBytes, iNotifiees[3].iRequiredBytes, iNotifiees[4].iRequiredBytes); + LOG4(_L("OfferedBytes ----- Name: %d, UUID16: %d, UUID128: %d, Manu: %d"), + iNotifiees[0].iOfferedBytes, iNotifiees[1].iOfferedBytes, iNotifiees[3].iOfferedBytes, iNotifiees[4].iOfferedBytes); + LOG1(_L("totalSpare: %d"), totalSpare); + } + +/* compute offer based on client's request and the reserved bytes for this client*/ +void CEirManager::ComputeClientInitialOffer(TEirTag aTag, TInt& aTotalSpare, TInt aReservedBytes, + TBool aIsAllOrNothing) + { + if(TagPresent(aTag)) + { + if(aIsAllOrNothing) + { + if(iNotifiees[aTag].iRequiredBytes <= aReservedBytes) + { + iNotifiees[aTag].iOfferedBytes = iNotifiees[aTag].iRequiredBytes; + aTotalSpare += (aReservedBytes - iNotifiees[aTag].iRequiredBytes); + iNotifiees[aTag].iEirDataMode = EEirDataComplete; + } + else + { + // the request exceeds reserved bytes for this client, the client is offered nothing + iNotifiees[aTag].iOfferedBytes = 0; + iNotifiees[aTag].iEirDataMode = EEirDataPartial; + aTotalSpare += aReservedBytes; + } + } + else + { + if(iNotifiees[aTag].iRequiredBytes <= aReservedBytes) + { + iNotifiees[aTag].iOfferedBytes = iNotifiees[aTag].iRequiredBytes; + aTotalSpare += (aReservedBytes - iNotifiees[aTag].iRequiredBytes); + iNotifiees[aTag].iEirDataMode = EEirDataComplete; + } + else + { + iNotifiees[aTag].iOfferedBytes = aReservedBytes; + iNotifiees[aTag].iEirDataMode = EEirDataPartial; + } + } + } + else + { + aTotalSpare += aReservedBytes; + } + } + +/* compute offer based on client's request and the spared bytes from the initial offers*/ +void CEirManager::ComputeClientFinalOffer(TEirTag aTag, TInt& aTotalSpare, TUint aGranularity, + TBool aIsAllOrNothing) + { + if(iNotifiees[aTag].iRequiredBytes > iNotifiees[aTag].iOfferedBytes) + { + if(aIsAllOrNothing) + { + // this means we have more data to publish, which also indicates + // it required bytes are more than reserved bytes for this tag + if(iNotifiees[aTag].iRequiredBytes <= aTotalSpare) + { + aTotalSpare -= iNotifiees[aTag].iRequiredBytes; + iNotifiees[aTag].iOfferedBytes = iNotifiees[aTag].iRequiredBytes; + iNotifiees[aTag].iEirDataMode = EEirDataComplete; + } + } + else + { + // this means we have more data to publish + if((iNotifiees[aTag].iRequiredBytes - iNotifiees[aTag].iOfferedBytes) <= aTotalSpare) + { + aTotalSpare -= (iNotifiees[aTag].iRequiredBytes - iNotifiees[aTag].iOfferedBytes); + iNotifiees[aTag].iOfferedBytes = iNotifiees[aTag].iRequiredBytes; + iNotifiees[aTag].iEirDataMode = EEirDataComplete; + } + else + { + iNotifiees[aTag].iOfferedBytes += (aTotalSpare - aTotalSpare % aGranularity); + aTotalSpare %= aGranularity; + } + } + } + } + +// Calculate the valid size of EIR packet, excluding payload header +TInt CEirManager::EIRPacketSize() const + { + LOG_FUNC + TInt total = 0; + // default value of valid EIR packet is for using DM1 + TInt ret = KEirPacketSize - KEirDM1PayloadHeaderSize; + for (TInt i = 0; i 0) + { + total += iNotifiees[i].iRequiredBytes; + // Including 2 bytes for the length and tag + total += KEirLengthTagLength; + } + } + + if(total > KEirDM1PacketSize) + { + // None DM1 packet has 2 bytes of payload header, recalculate... + ret = KEirPacketSize - KEirNoneDM1PayloadHeaderSize; + } + + return ret; + } + +void CEirManager::UpdateHostControllerL() + { + LOG_FUNC + + /* We don't handle the situation of handware swapping after stack is started + * when the device changed from 2.1 to 2.0 and EIR is no longer supports, + * we'll quietly stop writing EIR and return KErrNotSupported for future requests. + we + */ + __ASSERT_DEBUG(iLinkMgrProtocol.IsExtendedInquiryResponseSupportedLocally(), EIR_MANAGER_PANIC(EEirManagerEirNotSupported)); + TBuf8 eir; + + for (TInt i = 0; iLength() + 1); + // Data Structure data: Data Type + eir.Append(dataType); + // Data Structure data: EIR data + eir.Append(*(iNotifiees[i].iTagData)); + } + else + { + // Here is the case of empty device name again + eir.Append(1); + // Data Structure data: Data Type + eir.Append(dataType); + } + } + } + } + + LOG1(_L("CEirManager::UpdateHostController about to send %d bytes of EIR data"), eir.Length()); + // Send this down HCI + WriteExtendedInquiryResponseL(ETrue, eir); + + LOG(_L("------- EIR verbose display")); + LOGHEXDESC(eir); + } + +void CEirManager::TryToUpdateHostController() + { + // In case there is an outstanding write eir command (a timer running) already, Cancel() + iWriteEirTimer->Cancel(); + + // Start the timer to gurad against from clients' updates flooding the controller + iWriteEirTimer->Start(); + } + + +CWriteEirTimer* CWriteEirTimer::NewL(CEirManager& aEirManager) + { + CWriteEirTimer* timer = new (ELeave) CWriteEirTimer(aEirManager); + CleanupStack::PushL(timer); + timer->ConstructL(); + CleanupStack::Pop(timer); + return timer; + } + + +CWriteEirTimer::CWriteEirTimer(CEirManager& aEirManager) +: CTimer(EPriorityStandard), iEirManager(aEirManager) + { + } + +void CWriteEirTimer::Start() + { + After(KWriteEirTimeout); + } + +void CWriteEirTimer::ConstructL() + { + CTimer::ConstructL(); + CActiveScheduler::Add(this); + } + +void CWriteEirTimer::RunL() + { + // timer expires, which means no more write eir request within life of the time (1 second) + // write eir here now + iEirManager.UpdateHostControllerL(); + } + +TInt CWriteEirTimer::RunError(TInt IF_FLOGGING(aError)) + { + LOG_FUNC + // The only reason the error occurs is that the WriteExtendedInquiryResponseL has failed + // This may be caused by invalid EIR data, we'll do nothing here. + LOG1(_L("CWriteEirTimer::RunError() %d: "), aError); + return KErrNone; + }