libqmi-qmuxd/gobi-api/GobiAPI_1.0.40/Shared/GobiQMICore.cpp

1019 lines
26 KiB
C++
Executable File

/*===========================================================================
FILE:
GobiQMICore.cpp
DESCRIPTION:
QUALCOMM Gobi QMI Based API Core
PUBLIC CLASSES AND FUNCTIONS:
cGobiQMICore
Copyright (c) 2011, Code Aurora Forum. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Code Aurora Forum nor
the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
==========================================================================*/
//---------------------------------------------------------------------------
// Include Files
//---------------------------------------------------------------------------
#include "StdAfx.h"
#include "GobiQMICore.h"
#include "QMIBuffers.h"
#include "ProtocolNotification.h"
#include "CoreUtilities.h"
//---------------------------------------------------------------------------
// Definitions
//---------------------------------------------------------------------------
// Default timeout for Gobi QMI requests
const ULONG DEFAULT_GOBI_QMI_TIMEOUT = 2000;
/*=========================================================================*/
// Free Methods
/*=========================================================================*/
/*===========================================================================
METHOD:
FindTLV (Free Method)
DESCRIPTION:
Find the given TLV
PARAMETERS:
tlvs [ I ] - TLV parsing input vector
tlvKey [ I ] - Key of the TLV that is to be found
RETURN VALUE:
cDataParser::tParsedFields
===========================================================================*/
sDB2NavInput FindTLV(
const std::vector <sDB2NavInput> & tlvs,
const sProtocolEntityKey & tlvKey )
{
sDB2NavInput retNI;
// We need some TLVs to parse and a valid QMI DB key
ULONG tlvCount = (ULONG)tlvs.size();
if (tlvCount == 0 || tlvKey.mKey.size() < 3)
{
return retNI;
}
for (ULONG t = 0; t < tlvCount; t++)
{
const sDB2NavInput & ni = tlvs[t];
if (tlvKey.mKey == ni.mKey)
{
retNI = ni;
break;
}
}
return retNI;
}
/*===========================================================================
METHOD:
ParseTLV (Free Method)
DESCRIPTION:
Parse the given TLV to fields
PARAMETERS:
db [ I ] - Database to use
qmiBuf [ I ] - Original buffer containing TLV (locks data)
tlvs [ I ] - TLV parsing input vector
tlvKey [ I ] - Key of the TLV that is to be parsed
bFieldStrings [ I ] - Generate field value strings?
RETURN VALUE:
cDataParser::tParsedFields
===========================================================================*/
cDataParser::tParsedFields ParseTLV(
const cCoreDatabase & db,
const sProtocolBuffer & qmiBuf,
const std::vector <sDB2NavInput> & tlvs,
const sProtocolEntityKey & tlvKey,
bool bFieldStrings )
{
cDataParser::tParsedFields retFields;
// We need some TLVs to parse and a valid QMI DB key
ULONG tlvCount = (ULONG)tlvs.size();
if (tlvCount == 0 || tlvKey.mKey.size() < 3)
{
return retFields;
}
for (ULONG t = 0; t < tlvCount; t++)
{
const sDB2NavInput & ni = tlvs[t];
if (tlvKey.mKey == ni.mKey)
{
cDataParser dp( db, qmiBuf, tlvKey, ni.mpPayload, ni.mPayloadLen );
dp.Parse( bFieldStrings, false );
retFields = dp.GetFields();
break;
}
}
return retFields;
}
/*=========================================================================*/
// cGobiQMICore Methods
/*=========================================================================*/
/*===========================================================================
METHOD:
cGobiQMICore (Public Method)
DESCRIPTION:
Constructor
RETURN VALUE:
None
===========================================================================*/
cGobiQMICore::cGobiQMICore()
: mbFailOnMultipleDevices( false ),
mDeviceNode( "" ),
mDeviceKey( "" ),
mLastError( eGOBI_ERR_NONE ),
mRequests( 16 ),
mLastNetStartID( (WORD)INVALID_QMI_TRANSACTION_ID )
{
// Nothing to do
}
/*===========================================================================
METHOD:
~cGobiQMICore (Public Method)
DESCRIPTION:
Destructor
RETURN VALUE:
BOOL
===========================================================================*/
cGobiQMICore::~cGobiQMICore()
{
Cleanup();
}
/*===========================================================================
METHOD:
Initialize (Public Method)
DESCRIPTION:
Initialize the object
RETURN VALUE:
bool
===========================================================================*/
bool cGobiQMICore::Initialize()
{
// Initialize database
mDB.Initialize();
// Allocate configured QMI servers
bool bOK = true;
std::set <tServerConfig>::const_iterator pIter = mServerConfig.begin();
while (pIter != mServerConfig.end())
{
cQMIProtocolServer * pSvr = 0;
pSvr = new cQMIProtocolServer( pIter->first, 8192, 512 );
if (pSvr == 0)
{
if (pIter->second == true)
{
bOK = false;
break;
}
}
else
{
mServers[pIter->first] = pSvr;
}
pIter++;
}
if (bOK == false)
{
Cleanup();
}
return bOK;
}
/*===========================================================================
METHOD:
Cleanup (Public Method)
DESCRIPTION:
Cleanup the object
RETURN VALUE:
bool
===========================================================================*/
bool cGobiQMICore::Cleanup()
{
Disconnect();
// Free allocated QMI servers
std::map <eQMIService, cQMIProtocolServer *>::const_iterator pIter;
pIter = mServers.begin();
while (pIter != mServers.end())
{
cQMIProtocolServer * pSvr = pIter->second;
if (pSvr != 0)
{
delete pSvr;
}
pIter++;
}
mServers.clear();
return true;
}
/*===========================================================================
METHOD:
GetAvailableDevices (Public Method)
DESCRIPTION:
Return the set of available Gobi network devices
RETURN VALUE:
std::vector <tDeviceID> - Vector of device ID and device key pairs
===========================================================================*/
std::vector <cGobiQMICore::tDeviceID>
cGobiQMICore::GetAvailableDevices()
{
std::vector <tDeviceID> devices;
std::string path = "/sys/bus/usb/devices/";
std::vector <std::string> files;
DepthSearch( path,
3,
"qcqmi",
files );
int fileNum = files.size();
for (int i = 0; i < fileNum; i++)
{
// Example "/sys/bus/usb/devices/8-1/8-1:1.0/GobiQMI/qcqmi0"
std::string nodePath = files[i];
int lastSlash = nodePath.find_last_of( "/" );
// This is what we want to return if everything else matches
std::string deviceNode = nodePath.substr( lastSlash + 1 );
// Move down two directories to the interface level
std::string curPath = nodePath.substr( 0, lastSlash );
curPath = curPath.substr( 0, curPath.find_last_of( "/" ) );
// Read bInterfaceNumber
int handle = open( (curPath + "/bInterfaceNumber").c_str(),
O_RDONLY );
if (handle == -1)
{
continue;
}
char buff[4];
memset( buff, 0, 4 );
bool bFound = false;
int ret = read( handle, buff, 2 );
if (ret == 2)
{
ret = strncmp( buff, "00", 2 );
if (ret == 0)
{
bFound = true;
}
ret = strncmp( buff, "05", 2 );
if (ret == 0)
{
bFound = true;
}
}
close( handle );
if (bFound == false)
{
continue;
}
// Move down one directory to the device level
curPath = curPath.substr( 0, curPath.find_last_of( "/" ) );
// Read idVendor
handle = open( (curPath + "/idVendor").c_str(), O_RDONLY );
if (handle == -1)
{
continue;
}
bFound = false;
ret = read( handle, buff, 4 );
if (ret == 4)
{
ret = strncmp( buff, "05c6", 4 );
if (ret == 0)
{
bFound = true;
}
}
close( handle );
if (bFound == false)
{
continue;
}
// Read idProduct
handle = open( (curPath + "/idProduct").c_str(), O_RDONLY );
if (handle == -1)
{
continue;
}
bFound = false;
ret = read( handle, buff, 4 );
if (ret == 4)
{
ret = strncmp( buff, "920d", 4 );
if (ret == 0)
{
bFound = true;
}
}
close( handle );
if (bFound == false)
{
continue;
}
// Device node success!
// Get MEID of device node (via ioctl) to use as key
std::string deviceStr = "/dev/" + deviceNode;
std::string key = cQMIProtocolServer::GetDeviceMEID( deviceStr );
tDeviceID device;
device.first = deviceNode;
device.second = key;
devices.push_back( device );
}
return devices;
}
/*===========================================================================
METHOD:
Connect (Public Method)
DESCRIPTION:
Connect to the specified (or first detected) Gobi device
Both device node and key are case sensitive
PARAMETERS:
pDeviceNode [ I ] - The device node
pDeviceKey [ I ] - The device key (unique, stored on-device)
RETURN VALUE:
bool
===========================================================================*/
bool cGobiQMICore::Connect(
LPCSTR pDeviceNode,
LPCSTR pDeviceKey )
{
// Assume failure
bool bRC = false;
// Clear last error recorded
ClearLastError();
// If you specify a device key then you have to specify a device ID
if (pDeviceNode == 0 && pDeviceKey != 0)
{
mLastError = eGOBI_ERR_INVALID_ARG;
return bRC;
}
// First we terminate the current connection
Disconnect();
// Query system for list of active Gobi devices
std::vector <tDeviceID> devices = GetAvailableDevices();
// Did we find any devices?
ULONG deviceCount = (ULONG)devices.size();
if (deviceCount == 0)
{
mLastError = eGOBI_ERR_NO_DEVICE;
return bRC;
}
std::string deviceKey = "";
if (pDeviceKey != 0)
{
deviceKey = pDeviceKey;
}
// Filter that list to include only the specified device?
if (pDeviceNode != 0)
{
std::vector <tDeviceID>::iterator current = devices.begin();
while (current != devices.end())
{
// Remove if device node doesn't match
if (current->first.compare( pDeviceNode ) != 0)
{
// Erase current element and update ourself to point to next
current = devices.erase( current );
}
// Remove if invalid key is specified
else if (deviceKey.size() != 0
&& current->second.compare( deviceKey ) != 0)
{
current = devices.erase( current );
}
// All necessary parameters match
else
{
current++;
}
}
}
// Anything left after filtering?
deviceCount = (ULONG)devices.size();
if (deviceCount == 0)
{
mLastError = eGOBI_ERR_NO_DEVICE;
return bRC;
}
// Too many to choose from?
if (deviceCount > 1 && mbFailOnMultipleDevices == true)
{
mLastError = eGOBI_ERR_MULTIPLE_DEVICES;
return bRC;
}
// Store device ID/key strings
mDeviceNode = devices[0].first;
mDeviceKey = devices[0].second;
// Initalize/connect all configured QMI servers
std::map <eQMIService, cQMIProtocolServer *>::const_iterator pIter;
pIter = mServers.begin();
while (pIter != mServers.end())
{
cQMIProtocolServer * pSvr = pIter->second;
if (pSvr != 0)
{
// Initialize server (we don't care about the return code
// since the following Connect() call will fail if we are
// unable to initialize the server)
pSvr->Initialize();
std::string deviceStr = "/dev/" + mDeviceNode;
bRC = pSvr->Connect( deviceStr.c_str() );
if (bRC == false)
{
tServerConfig tsc( pIter->first, true );
if (mServerConfig.find( tsc ) != mServerConfig.end())
{
// Failure on essential server
break;
}
else
{
// QMI server non-essential (ignore failure)
bRC = true;
}
}
}
pIter++;
}
// Any server fail?
if (bRC == false)
{
// Yes, disconnect them all
Disconnect();
// ... and set the error code
mLastError = eGOBI_ERR_CONNECT;
}
return bRC;
}
/*===========================================================================
METHOD:
Disconnect (Public Method)
DESCRIPTION:
Disconnect from the currently connected Gobi device
RETURN VALUE:
bool
===========================================================================*/
bool cGobiQMICore::Disconnect()
{
// Clear last error recorded
ClearLastError();
// Assume failure
bool bRC = false;
if (mDeviceNode.size() > 0)
{
mDeviceNode.clear();
mDeviceKey.clear();
bRC = true;
}
else
{
mLastError = eGOBI_ERR_NO_CONNECTION;
}
// Disconnect/clean-up all configured QMI servers
std::map <eQMIService, cQMIProtocolServer *>::const_iterator pIter;
pIter = mServers.begin();
while (pIter != mServers.end())
{
cQMIProtocolServer * pSvr = pIter->second;
if (pSvr != 0)
{
pSvr->Disconnect();
pSvr->Exit();
}
pIter++;
}
return bRC;
}
/*===========================================================================
METHOD:
GetConnectedDeviceID (Public Method)
DESCRIPTION:
Get the device node/key of the currently connected Gobi device
PARAMETERS:
devNode [ O ] - Device node (IE: qcqmi0)
devKey [ O ] - Device key (may be empty)
RETURN VALUE:
bool
===========================================================================*/
bool cGobiQMICore::GetConnectedDeviceID(
std::string & devNode,
std::string & devKey )
{
// Clear last error recorded
ClearLastError();
// Assume failure
bool bFound = false;
devNode.clear();
devKey.clear();
// Are all required servers connected?
bool bAllConnected = true;
std::map <eQMIService, cQMIProtocolServer *>::const_iterator pIter;
pIter = mServers.begin();
while (pIter != mServers.end())
{
tServerConfig tsc( pIter->first, true );
cQMIProtocolServer * pSvr = pIter->second;
if (mServerConfig.find( tsc ) != mServerConfig.end() && pSvr != 0)
{
if (pSvr->IsConnected() == false)
{
// Failure on essential server
bAllConnected = false;
break;
}
}
pIter++;
}
// Were we once connected?
if (mDeviceNode.size() > 0 && bAllConnected == true)
{
// Yes, but is our device still present?
// NOTE: This does not guarantee the device did not leave and come back
std::vector <tDeviceID> devices = GetAvailableDevices();
ULONG deviceCount = (ULONG)devices.size();
for (ULONG a = 0; a < deviceCount; a++)
{
if (devices[a].first == mDeviceNode)
{
// If there is a device key specified, it must match.
if (mDeviceKey.size() > 0)
{
if (devices[a].second == mDeviceKey)
{
devNode = devices[a].first;
devKey = devices[a].second;
bFound = true;
break;
}
}
else
{
devNode = devices[a].first;
bFound = true;
break;
}
}
}
if (bFound == false)
{
mLastError = eGOBI_ERR_NO_DEVICE;
}
}
else
{
// We are not connected
mLastError = eGOBI_ERR_NO_CONNECTION;
}
return bFound;
}
/*===========================================================================
METHOD:
Send (Public Method)
DESCRIPTION:
Send a request using the specified QMI protocol server and wait for (and
then return) the response
PARAMETERS:
svc [ I ] - QMI service type
pRequest [ I ] - Request to schedule
to [ I ] - Timeout value (in milliseconds)
RETURN VALUE:
sProtocolBuffer - The response (invalid when no response was received)
===========================================================================*/
sProtocolBuffer cGobiQMICore::Send(
eQMIService svc,
sSharedBuffer * pRequest,
ULONG to )
{
// Clear last error recorded
ClearLastError();
// Returned response
sProtocolBuffer rsp;
// Validate the arguments
if (pRequest == 0)
{
mLastError = eGOBI_ERR_MEMORY;
return rsp;
}
// We use the event based notification approach
cSyncQueue <sProtocolNotificationEvent> evts( 12, true );
cProtocolQueueNotification pn( &evts );
// Process up to the indicated timeout
cEvent & sigEvt = evts.GetSignalEvent();
// Build the request object
sProtocolRequest req( pRequest, 0, to, 1, 1, &pn );
if (to == 0)
{
mLastError = eGOBI_ERR_INTERNAL;
return rsp;
}
// Grab the server
cQMIProtocolServer * pSvr = GetServer( svc );
if (pSvr == 0)
{
mLastError = eGOBI_ERR_INTERNAL;
return rsp;
}
// Are we connected?
if (mDeviceNode.size() <= 0 || pSvr->IsConnected() == false)
{
mLastError = eGOBI_ERR_NO_CONNECTION;
return rsp;
}
// Grab the log from the server
const cProtocolLog & protocolLog = pSvr->GetLog();
// Schedule the request
ULONG reqID = pSvr->AddRequest( req );
if (reqID == INVALID_REQUEST_ID)
{
mLastError = eGOBI_ERR_REQ_SCHEDULE;
return rsp;
}
// Store for external cancel
tServiceRequest sr( svc, reqID );
mRequests.AddElement( sr );
bool bReq = false;
bool bExit = false;
DWORD idx;
// Process up to the indicated timeout
while (bExit == false)
{
int wc = sigEvt.Wait( to, idx );
if (wc == ETIME)
{
if (bReq == true)
{
mLastError = eGOBI_ERR_RESPONSE_TO;
}
else
{
mLastError = eGOBI_ERR_REQUEST_TO;
}
break;
}
else if (wc != 0)
{
mLastError = eGOBI_ERR_INTERNAL;
break;
}
sProtocolNotificationEvent evt;
bool bEvt = evts.GetElement( idx, evt );
if (bEvt == false)
{
mLastError = eGOBI_ERR_INTERNAL;
bExit = true;
break;
}
switch (evt.mEventType)
{
case ePROTOCOL_EVT_REQ_ERR:
mLastError = eGOBI_ERR_REQUEST;
bExit = true;
break;
case ePROTOCOL_EVT_RSP_ERR:
mLastError = eGOBI_ERR_RESPONSE;
bExit = true;
break;
case ePROTOCOL_EVT_REQ_SENT:
{
// Are we doing WDS business?
if (svc == eQMI_SVC_WDS)
{
// Grab the as-sent request
DWORD id = evt.mParam2;
sProtocolBuffer tmpReq = protocolLog.GetBuffer( id );
sSharedBuffer * pTmpRequest = tmpReq.GetSharedBuffer();
if (pTmpRequest != 0)
{
// Check the message ID
sQMIServiceBuffer actualReq( pTmpRequest );
ULONG msgID = actualReq.GetMessageID();
if (msgID == (ULONG)eQMI_WDS_START_NET)
{
// Grab the transaction ID
mLastNetStartID = actualReq.GetTransactionID();
}
}
}
bReq = true;
}
break;
case ePROTOCOL_EVT_RSP_RECV:
// Success!
rsp = protocolLog.GetBuffer( evt.mParam2 );
bExit = true;
break;
}
}
if ( (mLastError == eGOBI_ERR_INTERNAL)
|| (mLastError == eGOBI_ERR_REQUEST_TO)
|| (mLastError == eGOBI_ERR_RESPONSE_TO) )
{
// Remove the request as our protocol notification object is
// about to go out of scope and hence be destroyed
pSvr->RemoveRequest( reqID );
}
// Check that the device is still there?
if ( (mLastError == eGOBI_ERR_REQUEST)
|| (mLastError == eGOBI_ERR_RESPONSE)
|| (mLastError == eGOBI_ERR_REQUEST_TO)
|| (mLastError == eGOBI_ERR_RESPONSE_TO) )
{
eGobiError tmp = mLastError;
std::string dummy;
GetConnectedDeviceID( dummy, dummy );
if (mLastError == eGOBI_ERR_NONE)
{
mLastError = tmp;
}
}
return rsp;
}
/*===========================================================================
METHOD:
SendAndCheckReturn (Public Method)
DESCRIPTION:
Send a request using the specified QMI protocol server and wait for (and
then validate) the response
PARAMETERS:
svc [ I ] - QMI service type
pRequest [ I ] - Request to schedule
to [ I ] - Timeout value (in milliseconds)
RETURN VALUE:
eGobiError - Corrected error code
===========================================================================*/
eGobiError cGobiQMICore::SendAndCheckReturn(
eQMIService svc,
sSharedBuffer * pRequest,
ULONG to )
{
sProtocolBuffer rsp = Send( svc, pRequest, to );
if (rsp.IsValid() == false)
{
return GetCorrectedLastError();
}
// Did we receive a valid QMI response?
sQMIServiceBuffer qmiRsp( rsp.GetSharedBuffer() );
if (qmiRsp.IsValid() == false)
{
mLastError = eGOBI_ERR_MALFORMED_RSP;
return mLastError;
}
// Check the mandatory QMI result TLV for success
ULONG rc = 0;
ULONG ec = 0;
bool bResult = qmiRsp.GetResult( rc, ec );
if (bResult == false)
{
mLastError = eGOBI_ERR_MALFORMED_RSP;
return mLastError;
}
else if (rc != 0)
{
return GetCorrectedQMIError( ec );
}
// Success!
return eGOBI_ERR_NONE;
}
/*===========================================================================
METHOD:
SendSimple (Public Method)
DESCRIPTION:
Generate/send a request using the specified QMI protocol server and
wait for (and then return) the response
PARAMETERS:
svc [ I ] - QMI service type
msgID [ I ] - QMI message ID of the request to generate
to [ I ] - Timeout value (in milliseconds)
NOTE: The request has to be a single byte in length, i.e. just a
command code, in order for success
RETURN VALUE:
sProtocolBuffer - The response (invalid when no response was received)
===========================================================================*/
sProtocolBuffer cGobiQMICore::SendSimple(
eQMIService svc,
WORD msgID,
ULONG to )
{
// Clear last error recorded
ClearLastError();
sProtocolBuffer rsp;
sSharedBuffer * pReq = 0;
pReq = sQMIServiceBuffer::BuildBuffer( svc, msgID );
if (pReq == 0)
{
mLastError = eGOBI_ERR_MEMORY;
return rsp;
}
rsp = Send( svc, pReq, to );
return rsp;
}
/*===========================================================================
METHOD:
CancelSend (Public Method)
DESCRIPTION:
Cancel the most recent in-progress Send() based operation
RETURN VALUE:
eGobiError
===========================================================================*/
eGobiError cGobiQMICore::CancelSend()
{
ULONG reqs = mRequests.GetTotalCount();
if (reqs == 0)
{
return eGOBI_ERR_NO_CANCELABLE_OP;
}
tServiceRequest elem( eQMI_SVC_ENUM_BEGIN, INVALID_REQUEST_ID );
bool bElem = mRequests.GetElement( --reqs, elem );
if (bElem == false)
{
return eGOBI_ERR_INTERNAL;
}
cQMIProtocolServer * pSvr = GetServer( elem.first );
if (pSvr == 0)
{
return eGOBI_ERR_INTERNAL;
}
bool bRemove = pSvr->RemoveRequest( elem.second );
if (bRemove == false)
{
return eGOBI_ERR_CANCEL_OP;
}
return eGOBI_ERR_NONE;
}