From 7f4494ffc5b239b9aaa4830bd435a1e62cc3f665 Mon Sep 17 00:00:00 2001 From: Alok Kumar Date: Tue, 14 Aug 2018 16:55:42 +0530 Subject: [PATCH] qcacmn: Avoid race condition between tasklet schedule and kill Tasklet get stuck when cds_is_driver_recovering and the driver state is unloading. It happens due to race between schedule of tasklet and tasklet_kill. Here one thread is trying to disable & kill the tasklet while another thread is trying to concurrently schedule it. Cancel the tasklet work and make tasklets[i].inited = false before disabling the tasklet in ce_tasklet_kill() and validate if tasket is inited before rescheduling the tasklet. Change-Id: Ib6f4013a73bca8558da097b73535bb8a8f80354d CRs-Fixed: 2290951 --- hif/src/ce/ce_tasklet.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/hif/src/ce/ce_tasklet.c b/hif/src/ce/ce_tasklet.c index c60cafad28f3..e49c3f10c841 100644 --- a/hif/src/ce/ce_tasklet.c +++ b/hif/src/ce/ce_tasklet.c @@ -73,7 +73,8 @@ static void reschedule_ce_tasklet_work_handler(struct work_struct *work) HIF_ERROR("%s: wlan driver is unloaded", __func__); return; } - tasklet_schedule(&hif_ce_state->tasklets[ce_work->id].intr_tq); + if (hif_ce_state->tasklets[ce_work->id].inited) + tasklet_schedule(&hif_ce_state->tasklets[ce_work->id].intr_tq); } static struct tasklet_work tasklet_workers[CE_ID_MAX]; @@ -228,6 +229,7 @@ void ce_tasklet_init(struct HIF_CE_state *hif_ce_state, uint32_t mask) * ce_tasklet_kill() - ce_tasklet_kill * @hif_ce_state: hif_ce_state * + * Context: Non-Atomic context * Return: N/A */ void ce_tasklet_kill(struct hif_softc *scn) @@ -235,10 +237,21 @@ void ce_tasklet_kill(struct hif_softc *scn) int i; struct HIF_CE_state *hif_ce_state = HIF_GET_CE_STATE(scn); + work_initialized = false; + for (i = 0; i < CE_COUNT_MAX; i++) if (hif_ce_state->tasklets[i].inited) { - tasklet_disable(&hif_ce_state->tasklets[i].intr_tq); hif_ce_state->tasklets[i].inited = false; + /* + * Cancel the tasklet work before tasklet_disable + * to avoid race between tasklet_schedule and + * tasklet_kill. Here cancel_work_sync() won't + * return before reschedule_ce_tasklet_work_handler() + * completes. Even if tasklet_schedule() happens + * tasklet_disable() will take care of that. + */ + cancel_work_sync(&tasklet_workers[i].work); + tasklet_disable(&hif_ce_state->tasklets[i].intr_tq); tasklet_kill(&hif_ce_state->tasklets[i].intr_tq); } qdf_atomic_set(&scn->active_tasklet_cnt, 0);