From 07ce203ddee206785244a083ba030ed99af07392 Mon Sep 17 00:00:00 2001 From: Anilkumar Kolli Date: Wed, 9 Sep 2020 19:53:01 +0530 Subject: [PATCH 13/15] ath11k: ath11k: add support for device recovery Test command: echo assert > /sys/kernel/debug/ath11k/qca6390/simulate_fw_crash Then ath11k_mhi_notify_status receive MHI_CB_EE_RDDM notify and start to do recovery process. It need add workqueue_aux, because ab->workqueue is used when receive ATH11K_QMI_EVENT_FW_READY in recovery process(queue_work(ab->workqueue, &ab->restart_work;)), and ath11k_core_reset will wait for max ATH11K_RESET_TIMEOUT_HZ for the previous restart_work finished, if ath11k_core_reset also queued in ab->workqueue, then it will delay restart_work of previous recovery and lead previous recovery fail. With this patch, ath11k recovery success Signed-off-by: Wen Gong --- drivers/net/wireless/ath/ath11k/core.c | 48 ++++++++++++++++++++++++++++++++++ drivers/net/wireless/ath/ath11k/core.h | 6 +++++ drivers/net/wireless/ath/ath11k/mhi.c | 31 ++++++++++++++++++++++ drivers/net/wireless/ath/ath11k/pci.c | 1 + 4 files changed, 86 insertions(+) --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -1075,9 +1075,49 @@ static void ath11k_core_restart(struct w } mutex_unlock(&ar->conf_mutex); } + + if (ab->is_reset) { + atomic_dec(&ab->reset_count); + complete(&ab->reset_complete); + ab->is_reset = false; + } + complete(&ab->driver_recovery); } +static void ath11k_core_reset(struct work_struct *work) +{ + struct ath11k_base *ab = container_of(work, struct ath11k_base, reset_work); + int reset_count; + long time_left; + + reset_count = atomic_add_return(1, &ab->reset_count); + + if (reset_count > 1) { + ath11k_warn(ab, "already reseting count %d\n", reset_count); + + reinit_completion(&ab->reset_complete); + time_left = wait_for_completion_timeout(&ab->reset_complete, + ATH11K_RESET_TIMEOUT_HZ); + ath11k_info(ab, "reset wait time left %ld\n", time_left); + + if (time_left) { + ath11k_info(ab, "to skip reset\n"); + atomic_dec(&ab->reset_count); + return; + } + } + + ath11k_info(ab, "reset starting\n"); + + ab->is_reset = true; + + ath11k_hif_power_down(ab); + ath11k_hif_power_up(ab); + + ath11k_info(ab, "reset started\n"); +} + static int ath11k_init_hw_params(struct ath11k_base *ab) { const struct ath11k_hw_params *hw_params = NULL; @@ -1179,14 +1219,20 @@ struct ath11k_base *ath11k_core_alloc(st if (!ab->workqueue) goto err_sc_free; + ab->workqueue_aux = create_singlethread_workqueue("ath11k_aux_wq"); + if (!ab->workqueue_aux) + goto err_free_wq; + mutex_init(&ab->core_lock); spin_lock_init(&ab->base_lock); + init_completion(&ab->reset_complete); INIT_LIST_HEAD(&ab->peers); init_waitqueue_head(&ab->peer_mapping_wq); init_waitqueue_head(&ab->wmi_ab.tx_credits_wq); init_waitqueue_head(&ab->qmi.cold_boot_waitq); INIT_WORK(&ab->restart_work, ath11k_core_restart); + INIT_WORK(&ab->reset_work, ath11k_core_reset); timer_setup(&ab->rx_replenish_retry, ath11k_ce_rx_replenish_retry, 0); init_completion(&ab->htc_suspend); init_completion(&ab->wow.wakeup_completed); @@ -1197,6 +1243,8 @@ struct ath11k_base *ath11k_core_alloc(st return ab; +err_free_wq: + destroy_workqueue(ab->workqueue); err_sc_free: kfree(ab); return NULL; --- a/drivers/net/wireless/ath/ath11k/core.h +++ b/drivers/net/wireless/ath/ath11k/core.h @@ -36,6 +36,7 @@ /* Pending management packets threshold for dropping probe responses */ #define ATH11K_PRB_RSP_DROP_THRESHOLD ((ATH11K_TX_MGMT_TARGET_MAX_SUPPORT_WMI * 3) / 4) +#define ATH11K_RESET_TIMEOUT_HZ (10 * HZ) #define ATH11K_INVALID_HW_MAC_ID 0xFF #define ATH11K_RX_RATE_TABLE_NUM 320 #define ATH11K_RX_RATE_TABLE_11AX_NUM 576 @@ -819,6 +820,11 @@ struct ath11k_base { struct completion driver_recovery; struct workqueue_struct *workqueue; struct work_struct restart_work; + struct workqueue_struct *workqueue_aux; + struct work_struct reset_work; + atomic_t reset_count; + bool is_reset; + struct completion reset_complete; struct { /* protected by data_lock */ u32 fw_crash_counter; --- a/drivers/net/wireless/ath/ath11k/mhi.c +++ b/drivers/net/wireless/ath/ath11k/mhi.c @@ -281,6 +281,29 @@ static int ath11k_mhi_get_msi(struct ath return 0; } +static char *ath11k_mhi_callback_to_str(int reason) +{ + switch (reason) { + case MHI_CB_IDLE: + return "MHI_CB_IDLE"; + case MHI_CB_PENDING_DATA: + return "MHI_CB_PENDING_DATA"; + case MHI_CB_LPM_ENTER: + return "MHI_CB_LPM_ENTER"; + case MHI_CB_LPM_EXIT: + return "MHI_CB_LPM_EXIT"; + case MHI_CB_EE_RDDM: + return "MHI_CB_EE_RDDM"; + case MHI_CB_SYS_ERROR: + return "MHI_CB_SYS_ERROR"; + case MHI_CB_FATAL_ERROR: + return "MHI_CB_FATAL_ERROR"; + default: + return "UNKNOWN"; + } +}; + + static int ath11k_mhi_op_runtime_get(struct mhi_controller *mhi_cntrl) { return 0; @@ -288,6 +311,7 @@ static int ath11k_mhi_op_runtime_get(str static void ath11k_mhi_op_runtime_put(struct mhi_controller *mhi_cntrl) { + } static void ath11k_mhi_op_status_cb(struct mhi_controller *mhi_cntrl, @@ -295,10 +319,16 @@ static void ath11k_mhi_op_status_cb(stru { struct ath11k_base *ab = dev_get_drvdata(mhi_cntrl->cntrl_dev); + ath11k_info(ab, "mhi notify status reason %s\n", + ath11k_mhi_callback_to_str(cb)); + switch (cb) { case MHI_CB_SYS_ERROR: ath11k_warn(ab, "firmware crashed: MHI_CB_SYS_ERROR\n"); break; + case MHI_CB_EE_RDDM: + queue_work(ab->workqueue_aux, &ab->reset_work); + break; default: break; } --- a/drivers/net/wireless/ath/ath11k/pci.c +++ b/drivers/net/wireless/ath/ath11k/pci.c @@ -1334,6 +1334,7 @@ static void ath11k_pci_remove(struct pci } set_bit(ATH11K_FLAG_UNREGISTERING, &ab->dev_flags); + cancel_work_sync(&ab->reset_work); ath11k_core_deinit(ab);