/* * 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; }