diff --git a/hif/inc/hif.h b/hif/inc/hif.h index e871c15f20..7af5160813 100644 --- a/hif/inc/hif.h +++ b/hif/inc/hif.h @@ -1306,6 +1306,28 @@ int hif_apps_enable_irq_wake(struct hif_opaque_softc *hif_ctx); */ int hif_apps_disable_irq_wake(struct hif_opaque_softc *hif_ctx); +/** + * hif_apps_enable_irqs_except_wake_irq() - Enables all irqs except wake_irq + * @hif_ctx: an opaque HIF handle to use + * + * As opposed to the standard hif_irq_enable, this function always applies to + * the APPS side kernel interrupt handling. + * + * Return: errno + */ +int hif_apps_enable_irqs_except_wake_irq(struct hif_opaque_softc *hif_ctx); + +/** + * hif_apps_disable_irqs_except_wake_irq() - Disables all irqs except wake_irq + * @hif_ctx: an opaque HIF handle to use + * + * As opposed to the standard hif_irq_disable, this function always applies to + * the APPS side kernel interrupt handling. + * + * Return: errno + */ +int hif_apps_disable_irqs_except_wake_irq(struct hif_opaque_softc *hif_ctx); + #ifdef FEATURE_RUNTIME_PM int hif_pre_runtime_suspend(struct hif_opaque_softc *hif_ctx); void hif_pre_runtime_resume(struct hif_opaque_softc *hif_ctx); diff --git a/hif/src/ce/ce_internal.h b/hif/src/ce/ce_internal.h index 94b148746e..9f46ce4839 100644 --- a/hif/src/ce/ce_internal.h +++ b/hif/src/ce/ce_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2020 The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2021 The Linux Foundation. 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 @@ -552,6 +552,15 @@ static inline void ce_t2h_msg_ce_cleanup(struct CE_handle *ce_hdl) */ int hif_get_wake_ce_id(struct hif_softc *scn, uint8_t *ce_id); +/** + * hif_get_fw_diag_ce_id() - gets the copy engine id used for FW diag + * @scn: The hif context to use + * @ce_id: a pointer where the copy engine Id should be populated + * + * Return: errno + */ +int hif_get_fw_diag_ce_id(struct hif_softc *scn, uint8_t *ce_id); + #if defined(HIF_CONFIG_SLUB_DEBUG_ON) || defined(HIF_CE_DEBUG_DATA_BUF) #ifndef HIF_CE_HISTORY_MAX diff --git a/hif/src/ce/ce_main.c b/hif/src/ce/ce_main.c index 98512e6a85..d4e0fef54b 100644 --- a/hif/src/ce/ce_main.c +++ b/hif/src/ce/ce_main.c @@ -4480,6 +4480,27 @@ int hif_get_wake_ce_id(struct hif_softc *scn, uint8_t *ce_id) return 0; } +int hif_get_fw_diag_ce_id(struct hif_softc *scn, uint8_t *ce_id) +{ + int status; + uint8_t ul_pipe, dl_pipe; + int ul_is_polled, dl_is_polled; + + /* DL pipe for WMI_CONTROL_DIAG_SVC should map to the FW DIAG CE_ID */ + status = hif_map_service_to_pipe(GET_HIF_OPAQUE_HDL(scn), + WMI_CONTROL_DIAG_SVC, + &ul_pipe, &dl_pipe, + &ul_is_polled, &dl_is_polled); + if (status) { + hif_err("Failed to map pipe: %d", status); + return status; + } + + *ce_id = dl_pipe; + + return 0; +} + #ifdef HIF_CE_LOG_INFO /** * ce_get_index_info(): Get CE index info diff --git a/hif/src/ce/ce_tasklet.c b/hif/src/ce/ce_tasklet.c index eea4d1faa1..c4d86772f0 100644 --- a/hif/src/ce/ce_tasklet.c +++ b/hif/src/ce/ce_tasklet.c @@ -612,6 +612,64 @@ static inline bool hif_tasklet_schedule(struct hif_opaque_softc *hif_ctx, return true; } +/** + * ce_poll_reap_by_id() - reap the available frames from CE by polling per ce_id + * @scn: hif context + * @ce_id: CE id + * + * This function needs to be called once after all the irqs are disabled + * and tasklets are drained during bus suspend. + * + * Return: 0 on success, unlikely -EBUSY if reaping goes infinite loop + */ +static int ce_poll_reap_by_id(struct hif_softc *scn, enum ce_id_type ce_id) +{ + struct HIF_CE_state *hif_ce_state = (struct HIF_CE_state *)scn; + struct CE_state *CE_state = scn->ce_id_to_state[ce_id]; + + if (scn->ce_latency_stats) + hif_record_tasklet_exec_entry_ts(scn, ce_id); + + hif_record_ce_desc_event(scn, ce_id, HIF_CE_REAP_ENTRY, + NULL, NULL, -1, 0); + + ce_per_engine_service(scn, ce_id); + + /* + * In an unlikely case, if frames are still pending to reap, + * could be an infinite loop, so return -EBUSY. + */ + if (ce_check_rx_pending(CE_state)) + return -EBUSY; + + hif_record_ce_desc_event(scn, ce_id, HIF_CE_REAP_EXIT, + NULL, NULL, -1, 0); + + if (scn->ce_latency_stats) + ce_tasklet_update_bucket(hif_ce_state, ce_id); + + return 0; +} + +/** + * hif_drain_fw_diag_ce() - reap all the available FW diag logs from CE + * @scn: hif context + * + * This function needs to be called once after all the irqs are disabled + * and tasklets are drained during bus suspend. + * + * Return: 0 on success, unlikely -EBUSY if reaping goes infinite loop + */ +int hif_drain_fw_diag_ce(struct hif_softc *scn) +{ + uint8_t ce_id; + + if (hif_get_fw_diag_ce_id(scn, &ce_id)) + return 0; + + return ce_poll_reap_by_id(scn, ce_id); +} + /** * ce_dispatch_interrupt() - dispatch an interrupt to a processing context * @ce_id: ce_id diff --git a/hif/src/ce/ce_tasklet.h b/hif/src/ce/ce_tasklet.h index 7d78fa2cf2..bfbd2eb57d 100644 --- a/hif/src/ce/ce_tasklet.h +++ b/hif/src/ce/ce_tasklet.h @@ -1,5 +1,6 @@ /* - * Copyright (c) 2015-2016,2018,2020 The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2016,2018,2020-2021 The Linux Foundation. + * 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 @@ -24,6 +25,7 @@ void deinit_tasklet_workers(struct hif_opaque_softc *scn); void ce_tasklet_init(struct HIF_CE_state *hif_ce_state, uint32_t mask); void ce_tasklet_kill(struct hif_softc *scn); int hif_drain_tasklets(struct hif_softc *scn); +int hif_drain_fw_diag_ce(struct hif_softc *scn); QDF_STATUS ce_register_irq(struct HIF_CE_state *hif_ce_state, uint32_t mask); QDF_STATUS ce_unregister_irq(struct HIF_CE_state *hif_ce_state, uint32_t mask); irqreturn_t ce_dispatch_interrupt(int irq, diff --git a/hif/src/dispatcher/multibus.c b/hif/src/dispatcher/multibus.c index 00d1150556..6fe7a6841d 100644 --- a/hif/src/dispatcher/multibus.c +++ b/hif/src/dispatcher/multibus.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018, 2020 The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2018, 2020-2021 The Linux Foundation. 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 @@ -555,6 +555,46 @@ int hif_apps_enable_irq_wake(struct hif_opaque_softc *hif_ctx) return enable_irq_wake(scn->wake_irq); } +int hif_apps_disable_irqs_except_wake_irq(struct hif_opaque_softc *hif_ctx) +{ + struct hif_softc *scn; + int i; + + QDF_BUG(hif_ctx); + scn = HIF_GET_SOFTC(hif_ctx); + if (!scn) + return -EINVAL; + + for (i = 0; i < scn->ce_count; ++i) { + int irq = scn->bus_ops.hif_map_ce_to_irq(scn, i); + + if (irq != scn->wake_irq) + disable_irq(irq); + } + + return 0; +} + +int hif_apps_enable_irqs_except_wake_irq(struct hif_opaque_softc *hif_ctx) +{ + struct hif_softc *scn; + int i; + + QDF_BUG(hif_ctx); + scn = HIF_GET_SOFTC(hif_ctx); + if (!scn) + return -EINVAL; + + for (i = 0; i < scn->ce_count; ++i) { + int irq = scn->bus_ops.hif_map_ce_to_irq(scn, i); + + if (irq != scn->wake_irq) + enable_irq(irq); + } + + return 0; +} + #ifdef WLAN_FEATURE_BMI bool hif_needs_bmi(struct hif_opaque_softc *scn) { diff --git a/hif/src/ipcie/if_ipci.c b/hif/src/ipcie/if_ipci.c index f10a63415f..4d0bc168f8 100644 --- a/hif/src/ipcie/if_ipci.c +++ b/hif/src/ipcie/if_ipci.c @@ -307,29 +307,70 @@ int hif_ipci_bus_suspend(struct hif_softc *scn) { int ret; + ret = hif_apps_disable_irqs_except_wake_irq(GET_HIF_OPAQUE_HDL(scn)); + if (ret) { + hif_err("Failed to disable IRQs"); + goto disable_irq_fail; + } + ret = hif_apps_enable_irq_wake(GET_HIF_OPAQUE_HDL(scn)); + if (ret) { + hif_err("Failed to enable Wake-IRQ"); + goto enable_wake_irq_fail; + } - if (!ret) - scn->bus_suspended = true; + if (QDF_IS_STATUS_ERROR(hif_try_complete_tasks(scn))) { + hif_err("hif_try_complete_tasks timed-out, so abort suspend"); + ret = -EBUSY; + goto drain_tasks_fail; + } + /* + * In an unlikely case, if draining becomes infinite loop, + * it returns an error, shall abort the bus suspend. + */ + ret = hif_drain_fw_diag_ce(scn); + if (ret) { + hif_err("draining fw_diag_ce goes infinite, so abort suspend"); + goto drain_tasks_fail; + } + + scn->bus_suspended = true; + + return 0; + +drain_tasks_fail: + hif_apps_disable_irq_wake(GET_HIF_OPAQUE_HDL(scn)); + +enable_wake_irq_fail: + hif_apps_enable_irqs_except_wake_irq(GET_HIF_OPAQUE_HDL(scn)); + +disable_irq_fail: return ret; } int hif_ipci_bus_resume(struct hif_softc *scn) { + int ret = 0; + + ret = hif_apps_disable_irq_wake(GET_HIF_OPAQUE_HDL(scn)); + if (ret) { + hif_err("Failed to disable Wake-IRQ"); + goto fail; + } + + ret = hif_apps_enable_irqs_except_wake_irq(GET_HIF_OPAQUE_HDL(scn)); + if (ret) + hif_err("Failed to enable IRQs"); + scn->bus_suspended = false; - return hif_apps_disable_irq_wake(GET_HIF_OPAQUE_HDL(scn)); +fail: + return ret; } int hif_ipci_bus_suspend_noirq(struct hif_softc *scn) { - QDF_STATUS ret; - - ret = hif_try_complete_tasks(scn); - if (QDF_IS_STATUS_ERROR(ret)) - return -EBUSY; - return 0; }