/*
 * Copyright (c) 2013 Qualcomm Atheros, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted (subject to the limitations in the
 * disclaimer below) 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 Qualcomm Atheros nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
 * GRANTED BY THIS LICENSE.  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.
 */
/*
 * @File:
 *
 * @Abstract: host target communications
 *
 * @Notes:
 */
#include <osapi.h>
#include <Magpie_api.h>
#include <htc.h>
#include <htc_api.h>
#include <hif_api.h>
#include <adf_os_mem.h>
#include <adf_os_io.h>

#include "htc_internal.h"

#define A_UNCACHED_ADDR(addr) addr

/* prototypes */
LOCAL void HTCControlSvcProcessMsg(HTC_ENDPOINT_ID EndpointID, adf_nbuf_t hdr_buf, adf_nbuf_t pBuffers, void *arg);
LOCAL void HTCControlSvcProcessSendComplete(HTC_ENDPOINT_ID EndpointID, adf_nbuf_t pBuffers, void *arg);
LOCAL void HTCMsgRecvHandler(adf_nbuf_t hdr_buf, adf_nbuf_t buf, void *context);
LOCAL void HTCSendDoneHandler(adf_nbuf_t buf, void *context);
LOCAL void HTCFreeMsgBuffer(HTC_CONTEXT *pHTC, adf_nbuf_t pBuffer);
LOCAL adf_nbuf_t HTCAllocMsgBuffer(HTC_CONTEXT *pHTC);
LOCAL void HTCCheckAndSendCreditReport(HTC_CONTEXT *pHTC, A_UINT32 EpMask, HTC_ENDPOINT *pEndpoint, HTC_ENDPOINT_ID Id);
LOCAL void AdjustCreditThreshold(HTC_ENDPOINT  *pEndpoint);
LOCAL void HTC_AssembleBuffers(HTC_CONTEXT *pHTC, int Count, int Size);
LOCAL htc_handle_t _HTC_Init(/*A_UINT32 dataAddr,*/
        HTC_SETUP_COMPLETE_CB SetupComplete,
	HTC_CONFIG *pConfig);
LOCAL void _HTC_RegisterService(htc_handle_t handle, HTC_SERVICE *pService);
LOCAL void _HTC_Ready(htc_handle_t handle);
LOCAL void ReturnBuffers(htc_handle_t htcHandle, HTC_ENDPOINT_ID EndpointID, adf_nbuf_t pBuffers, A_BOOL sendCreditFlag);
LOCAL void _HTC_ReturnBuffers(htc_handle_t handle, HTC_ENDPOINT_ID EndpointID, adf_nbuf_t pBuffers);
LOCAL void _HTC_ReturnBuffersList(htc_handle_t htcHandle, HTC_ENDPOINT_ID EndpointID, adf_nbuf_queue_t bufHead);
LOCAL void _HTC_SendMsg(htc_handle_t handle, HTC_ENDPOINT_ID EndpointID, adf_nbuf_t pBuffers);
void _HTC_PauseRecv(HTC_ENDPOINT_ID EndpointID);
void _HTC_ResumeRecv(HTC_ENDPOINT_ID EndpointID);
LOCAL void HTCProcessConnectMsg(HTC_CONTEXT *pHTC, HTC_CONNECT_SERVICE_MSG *pMsg);
LOCAL void HTCProcessConfigPipeMsg(HTC_CONTEXT *pHTC, HTC_CONFIG_PIPE_MSG *pMsg);
LOCAL void RedistributeCredit(adf_nbuf_t buf, int toPipeId);
LOCAL void _HTC_Shutdown(htc_handle_t htcHandle);

/* macro to check if the service wants to prevent credit dribbling by using
   a dynamic threshold */
#define CHECK_AND_ADJUST_CREDIT_THRESHOLD(pEndpoint)			\
	if ((pEndpoint)->ConnectionFlags & HTC_CONNECT_FLAGS_REDUCE_CREDIT_DRIBBLE) { \
		AdjustCreditThreshold((pEndpoint));			\
	}

LOCAL void HTC_AssembleBuffers(HTC_CONTEXT *pHTC, int Count, int Size)
{
	BUF_Pool_create_pool(pHTC->PoolHandle, POOL_ID_HTC_CONTROL, Count, Size);
}

LOCAL htc_handle_t _HTC_Init(HTC_SETUP_COMPLETE_CB SetupComplete,
                             HTC_CONFIG *pConfig)
{
	HIF_CALLBACK hifCBConfig;
	HTC_CONTEXT *pHTC;

        pHTC = (HTC_CONTEXT *)adf_os_mem_alloc(sizeof(HTC_CONTEXT));

	adf_os_mem_zero(pHTC, sizeof(HTC_CONTEXT));

	pHTC->OSHandle = pConfig->OSHandle;
	pHTC->PoolHandle = pConfig->PoolHandle;
	pHTC->hifHandle = pConfig->HIFHandle;

	hifCBConfig.send_buf_done = A_INDIR(htc._HTC_SendDoneHandler);
	hifCBConfig.recv_buf = A_INDIR(htc._HTC_MsgRecvHandler);
	hifCBConfig.context = pHTC;

	/* initialize hardware layer */
	HIF_register_callback(pConfig->HIFHandle, &hifCBConfig);

        /* see if the host wants us to override the number of ctrl buffers */
	pHTC->NumBuffersForCreditRpts = 0;

	if (0 == pHTC->NumBuffersForCreditRpts) {
		/* nothing to override, simply set default */
		pHTC->NumBuffersForCreditRpts = HTC_DEFAULT_NUM_CTRL_BUFFERS;
	}

	pHTC->MaxEpPendingCreditRpts = 0;

	if (0 == pHTC->MaxEpPendingCreditRpts) {
		pHTC->MaxEpPendingCreditRpts = HTC_DEFAULT_MAX_EP_PENDING_CREDIT_REPORTS;
	}
	/* calculate the total allocation size based on the number of credit report buffers */
	pHTC->CtrlBufferAllocSize = MIN_CREDIT_BUFFER_ALLOC_SIZE * pHTC->NumBuffersForCreditRpts;
	/* we need at least enough buffer space for 1 ctrl message */
	pHTC->CtrlBufferAllocSize = A_MAX(pHTC->CtrlBufferAllocSize,MAX_HTC_SETUP_MSG_SIZE);

	/* save the size of each buffer/credit we will receive */
	pHTC->RecvBufferSize = pConfig->CreditSize; //RecvBufferSize;
	pHTC->TotalCredits = pConfig->CreditNumber;
	pHTC->TotalCreditsAssigned = 0;

	/* setup the pseudo service that handles HTC control messages */
	pHTC->HTCControlService.ProcessRecvMsg = A_INDIR(htc._HTC_ControlSvcProcessMsg);
	pHTC->HTCControlService.ProcessSendBufferComplete = A_INDIR(htc._HTC_ControlSvcProcessSendComplete);
	pHTC->HTCControlService.TrailerSpcCheckLimit = HTC_CTRL_BUFFER_CHECK_SIZE;
	pHTC->HTCControlService.MaxSvcMsgSize = MAX_HTC_SETUP_MSG_SIZE;
	pHTC->HTCControlService.ServiceCtx = pHTC;

	/* automatically register this pseudo service to endpoint 1 */
	pHTC->Endpoints[ENDPOINT0].pService = &pHTC->HTCControlService;
	HIF_get_default_pipe(pHTC->hifHandle, &pHTC->Endpoints[ENDPOINT0].UpLinkPipeID,
			     &pHTC->Endpoints[ENDPOINT0].DownLinkPipeID);

	/* Initialize control pipe so we could receive the HTC control packets */
	// @TODO: msg size!
	HIF_config_pipe(pHTC->hifHandle, pHTC->Endpoints[ENDPOINT0].UpLinkPipeID, 1);

	/* set the first free endpoint */
	pHTC->CurrentEpIndex = ENDPOINT1;
	pHTC->SetupCompleteCb = SetupComplete;

        /* setup buffers for just the setup phase, we only need 1 buffer to handle
	 * setup */
	HTC_AssembleBuffers(pHTC, 4, MAX_HTC_SETUP_MSG_SIZE);

	/* start hardware layer so that we can queue buffers */
	HIF_start(pHTC->hifHandle);

	return pHTC;
}

LOCAL void _HTC_Shutdown(htc_handle_t htcHandle)
{
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)htcHandle;

	adf_os_mem_free(pHTC);
}

LOCAL void _HTC_RegisterService(htc_handle_t htcHandle, HTC_SERVICE *pService)
{
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)htcHandle;

        /* add it to the list */
	pService->pNext = pHTC->pServiceList;
	pHTC->pServiceList = pService;
}

LOCAL void _HTC_Ready(htc_handle_t htcHandle)
{
	adf_nbuf_t pBuffer;
	HTC_READY_MSG *pReady;
	a_uint8_t *addr;
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)htcHandle;

	pBuffer = HTCAllocMsgBuffer(pHTC);

	/* an optimization... the header length is chosen to
	 * be aligned on a 16 bit bounday, the fields in the message are designed to
	 * be aligned */
	addr = adf_nbuf_put_tail(pBuffer, sizeof(HTC_READY_MSG));
	pReady = (HTC_READY_MSG *)addr;
	A_MEMZERO(pReady,sizeof(HTC_READY_MSG));
	pReady->MessageID = adf_os_htons(HTC_MSG_READY_ID);
	pReady->CreditSize = adf_os_htons((A_UINT16)pHTC->RecvBufferSize);
	pReady->CreditCount = adf_os_htons((A_UINT16)pHTC->TotalCredits);
	pReady->MaxEndpoints = ENDPOINT_MAX;

	/* send out the message */
	HTC_SendMsg(pHTC, ENDPOINT0, pBuffer);
	/* now we need to wait for service connection requests */
}

LOCAL void ReturnBuffers(htc_handle_t htcHandle, HTC_ENDPOINT_ID EndpointID,
			 adf_nbuf_t pBuffers, A_BOOL sendCreditFlag)
{
	int         nbufs = 1;
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)htcHandle;

	/* supply some head-room again */
	adf_nbuf_push_head(pBuffers, HTC_HDR_LENGTH);

	/* enqueue all buffers to the single mailbox */
	HIF_return_recv_buf(pHTC->hifHandle, pHTC->Endpoints[EndpointID].UpLinkPipeID, pBuffers);

	if (pHTC->StateFlags & HTC_STATE_SETUP_COMPLETE) {
		A_UINT32    epCreditMask = (1 << EndpointID);
		/* we are running normally */
		/* update pending credit counts with the number of buffers that were added */
		pHTC->Endpoints[EndpointID].CreditsToReturn += (A_INT16)nbufs;
		pHTC->Endpoints[EndpointID].CreditsConsumed -= (A_INT16)nbufs;
		/* update bit map that this endpoint has non-zero credits */
		pHTC->EpCreditPendingMap |= epCreditMask;

		if (sendCreditFlag) {
			HTCCheckAndSendCreditReport(pHTC, epCreditMask,&pHTC->Endpoints[EndpointID],EndpointID);
		}

	} else {
		/* we have not started yet so all return operations are simply adding buffers
		 * to the interface at startup, so we can keep track of how many total
		 * credits we get */
		/* update global count that will be returned to the host */
		pHTC->TotalCredits += nbufs;
	}
}

LOCAL void _HTC_ReturnBuffersList(htc_handle_t htcHandle,
				  HTC_ENDPOINT_ID EndpointID,
				  adf_nbuf_queue_t bufHead)
{
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)htcHandle;
	adf_nbuf_t netbuf, tmpNbuf;

	/* retrieve each nbuf in the queue */
	netbuf = adf_nbuf_queue_first(&bufHead);

	while (netbuf) {

		tmpNbuf = netbuf;
		netbuf = adf_nbuf_queue_next(netbuf);

		ReturnBuffers(htcHandle, EndpointID, tmpNbuf, FALSE);
	}

	HTCCheckAndSendCreditReport(pHTC, (1 << EndpointID),&pHTC->Endpoints[EndpointID],EndpointID);
}

LOCAL void _HTC_ReturnBuffers(htc_handle_t htcHandle, HTC_ENDPOINT_ID EndpointID,
			      adf_nbuf_t pBuffers)
{
	ReturnBuffers(htcHandle, EndpointID, pBuffers, TRUE);
}

LOCAL void _HTC_SendMsg(htc_handle_t htcHandle, HTC_ENDPOINT_ID EndpointID,
			adf_nbuf_t pBuffers)
{
	HTC_FRAME_HDR *pHTCHdr;
	int totsz;
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)htcHandle;
	HTC_BUF_CONTEXT *ctx;

	ctx = (HTC_BUF_CONTEXT *)adf_nbuf_get_priv(pBuffers);

	/* init total size (this does not include the space we will put in for the HTC header) */
	totsz = adf_nbuf_len(pBuffers);

	/* the first buffer stores the header */
        /* back up buffer by a header size when we pass it down, by agreed upon convention the caller
	 * points the buffer to it's payload and leaves head room for the HTC header
	 * Note: in HTCSendDoneHandler(), we undo this so that the caller get's it's buffer
	 * back untainted */
	pHTCHdr = (HTC_FRAME_HDR *)adf_nbuf_push_head(pBuffers, HTC_HDR_LENGTH);

	/* flag that this is the header buffer that was modified */
	ctx->htc_flags |= HTC_FLAGS_BUF_HDR;
	/* mark where this buffer came from */
	ctx->end_point = EndpointID;
	/* the header start is ALWAYS aligned since we DMA it directly */

        /* set some fields, the rest of them will be filled below when we check for
	 * trailer space */
	pHTCHdr->Flags = 0;
	pHTCHdr->EndpointID = EndpointID;

	/* check opportunistically if we can return any reports via a trailer */
	do {
		int               room,i,totalReportBytes;
		A_UINT32          creditsPendingMap, compareMask;
		HTC_CREDIT_REPORT *pCreditRpt;
		HTC_RECORD_HDR    *pRecHdr;
		int               pipeMaxLen;
		A_UINT32          roomForPipeMaxLen;

		/* figure out how much room the last buffer can spare */
		pipeMaxLen = HIF_get_max_msg_len(pHTC->hifHandle,
						 pHTC->Endpoints[EndpointID].DownLinkPipeID);
		roomForPipeMaxLen = pipeMaxLen - adf_nbuf_headroom(pBuffers) - adf_nbuf_len(pBuffers);
		if ( roomForPipeMaxLen < 0 ) {
			roomForPipeMaxLen = 0;
		}

		room = adf_os_min( adf_nbuf_tailroom(pBuffers), roomForPipeMaxLen);
		if (room < (int)(sizeof(HTC_CREDIT_REPORT) + sizeof(HTC_RECORD_HDR))) {
			/* no room for any reports */
			break;
		}
		/* note, a record header only has 8 bit fields, so this is safe.
		 * we need an uncached pointer here too */
		totalReportBytes = 0;

		/* get a copy */
		creditsPendingMap = pHTC->EpCreditPendingMap;

		/* test pending map to see if we can send a report , if any
		 * credits are available, we might as well send them on the
		 * unused space in the buffer */
		if (creditsPendingMap) {

			pRecHdr = (HTC_RECORD_HDR *)adf_nbuf_put_tail(pBuffers,
							      sizeof(HTC_RECORD_HDR));

			/* set the ID, the length will be updated with the number of credit reports we
			 * can fit (see below) */
			pRecHdr->RecordID = HTC_RECORD_CREDITS;
			pRecHdr->Length = 0;
			/* the credit report follows the record header */
			totalReportBytes += sizeof(HTC_RECORD_HDR);
			room -= sizeof(HTC_RECORD_HDR);

			/* walkthrough pending credits map and build the records */
			for (i = 0;
			     (creditsPendingMap != 0) && (room >= (int)sizeof(HTC_CREDIT_REPORT));
			     i++) {
				compareMask = (1 << i);
				if (compareMask & creditsPendingMap) {

					pCreditRpt = (HTC_CREDIT_REPORT *)adf_nbuf_put_tail(pBuffers,
									    sizeof(HTC_CREDIT_REPORT));

					/* clear pending mask, we are going to return all these credits */
					creditsPendingMap &= ~(compareMask);
					/* add this record */
					pCreditRpt->EndpointID = i;
					pCreditRpt->Credits = (A_UINT8)pHTC->Endpoints[i].CreditsToReturn;
					/* remove pending credits, we always send deltas */
					pHTC->Endpoints[i].CreditsToReturn = 0;
					/* adjust new threshold for this endpoint if needed */
					CHECK_AND_ADJUST_CREDIT_THRESHOLD(&pHTC->Endpoints[i]);
					/* update this record length */
					pRecHdr->Length += sizeof(HTC_CREDIT_REPORT);
					room -= sizeof(HTC_CREDIT_REPORT);
					totalReportBytes += sizeof(HTC_CREDIT_REPORT);

					if ( room < sizeof(HTC_CREDIT_REPORT) ) {
						break;
					}
				}
			}

			/* update new pending credits map */
			pHTC->EpCreditPendingMap = creditsPendingMap;
		}

		if (totalReportBytes <= 0) {
			break;
		}

		/* must fit into a byte, this should never actually happen since
		 * the maximum possible number of endpoints is 32.
		 * The trailer can have at most 1 credit record with up to 32  reports in the record.
		 * The trailer can have at most 1 lookahead record with only 1 lookahead report in the record.
		 */

		/* set header option bytes */
		pHTCHdr->ControlBytes[0] = totalReportBytes;
		/* HTC frame contains a trailer */
		pHTCHdr->Flags |= HTC_FLAGS_RECV_TRAILER;
		/* increment total size by the reports we added */
		totsz += totalReportBytes;
		/* adjust the last buffer we used for adding on the trailer */
	} while (FALSE);

	if (totsz == 0) {
	}

	/* set length for message (this includes any reports that were added above) */
	pHTCHdr->PayloadLen = adf_os_htons(totsz);
	HIF_send_buffer(pHTC->hifHandle, pHTC->Endpoints[EndpointID].DownLinkPipeID, pBuffers);
}

void _HTC_PauseRecv(HTC_ENDPOINT_ID EndpointID)
{
}

void _HTC_ResumeRecv(HTC_ENDPOINT_ID EndpointID)
{
}

int _HTC_GetReservedHeadroom(htc_handle_t htcHandle)
{
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)htcHandle;

	return HTC_HDR_LENGTH + HIF_get_reserved_headroom(pHTC->hifHandle);
}

void htc_module_install(struct htc_apis *pAPIs)
{
	pAPIs->_HTC_Init = _HTC_Init;
	pAPIs->_HTC_ReturnBuffers = _HTC_ReturnBuffers;
	pAPIs->_HTC_ReturnBuffersList = _HTC_ReturnBuffersList;
	pAPIs->_HTC_Ready = _HTC_Ready;
	pAPIs->_HTC_RegisterService = _HTC_RegisterService;
	pAPIs->_HTC_SendMsg = _HTC_SendMsg;
	pAPIs->_HTC_Shutdown = _HTC_Shutdown;
	pAPIs->_HTC_GetReservedHeadroom = _HTC_GetReservedHeadroom;
	pAPIs->_HTC_MsgRecvHandler = HTCMsgRecvHandler;
	pAPIs->_HTC_SendDoneHandler = HTCSendDoneHandler;
	pAPIs->_HTC_ControlSvcProcessMsg = HTCControlSvcProcessMsg;
	pAPIs->_HTC_ControlSvcProcessSendComplete = HTCControlSvcProcessSendComplete;
}

/* free message to the free list */
LOCAL void HTCFreeMsgBuffer(HTC_CONTEXT *pHTC, adf_nbuf_t buf)
{
	BUF_Pool_free_buf(pHTC->PoolHandle, POOL_ID_HTC_CONTROL, buf);
}

/* HTC control message allocator (also used for empty frames to send trailer options) */
LOCAL adf_nbuf_t HTCAllocMsgBuffer(HTC_CONTEXT *pHTC)
{
	return BUF_Pool_alloc_buf(pHTC->PoolHandle,
				  POOL_ID_HTC_CONTROL,
				  HTC_GetReservedHeadroom(pHTC));
}

LOCAL void HTCCheckAndSendCreditReport(HTC_CONTEXT *pHTC, A_UINT32 EpMask,
				       HTC_ENDPOINT *pEndpoint, HTC_ENDPOINT_ID Eid)
{
	adf_nbuf_t pCredBuffer;
	HTC_BUF_CONTEXT *ctx;

	do {
		/* check if host needs credits */
		if (!(pHTC->EpHostNeedsCreditMap & EpMask)) {
			/* host does not need any credits for this set */
			break;
		}
		/* check if any are pending */
		if (!(pHTC->EpCreditPendingMap & EpMask)) {
			/* nothing to send up */
			break;
		}
		/* was an endpoint specified? */
		if (pEndpoint != NULL) {
			/* see if a threshold is in effect for this endpoint */
			if (pEndpoint->CreditReturnThreshhold != 0) {
				if (pEndpoint->CreditsToReturn < pEndpoint->CreditReturnThreshhold) {
					/* this endpoint is using a threshold to prevent credits from dribbling
					 * back to the host */
					break;
				}
			}

			if (pEndpoint->PendingCreditReports >= pHTC->MaxEpPendingCreditRpts) {
				/* this endpoint already has some reports outstanding */
				/* flag that as soon as a buffer is reaped, we issue a credit update to
				 * pick up this credit that is being held up because the endpoint has already
				 * exceeded the max outstanding credit report limit */
				pHTC->StateFlags |= HTC_SEND_CREDIT_UPDATE_SOON;
				break;
			}
		}

		/* if we get here we have some credits to send up */

		/* allocate a message buffer for the trailer */
		pCredBuffer = HTCAllocMsgBuffer(pHTC);
		if (NULL == pCredBuffer) {
			/* no buffers left to send an empty message with trailers, host will just
			 * have to wait until we get our endpoint 0 messages back.. */
			/* mark that we need to send an update as soon as we can get a buffer back */
			pHTC->StateFlags |= HTC_SEND_CREDIT_UPDATE_SOON;
			break;
		}

		ctx = (HTC_BUF_CONTEXT *)adf_nbuf_get_priv(pCredBuffer);
		if (pEndpoint != NULL) {
			/* keep track of pending reports */
			pEndpoint->PendingCreditReports++;
			/* save the endpoint in order to decrement the count when the send completes */
			ctx->htc_flags = Eid | HTC_FLAGS_CREDIT_RPT;
		}

		/* this is an empty message, the HTC_SendMsg will tack on a trailer in the remaining
		 * space, NOTE: no need to flush the cache, the header and trailers are assembled
		 * using uncached addresses */
		HTC_SendMsg(pHTC, ENDPOINT0, pCredBuffer);

	} while (FALSE);
}

/* called in response to the arrival of a service connection message */
LOCAL void HTCProcessConnectMsg(HTC_CONTEXT *pHTC, HTC_CONNECT_SERVICE_MSG *pMsg)
{
	HTC_SERVICE *pService = pHTC->pServiceList;
	A_UINT8 connectStatus = HTC_SERVICE_NOT_FOUND;
	adf_nbuf_t pBuffer;
	HTC_CONNECT_SERVICE_RESPONSE_MSG *pRspMsg;
	int metaDataOutLen = 0;
	A_UINT16 serviceId = adf_os_ntohs(pMsg->ServiceID);

	pBuffer = HTCAllocMsgBuffer(pHTC);
	/* note : this will be aligned */
	pRspMsg = (HTC_CONNECT_SERVICE_RESPONSE_MSG *)
                adf_nbuf_put_tail(pBuffer, sizeof(HTC_CONNECT_SERVICE_RESPONSE_MSG));

	A_MEMZERO(pRspMsg,sizeof(HTC_CONNECT_SERVICE_RESPONSE_MSG));
	pRspMsg->MessageID = adf_os_htons(HTC_MSG_CONNECT_SERVICE_RESPONSE_ID);
	/* reflect the service ID for this connect attempt */
	pRspMsg->ServiceID = adf_os_htons(serviceId);

	while (pService) {

		if (pHTC->CurrentEpIndex >= ENDPOINT_MAX) {
			/* no more endpoints */
			connectStatus = HTC_SERVICE_NO_RESOURCES;
			break;
		}

		if (serviceId == pService->ServiceID) {
			/* we found a match */
			A_UINT8 *pMetaDataIN = NULL;
			A_UINT8 *pMetaDataOut;

			/* outgoing meta data resides in the space after the response message */
			pMetaDataOut = ((A_UINT8 *)pRspMsg) + sizeof(HTC_CONNECT_SERVICE_RESPONSE_MSG);

			if (pMsg->ServiceMetaLength != 0) {
				/* the meta data follows the connect service message */
				pMetaDataIN = ((A_UINT8 *)pMsg) + sizeof(HTC_CONNECT_SERVICE_MSG);
			}

			/* call the connect callback with the endpoint to use and pointers to meta data */
			connectStatus = pService->ProcessConnect(pService,
								 pHTC->CurrentEpIndex,
								 pMetaDataIN,
								 pMsg->ServiceMetaLength,
								 pMetaDataOut,
								 &metaDataOutLen);

			/* check if the service accepted this connection request */
			if (HTC_SERVICE_SUCCESS == connectStatus) {
				/* set the length of the response meta data going back to the host */
				pRspMsg->ServiceMetaLength = (A_UINT8)metaDataOutLen;
				/* set the endpoint ID the host will now communicate over */
				pRspMsg->EndpointID = pHTC->CurrentEpIndex;
				/* return the maximum message size for this service */
				pRspMsg->MaxMsgSize = adf_os_htons((A_UINT16)pService->MaxSvcMsgSize);
				/* assign this endpoint to this service, this will be used in routing messages */
				pHTC->Endpoints[pHTC->CurrentEpIndex].pService = pService;
				/* set connection flags */
				pHTC->Endpoints[pHTC->CurrentEpIndex].ConnectionFlags = pMsg->ConnectionFlags;

				pHTC->Endpoints[pHTC->CurrentEpIndex].DownLinkPipeID = pMsg->DownLinkPipeID;
				pHTC->Endpoints[pHTC->CurrentEpIndex].UpLinkPipeID = pMsg->UpLinkPipeID;

				/* mark that we are now connected */
				pService->ServiceFlags |= HTC_SERVICE_FLAGS_CONNECTED;
				/* bump up our index, this EP is now in use */
				pHTC->CurrentEpIndex++;
			}

			break;
		}

		pService = pService->pNext;
	}

	pRspMsg->Status = connectStatus;

	/* send out the response message */
	HTC_SendMsg(pHTC, ENDPOINT0, pBuffer);
}

LOCAL void HTCProcessConfigPipeMsg(HTC_CONTEXT *pHTC, HTC_CONFIG_PIPE_MSG *pMsg)
{
	adf_nbuf_t pBuffer;
	HTC_CONFIG_PIPE_RESPONSE_MSG *pRspMsg;

	pBuffer = HTCAllocMsgBuffer(pHTC);

	/* note : this will be aligned */
	pRspMsg = (HTC_CONFIG_PIPE_RESPONSE_MSG *)
                adf_nbuf_put_tail(pBuffer, sizeof(HTC_CONFIG_PIPE_RESPONSE_MSG));

	A_MEMZERO(pRspMsg,sizeof(HTC_CONFIG_PIPE_RESPONSE_MSG));

	pRspMsg->MessageID = adf_os_htons(HTC_MSG_CONFIG_PIPE_RESPONSE_ID);
	/* reflect the service ID for this connect attempt */
	pRspMsg->PipeID = pMsg->PipeID;

	if ( HIF_is_pipe_supported(pHTC->hifHandle, pMsg->PipeID) ) {
		pRspMsg->Status = 0;
	} else {
		pRspMsg->Status = 1;
		goto config_done;
	}

	if ( (pHTC->TotalCreditsAssigned + pMsg->CreditCount) <= pHTC->TotalCredits ) {
		pHTC->TotalCreditsAssigned += pMsg->CreditCount;
	} else {
		pRspMsg->Status = 2;
		goto config_done;
	}

	HIF_config_pipe(pHTC->hifHandle, pMsg->PipeID, pMsg->CreditCount);

config_done:
	/* send out the response message */
	HTC_SendMsg(pHTC, ENDPOINT0, pBuffer);
}

/* process an incomming control message from the host */
LOCAL void HTCControlSvcProcessMsg(HTC_ENDPOINT_ID EndpointID, adf_nbuf_t hdr_buf,
				   adf_nbuf_t pBuffers, void *arg)
{
	A_BOOL setupComplete = FALSE;
	a_uint8_t *anbdata;
	a_uint32_t anblen;
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)arg;
	HTC_UNKNOWN_MSG  *pMsg;

	adf_os_assert(hdr_buf == ADF_NBUF_NULL);

	/* we assume buffers are aligned such that we can access the message
	 * parameters directly*/
	adf_nbuf_peek_header(pBuffers, &anbdata, &anblen);
	pMsg = (HTC_UNKNOWN_MSG *)anbdata;

	/* we cannot handle fragmented messages across buffers */

	switch ( adf_os_ntohs(pMsg->MessageID) ) {
        case HTC_MSG_CONNECT_SERVICE_ID:
		HTCProcessConnectMsg(pHTC, (HTC_CONNECT_SERVICE_MSG *)pMsg);
		break;
        case HTC_MSG_CONFIG_PIPE_ID:
		HTCProcessConfigPipeMsg(pHTC, (HTC_CONFIG_PIPE_MSG *)pMsg);
		break;
        case HTC_MSG_SETUP_COMPLETE_ID:
                /* the host has indicated that it has completed all
		   setup tasks and we can now let the services take over to
		   run the rest of the application */
		setupComplete = TRUE;
		/* can't get this more than once */
		break;
        default:
		;
	}

	if (pHTC->StateFlags & HTC_STATE_SETUP_COMPLETE) {
		/* recycle buffer only if we are fully running */
		HTC_ReturnBuffers(pHTC, ENDPOINT0,pBuffers);
	} else {
		/* supply some head-room again */
		adf_nbuf_push_head(pBuffers, HTC_HDR_LENGTH);

		/* otherwise return the packet back to mbox */
		HIF_return_recv_buf(pHTC->hifHandle, pHTC->Endpoints[EndpointID].UpLinkPipeID, pBuffers);
	}

	if (setupComplete) {
		/* mark that setup has completed */
		pHTC->StateFlags |= HTC_STATE_SETUP_COMPLETE;
		if (pHTC->SetupCompleteCb != NULL) {
			pHTC->SetupCompleteCb();
		}
	}
}

/* callback when endpoint 0 send buffers are completed */
LOCAL void HTCControlSvcProcessSendComplete(HTC_ENDPOINT_ID EndpointID,
					    adf_nbuf_t pBuffers, void *arg)
{
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)arg;
	HTC_BUF_CONTEXT *ctx;
	HTC_ENDPOINT_ID creditRptEndpoint;

	ctx = (HTC_BUF_CONTEXT *)adf_nbuf_get_priv(pBuffers);

	/* put them back into the pool */
	if ( ctx->htc_flags & HTC_FLAGS_CREDIT_RPT ) {
		/* extract the endpoint number that requested this credit report */
		creditRptEndpoint = ctx->htc_flags & HTC_FLAGS_CRPT_EP_MASK;
		pHTC->Endpoints[creditRptEndpoint].PendingCreditReports--;
	}

	HTCFreeMsgBuffer(pHTC, pBuffers);

	if (pHTC->StateFlags & HTC_SEND_CREDIT_UPDATE_SOON) {
		/* this flag is set when the host could not send a credit report
		 * because we ran out of HTC control buffers */
		pHTC->StateFlags &= ~HTC_SEND_CREDIT_UPDATE_SOON;
		/* send out a report if anything is pending */
		HTCCheckAndSendCreditReport(pHTC, HTC_ANY_ENDPOINT_MASK,NULL,ENDPOINT_MAX);
	}
}

LOCAL void HTCSendDoneHandler(adf_nbuf_t buf, void *context)
{
	A_UINT8 current_eid;
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)context;
	HTC_BUF_CONTEXT *ctx;

	ctx = (HTC_BUF_CONTEXT *)adf_nbuf_get_priv(buf);
	current_eid = ctx->end_point;

	/* Walk through the buffers and fixup the ones we used for HTC headers.
	 * The buffer list may contain more than one string of HTC buffers comprising of an
	 * HTC message so we need to check every buffer */
	adf_nbuf_pull_head(buf, HTC_HDR_LENGTH);

	pHTC->Endpoints[current_eid].pService->
		ProcessSendBufferComplete(current_eid,
					  buf,
					  pHTC->Endpoints[current_eid].pService->ServiceCtx);
}

LOCAL void AdjustCreditThreshold(HTC_ENDPOINT  *pEndpoint)
{
	A_INT16 creditsOutstanding = pEndpoint->CreditsToReturn + pEndpoint->CreditsConsumed;
        /* set the new threshold based on the number of credits that have been consumed
         * and which have not been returned by the app.
         * Note: it is okay for this threshold to be zero which indicates no threshold
         * is in use */
	switch (pEndpoint->ConnectionFlags & HTC_CONNECT_FLAGS_THRESHOLD_LEVEL_MASK) {
        case HTC_CONNECT_FLAGS_THRESHOLD_LEVEL_ONE_FOURTH :
		creditsOutstanding >>= 2;
		break;
        case HTC_CONNECT_FLAGS_THRESHOLD_LEVEL_ONE_HALF :
		creditsOutstanding >>= 1;
		break;
        case HTC_CONNECT_FLAGS_THRESHOLD_LEVEL_THREE_FOURTHS :
		creditsOutstanding = (creditsOutstanding * 3) >> 2;
		break;
		/* default case is unity */
	}

	pEndpoint->CreditReturnThreshhold = creditsOutstanding;

}

LOCAL void RedistributeCredit(adf_nbuf_t buf, int toPipeId)
{

}

/* callback from the mailbox hardware layer when a full message arrives */
LOCAL void HTCMsgRecvHandler(adf_nbuf_t hdr_buf, adf_nbuf_t buffer, void *context)
{
	HTC_ENDPOINT  *pEndpoint;
	A_UINT32 eidMask;
	int eid;
	a_uint8_t *anbdata;
	a_uint32_t anblen;
	HTC_FRAME_HDR *pHTCHdr;
	HTC_CONTEXT *pHTC = (HTC_CONTEXT *)context;
	adf_nbuf_t tmp_nbuf;

	if (hdr_buf == ADF_NBUF_NULL) {
		/* HTC hdr is not in the hdr_buf */
		tmp_nbuf = buffer;
	}
	else {
		tmp_nbuf = hdr_buf;
	}

	adf_nbuf_peek_header(tmp_nbuf, &anbdata, &anblen);
	pHTCHdr = (HTC_FRAME_HDR *)anbdata;

	eid = pHTCHdr->EndpointID;

	pEndpoint = &pHTC->Endpoints[eid];
	eidMask = 1 << eid;

	if (pHTCHdr->Flags & HTC_FLAGS_CREDIT_REDISTRIBUTION) {
		/* The pipe id where the credit is redistributed to is carried in Control
		 * Byte 0 */
		RedistributeCredit(tmp_nbuf, pHTCHdr->ControlBytes[0]);
		return;
	}

	if (pHTC->StateFlags & HTC_STATE_SETUP_COMPLETE) {
		/* after setup we keep track of credit consumption to allow us to
		 * adjust thresholds to reduce credit dribbling */
		pEndpoint->CreditsConsumed ++;
	}

	/* from the design document, we put the endpoint into a "host-needs-credit" state
	 * when we receive a frame with the NEED_CREDIT_UPDATE flag set .
	 * if the host received credits through an opportunistic path, then it can
	 * issue a another frame with this bit cleared, this signals the target to clear
	 * the "host-needs-credit" state */
	if (pHTCHdr->Flags & HTC_FLAGS_NEED_CREDIT_UPDATE) {
		/* the host is running low (or is out) of credits on this
		 * endpoint, update mask */
		pHTC->EpHostNeedsCreditMap |= eidMask;
		/* check and set new threshold since host has reached a low credit situation */
		CHECK_AND_ADJUST_CREDIT_THRESHOLD(pEndpoint);
	} else {
		/* clear the flag */
		pHTC->EpHostNeedsCreditMap &= ~(eidMask);
		pEndpoint->CreditReturnThreshhold = 0;
	}

	/* Adjust the first buffer to point to the start of the actual
	   payload, the first buffer contains the header */
	adf_nbuf_pull_head(tmp_nbuf, HTC_HDR_LENGTH);

	/* NOTE : This callback could re-queue the recv buffers within this calling context.
	 *        The callback could also send a response message within the context of this callback
	 *        as the result of parsing this message.  In either case, if there are
	 *        pending credits and the host needs them, a credit report will be sent either through
	 *        the response message trailer or a NULL message through HTC_ReturnBuffers().
	 */

	pEndpoint->pService->ProcessRecvMsg(eid, hdr_buf, buffer, pEndpoint->pService->ServiceCtx);

	/* Calls to HTC_ReturnBuffers drives the endpoint credit reporting state machine.
	 * We do not want to delay credits for too long in the event that the application is
	 * holding onto buffers for excessive periods of time.  This gives us "some" better
	 * opportunities to send up credits. */
	HTCCheckAndSendCreditReport(pHTC, eidMask, pEndpoint, eid);
}
