| //------------------------------------------------------------------------------ |
| // <copyright file="htc_send.c" company="Atheros"> |
| // Copyright (c) 2007-2010 Atheros Corporation. All rights reserved. |
| // |
| // |
| // Permission to use, copy, modify, and/or distribute this software for any |
| // purpose with or without fee is hereby granted, provided that the above |
| // copyright notice and this permission notice appear in all copies. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| // |
| // |
| //------------------------------------------------------------------------------ |
| //============================================================================== |
| // Author(s): ="Atheros" |
| //============================================================================== |
| #include "htc_internal.h" |
| |
| typedef enum _HTC_SEND_QUEUE_RESULT { |
| HTC_SEND_QUEUE_OK = 0, /* packet was queued */ |
| HTC_SEND_QUEUE_DROP = 1, /* this packet should be dropped */ |
| } HTC_SEND_QUEUE_RESULT; |
| |
| #define DO_EP_TX_COMPLETION(ep,q) DoSendCompletion(ep,q) |
| |
| /* call the distribute credits callback with the distribution */ |
| #define DO_DISTRIBUTION(t,reason,description,pList) \ |
| { \ |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, \ |
| (" calling distribute function (%s) (dfn:0x%lX, ctxt:0x%lX, dist:0x%lX) \n", \ |
| (description), \ |
| (unsigned long)(t)->DistributeCredits, \ |
| (unsigned long)(t)->pCredDistContext, \ |
| (unsigned long)pList)); \ |
| (t)->DistributeCredits((t)->pCredDistContext, \ |
| (pList), \ |
| (reason)); \ |
| } |
| |
| static void DoSendCompletion(struct htc_endpoint *pEndpoint, |
| HTC_PACKET_QUEUE *pQueueToIndicate) |
| { |
| do { |
| |
| if (HTC_QUEUE_EMPTY(pQueueToIndicate)) { |
| /* nothing to indicate */ |
| break; |
| } |
| |
| if (pEndpoint->EpCallBacks.EpTxCompleteMultiple != NULL) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, (" HTC calling ep %d, send complete multiple callback (%d pkts) \n", |
| pEndpoint->Id, HTC_PACKET_QUEUE_DEPTH(pQueueToIndicate))); |
| /* a multiple send complete handler is being used, pass the queue to the handler */ |
| pEndpoint->EpCallBacks.EpTxCompleteMultiple(pEndpoint->EpCallBacks.pContext, |
| pQueueToIndicate); |
| /* all packets are now owned by the callback, reset queue to be safe */ |
| INIT_HTC_PACKET_QUEUE(pQueueToIndicate); |
| } else { |
| struct htc_packet *pPacket; |
| /* using legacy EpTxComplete */ |
| do { |
| pPacket = HTC_PACKET_DEQUEUE(pQueueToIndicate); |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, (" HTC calling ep %d send complete callback on packet 0x%lX \n", \ |
| pEndpoint->Id, (unsigned long)(pPacket))); |
| pEndpoint->EpCallBacks.EpTxComplete(pEndpoint->EpCallBacks.pContext, pPacket); |
| } while (!HTC_QUEUE_EMPTY(pQueueToIndicate)); |
| } |
| |
| } while (false); |
| |
| } |
| |
| /* do final completion on sent packet */ |
| static INLINE void CompleteSentPacket(HTC_TARGET *target, struct htc_endpoint *pEndpoint, struct htc_packet *pPacket) |
| { |
| pPacket->Completion = NULL; |
| |
| if (pPacket->Status) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("CompleteSentPacket: request failed (status:%d, ep:%d, length:%d creds:%d) \n", |
| pPacket->Status, pPacket->Endpoint, pPacket->ActualLength, pPacket->PktInfo.AsTx.CreditsUsed)); |
| /* on failure to submit, reclaim credits for this packet */ |
| LOCK_HTC_TX(target); |
| pEndpoint->CreditDist.TxCreditsToDist += pPacket->PktInfo.AsTx.CreditsUsed; |
| pEndpoint->CreditDist.TxQueueDepth = HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue); |
| DO_DISTRIBUTION(target, |
| HTC_CREDIT_DIST_SEND_COMPLETE, |
| "Send Complete", |
| target->EpCreditDistributionListHead->pNext); |
| UNLOCK_HTC_TX(target); |
| } |
| /* first, fixup the head room we allocated */ |
| pPacket->pBuffer += HTC_HDR_LENGTH; |
| } |
| |
| /* our internal send packet completion handler when packets are submited to the AR6K device |
| * layer */ |
| static void HTCSendPktCompletionHandler(void *Context, struct htc_packet *pPacket) |
| { |
| HTC_TARGET *target = (HTC_TARGET *)Context; |
| struct htc_endpoint *pEndpoint = &target->EndPoint[pPacket->Endpoint]; |
| HTC_PACKET_QUEUE container; |
| |
| CompleteSentPacket(target,pEndpoint,pPacket); |
| INIT_HTC_PACKET_QUEUE_AND_ADD(&container,pPacket); |
| /* do completion */ |
| DO_EP_TX_COMPLETION(pEndpoint,&container); |
| } |
| |
| int HTCIssueSend(HTC_TARGET *target, struct htc_packet *pPacket) |
| { |
| int status; |
| bool sync = false; |
| |
| if (pPacket->Completion == NULL) { |
| /* mark that this request was synchronously issued */ |
| sync = true; |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, |
| ("+-HTCIssueSend: transmit length : %d (%s) \n", |
| pPacket->ActualLength + (u32)HTC_HDR_LENGTH, |
| sync ? "SYNC" : "ASYNC" )); |
| |
| /* send message to device */ |
| status = DevSendPacket(&target->Device, |
| pPacket, |
| pPacket->ActualLength + HTC_HDR_LENGTH); |
| |
| if (sync) { |
| /* use local sync variable. If this was issued asynchronously, pPacket is no longer |
| * safe to access. */ |
| pPacket->pBuffer += HTC_HDR_LENGTH; |
| } |
| |
| /* if this request was asynchronous, the packet completion routine will be invoked by |
| * the device layer when the HIF layer completes the request */ |
| |
| return status; |
| } |
| |
| /* get HTC send packets from the TX queue on an endpoint */ |
| static INLINE void GetHTCSendPackets(HTC_TARGET *target, |
| struct htc_endpoint *pEndpoint, |
| HTC_PACKET_QUEUE *pQueue) |
| { |
| int creditsRequired; |
| int remainder; |
| u8 sendFlags; |
| struct htc_packet *pPacket; |
| unsigned int transferLength; |
| |
| /****** NOTE : the TX lock is held when this function is called *****************/ |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("+GetHTCSendPackets \n")); |
| |
| /* loop until we can grab as many packets out of the queue as we can */ |
| while (true) { |
| |
| sendFlags = 0; |
| /* get packet at head, but don't remove it */ |
| pPacket = HTC_GET_PKT_AT_HEAD(&pEndpoint->TxQueue); |
| if (pPacket == NULL) { |
| break; |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" Got head packet:0x%lX , Queue Depth: %d\n", |
| (unsigned long)pPacket, HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue))); |
| |
| transferLength = DEV_CALC_SEND_PADDED_LEN(&target->Device, pPacket->ActualLength + HTC_HDR_LENGTH); |
| |
| if (transferLength <= target->TargetCreditSize) { |
| creditsRequired = 1; |
| } else { |
| /* figure out how many credits this message requires */ |
| creditsRequired = transferLength / target->TargetCreditSize; |
| remainder = transferLength % target->TargetCreditSize; |
| |
| if (remainder) { |
| creditsRequired++; |
| } |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" Creds Required:%d Got:%d\n", |
| creditsRequired, pEndpoint->CreditDist.TxCredits)); |
| |
| if (pEndpoint->CreditDist.TxCredits < creditsRequired) { |
| |
| /* not enough credits */ |
| if (pPacket->Endpoint == ENDPOINT_0) { |
| /* leave it in the queue */ |
| break; |
| } |
| /* invoke the registered distribution function only if this is not |
| * endpoint 0, we let the driver layer provide more credits if it can. |
| * We pass the credit distribution list starting at the endpoint in question |
| * */ |
| |
| /* set how many credits we need */ |
| pEndpoint->CreditDist.TxCreditsSeek = |
| creditsRequired - pEndpoint->CreditDist.TxCredits; |
| DO_DISTRIBUTION(target, |
| HTC_CREDIT_DIST_SEEK_CREDITS, |
| "Seek Credits", |
| &pEndpoint->CreditDist); |
| pEndpoint->CreditDist.TxCreditsSeek = 0; |
| |
| if (pEndpoint->CreditDist.TxCredits < creditsRequired) { |
| /* still not enough credits to send, leave packet in the queue */ |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, |
| (" Not enough credits for ep %d leaving packet in queue..\n", |
| pPacket->Endpoint)); |
| break; |
| } |
| |
| } |
| |
| pEndpoint->CreditDist.TxCredits -= creditsRequired; |
| INC_HTC_EP_STAT(pEndpoint, TxCreditsConsummed, creditsRequired); |
| |
| /* check if we need credits back from the target */ |
| if (pEndpoint->CreditDist.TxCredits < pEndpoint->CreditDist.TxCreditsPerMaxMsg) { |
| /* we are getting low on credits, see if we can ask for more from the distribution function */ |
| pEndpoint->CreditDist.TxCreditsSeek = |
| pEndpoint->CreditDist.TxCreditsPerMaxMsg - pEndpoint->CreditDist.TxCredits; |
| |
| DO_DISTRIBUTION(target, |
| HTC_CREDIT_DIST_SEEK_CREDITS, |
| "Seek Credits", |
| &pEndpoint->CreditDist); |
| |
| pEndpoint->CreditDist.TxCreditsSeek = 0; |
| /* see if we were successful in getting more */ |
| if (pEndpoint->CreditDist.TxCredits < pEndpoint->CreditDist.TxCreditsPerMaxMsg) { |
| /* tell the target we need credits ASAP! */ |
| sendFlags |= HTC_FLAGS_NEED_CREDIT_UPDATE; |
| INC_HTC_EP_STAT(pEndpoint, TxCreditLowIndications, 1); |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" Host Needs Credits \n")); |
| } |
| } |
| |
| /* now we can fully dequeue */ |
| pPacket = HTC_PACKET_DEQUEUE(&pEndpoint->TxQueue); |
| /* save the number of credits this packet consumed */ |
| pPacket->PktInfo.AsTx.CreditsUsed = creditsRequired; |
| /* all TX packets are handled asynchronously */ |
| pPacket->Completion = HTCSendPktCompletionHandler; |
| pPacket->pContext = target; |
| INC_HTC_EP_STAT(pEndpoint, TxIssued, 1); |
| /* save send flags */ |
| pPacket->PktInfo.AsTx.SendFlags = sendFlags; |
| pPacket->PktInfo.AsTx.SeqNo = pEndpoint->SeqNo; |
| pEndpoint->SeqNo++; |
| /* queue this packet into the caller's queue */ |
| HTC_PACKET_ENQUEUE(pQueue,pPacket); |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("-GetHTCSendPackets \n")); |
| |
| } |
| |
| static void HTCAsyncSendScatterCompletion(struct hif_scatter_req *pScatterReq) |
| { |
| int i; |
| struct htc_packet *pPacket; |
| struct htc_endpoint *pEndpoint = (struct htc_endpoint *)pScatterReq->Context; |
| HTC_TARGET *target = (HTC_TARGET *)pEndpoint->target; |
| int status = 0; |
| HTC_PACKET_QUEUE sendCompletes; |
| |
| INIT_HTC_PACKET_QUEUE(&sendCompletes); |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("+HTCAsyncSendScatterCompletion TotLen: %d Entries: %d\n", |
| pScatterReq->TotalLength, pScatterReq->ValidScatterEntries)); |
| |
| DEV_FINISH_SCATTER_OPERATION(pScatterReq); |
| |
| if (pScatterReq->CompletionStatus) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("** Send Scatter Request Failed: %d \n",pScatterReq->CompletionStatus)); |
| status = A_ERROR; |
| } |
| |
| /* walk through the scatter list and process */ |
| for (i = 0; i < pScatterReq->ValidScatterEntries; i++) { |
| pPacket = (struct htc_packet *)(pScatterReq->ScatterList[i].pCallerContexts[0]); |
| A_ASSERT(pPacket != NULL); |
| pPacket->Status = status; |
| CompleteSentPacket(target,pEndpoint,pPacket); |
| /* add it to the completion queue */ |
| HTC_PACKET_ENQUEUE(&sendCompletes, pPacket); |
| } |
| |
| /* free scatter request */ |
| DEV_FREE_SCATTER_REQ(&target->Device,pScatterReq); |
| /* complete all packets */ |
| DO_EP_TX_COMPLETION(pEndpoint,&sendCompletes); |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("-HTCAsyncSendScatterCompletion \n")); |
| } |
| |
| /* drain a queue and send as bundles |
| * this function may return without fully draining the queue under the following conditions : |
| * - scatter resources are exhausted |
| * - a message that will consume a partial credit will stop the bundling process early |
| * - we drop below the minimum number of messages for a bundle |
| * */ |
| static void HTCIssueSendBundle(struct htc_endpoint *pEndpoint, |
| HTC_PACKET_QUEUE *pQueue, |
| int *pBundlesSent, |
| int *pTotalBundlesPkts) |
| { |
| int pktsToScatter; |
| unsigned int scatterSpaceRemaining; |
| struct hif_scatter_req *pScatterReq = NULL; |
| int i, packetsInScatterReq; |
| unsigned int transferLength; |
| struct htc_packet *pPacket; |
| bool done = false; |
| int bundlesSent = 0; |
| int totalPktsInBundle = 0; |
| HTC_TARGET *target = pEndpoint->target; |
| int creditRemainder = 0; |
| int creditPad; |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("+HTCIssueSendBundle \n")); |
| |
| while (!done) { |
| |
| pktsToScatter = HTC_PACKET_QUEUE_DEPTH(pQueue); |
| pktsToScatter = min(pktsToScatter, target->MaxMsgPerBundle); |
| |
| if (pktsToScatter < HTC_MIN_HTC_MSGS_TO_BUNDLE) { |
| /* not enough to bundle */ |
| break; |
| } |
| |
| pScatterReq = DEV_ALLOC_SCATTER_REQ(&target->Device); |
| |
| if (pScatterReq == NULL) { |
| /* no scatter resources */ |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" No more scatter resources \n")); |
| break; |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" pkts to scatter: %d \n", pktsToScatter)); |
| |
| pScatterReq->TotalLength = 0; |
| pScatterReq->ValidScatterEntries = 0; |
| |
| packetsInScatterReq = 0; |
| scatterSpaceRemaining = DEV_GET_MAX_BUNDLE_SEND_LENGTH(&target->Device); |
| |
| for (i = 0; i < pktsToScatter; i++) { |
| |
| pScatterReq->ScatterList[i].pCallerContexts[0] = NULL; |
| |
| pPacket = HTC_GET_PKT_AT_HEAD(pQueue); |
| if (pPacket == NULL) { |
| A_ASSERT(false); |
| break; |
| } |
| |
| creditPad = 0; |
| transferLength = DEV_CALC_SEND_PADDED_LEN(&target->Device, |
| pPacket->ActualLength + HTC_HDR_LENGTH); |
| /* see if the padded transfer length falls on a credit boundary */ |
| creditRemainder = transferLength % target->TargetCreditSize; |
| |
| if (creditRemainder != 0) { |
| /* the transfer consumes a "partial" credit, this packet cannot be bundled unless |
| * we add additional "dummy" padding (max 255 bytes) to consume the entire credit |
| *** NOTE: only allow the send padding if the endpoint is allowed to */ |
| if (pEndpoint->LocalConnectionFlags & HTC_LOCAL_CONN_FLAGS_ENABLE_SEND_BUNDLE_PADDING) { |
| if (transferLength < target->TargetCreditSize) { |
| /* special case where the transfer is less than a credit */ |
| creditPad = target->TargetCreditSize - transferLength; |
| } else { |
| creditPad = creditRemainder; |
| } |
| |
| /* now check to see if we can indicate padding in the HTC header */ |
| if ((creditPad > 0) && (creditPad <= 255)) { |
| /* adjust the transferlength of this packet with the new credit padding */ |
| transferLength += creditPad; |
| } else { |
| /* the amount to pad is too large, bail on this packet, we have to |
| * send it using the non-bundled method */ |
| pPacket = NULL; |
| } |
| } else { |
| /* bail on this packet, user does not want padding applied */ |
| pPacket = NULL; |
| } |
| } |
| |
| if (NULL == pPacket) { |
| /* can't bundle */ |
| done = true; |
| break; |
| } |
| |
| if (scatterSpaceRemaining < transferLength) { |
| /* exceeds what we can transfer */ |
| break; |
| } |
| |
| scatterSpaceRemaining -= transferLength; |
| /* now remove it from the queue */ |
| pPacket = HTC_PACKET_DEQUEUE(pQueue); |
| /* save it in the scatter list */ |
| pScatterReq->ScatterList[i].pCallerContexts[0] = pPacket; |
| /* prepare packet and flag message as part of a send bundle */ |
| HTC_PREPARE_SEND_PKT(pPacket, |
| pPacket->PktInfo.AsTx.SendFlags | HTC_FLAGS_SEND_BUNDLE, |
| creditPad, |
| pPacket->PktInfo.AsTx.SeqNo); |
| pScatterReq->ScatterList[i].pBuffer = pPacket->pBuffer; |
| pScatterReq->ScatterList[i].Length = transferLength; |
| A_ASSERT(transferLength); |
| pScatterReq->TotalLength += transferLength; |
| pScatterReq->ValidScatterEntries++; |
| packetsInScatterReq++; |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" %d, Adding packet : 0x%lX, len:%d (remaining space:%d) \n", |
| i, (unsigned long)pPacket,transferLength,scatterSpaceRemaining)); |
| } |
| |
| if (packetsInScatterReq >= HTC_MIN_HTC_MSGS_TO_BUNDLE) { |
| /* send path is always asynchronous */ |
| pScatterReq->CompletionRoutine = HTCAsyncSendScatterCompletion; |
| pScatterReq->Context = pEndpoint; |
| bundlesSent++; |
| totalPktsInBundle += packetsInScatterReq; |
| packetsInScatterReq = 0; |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" Send Scatter total bytes: %d , entries: %d\n", |
| pScatterReq->TotalLength,pScatterReq->ValidScatterEntries)); |
| DevSubmitScatterRequest(&target->Device, pScatterReq, DEV_SCATTER_WRITE, DEV_SCATTER_ASYNC); |
| /* we don't own this anymore */ |
| pScatterReq = NULL; |
| /* try to send some more */ |
| continue; |
| } |
| |
| /* not enough packets to use the scatter request, cleanup */ |
| if (pScatterReq != NULL) { |
| if (packetsInScatterReq > 0) { |
| /* work backwards to requeue requests */ |
| for (i = (packetsInScatterReq - 1); i >= 0; i--) { |
| pPacket = (struct htc_packet *)(pScatterReq->ScatterList[i].pCallerContexts[0]); |
| if (pPacket != NULL) { |
| /* undo any prep */ |
| HTC_UNPREPARE_SEND_PKT(pPacket); |
| /* queue back to the head */ |
| HTC_PACKET_ENQUEUE_TO_HEAD(pQueue,pPacket); |
| } |
| } |
| } |
| DEV_FREE_SCATTER_REQ(&target->Device,pScatterReq); |
| } |
| |
| /* if we get here, we sent all that we could, get out */ |
| break; |
| |
| } |
| |
| *pBundlesSent = bundlesSent; |
| *pTotalBundlesPkts = totalPktsInBundle; |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("-HTCIssueSendBundle (sent:%d) \n",bundlesSent)); |
| |
| return; |
| } |
| |
| /* |
| * if there are no credits, the packet(s) remains in the queue. |
| * this function returns the result of the attempt to send a queue of HTC packets */ |
| static HTC_SEND_QUEUE_RESULT HTCTrySend(HTC_TARGET *target, |
| struct htc_endpoint *pEndpoint, |
| HTC_PACKET_QUEUE *pCallersSendQueue) |
| { |
| HTC_PACKET_QUEUE sendQueue; /* temp queue to hold packets at various stages */ |
| struct htc_packet *pPacket; |
| int bundlesSent; |
| int pktsInBundles; |
| int overflow; |
| HTC_SEND_QUEUE_RESULT result = HTC_SEND_QUEUE_OK; |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("+HTCTrySend (Queue:0x%lX Depth:%d)\n", |
| (unsigned long)pCallersSendQueue, |
| (pCallersSendQueue == NULL) ? 0 : HTC_PACKET_QUEUE_DEPTH(pCallersSendQueue))); |
| |
| /* init the local send queue */ |
| INIT_HTC_PACKET_QUEUE(&sendQueue); |
| |
| do { |
| |
| if (NULL == pCallersSendQueue) { |
| /* caller didn't provide a queue, just wants us to check queues and send */ |
| break; |
| } |
| |
| if (HTC_QUEUE_EMPTY(pCallersSendQueue)) { |
| /* empty queue */ |
| result = HTC_SEND_QUEUE_DROP; |
| break; |
| } |
| |
| if (HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue) >= pEndpoint->MaxTxQueueDepth) { |
| /* we've already overflowed */ |
| overflow = HTC_PACKET_QUEUE_DEPTH(pCallersSendQueue); |
| } else { |
| /* figure out how much we will overflow by */ |
| overflow = HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue); |
| overflow += HTC_PACKET_QUEUE_DEPTH(pCallersSendQueue); |
| /* figure out how much we will overflow the TX queue by */ |
| overflow -= pEndpoint->MaxTxQueueDepth; |
| } |
| |
| /* if overflow is negative or zero, we are okay */ |
| if (overflow > 0) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, |
| (" Endpoint %d, TX queue will overflow :%d , Tx Depth:%d, Max:%d \n", |
| pEndpoint->Id, overflow, HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue), pEndpoint->MaxTxQueueDepth)); |
| } |
| if ((overflow <= 0) || (pEndpoint->EpCallBacks.EpSendFull == NULL)) { |
| /* all packets will fit or caller did not provide send full indication handler |
| * -- just move all of them to the local sendQueue object */ |
| HTC_PACKET_QUEUE_TRANSFER_TO_TAIL(&sendQueue, pCallersSendQueue); |
| } else { |
| int i; |
| int goodPkts = HTC_PACKET_QUEUE_DEPTH(pCallersSendQueue) - overflow; |
| |
| A_ASSERT(goodPkts >= 0); |
| /* we have overflowed, and a callback is provided */ |
| /* dequeue all non-overflow packets into the sendqueue */ |
| for (i = 0; i < goodPkts; i++) { |
| /* pop off caller's queue*/ |
| pPacket = HTC_PACKET_DEQUEUE(pCallersSendQueue); |
| A_ASSERT(pPacket != NULL); |
| /* insert into local queue */ |
| HTC_PACKET_ENQUEUE(&sendQueue,pPacket); |
| } |
| |
| /* the caller's queue has all the packets that won't fit*/ |
| /* walk through the caller's queue and indicate each one to the send full handler */ |
| ITERATE_OVER_LIST_ALLOW_REMOVE(&pCallersSendQueue->QueueHead, pPacket, struct htc_packet, ListLink) { |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, (" Indicating overflowed TX packet: 0x%lX \n", |
| (unsigned long)pPacket)); |
| if (pEndpoint->EpCallBacks.EpSendFull(pEndpoint->EpCallBacks.pContext, |
| pPacket) == HTC_SEND_FULL_DROP) { |
| /* callback wants the packet dropped */ |
| INC_HTC_EP_STAT(pEndpoint, TxDropped, 1); |
| /* leave this one in the caller's queue for cleanup */ |
| } else { |
| /* callback wants to keep this packet, remove from caller's queue */ |
| HTC_PACKET_REMOVE(pCallersSendQueue, pPacket); |
| /* put it in the send queue */ |
| HTC_PACKET_ENQUEUE(&sendQueue,pPacket); |
| } |
| |
| } ITERATE_END; |
| |
| if (HTC_QUEUE_EMPTY(&sendQueue)) { |
| /* no packets made it in, caller will cleanup */ |
| result = HTC_SEND_QUEUE_DROP; |
| break; |
| } |
| } |
| |
| } while (false); |
| |
| if (result != HTC_SEND_QUEUE_OK) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("-HTCTrySend: \n")); |
| return result; |
| } |
| |
| LOCK_HTC_TX(target); |
| |
| if (!HTC_QUEUE_EMPTY(&sendQueue)) { |
| /* transfer packets */ |
| HTC_PACKET_QUEUE_TRANSFER_TO_TAIL(&pEndpoint->TxQueue,&sendQueue); |
| A_ASSERT(HTC_QUEUE_EMPTY(&sendQueue)); |
| INIT_HTC_PACKET_QUEUE(&sendQueue); |
| } |
| |
| /* increment tx processing count on entry */ |
| pEndpoint->TxProcessCount++; |
| if (pEndpoint->TxProcessCount > 1) { |
| /* another thread or task is draining the TX queues on this endpoint |
| * that thread will reset the tx processing count when the queue is drained */ |
| pEndpoint->TxProcessCount--; |
| UNLOCK_HTC_TX(target); |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("-HTCTrySend (busy) \n")); |
| return HTC_SEND_QUEUE_OK; |
| } |
| |
| /***** beyond this point only 1 thread may enter ******/ |
| |
| /* now drain the endpoint TX queue for transmission as long as we have enough |
| * credits */ |
| while (true) { |
| |
| if (HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue) == 0) { |
| break; |
| } |
| |
| /* get all the packets for this endpoint that we can for this pass */ |
| GetHTCSendPackets(target, pEndpoint, &sendQueue); |
| |
| if (HTC_PACKET_QUEUE_DEPTH(&sendQueue) == 0) { |
| /* didn't get any packets due to a lack of credits */ |
| break; |
| } |
| |
| UNLOCK_HTC_TX(target); |
| |
| /* any packets to send are now in our local send queue */ |
| |
| bundlesSent = 0; |
| pktsInBundles = 0; |
| |
| while (true) { |
| |
| /* try to send a bundle on each pass */ |
| if ((target->SendBundlingEnabled) && |
| (HTC_PACKET_QUEUE_DEPTH(&sendQueue) >= HTC_MIN_HTC_MSGS_TO_BUNDLE)) { |
| int temp1,temp2; |
| /* bundling is enabled and there is at least a minimum number of packets in the send queue |
| * send what we can in this pass */ |
| HTCIssueSendBundle(pEndpoint, &sendQueue, &temp1, &temp2); |
| bundlesSent += temp1; |
| pktsInBundles += temp2; |
| } |
| |
| /* if not bundling or there was a packet that could not be placed in a bundle, pull it out |
| * and send it the normal way */ |
| pPacket = HTC_PACKET_DEQUEUE(&sendQueue); |
| if (NULL == pPacket) { |
| /* local queue is fully drained */ |
| break; |
| } |
| HTC_PREPARE_SEND_PKT(pPacket, |
| pPacket->PktInfo.AsTx.SendFlags, |
| 0, |
| pPacket->PktInfo.AsTx.SeqNo); |
| HTCIssueSend(target, pPacket); |
| |
| /* go back and see if we can bundle some more */ |
| } |
| |
| LOCK_HTC_TX(target); |
| |
| INC_HTC_EP_STAT(pEndpoint, TxBundles, bundlesSent); |
| INC_HTC_EP_STAT(pEndpoint, TxPacketsBundled, pktsInBundles); |
| |
| } |
| |
| /* done with this endpoint, we can clear the count */ |
| pEndpoint->TxProcessCount = 0; |
| UNLOCK_HTC_TX(target); |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("-HTCTrySend: \n")); |
| |
| return HTC_SEND_QUEUE_OK; |
| } |
| |
| int HTCSendPktsMultiple(HTC_HANDLE HTCHandle, HTC_PACKET_QUEUE *pPktQueue) |
| { |
| HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| struct htc_endpoint *pEndpoint; |
| struct htc_packet *pPacket; |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("+HTCSendPktsMultiple: Queue: 0x%lX, Pkts %d \n", |
| (unsigned long)pPktQueue, HTC_PACKET_QUEUE_DEPTH(pPktQueue))); |
| |
| /* get packet at head to figure out which endpoint these packets will go into */ |
| pPacket = HTC_GET_PKT_AT_HEAD(pPktQueue); |
| if (NULL == pPacket) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("-HTCSendPktsMultiple \n")); |
| return A_EINVAL; |
| } |
| |
| AR_DEBUG_ASSERT(pPacket->Endpoint < ENDPOINT_MAX); |
| pEndpoint = &target->EndPoint[pPacket->Endpoint]; |
| |
| HTCTrySend(target, pEndpoint, pPktQueue); |
| |
| /* do completion on any packets that couldn't get in */ |
| if (!HTC_QUEUE_EMPTY(pPktQueue)) { |
| |
| HTC_PACKET_QUEUE_ITERATE_ALLOW_REMOVE(pPktQueue,pPacket) { |
| if (HTC_STOPPING(target)) { |
| pPacket->Status = A_ECANCELED; |
| } else { |
| pPacket->Status = A_NO_RESOURCE; |
| } |
| } HTC_PACKET_QUEUE_ITERATE_END; |
| |
| DO_EP_TX_COMPLETION(pEndpoint,pPktQueue); |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("-HTCSendPktsMultiple \n")); |
| |
| return 0; |
| } |
| |
| /* HTC API - HTCSendPkt */ |
| int HTCSendPkt(HTC_HANDLE HTCHandle, struct htc_packet *pPacket) |
| { |
| HTC_PACKET_QUEUE queue; |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, |
| ("+-HTCSendPkt: Enter endPointId: %d, buffer: 0x%lX, length: %d \n", |
| pPacket->Endpoint, (unsigned long)pPacket->pBuffer, pPacket->ActualLength)); |
| INIT_HTC_PACKET_QUEUE_AND_ADD(&queue,pPacket); |
| return HTCSendPktsMultiple(HTCHandle, &queue); |
| } |
| |
| /* check TX queues to drain because of credit distribution update */ |
| static INLINE void HTCCheckEndpointTxQueues(HTC_TARGET *target) |
| { |
| struct htc_endpoint *pEndpoint; |
| struct htc_endpoint_credit_dist *pDistItem; |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("+HTCCheckEndpointTxQueues \n")); |
| pDistItem = target->EpCreditDistributionListHead; |
| |
| /* run through the credit distribution list to see |
| * if there are packets queued |
| * NOTE: no locks need to be taken since the distribution list |
| * is not dynamic (cannot be re-ordered) and we are not modifying any state */ |
| while (pDistItem != NULL) { |
| pEndpoint = (struct htc_endpoint *)pDistItem->pHTCReserved; |
| |
| if (HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue) > 0) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, (" Ep %d has %d credits and %d Packets in TX Queue \n", |
| pDistItem->Endpoint, pEndpoint->CreditDist.TxCredits, HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue))); |
| /* try to start the stalled queue, this list is ordered by priority. |
| * Highest priority queue get's processed first, if there are credits available the |
| * highest priority queue will get a chance to reclaim credits from lower priority |
| * ones */ |
| HTCTrySend(target, pEndpoint, NULL); |
| } |
| |
| pDistItem = pDistItem->pNext; |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("-HTCCheckEndpointTxQueues \n")); |
| } |
| |
| /* process credit reports and call distribution function */ |
| void HTCProcessCreditRpt(HTC_TARGET *target, HTC_CREDIT_REPORT *pRpt, int NumEntries, HTC_ENDPOINT_ID FromEndpoint) |
| { |
| int i; |
| struct htc_endpoint *pEndpoint; |
| int totalCredits = 0; |
| bool doDist = false; |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("+HTCProcessCreditRpt, Credit Report Entries:%d \n", NumEntries)); |
| |
| /* lock out TX while we update credits */ |
| LOCK_HTC_TX(target); |
| |
| for (i = 0; i < NumEntries; i++, pRpt++) { |
| if (pRpt->EndpointID >= ENDPOINT_MAX) { |
| AR_DEBUG_ASSERT(false); |
| break; |
| } |
| |
| pEndpoint = &target->EndPoint[pRpt->EndpointID]; |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, (" Endpoint %d got %d credits \n", |
| pRpt->EndpointID, pRpt->Credits)); |
| |
| |
| #ifdef HTC_EP_STAT_PROFILING |
| |
| INC_HTC_EP_STAT(pEndpoint, TxCreditRpts, 1); |
| INC_HTC_EP_STAT(pEndpoint, TxCreditsReturned, pRpt->Credits); |
| |
| if (FromEndpoint == pRpt->EndpointID) { |
| /* this credit report arrived on the same endpoint indicating it arrived in an RX |
| * packet */ |
| INC_HTC_EP_STAT(pEndpoint, TxCreditsFromRx, pRpt->Credits); |
| INC_HTC_EP_STAT(pEndpoint, TxCreditRptsFromRx, 1); |
| } else if (FromEndpoint == ENDPOINT_0) { |
| /* this credit arrived on endpoint 0 as a NULL message */ |
| INC_HTC_EP_STAT(pEndpoint, TxCreditsFromEp0, pRpt->Credits); |
| INC_HTC_EP_STAT(pEndpoint, TxCreditRptsFromEp0, 1); |
| } else { |
| /* arrived on another endpoint */ |
| INC_HTC_EP_STAT(pEndpoint, TxCreditsFromOther, pRpt->Credits); |
| INC_HTC_EP_STAT(pEndpoint, TxCreditRptsFromOther, 1); |
| } |
| |
| #endif |
| |
| if (ENDPOINT_0 == pRpt->EndpointID) { |
| /* always give endpoint 0 credits back */ |
| pEndpoint->CreditDist.TxCredits += pRpt->Credits; |
| } else { |
| /* for all other endpoints, update credits to distribute, the distribution function |
| * will handle giving out credits back to the endpoints */ |
| pEndpoint->CreditDist.TxCreditsToDist += pRpt->Credits; |
| /* flag that we have to do the distribution */ |
| doDist = true; |
| } |
| |
| /* refresh tx depth for distribution function that will recover these credits |
| * NOTE: this is only valid when there are credits to recover! */ |
| pEndpoint->CreditDist.TxQueueDepth = HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue); |
| |
| totalCredits += pRpt->Credits; |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, (" Report indicated %d credits to distribute \n", totalCredits)); |
| |
| if (doDist) { |
| /* this was a credit return based on a completed send operations |
| * note, this is done with the lock held */ |
| DO_DISTRIBUTION(target, |
| HTC_CREDIT_DIST_SEND_COMPLETE, |
| "Send Complete", |
| target->EpCreditDistributionListHead->pNext); |
| } |
| |
| UNLOCK_HTC_TX(target); |
| |
| if (totalCredits) { |
| HTCCheckEndpointTxQueues(target); |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("-HTCProcessCreditRpt \n")); |
| } |
| |
| /* flush endpoint TX queue */ |
| static void HTCFlushEndpointTX(HTC_TARGET *target, struct htc_endpoint *pEndpoint, HTC_TX_TAG Tag) |
| { |
| struct htc_packet *pPacket; |
| HTC_PACKET_QUEUE discardQueue; |
| HTC_PACKET_QUEUE container; |
| |
| /* initialize the discard queue */ |
| INIT_HTC_PACKET_QUEUE(&discardQueue); |
| |
| LOCK_HTC_TX(target); |
| |
| /* interate from the front of the TX queue and flush out packets */ |
| ITERATE_OVER_LIST_ALLOW_REMOVE(&pEndpoint->TxQueue.QueueHead, pPacket, struct htc_packet, ListLink) { |
| |
| /* check for removal */ |
| if ((HTC_TX_PACKET_TAG_ALL == Tag) || (Tag == pPacket->PktInfo.AsTx.Tag)) { |
| /* remove from queue */ |
| HTC_PACKET_REMOVE(&pEndpoint->TxQueue, pPacket); |
| /* add it to the discard pile */ |
| HTC_PACKET_ENQUEUE(&discardQueue, pPacket); |
| } |
| |
| } ITERATE_END; |
| |
| UNLOCK_HTC_TX(target); |
| |
| /* empty the discard queue */ |
| while (1) { |
| pPacket = HTC_PACKET_DEQUEUE(&discardQueue); |
| if (NULL == pPacket) { |
| break; |
| } |
| pPacket->Status = A_ECANCELED; |
| AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" Flushing TX packet:0x%lX, length:%d, ep:%d tag:0x%X \n", |
| (unsigned long)pPacket, pPacket->ActualLength, pPacket->Endpoint, pPacket->PktInfo.AsTx.Tag)); |
| INIT_HTC_PACKET_QUEUE_AND_ADD(&container,pPacket); |
| DO_EP_TX_COMPLETION(pEndpoint,&container); |
| } |
| |
| } |
| |
| void DumpCreditDist(struct htc_endpoint_credit_dist *pEPDist) |
| { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("--- EP : %d ServiceID: 0x%X --------------\n", |
| pEPDist->Endpoint, pEPDist->ServiceID)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" this:0x%lX next:0x%lX prev:0x%lX\n", |
| (unsigned long)pEPDist, (unsigned long)pEPDist->pNext, (unsigned long)pEPDist->pPrev)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" DistFlags : 0x%X \n", pEPDist->DistFlags)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsNorm : %d \n", pEPDist->TxCreditsNorm)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsMin : %d \n", pEPDist->TxCreditsMin)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCredits : %d \n", pEPDist->TxCredits)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsAssigned : %d \n", pEPDist->TxCreditsAssigned)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsSeek : %d \n", pEPDist->TxCreditsSeek)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditSize : %d \n", pEPDist->TxCreditSize)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsPerMaxMsg : %d \n", pEPDist->TxCreditsPerMaxMsg)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsToDist : %d \n", pEPDist->TxCreditsToDist)); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxQueueDepth : %d \n", |
| HTC_PACKET_QUEUE_DEPTH(&((struct htc_endpoint *)pEPDist->pHTCReserved)->TxQueue))); |
| AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("----------------------------------------------------\n")); |
| } |
| |
| void DumpCreditDistStates(HTC_TARGET *target) |
| { |
| struct htc_endpoint_credit_dist *pEPList = target->EpCreditDistributionListHead; |
| |
| while (pEPList != NULL) { |
| DumpCreditDist(pEPList); |
| pEPList = pEPList->pNext; |
| } |
| |
| if (target->DistributeCredits != NULL) { |
| DO_DISTRIBUTION(target, |
| HTC_DUMP_CREDIT_STATE, |
| "Dump State", |
| NULL); |
| } |
| } |
| |
| /* flush all send packets from all endpoint queues */ |
| void HTCFlushSendPkts(HTC_TARGET *target) |
| { |
| struct htc_endpoint *pEndpoint; |
| int i; |
| |
| if (AR_DEBUG_LVL_CHECK(ATH_DEBUG_TRC)) { |
| DumpCreditDistStates(target); |
| } |
| |
| for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) { |
| pEndpoint = &target->EndPoint[i]; |
| if (pEndpoint->ServiceID == 0) { |
| /* not in use.. */ |
| continue; |
| } |
| HTCFlushEndpointTX(target,pEndpoint,HTC_TX_PACKET_TAG_ALL); |
| } |
| |
| |
| } |
| |
| /* HTC API to flush an endpoint's TX queue*/ |
| void HTCFlushEndpoint(HTC_HANDLE HTCHandle, HTC_ENDPOINT_ID Endpoint, HTC_TX_TAG Tag) |
| { |
| HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| struct htc_endpoint *pEndpoint = &target->EndPoint[Endpoint]; |
| |
| if (pEndpoint->ServiceID == 0) { |
| AR_DEBUG_ASSERT(false); |
| /* not in use.. */ |
| return; |
| } |
| |
| HTCFlushEndpointTX(target, pEndpoint, Tag); |
| } |
| |
| /* HTC API to indicate activity to the credit distribution function */ |
| void HTCIndicateActivityChange(HTC_HANDLE HTCHandle, |
| HTC_ENDPOINT_ID Endpoint, |
| bool Active) |
| { |
| HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| struct htc_endpoint *pEndpoint = &target->EndPoint[Endpoint]; |
| bool doDist = false; |
| |
| if (pEndpoint->ServiceID == 0) { |
| AR_DEBUG_ASSERT(false); |
| /* not in use.. */ |
| return; |
| } |
| |
| LOCK_HTC_TX(target); |
| |
| if (Active) { |
| if (!(pEndpoint->CreditDist.DistFlags & HTC_EP_ACTIVE)) { |
| /* mark active now */ |
| pEndpoint->CreditDist.DistFlags |= HTC_EP_ACTIVE; |
| doDist = true; |
| } |
| } else { |
| if (pEndpoint->CreditDist.DistFlags & HTC_EP_ACTIVE) { |
| /* mark inactive now */ |
| pEndpoint->CreditDist.DistFlags &= ~HTC_EP_ACTIVE; |
| doDist = true; |
| } |
| } |
| |
| if (doDist) { |
| /* indicate current Tx Queue depth to the credit distribution function */ |
| pEndpoint->CreditDist.TxQueueDepth = HTC_PACKET_QUEUE_DEPTH(&pEndpoint->TxQueue); |
| /* do distribution again based on activity change |
| * note, this is done with the lock held */ |
| DO_DISTRIBUTION(target, |
| HTC_CREDIT_DIST_ACTIVITY_CHANGE, |
| "Activity Change", |
| target->EpCreditDistributionListHead->pNext); |
| } |
| |
| UNLOCK_HTC_TX(target); |
| |
| if (doDist && !Active) { |
| /* if a stream went inactive and this resulted in a credit distribution change, |
| * some credits may now be available for HTC packets that are stuck in |
| * HTC queues */ |
| HTCCheckEndpointTxQueues(target); |
| } |
| } |
| |
| bool HTCIsEndpointActive(HTC_HANDLE HTCHandle, |
| HTC_ENDPOINT_ID Endpoint) |
| { |
| HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| struct htc_endpoint *pEndpoint = &target->EndPoint[Endpoint]; |
| |
| if (pEndpoint->ServiceID == 0) { |
| return false; |
| } |
| |
| if (pEndpoint->CreditDist.DistFlags & HTC_EP_ACTIVE) { |
| return true; |
| } |
| |
| return false; |
| } |