qcacld-3.0: Add transition wait timeout detection to DSC
In order to catch and debug long waiting transitions, add a watchdog timer to Driver Synchronization Core (DSC) transition start wait calls. If the timer expires, panic the driver for offline debugging. Change-Id: I557f87ada182ced389e7d5e63fe8b78f47e1d6b5 CRs-Fixed: 2328594
Cette révision appartient à :
@@ -170,6 +170,57 @@ bool __dsc_ops_remove(struct dsc_ops *ops, const char *func)
|
|||||||
return ops->count == 0;
|
return ops->count == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WLAN_DSC_DEBUG
|
||||||
|
static void __dsc_dbg_tran_wait_timeout(void *opaque_tran)
|
||||||
|
{
|
||||||
|
struct dsc_tran *tran = opaque_tran;
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
|
||||||
|
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 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_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)
|
void __dsc_trans_init(struct dsc_trans *trans)
|
||||||
{
|
{
|
||||||
trans->active_desc = NULL;
|
trans->active_desc = NULL;
|
||||||
@@ -182,13 +233,27 @@ void __dsc_trans_deinit(struct dsc_trans *trans)
|
|||||||
trans->active_desc = NULL;
|
trans->active_desc = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void __dsc_trans_queue(struct dsc_trans *trans, struct dsc_tran *tran,
|
QDF_STATUS __dsc_trans_queue(struct dsc_trans *trans, struct dsc_tran *tran,
|
||||||
const char *desc)
|
const char *desc)
|
||||||
{
|
{
|
||||||
|
QDF_STATUS status;
|
||||||
|
|
||||||
tran->abort = false;
|
tran->abort = false;
|
||||||
tran->desc = desc;
|
tran->desc = desc;
|
||||||
qdf_event_create(&tran->event);
|
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);
|
qdf_list_insert_back(&trans->queue, &tran->node);
|
||||||
|
|
||||||
|
return QDF_STATUS_SUCCESS;
|
||||||
|
|
||||||
|
event_destroy:
|
||||||
|
qdf_event_destroy(&tran->event);
|
||||||
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,12 +266,16 @@ static struct dsc_tran *__dsc_trans_dequeue(struct dsc_trans *trans)
|
|||||||
{
|
{
|
||||||
QDF_STATUS status;
|
QDF_STATUS status;
|
||||||
qdf_list_node_t *node;
|
qdf_list_node_t *node;
|
||||||
|
struct dsc_tran *tran;
|
||||||
|
|
||||||
status = qdf_list_remove_front(&trans->queue, &node);
|
status = qdf_list_remove_front(&trans->queue, &node);
|
||||||
if (QDF_IS_STATUS_ERROR(status))
|
if (QDF_IS_STATUS_ERROR(status))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
return qdf_container_of(node, struct dsc_tran, node);
|
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)
|
bool __dsc_trans_abort(struct dsc_trans *trans)
|
||||||
@@ -256,7 +325,7 @@ QDF_STATUS __dsc_tran_wait(struct dsc_tran *tran)
|
|||||||
{
|
{
|
||||||
QDF_STATUS status;
|
QDF_STATUS status;
|
||||||
|
|
||||||
status = qdf_wait_single_event(&tran->event, DSC_TRANS_TIMEOUT);
|
status = qdf_wait_single_event(&tran->event, 0);
|
||||||
qdf_event_destroy(&tran->event);
|
qdf_event_destroy(&tran->event);
|
||||||
|
|
||||||
if (QDF_IS_STATUS_ERROR(status))
|
if (QDF_IS_STATUS_ERROR(status))
|
||||||
|
@@ -55,13 +55,9 @@ static inline bool __dsc_assert(const bool cond, const char *cond_str,
|
|||||||
#define dsc_assert_success(status) dsc_assert(QDF_IS_STATUS_SUCCESS(status))
|
#define dsc_assert_success(status) dsc_assert(QDF_IS_STATUS_SUCCESS(status))
|
||||||
|
|
||||||
#ifdef WLAN_DSC_DEBUG
|
#ifdef WLAN_DSC_DEBUG
|
||||||
#define DSC_TRANS_TIMEOUT 120000 /* 2 minutes */
|
#define DSC_OP_TIMEOUT_MS (1 * 60 * 1000) /* 1 minute */
|
||||||
#define DSC_OP_TIMEOUT_MS (1 * 60 * 1000) /* 1 minute */
|
#define DSC_TRANS_WAIT_TIMEOUT_MS (2 * 60 * 1000) /* 2 minutes */
|
||||||
#else
|
|
||||||
#define DSC_TRANS_TIMEOUT 0 /* no timeout */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef WLAN_DSC_DEBUG
|
|
||||||
/**
|
/**
|
||||||
* struct dsc_op - list node for operation tracking information
|
* struct dsc_op - list node for operation tracking information
|
||||||
* @node: list node
|
* @node: list node
|
||||||
@@ -90,17 +86,21 @@ struct dsc_ops {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct dsc_pending_trans - representation of a pending transition
|
* struct dsc_tran - representation of a pending transition
|
||||||
* @abort: used to indicate if the transition stopped waiting due to an abort
|
* @abort: used to indicate if the transition stopped waiting due to an abort
|
||||||
* @desc: unique description of the transition
|
* @desc: unique description of the transition
|
||||||
* @node: list node
|
* @node: list node
|
||||||
* @event: event used to wait in *_start_trans_wait() APIs
|
* @event: event used to wait in *_start_trans_wait() APIs
|
||||||
|
* @timeout_timer: a timer used to detect transition wait timeouts
|
||||||
*/
|
*/
|
||||||
struct dsc_tran {
|
struct dsc_tran {
|
||||||
bool abort;
|
bool abort;
|
||||||
const char *desc;
|
const char *desc;
|
||||||
qdf_list_node_t node;
|
qdf_list_node_t node;
|
||||||
qdf_event_t event;
|
qdf_event_t event;
|
||||||
|
#ifdef WLAN_DSC_DEBUG
|
||||||
|
qdf_timer_t timeout_timer;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -235,10 +235,10 @@ void __dsc_trans_deinit(struct dsc_trans *trans);
|
|||||||
* @tran: the transition to enqueue
|
* @tran: the transition to enqueue
|
||||||
* @desc: unique description of the transition being queued
|
* @desc: unique description of the transition being queued
|
||||||
*
|
*
|
||||||
* Return: None
|
* Return: QDF_STATUS
|
||||||
*/
|
*/
|
||||||
void __dsc_trans_queue(struct dsc_trans *trans, struct dsc_tran *tran,
|
QDF_STATUS __dsc_trans_queue(struct dsc_trans *trans, struct dsc_tran *tran,
|
||||||
const char *desc);
|
const char *desc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __dsc_tran_wait() - block until @tran completes
|
* __dsc_tran_wait() - block until @tran completes
|
||||||
|
@@ -193,17 +193,23 @@ __dsc_driver_trans_start_wait(struct dsc_driver *driver, const char *desc)
|
|||||||
|
|
||||||
__dsc_lock(driver);
|
__dsc_lock(driver);
|
||||||
|
|
||||||
|
/* try to start without waiting */
|
||||||
status = __dsc_driver_trans_start_nolock(driver, desc);
|
status = __dsc_driver_trans_start_nolock(driver, desc);
|
||||||
if (QDF_IS_STATUS_SUCCESS(status)) {
|
if (QDF_IS_STATUS_SUCCESS(status))
|
||||||
__dsc_unlock(driver);
|
goto unlock;
|
||||||
return QDF_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
__dsc_trans_queue(&driver->trans, &tran, desc);
|
status = __dsc_trans_queue(&driver->trans, &tran, desc);
|
||||||
|
if (QDF_IS_STATUS_ERROR(status))
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
__dsc_unlock(driver);
|
__dsc_unlock(driver);
|
||||||
|
|
||||||
return __dsc_tran_wait(&tran);
|
return __dsc_tran_wait(&tran);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
__dsc_unlock(driver);
|
||||||
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDF_STATUS
|
QDF_STATUS
|
||||||
|
@@ -198,17 +198,23 @@ __dsc_psoc_trans_start_wait(struct dsc_psoc *psoc, const char *desc)
|
|||||||
|
|
||||||
__dsc_driver_lock(psoc);
|
__dsc_driver_lock(psoc);
|
||||||
|
|
||||||
|
/* try to start without waiting */
|
||||||
status = __dsc_psoc_trans_start_nolock(psoc, desc);
|
status = __dsc_psoc_trans_start_nolock(psoc, desc);
|
||||||
if (QDF_IS_STATUS_SUCCESS(status)) {
|
if (QDF_IS_STATUS_SUCCESS(status))
|
||||||
__dsc_driver_unlock(psoc);
|
goto unlock;
|
||||||
return QDF_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
__dsc_trans_queue(&psoc->trans, &tran, desc);
|
status = __dsc_trans_queue(&psoc->trans, &tran, desc);
|
||||||
|
if (QDF_IS_STATUS_ERROR(status))
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
__dsc_driver_unlock(psoc);
|
__dsc_driver_unlock(psoc);
|
||||||
|
|
||||||
return __dsc_tran_wait(&tran);
|
return __dsc_tran_wait(&tran);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
__dsc_driver_unlock(psoc);
|
||||||
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDF_STATUS dsc_psoc_trans_start_wait(struct dsc_psoc *psoc, const char *desc)
|
QDF_STATUS dsc_psoc_trans_start_wait(struct dsc_psoc *psoc, const char *desc)
|
||||||
|
@@ -172,17 +172,23 @@ __dsc_vdev_trans_start_wait(struct dsc_vdev *vdev, const char *desc)
|
|||||||
|
|
||||||
__dsc_driver_lock(vdev);
|
__dsc_driver_lock(vdev);
|
||||||
|
|
||||||
|
/* try to start without waiting */
|
||||||
status = __dsc_vdev_trans_start_nolock(vdev, desc);
|
status = __dsc_vdev_trans_start_nolock(vdev, desc);
|
||||||
if (QDF_IS_STATUS_SUCCESS(status)) {
|
if (QDF_IS_STATUS_SUCCESS(status))
|
||||||
__dsc_driver_unlock(vdev);
|
goto unlock;
|
||||||
return QDF_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
__dsc_trans_queue(&vdev->trans, &tran, desc);
|
status = __dsc_trans_queue(&vdev->trans, &tran, desc);
|
||||||
|
if (QDF_IS_STATUS_ERROR(status))
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
__dsc_driver_unlock(vdev);
|
__dsc_driver_unlock(vdev);
|
||||||
|
|
||||||
return __dsc_tran_wait(&tran);
|
return __dsc_tran_wait(&tran);
|
||||||
|
|
||||||
|
unlock:
|
||||||
|
__dsc_driver_unlock(vdev);
|
||||||
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDF_STATUS dsc_vdev_trans_start_wait(struct dsc_vdev *vdev, const char *desc)
|
QDF_STATUS dsc_vdev_trans_start_wait(struct dsc_vdev *vdev, const char *desc)
|
||||||
|
Référencer dans un nouveau ticket
Bloquer un utilisateur