
Trigger panic if SSR timed out. Change-Id: I72d81da46745d2f6a5ff34a57b6d66b7f7171dde CRs-Fixed: 3398969
423 lines
9.6 KiB
C
423 lines
9.6 KiB
C
/*
|
|
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. 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"
|
|
#include "cds_api.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);
|
|
|
|
if (cds_is_fw_down() &&
|
|
!qdf_str_eq(trans->active_desc, "hdd_soc_recovery_shutdown"))
|
|
dsc_err("fw is down avoid panic");
|
|
else
|
|
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;
|
|
}
|
|
|