| //------------------------------------------------------------------------------ |
| // <copyright file="credit_dist.c" company="Atheros"> |
| // Copyright (c) 2004-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 "a_config.h" |
| #include "athdefs.h" |
| #include "a_types.h" |
| #include "a_osapi.h" |
| #define ATH_MODULE_NAME misc |
| #include "a_debug.h" |
| #include "htc_api.h" |
| #include "common_drv.h" |
| |
| /********* CREDIT DISTRIBUTION FUNCTIONS ******************************************/ |
| |
| #define NO_VO_SERVICE 1 /* currently WMI only uses 3 data streams, so we leave VO service inactive */ |
| #define CONFIG_GIVE_LOW_PRIORITY_STREAMS_MIN_CREDITS 1 |
| |
| #ifdef NO_VO_SERVICE |
| #define DATA_SVCS_USED 3 |
| #else |
| #define DATA_SVCS_USED 4 |
| #endif |
| |
| static void RedistributeCredits(COMMON_CREDIT_STATE_INFO *pCredInfo, |
| HTC_ENDPOINT_CREDIT_DIST *pEPDistList); |
| |
| static void SeekCredits(COMMON_CREDIT_STATE_INFO *pCredInfo, |
| HTC_ENDPOINT_CREDIT_DIST *pEPDistList); |
| |
| /* reduce an ep's credits back to a set limit */ |
| static INLINE void ReduceCredits(COMMON_CREDIT_STATE_INFO *pCredInfo, |
| HTC_ENDPOINT_CREDIT_DIST *pEpDist, |
| int Limit) |
| { |
| int credits; |
| |
| /* set the new limit */ |
| pEpDist->TxCreditsAssigned = Limit; |
| |
| if (pEpDist->TxCredits <= Limit) { |
| return; |
| } |
| |
| /* figure out how much to take away */ |
| credits = pEpDist->TxCredits - Limit; |
| /* take them away */ |
| pEpDist->TxCredits -= credits; |
| pCredInfo->CurrentFreeCredits += credits; |
| } |
| |
| /* give an endpoint some credits from the free credit pool */ |
| #define GiveCredits(pCredInfo,pEpDist,credits) \ |
| { \ |
| (pEpDist)->TxCredits += (credits); \ |
| (pEpDist)->TxCreditsAssigned += (credits); \ |
| (pCredInfo)->CurrentFreeCredits -= (credits); \ |
| } |
| |
| |
| /* default credit init callback. |
| * This function is called in the context of HTCStart() to setup initial (application-specific) |
| * credit distributions */ |
| static void ar6000_credit_init(void *Context, |
| HTC_ENDPOINT_CREDIT_DIST *pEPList, |
| int TotalCredits) |
| { |
| HTC_ENDPOINT_CREDIT_DIST *pCurEpDist; |
| int count; |
| COMMON_CREDIT_STATE_INFO *pCredInfo = (COMMON_CREDIT_STATE_INFO *)Context; |
| |
| pCredInfo->CurrentFreeCredits = TotalCredits; |
| pCredInfo->TotalAvailableCredits = TotalCredits; |
| |
| pCurEpDist = pEPList; |
| |
| /* run through the list and initialize */ |
| while (pCurEpDist != NULL) { |
| |
| /* set minimums for each endpoint */ |
| pCurEpDist->TxCreditsMin = pCurEpDist->TxCreditsPerMaxMsg; |
| |
| #ifdef CONFIG_GIVE_LOW_PRIORITY_STREAMS_MIN_CREDITS |
| |
| if (TotalCredits > 4) |
| { |
| if ((pCurEpDist->ServiceID == WMI_DATA_BK_SVC) || (pCurEpDist->ServiceID == WMI_DATA_BE_SVC)){ |
| /* assign at least min credits to lower than VO priority services */ |
| GiveCredits(pCredInfo,pCurEpDist,pCurEpDist->TxCreditsMin); |
| /* force active */ |
| SET_EP_ACTIVE(pCurEpDist); |
| } |
| } |
| |
| #endif |
| |
| if (pCurEpDist->ServiceID == WMI_CONTROL_SVC) { |
| /* give control service some credits */ |
| GiveCredits(pCredInfo,pCurEpDist,pCurEpDist->TxCreditsMin); |
| /* control service is always marked active, it never goes inactive EVER */ |
| SET_EP_ACTIVE(pCurEpDist); |
| } else if (pCurEpDist->ServiceID == WMI_DATA_BK_SVC) { |
| /* this is the lowest priority data endpoint, save this off for easy access */ |
| pCredInfo->pLowestPriEpDist = pCurEpDist; |
| } |
| |
| /* Streams have to be created (explicit | implicit)for all kinds |
| * of traffic. BE endpoints are also inactive in the beginning. |
| * When BE traffic starts it creates implicit streams that |
| * redistributes credits. |
| */ |
| |
| /* note, all other endpoints have minimums set but are initially given NO credits. |
| * Credits will be distributed as traffic activity demands */ |
| pCurEpDist = pCurEpDist->pNext; |
| } |
| |
| if (pCredInfo->CurrentFreeCredits <= 0) { |
| AR_DEBUG_PRINTF(ATH_LOG_INF, ("Not enough credits (%d) to do credit distributions \n", TotalCredits)); |
| A_ASSERT(FALSE); |
| return; |
| } |
| |
| /* reset list */ |
| pCurEpDist = pEPList; |
| /* now run through the list and set max operating credit limits for everyone */ |
| while (pCurEpDist != NULL) { |
| if (pCurEpDist->ServiceID == WMI_CONTROL_SVC) { |
| /* control service max is just 1 max message */ |
| pCurEpDist->TxCreditsNorm = pCurEpDist->TxCreditsPerMaxMsg; |
| } else { |
| /* for the remaining data endpoints, we assume that each TxCreditsPerMaxMsg are |
| * the same. |
| * We use a simple calculation here, we take the remaining credits and |
| * determine how many max messages this can cover and then set each endpoint's |
| * normal value equal to 3/4 this amount. |
| * */ |
| count = (pCredInfo->CurrentFreeCredits/pCurEpDist->TxCreditsPerMaxMsg) * pCurEpDist->TxCreditsPerMaxMsg; |
| count = (count * 3) >> 2; |
| count = max(count,pCurEpDist->TxCreditsPerMaxMsg); |
| /* set normal */ |
| pCurEpDist->TxCreditsNorm = count; |
| |
| } |
| pCurEpDist = pCurEpDist->pNext; |
| } |
| |
| } |
| |
| |
| /* default credit distribution callback |
| * This callback is invoked whenever endpoints require credit distributions. |
| * A lock is held while this function is invoked, this function shall NOT block. |
| * The pEPDistList is a list of distribution structures in prioritized order as |
| * defined by the call to the HTCSetCreditDistribution() api. |
| * |
| */ |
| static void ar6000_credit_distribute(void *Context, |
| HTC_ENDPOINT_CREDIT_DIST *pEPDistList, |
| HTC_CREDIT_DIST_REASON Reason) |
| { |
| HTC_ENDPOINT_CREDIT_DIST *pCurEpDist; |
| COMMON_CREDIT_STATE_INFO *pCredInfo = (COMMON_CREDIT_STATE_INFO *)Context; |
| |
| switch (Reason) { |
| case HTC_CREDIT_DIST_SEND_COMPLETE : |
| pCurEpDist = pEPDistList; |
| /* we are given the start of the endpoint distribution list. |
| * There may be one or more endpoints to service. |
| * Run through the list and distribute credits */ |
| while (pCurEpDist != NULL) { |
| |
| if (pCurEpDist->TxCreditsToDist > 0) { |
| /* return the credits back to the endpoint */ |
| pCurEpDist->TxCredits += pCurEpDist->TxCreditsToDist; |
| /* always zero out when we are done */ |
| pCurEpDist->TxCreditsToDist = 0; |
| |
| if (pCurEpDist->TxCredits > pCurEpDist->TxCreditsAssigned) { |
| /* reduce to the assigned limit, previous credit reductions |
| * could have caused the limit to change */ |
| ReduceCredits(pCredInfo, pCurEpDist, pCurEpDist->TxCreditsAssigned); |
| } |
| |
| if (pCurEpDist->TxCredits > pCurEpDist->TxCreditsNorm) { |
| /* oversubscribed endpoints need to reduce back to normal */ |
| ReduceCredits(pCredInfo, pCurEpDist, pCurEpDist->TxCreditsNorm); |
| } |
| |
| if (!IS_EP_ACTIVE(pCurEpDist)) { |
| /* endpoint is inactive, now check for messages waiting for credits */ |
| if (pCurEpDist->TxQueueDepth == 0) { |
| /* EP is inactive and there are no pending messages, |
| * reduce credits back to zero to recover credits */ |
| ReduceCredits(pCredInfo, pCurEpDist, 0); |
| } |
| } |
| } |
| |
| pCurEpDist = pCurEpDist->pNext; |
| } |
| |
| break; |
| |
| case HTC_CREDIT_DIST_ACTIVITY_CHANGE : |
| RedistributeCredits(pCredInfo,pEPDistList); |
| break; |
| case HTC_CREDIT_DIST_SEEK_CREDITS : |
| SeekCredits(pCredInfo,pEPDistList); |
| break; |
| case HTC_DUMP_CREDIT_STATE : |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Credit Distribution, total : %d, free : %d\n", |
| pCredInfo->TotalAvailableCredits, pCredInfo->CurrentFreeCredits)); |
| break; |
| default: |
| break; |
| |
| } |
| |
| /* sanity checks done after each distribution action */ |
| A_ASSERT(pCredInfo->CurrentFreeCredits <= pCredInfo->TotalAvailableCredits); |
| A_ASSERT(pCredInfo->CurrentFreeCredits >= 0); |
| |
| } |
| |
| /* redistribute credits based on activity change */ |
| static void RedistributeCredits(COMMON_CREDIT_STATE_INFO *pCredInfo, |
| HTC_ENDPOINT_CREDIT_DIST *pEPDistList) |
| { |
| HTC_ENDPOINT_CREDIT_DIST *pCurEpDist = pEPDistList; |
| |
| /* walk through the list and remove credits from inactive endpoints */ |
| while (pCurEpDist != NULL) { |
| |
| #ifdef CONFIG_GIVE_LOW_PRIORITY_STREAMS_MIN_CREDITS |
| |
| if ((pCurEpDist->ServiceID == WMI_DATA_BK_SVC) || (pCurEpDist->ServiceID == WMI_DATA_BE_SVC)) { |
| /* force low priority streams to always be active to retain their minimum credit distribution */ |
| SET_EP_ACTIVE(pCurEpDist); |
| } |
| #endif |
| |
| if (pCurEpDist->ServiceID != WMI_CONTROL_SVC) { |
| if (!IS_EP_ACTIVE(pCurEpDist)) { |
| if (pCurEpDist->TxQueueDepth == 0) { |
| /* EP is inactive and there are no pending messages, reduce credits back to zero */ |
| ReduceCredits(pCredInfo, pCurEpDist, 0); |
| } else { |
| /* we cannot zero the credits assigned to this EP, but to keep |
| * the credits available for these leftover packets, reduce to |
| * a minimum */ |
| ReduceCredits(pCredInfo, pCurEpDist, pCurEpDist->TxCreditsMin); |
| } |
| } |
| } |
| |
| /* NOTE in the active case, we do not need to do anything further, |
| * when an EP goes active and needs credits, HTC will call into |
| * our distribution function using a reason code of HTC_CREDIT_DIST_SEEK_CREDITS */ |
| |
| pCurEpDist = pCurEpDist->pNext; |
| } |
| |
| } |
| |
| /* HTC has an endpoint that needs credits, pEPDist is the endpoint in question */ |
| static void SeekCredits(COMMON_CREDIT_STATE_INFO *pCredInfo, |
| HTC_ENDPOINT_CREDIT_DIST *pEPDist) |
| { |
| HTC_ENDPOINT_CREDIT_DIST *pCurEpDist; |
| int credits = 0; |
| int need; |
| |
| do { |
| |
| if (pEPDist->ServiceID == WMI_CONTROL_SVC) { |
| /* we never oversubscribe on the control service, this is not |
| * a high performance path and the target never holds onto control |
| * credits for too long */ |
| break; |
| } |
| |
| #ifdef CONFIG_GIVE_LOW_PRIORITY_STREAMS_MIN_CREDITS |
| if (pEPDist->ServiceID == WMI_DATA_VI_SVC) { |
| if ((pEPDist->TxCreditsAssigned >= pEPDist->TxCreditsNorm)) { |
| /* limit VI service from oversubscribing */ |
| break; |
| } |
| } |
| |
| if (pEPDist->ServiceID == WMI_DATA_VO_SVC) { |
| if ((pEPDist->TxCreditsAssigned >= pEPDist->TxCreditsNorm)) { |
| /* limit VO service from oversubscribing */ |
| break; |
| } |
| } |
| #else |
| if (pEPDist->ServiceID == WMI_DATA_VI_SVC) { |
| if ((pEPDist->TxCreditsAssigned >= pEPDist->TxCreditsNorm) || |
| (pCredInfo->CurrentFreeCredits <= pEPDist->TxCreditsPerMaxMsg)) { |
| /* limit VI service from oversubscribing */ |
| /* at least one free credit will not be used by VI */ |
| break; |
| } |
| } |
| |
| if (pEPDist->ServiceID == WMI_DATA_VO_SVC) { |
| if ((pEPDist->TxCreditsAssigned >= pEPDist->TxCreditsNorm) || |
| (pCredInfo->CurrentFreeCredits <= pEPDist->TxCreditsPerMaxMsg)) { |
| /* limit VO service from oversubscribing */ |
| /* at least one free credit will not be used by VO */ |
| break; |
| } |
| } |
| #endif |
| |
| /* for all other services, we follow a simple algorithm of |
| * 1. checking the free pool for credits |
| * 2. checking lower priority endpoints for credits to take */ |
| |
| /* give what we can */ |
| credits = min(pCredInfo->CurrentFreeCredits,pEPDist->TxCreditsSeek); |
| |
| if (credits >= pEPDist->TxCreditsSeek) { |
| /* we found some to fullfill the seek request */ |
| break; |
| } |
| |
| /* we don't have enough in the free pool, try taking away from lower priority services |
| * |
| * The rule for taking away credits: |
| * 1. Only take from lower priority endpoints |
| * 2. Only take what is allocated above the minimum (never starve an endpoint completely) |
| * 3. Only take what you need. |
| * |
| * */ |
| |
| /* starting at the lowest priority */ |
| pCurEpDist = pCredInfo->pLowestPriEpDist; |
| |
| /* work backwards until we hit the endpoint again */ |
| while (pCurEpDist != pEPDist) { |
| /* calculate how many we need so far */ |
| need = pEPDist->TxCreditsSeek - pCredInfo->CurrentFreeCredits; |
| |
| if ((pCurEpDist->TxCreditsAssigned - need) >= pCurEpDist->TxCreditsMin) { |
| /* the current one has been allocated more than it's minimum and it |
| * has enough credits assigned above it's minimum to fullfill our need |
| * try to take away just enough to fullfill our need */ |
| ReduceCredits(pCredInfo, |
| pCurEpDist, |
| pCurEpDist->TxCreditsAssigned - need); |
| |
| if (pCredInfo->CurrentFreeCredits >= pEPDist->TxCreditsSeek) { |
| /* we have enough */ |
| break; |
| } |
| } |
| |
| pCurEpDist = pCurEpDist->pPrev; |
| } |
| |
| /* return what we can get */ |
| credits = min(pCredInfo->CurrentFreeCredits,pEPDist->TxCreditsSeek); |
| |
| } while (FALSE); |
| |
| /* did we find some credits? */ |
| if (credits) { |
| /* give what we can */ |
| GiveCredits(pCredInfo, pEPDist, credits); |
| } |
| |
| } |
| |
| /* initialize and setup credit distribution */ |
| A_STATUS ar6000_setup_credit_dist(HTC_HANDLE HTCHandle, COMMON_CREDIT_STATE_INFO *pCredInfo) |
| { |
| HTC_SERVICE_ID servicepriority[5]; |
| |
| A_MEMZERO(pCredInfo,sizeof(COMMON_CREDIT_STATE_INFO)); |
| |
| servicepriority[0] = WMI_CONTROL_SVC; /* highest */ |
| servicepriority[1] = WMI_DATA_VO_SVC; |
| servicepriority[2] = WMI_DATA_VI_SVC; |
| servicepriority[3] = WMI_DATA_BE_SVC; |
| servicepriority[4] = WMI_DATA_BK_SVC; /* lowest */ |
| |
| /* set callbacks and priority list */ |
| HTCSetCreditDistribution(HTCHandle, |
| pCredInfo, |
| ar6000_credit_distribute, |
| ar6000_credit_init, |
| servicepriority, |
| 5); |
| |
| return A_OK; |
| } |
| |