blob: 10e16d82b2b59b32f5da4e6ce25e5d13f023b166 [file] [log] [blame]
/*
* Copyright (c) 2018 The Linux Foundation. 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.
*/
#include "qdf_list.h"
#include "qdf_mem.h"
#include "qdf_status.h"
#include "qdf_str.h"
#include "qdf_threads.h"
#include "qdf_timer.h"
#include "__wlan_dsc.h"
#ifdef WLAN_DSC_DEBUG
static void __dsc_dbg_op_timeout(void *opaque_op)
{
struct dsc_op *op = opaque_op;
qdf_print_thread_trace(op->thread);
QDF_DEBUG_PANIC("Operation '%s' exceeded %ums",
op->func, DSC_OP_TIMEOUT_MS);
}
/**
* __dsc_dbg_ops_init() - initialize debug ops data structures
* @ops: the ops container to initialize
*
* Return: None
*/
static inline void __dsc_dbg_ops_init(struct dsc_ops *ops)
{
qdf_list_create(&ops->list, 0);
}
/**
* __dsc_dbg_ops_deinit() - de-initialize debug ops data structures
* @ops: the ops container to de-initialize
*
* Return: None
*/
static inline void __dsc_dbg_ops_deinit(struct dsc_ops *ops)
{
qdf_list_destroy(&ops->list);
}
/**
* __dsc_dbg_ops_insert() - insert @func into the debug information in @ops
* @ops: the ops container to insert into
* @func: the debug information to insert
*
* Return: QDF_STATUS
*/
static QDF_STATUS __dsc_dbg_ops_insert(struct dsc_ops *ops, const char *func)
{
QDF_STATUS status;
struct dsc_op *op;
op = qdf_mem_malloc(sizeof(*op));
if (!op)
return QDF_STATUS_E_NOMEM;
op->thread = qdf_get_current_task();
status = qdf_timer_init(NULL, &op->timeout_timer, __dsc_dbg_op_timeout,
op, QDF_TIMER_TYPE_SW);
if (QDF_IS_STATUS_ERROR(status))
goto free_op;
op->func = func;
qdf_timer_start(&op->timeout_timer, DSC_OP_TIMEOUT_MS);
qdf_list_insert_back(&ops->list, &op->node);
return QDF_STATUS_SUCCESS;
free_op:
qdf_mem_free(op);
return status;
}
/**
* __dsc_dbg_ops_remove() - remove @func from the debug information in @ops
* @ops: the ops container to remove from
* @func: the debug information to remove
*
* Return: None
*/
static void __dsc_dbg_ops_remove(struct dsc_ops *ops, const char *func)
{
struct dsc_op *op;
/* Global pending op depth is usually <=3. Use linear search for now */
qdf_list_for_each(&ops->list, op, node) {
if (!qdf_str_eq(op->func, func))
continue;
/* this is safe because we cease iteration */
qdf_list_remove_node(&ops->list, &op->node);
qdf_timer_stop(&op->timeout_timer);
qdf_timer_free(&op->timeout_timer);
qdf_mem_free(op);
return;
}
QDF_DEBUG_PANIC("Driver op '%s' is not pending", func);
}
#else
static inline void __dsc_dbg_ops_init(struct dsc_ops *ops) { }
static inline void __dsc_dbg_ops_deinit(struct dsc_ops *ops) { }
static inline QDF_STATUS
__dsc_dbg_ops_insert(struct dsc_ops *ops, const char *func)
{
return QDF_STATUS_SUCCESS;
}
static inline void
__dsc_dbg_ops_remove(struct dsc_ops *ops, const char *func) { }
#endif /* WLAN_DSC_DEBUG */
void __dsc_ops_init(struct dsc_ops *ops)
{
ops->count = 0;
qdf_event_create(&ops->event);
__dsc_dbg_ops_init(ops);
}
void __dsc_ops_deinit(struct dsc_ops *ops)
{
/* assert no ops in flight */
dsc_assert(!ops->count);
__dsc_dbg_ops_deinit(ops);
qdf_event_destroy(&ops->event);
}
QDF_STATUS __dsc_ops_insert(struct dsc_ops *ops, const char *func)
{
QDF_STATUS status;
status = __dsc_dbg_ops_insert(ops, func);
if (QDF_IS_STATUS_ERROR(status))
return status;
ops->count++;
return QDF_STATUS_SUCCESS;
}
bool __dsc_ops_remove(struct dsc_ops *ops, const char *func)
{
dsc_assert(ops->count);
ops->count--;
__dsc_dbg_ops_remove(ops, func);
return ops->count == 0;
}
#ifdef WLAN_DSC_DEBUG
static void __dsc_dbg_trans_timeout(void *opaque_trans)
{
struct dsc_trans *trans = opaque_trans;
qdf_print_thread_trace(trans->thread);
QDF_DEBUG_PANIC("Transition '%s' exceeded %ums",
trans->active_desc, DSC_TRANS_TIMEOUT_MS);
}
/**
* __dsc_dbg_trans_timeout_start() - start a timeout timer for @trans
* @trans: the active transition to start a timeout timer for
*
* Return: QDF_STATUS
*/
static QDF_STATUS __dsc_dbg_trans_timeout_start(struct dsc_trans *trans)
{
QDF_STATUS status;
trans->thread = qdf_get_current_task();
status = qdf_timer_init(NULL, &trans->timeout_timer,
__dsc_dbg_trans_timeout, trans,
QDF_TIMER_TYPE_SW);
if (QDF_IS_STATUS_ERROR(status))
return status;
qdf_timer_start(&trans->timeout_timer, DSC_TRANS_TIMEOUT_MS);
return QDF_STATUS_SUCCESS;
}
/**
* __dsc_dbg_trans_timeout_stop() - stop the timeout timer for @trans
* @trans: the active transition to stop the timeout timer for
*
* Return: None
*/
static void __dsc_dbg_trans_timeout_stop(struct dsc_trans *trans)
{
qdf_timer_stop(&trans->timeout_timer);
qdf_timer_free(&trans->timeout_timer);
}
static void __dsc_dbg_tran_wait_timeout(void *opaque_tran)
{
struct dsc_tran *tran = opaque_tran;
qdf_print_thread_trace(tran->thread);
QDF_DEBUG_PANIC("Transition '%s' waited more than %ums",
tran->desc, DSC_TRANS_WAIT_TIMEOUT_MS);
}
/**
* __dsc_dbg_tran_wait_timeout_start() - start a timeout timer for @tran
* @tran: the pending transition to start a timeout timer for
*
* Return: QDF_STATUS
*/
static QDF_STATUS __dsc_dbg_tran_wait_timeout_start(struct dsc_tran *tran)
{
QDF_STATUS status;
tran->thread = qdf_get_current_task();
status = qdf_timer_init(NULL, &tran->timeout_timer,
__dsc_dbg_tran_wait_timeout, tran,
QDF_TIMER_TYPE_SW);
if (QDF_IS_STATUS_ERROR(status))
return status;
qdf_timer_start(&tran->timeout_timer, DSC_TRANS_WAIT_TIMEOUT_MS);
return QDF_STATUS_SUCCESS;
}
/**
* __dsc_dbg_tran_wait_timeout_stop() - stop the timeout timer for @tran
* @tran: the pending transition to stop the timeout timer for
*
* Return: None
*/
static void __dsc_dbg_tran_wait_timeout_stop(struct dsc_tran *tran)
{
qdf_timer_stop(&tran->timeout_timer);
qdf_timer_free(&tran->timeout_timer);
}
#else
static inline QDF_STATUS __dsc_dbg_trans_timeout_start(struct dsc_trans *trans)
{
return QDF_STATUS_SUCCESS;
}
static inline void __dsc_dbg_trans_timeout_stop(struct dsc_trans *trans) { }
static inline QDF_STATUS
__dsc_dbg_tran_wait_timeout_start(struct dsc_tran *tran)
{
return QDF_STATUS_SUCCESS;
}
static inline void __dsc_dbg_tran_wait_timeout_stop(struct dsc_tran *tran) { }
#endif /* WLAN_DSC_DEBUG */
void __dsc_trans_init(struct dsc_trans *trans)
{
trans->active_desc = NULL;
qdf_list_create(&trans->queue, 0);
}
void __dsc_trans_deinit(struct dsc_trans *trans)
{
qdf_list_destroy(&trans->queue);
trans->active_desc = NULL;
}
QDF_STATUS __dsc_trans_start(struct dsc_trans *trans, const char *desc)
{
QDF_STATUS status;
status = __dsc_dbg_trans_timeout_start(trans);
if (QDF_IS_STATUS_ERROR(status))
return status;
dsc_assert(!trans->active_desc);
trans->active_desc = desc;
return QDF_STATUS_SUCCESS;
}
void __dsc_trans_stop(struct dsc_trans *trans)
{
dsc_assert(trans->active_desc);
trans->active_desc = NULL;
__dsc_dbg_trans_timeout_stop(trans);
}
QDF_STATUS __dsc_trans_queue(struct dsc_trans *trans, struct dsc_tran *tran,
const char *desc)
{
QDF_STATUS status;
tran->abort = false;
tran->desc = desc;
qdf_event_create(&tran->event);
status = __dsc_dbg_tran_wait_timeout_start(tran);
if (QDF_IS_STATUS_ERROR(status))
goto event_destroy;
qdf_list_insert_back(&trans->queue, &tran->node);
return QDF_STATUS_SUCCESS;
event_destroy:
qdf_event_destroy(&tran->event);
return status;
}
/**
* __dsc_trans_dequeue() - dequeue the next queued transition from @trans
* @trans: the transactions container to dequeue from
*
* Return: the dequeued transition, or NULL if @trans is empty
*/
static struct dsc_tran *__dsc_trans_dequeue(struct dsc_trans *trans)
{
QDF_STATUS status;
qdf_list_node_t *node;
struct dsc_tran *tran;
status = qdf_list_remove_front(&trans->queue, &node);
if (QDF_IS_STATUS_ERROR(status))
return NULL;
tran = qdf_container_of(node, struct dsc_tran, node);
__dsc_dbg_tran_wait_timeout_stop(tran);
return tran;
}
bool __dsc_trans_abort(struct dsc_trans *trans)
{
struct dsc_tran *tran;
tran = __dsc_trans_dequeue(trans);
if (!tran)
return false;
tran->abort = true;
qdf_event_set(&tran->event);
return true;
}
bool __dsc_trans_trigger(struct dsc_trans *trans)
{
struct dsc_tran *tran;
tran = __dsc_trans_dequeue(trans);
if (!tran)
return false;
__dsc_trans_start(trans, tran->desc);
qdf_event_set(&tran->event);
return true;
}
bool __dsc_trans_active(struct dsc_trans *trans)
{
return !!trans->active_desc;
}
bool __dsc_trans_queued(struct dsc_trans *trans)
{
return !qdf_list_empty(&trans->queue);
}
bool __dsc_trans_active_or_queued(struct dsc_trans *trans)
{
return __dsc_trans_active(trans) || __dsc_trans_queued(trans);
}
QDF_STATUS __dsc_tran_wait(struct dsc_tran *tran)
{
QDF_STATUS status;
status = qdf_wait_single_event(&tran->event, 0);
qdf_event_destroy(&tran->event);
if (QDF_IS_STATUS_ERROR(status))
return status;
if (tran->abort)
return QDF_STATUS_E_ABORTED;
return QDF_STATUS_SUCCESS;
}