| /* |
| * Copyright (c) 2011-2012, 2014-2016 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by Qualcomm Atheros, Inc. |
| * |
| * |
| * 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. |
| */ |
| |
| /* |
| * This file was originally distributed by Qualcomm Atheros, Inc. |
| * under proprietary terms before Copyright ownership was assigned |
| * to the Linux Foundation. |
| */ |
| |
| /** ------------------------------------------------------------------------- * |
| ------------------------------------------------------------------------- * |
| \file csr_link_list.c |
| |
| Implementation for the Common link list interfaces. |
| ========================================================================== */ |
| |
| #include "csr_link_list.h" |
| #include "qdf_lock.h" |
| #include "qdf_mem.h" |
| #include "qdf_trace.h" |
| #include "qdf_mc_timer.h" |
| |
| static inline void csr_list_init(tListElem *pList) |
| { |
| pList->last = pList->next = pList; |
| } |
| |
| static inline void csr_list_remove_entry(tListElem *pEntry) |
| { |
| tListElem *pLast; |
| tListElem *pNext; |
| |
| pLast = pEntry->last; |
| pNext = pEntry->next; |
| pLast->next = pNext; |
| pNext->last = pLast; |
| } |
| |
| static inline tListElem *csr_list_remove_head(tListElem *pHead) |
| { |
| tListElem *pEntry; |
| tListElem *pNext; |
| |
| pEntry = pHead->next; |
| pNext = pEntry->next; |
| pHead->next = pNext; |
| pNext->last = pHead; |
| |
| return pEntry; |
| } |
| |
| static inline tListElem *csr_list_remove_tail(tListElem *pHead) |
| { |
| tListElem *pEntry; |
| tListElem *pLast; |
| |
| pEntry = pHead->last; |
| pLast = pEntry->last; |
| pHead->last = pLast; |
| pLast->next = pHead; |
| |
| return pEntry; |
| } |
| |
| static inline void csr_list_insert_tail(tListElem *pHead, tListElem *pEntry) |
| { |
| tListElem *pLast; |
| |
| pLast = pHead->last; |
| pEntry->last = pLast; |
| pEntry->next = pHead; |
| pLast->next = pEntry; |
| pHead->last = pEntry; |
| } |
| |
| static inline void csr_list_insert_head(tListElem *pHead, tListElem *pEntry) |
| { |
| tListElem *pNext; |
| |
| pNext = pHead->next; |
| pEntry->next = pNext; |
| pEntry->last = pHead; |
| pNext->last = pEntry; |
| pHead->next = pEntry; |
| } |
| |
| /* Insert pNewEntry before pEntry */ |
| static void csr_list_insert_entry(tListElem *pEntry, tListElem *pNewEntry) |
| { |
| tListElem *pLast; |
| if (!pEntry) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pEntry is Null", __func__); |
| return; |
| } |
| |
| pLast = pEntry->last; |
| pLast->next = pNewEntry; |
| pEntry->last = pNewEntry; |
| pNewEntry->next = pEntry; |
| pNewEntry->last = pLast; |
| } |
| |
| uint32_t csr_ll_count(tDblLinkList *pList) |
| { |
| uint32_t c = 0; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return c; |
| } |
| |
| if (pList && (LIST_FLAG_OPEN == pList->Flag)) { |
| c = pList->Count; |
| } |
| |
| return c; |
| } |
| |
| void csr_ll_lock(tDblLinkList *pList) |
| { |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| qdf_mutex_acquire(&pList->Lock); |
| } |
| } |
| |
| void csr_ll_unlock(tDblLinkList *pList) |
| { |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| qdf_mutex_release(&pList->Lock); |
| } |
| } |
| |
| bool csr_ll_is_list_empty(tDblLinkList *pList, bool fInterlocked) |
| { |
| bool fEmpty = true; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return fEmpty; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| |
| fEmpty = csrIsListEmpty(&pList->ListHead); |
| |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| return fEmpty; |
| } |
| |
| bool csr_ll_find_entry(tDblLinkList *pList, tListElem *pEntryToFind) |
| { |
| bool fFound = false; |
| tListElem *pEntry; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return fFound; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| pEntry = csr_ll_peek_head(pList, LL_ACCESS_NOLOCK); |
| |
| /* Have to make sure we don't loop back to the head of the list, which will */ |
| /* happen if the entry is NOT on the list... */ |
| |
| while (pEntry && (pEntry != &pList->ListHead)) { |
| if (pEntry == pEntryToFind) { |
| fFound = true; |
| break; |
| } |
| pEntry = pEntry->next; |
| } |
| |
| } |
| return fFound; |
| } |
| |
| QDF_STATUS csr_ll_open(tHddHandle hHdd, tDblLinkList *pList) |
| { |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| QDF_STATUS qdf_status; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| if (LIST_FLAG_OPEN != pList->Flag) { |
| pList->Count = 0; |
| pList->cmdTimeoutTimer = NULL; |
| qdf_status = qdf_mutex_create(&pList->Lock); |
| |
| if (QDF_IS_STATUS_SUCCESS(qdf_status)) { |
| csr_list_init(&pList->ListHead); |
| pList->Flag = LIST_FLAG_OPEN; |
| pList->hHdd = hHdd; |
| } else { |
| status = QDF_STATUS_E_FAILURE; |
| } |
| } |
| return status; |
| } |
| |
| void csr_ll_close(tDblLinkList *pList) |
| { |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| /* Make sure the list is empty... */ |
| csr_ll_purge(pList, LL_ACCESS_LOCK); |
| qdf_mutex_destroy(&pList->Lock); |
| pList->Flag = LIST_FLAG_CLOSE; |
| } |
| } |
| |
| void csr_ll_insert_tail(tDblLinkList *pList, tListElem *pEntry, |
| bool fInterlocked) |
| { |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| csr_list_insert_tail(&pList->ListHead, pEntry); |
| pList->Count++; |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| } |
| |
| void csr_ll_insert_head(tDblLinkList *pList, tListElem *pEntry, |
| bool fInterlocked) |
| { |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| csr_list_insert_head(&pList->ListHead, pEntry); |
| pList->Count++; |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| if (pList->cmdTimeoutTimer && pList->cmdTimeoutDuration) { |
| /* timer to detect pending command in activelist */ |
| qdf_mc_timer_start(pList->cmdTimeoutTimer, |
| pList->cmdTimeoutDuration); |
| } |
| } |
| } |
| |
| void csr_ll_insert_entry(tDblLinkList *pList, tListElem *pEntry, |
| tListElem *pNewEntry, bool fInterlocked) |
| { |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| csr_list_insert_entry(pEntry, pNewEntry); |
| pList->Count++; |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| } |
| |
| tListElem *csr_ll_remove_tail(tDblLinkList *pList, bool fInterlocked) |
| { |
| tListElem *pEntry = NULL; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return pEntry; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| |
| if (!csrIsListEmpty(&pList->ListHead)) { |
| |
| pEntry = csr_list_remove_tail(&pList->ListHead); |
| pList->Count--; |
| } |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| |
| return pEntry; |
| } |
| |
| tListElem *csr_ll_peek_tail(tDblLinkList *pList, bool fInterlocked) |
| { |
| tListElem *pEntry = NULL; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return pEntry; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| |
| if (!csrIsListEmpty(&pList->ListHead)) { |
| pEntry = pList->ListHead.last; |
| } |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| |
| return pEntry; |
| } |
| |
| tListElem *csr_ll_remove_head(tDblLinkList *pList, bool fInterlocked) |
| { |
| tListElem *pEntry = NULL; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return pEntry; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| |
| if (!csrIsListEmpty(&pList->ListHead)) { |
| pEntry = csr_list_remove_head(&pList->ListHead); |
| pList->Count--; |
| } |
| |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| |
| return pEntry; |
| } |
| |
| tListElem *csr_ll_peek_head(tDblLinkList *pList, bool fInterlocked) |
| { |
| tListElem *pEntry = NULL; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return pEntry; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| |
| if (!csrIsListEmpty(&pList->ListHead)) { |
| pEntry = pList->ListHead.next; |
| } |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| |
| return pEntry; |
| } |
| |
| void csr_ll_purge(tDblLinkList *pList, bool fInterlocked) |
| { |
| tListElem *pEntry; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| while ((pEntry = csr_ll_remove_head(pList, LL_ACCESS_NOLOCK))) { |
| /* just remove everything from the list until */ |
| /* nothing left on the list. */ |
| } |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| } |
| |
| bool csr_ll_remove_entry(tDblLinkList *pList, tListElem *pEntryToRemove, |
| bool fInterlocked) |
| { |
| bool fFound = false; |
| tListElem *pEntry; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return fFound; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| |
| pEntry = csr_ll_peek_head(pList, LL_ACCESS_NOLOCK); |
| |
| /* Have to make sure we don't loop back to the head of the list, which will */ |
| /* happen if the entry is NOT on the list... */ |
| while (pEntry && (pEntry != &pList->ListHead)) { |
| if (pEntry == pEntryToRemove) { |
| csr_list_remove_entry(pEntry); |
| pList->Count--; |
| |
| fFound = true; |
| break; |
| } |
| |
| pEntry = pEntry->next; |
| } |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| if (pList->cmdTimeoutTimer) { |
| qdf_mc_timer_stop(pList->cmdTimeoutTimer); |
| } |
| } |
| |
| return fFound; |
| } |
| |
| tListElem *csr_ll_next(tDblLinkList *pList, tListElem *pEntry, |
| bool fInterlocked) |
| { |
| tListElem *pNextEntry = NULL; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return pNextEntry; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| |
| if (!csrIsListEmpty(&pList->ListHead) |
| && csr_ll_find_entry(pList, pEntry)) { |
| pNextEntry = pEntry->next; |
| /* Make sure we don't walk past the head */ |
| if (pNextEntry == &pList->ListHead) { |
| pNextEntry = NULL; |
| } |
| } |
| |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| |
| return pNextEntry; |
| } |
| |
| tListElem *csr_ll_previous(tDblLinkList *pList, tListElem *pEntry, |
| bool fInterlocked) |
| { |
| tListElem *pNextEntry = NULL; |
| |
| if (!pList) { |
| QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_FATAL, |
| "%s: Error!! pList is Null", __func__); |
| return pNextEntry; |
| } |
| |
| if (LIST_FLAG_OPEN == pList->Flag) { |
| if (fInterlocked) { |
| csr_ll_lock(pList); |
| } |
| |
| if (!csrIsListEmpty(&pList->ListHead) |
| && csr_ll_find_entry(pList, pEntry)) { |
| pNextEntry = pEntry->last; |
| /* Make sure we don't walk past the head */ |
| if (pNextEntry == &pList->ListHead) { |
| pNextEntry = NULL; |
| } |
| } |
| |
| if (fInterlocked) { |
| csr_ll_unlock(pList); |
| } |
| } |
| |
| return pNextEntry; |
| } |