blob: 520984f12bc8ff2390ca16c2f6d2829eb560c4f0 [file] [log] [blame]
/*
* Copyright (c) 2011-2012, 2014-2017 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.
*/
/*
* DOC: 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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%s: Error!! pList is Null", __func__);
return;
}
if (LIST_FLAG_OPEN == pList->Flag) {
if (fInterlocked)
csr_ll_lock(pList);
/* Remove everything from the list */
while ((pEntry = csr_ll_remove_head(pList, LL_ACCESS_NOLOCK)))
;
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_ERROR,
"%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_ERROR,
"%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_ERROR,
"%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;
}