/* * Copyright (c) 2015-2021 The Linux Foundation. All rights reserved. * Copyright (c) 2021-2024 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 #include #include "cds_api.h" #include "qdf_status.h" #include "qdf_lock.h" #include "cds_sched.h" #include "osdep.h" #include "hif.h" #include "htc.h" #include "epping_main.h" #include "osif_sync.h" #include "wlan_hdd_main.h" #include "wlan_hdd_power.h" #include "wlan_logging_sock_svc.h" #include "wma_api.h" #include "wlan_hdd_napi.h" #include "wlan_policy_mgr_api.h" #include "qwlan_version.h" #include "bmi.h" #include #include "cdp_txrx_bus.h" #include "cdp_txrx_misc.h" #include "pld_common.h" #include "wlan_hdd_driver_ops.h" #include "wlan_ipa_ucfg_api.h" #include "wlan_hdd_debugfs.h" #include "cfg_ucfg_api.h" #include #include #include #include "wlan_hdd_thermal.h" #include "wlan_dp_ucfg_api.h" #include "qdf_ssr_driver_dump.h" #include "wlan_hdd_ioctl.h" #ifdef MODULE #ifdef WLAN_WEAR_CHIPSET #define WLAN_MODULE_NAME "wlan" #else #define WLAN_MODULE_NAME module_name(THIS_MODULE) #endif #else #define WLAN_MODULE_NAME "wlan" #endif #define SSR_MAX_FAIL_CNT 3 static uint8_t re_init_fail_cnt, probe_fail_cnt; /* An atomic flag to check if SSR cleanup has been done or not */ static qdf_atomic_t is_recovery_cleanup_done; /* firmware/host hang event data */ static uint8_t g_fw_host_hang_event[QDF_HANG_EVENT_DATA_SIZE]; /* * In BMI Phase we are only sending small chunk (256 bytes) of the FW image at * a time, and wait for the completion interrupt to start the next transfer. * During this phase, the KRAIT is entering IDLE/StandAlone(SA) Power Save(PS). * The delay incurred for resuming from IDLE/SA PS is huge during driver load. * So prevent APPS IDLE/SA PS durint driver load for reducing interrupt latency. */ static inline void hdd_request_pm_qos(struct device *dev, int val) { pld_request_pm_qos(dev, val); } static inline void hdd_remove_pm_qos(struct device *dev) { pld_remove_pm_qos(dev); } /** * hdd_get_bandwidth_level() - get current bandwidth level * @data: Context * * Return: current bandwidth level */ static int hdd_get_bandwidth_level(void *data) { int ret = PLD_BUS_WIDTH_NONE; struct hdd_context *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); if (hdd_ctx) ret = ucfg_dp_get_current_throughput_level(hdd_ctx->psoc); return ret; } #ifdef DP_MEM_PRE_ALLOC /** * hdd_get_consistent_mem_unaligned() - API to get consistent unaligned mem * @size: Size of memory required * @paddr: Pointer to paddr to be filled in by API * @ring_type: Pointer to ring type for which consistent memory is needed * * Return: Virtual address of consistent memory on success, else null */ static void *hdd_get_consistent_mem_unaligned(size_t size, qdf_dma_addr_t *paddr, uint32_t ring_type) { return ucfg_dp_prealloc_get_consistent_mem_unaligned(size, paddr, ring_type); } /** * hdd_put_consistent_mem_unaligned() - API to put consistent unaligned mem * @vaddr: Virtual address of memory * * Return: None */ static void hdd_put_consistent_mem_unaligned(void *vaddr) { ucfg_dp_prealloc_put_consistent_mem_unaligned(vaddr); } /** * hdd_dp_prealloc_get_multi_pages() - gets pre-alloc DP multi-pages memory * @desc_type: descriptor type * @elem_size: single element size * @elem_num: total number of elements should be allocated * @pages: multi page information storage * @cacheable: coherent memory or cacheable memory * * Return: None */ static void hdd_dp_prealloc_get_multi_pages(uint32_t desc_type, qdf_size_t elem_size, uint16_t elem_num, struct qdf_mem_multi_page_t *pages, bool cacheable) { ucfg_dp_prealloc_get_multi_pages(desc_type, elem_size, elem_num, pages, cacheable); } /** * hdd_dp_prealloc_put_multi_pages() - puts back pre-alloc DP multi-pages memory * @desc_type: descriptor type * @pages: multi page information storage * * Return: None */ static void hdd_dp_prealloc_put_multi_pages(uint32_t desc_type, struct qdf_mem_multi_page_t *pages) { ucfg_dp_prealloc_put_multi_pages(desc_type, pages); } #else static void *hdd_get_consistent_mem_unaligned(size_t size, qdf_dma_addr_t *paddr, uint32_t ring_type) { hdd_err_rl("prealloc not support!"); return NULL; } static void hdd_put_consistent_mem_unaligned(void *vaddr) { hdd_err_rl("prealloc not support!"); } static inline void hdd_dp_prealloc_get_multi_pages(uint32_t desc_type, qdf_size_t elem_size, uint16_t elem_num, struct qdf_mem_multi_page_t *pages, bool cacheable) { } static inline void hdd_dp_prealloc_put_multi_pages(uint32_t desc_type, struct qdf_mem_multi_page_t *pages) { } #endif /** * hdd_is_driver_unloading() - API to query if driver is unloading * @data: Private Data * * Return: True/False */ static bool hdd_is_driver_unloading(void *data) { return cds_is_driver_unloading(); } /** * hdd_is_load_or_unload_in_progress() - API to query if driver is * loading/unloading * @data: Private Data * * Return: bool */ static bool hdd_is_load_or_unload_in_progress(void *data) { return cds_is_load_or_unload_in_progress(); } /** * hdd_is_recovery_in_progress() - API to query if recovery in progress * @data: Private Data * * Return: bool */ static bool hdd_is_recovery_in_progress(void *data) { return cds_is_driver_recovering(); } /** * hdd_is_target_ready() - API to query if target is in ready state * @data: Private Data * * Return: bool */ static bool hdd_is_target_ready(void *data) { return cds_is_target_ready(); } /** * hdd_send_driver_ready_to_user() - API to indicate driver ready * to userspace. */ static void hdd_send_driver_ready_to_user(void) { struct sk_buff *nl_event; struct hdd_context *hdd_ctx; int flags = cds_get_gfp_flags(); hdd_enter(); hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); if (!hdd_ctx) { hdd_err("HDD Context is NULL"); return; } nl_event = wlan_cfg80211_vendor_event_alloc( hdd_ctx->wiphy, NULL, 0, QCA_NL80211_VENDOR_SUBCMD_DRIVER_READY_INDEX, flags); if (!nl_event) { hdd_err("wlan_cfg80211_vendor_event_alloc failed"); return; } wlan_cfg80211_vendor_event(nl_event, flags); } #ifdef FEATURE_WLAN_DIAG_SUPPORT /** * hdd_wlan_ssr_shutdown_event()- send ssr shutdown state * * This Function sends ssr shutdown state diag event * * Return: void. */ static void hdd_wlan_ssr_shutdown_event(void) { WLAN_HOST_DIAG_EVENT_DEF(ssr_shutdown, struct host_event_wlan_ssr_shutdown); qdf_mem_zero(&ssr_shutdown, sizeof(ssr_shutdown)); ssr_shutdown.status = SSR_SUB_SYSTEM_SHUTDOWN; WLAN_HOST_DIAG_EVENT_REPORT(&ssr_shutdown, EVENT_WLAN_SSR_SHUTDOWN_SUBSYSTEM); } #else static inline void hdd_wlan_ssr_shutdown_event(void) { } #endif /** * hdd_psoc_shutdown_notify() - notify the various interested parties that the * soc is starting recovery shutdown * @hdd_ctx: the HDD context corresponding to the soc undergoing shutdown * * Return: None */ static void hdd_psoc_shutdown_notify(struct hdd_context *hdd_ctx) { hdd_enter(); wlan_cfg80211_cleanup_scan_queue(hdd_ctx->pdev, NULL); cds_shutdown_notifier_call(); cds_shutdown_notifier_purge(); hdd_wlan_ssr_shutdown_event(); hdd_exit(); } /** * hdd_soc_recovery_cleanup() - Perform SSR related cleanup activities. * * This function will perform cleanup activities related to when driver * undergoes SSR. Activities include stopping idle timer and invoking shutdown * notifier. * * Return: None */ static void hdd_soc_recovery_cleanup(void) { struct hdd_context *hdd_ctx; hdd_enter(); hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); if (!hdd_ctx) return; /* cancel/flush any pending/active idle shutdown work */ hdd_psoc_idle_timer_stop(hdd_ctx); ucfg_dp_bus_bw_compute_timer_stop(hdd_ctx->psoc); /* nothing to do if the soc is already unloaded */ if (hdd_ctx->driver_status == DRIVER_MODULES_CLOSED) { hdd_info("Driver modules are already closed"); return; } if (cds_is_load_or_unload_in_progress()) { hdd_info("Load/unload in progress, ignore SSR shutdown"); return; } hdd_psoc_shutdown_notify(hdd_ctx); hdd_exit(); } /** * hdd_set_recovery_in_progress() - API to set recovery in progress * @data: Context * @val: Value to set * * Return: None */ static void hdd_set_recovery_in_progress(void *data, uint8_t val) { cds_set_recovery_in_progress(val); /* SSR can be triggred late cleanup existing queue for kernel handshake */ if (!qdf_in_interrupt()) hdd_soc_recovery_cleanup(); } /** * hdd_hif_init_driver_state_callbacks() - API to initialize HIF callbacks * @data: Private Data * @cbk: HIF Driver State callbacks * * HIF should be independent of CDS calls. Pass CDS Callbacks to HIF, HIF will * call the callbacks. * * Return: void */ static void hdd_hif_init_driver_state_callbacks(void *data, struct hif_driver_state_callbacks *cbk) { cbk->context = data; cbk->set_recovery_in_progress = hdd_set_recovery_in_progress; cbk->is_recovery_in_progress = hdd_is_recovery_in_progress; cbk->is_load_unload_in_progress = hdd_is_load_or_unload_in_progress; cbk->is_driver_unloading = hdd_is_driver_unloading; cbk->is_target_ready = hdd_is_target_ready; cbk->get_bandwidth_level = hdd_get_bandwidth_level; cbk->prealloc_get_consistent_mem_unaligned = hdd_get_consistent_mem_unaligned; cbk->prealloc_put_consistent_mem_unaligned = hdd_put_consistent_mem_unaligned; cbk->prealloc_get_multi_pages = hdd_dp_prealloc_get_multi_pages; cbk->prealloc_put_multi_pages = hdd_dp_prealloc_put_multi_pages; } #ifdef HIF_DETECTION_LATENCY_ENABLE void hdd_hif_set_enable_detection(struct hif_opaque_softc *hif_ctx, bool value) { hif_set_enable_detection(hif_ctx, value); } #endif #ifdef FORCE_WAKE void hdd_set_hif_init_phase(struct hif_opaque_softc *hif_ctx, bool hal_init_phase) { hif_srng_init_phase(hif_ctx, hal_init_phase); } #endif /* FORCE_WAKE */ /** * hdd_hif_set_attribute() - API to set CE attribute if memory is limited * @hif_ctx: hif context * * Return: None */ #ifdef SLUB_MEM_OPTIMIZE static void hdd_hif_set_attribute(struct hif_opaque_softc *hif_ctx) { hif_set_attribute(hif_ctx, HIF_LOWDESC_CE_NO_PKTLOG_CFG); } #else static void hdd_hif_set_attribute(struct hif_opaque_softc *hif_ctx) {} #endif /** * hdd_hif_register_shutdown_notifier() - Register HIF shutdown notifier * @hif_ctx: HIF Context * * Return: success/failure */ static QDF_STATUS hdd_hif_register_shutdown_notifier(struct hif_opaque_softc *hif_ctx) { return cds_shutdown_notifier_register( hif_shutdown_notifier_cb, hif_ctx); } /** * hdd_hif_set_ce_max_yield_time() - Wrapper API to set CE max yield time * @hif_ctx: hif context * @bus_type: underlying bus type * @ce_service_max_yield_time: max yield time to be set * * Return: None */ #if defined(CONFIG_SLUB_DEBUG_ON) static void hdd_hif_set_ce_max_yield_time(struct hif_opaque_softc *hif_ctx, enum qdf_bus_type bus_type, uint32_t ce_service_max_yield_time) { #define CE_SNOC_MAX_YIELD_TIME_US 2000 if (bus_type == QDF_BUS_TYPE_SNOC && ce_service_max_yield_time < CE_SNOC_MAX_YIELD_TIME_US) ce_service_max_yield_time = CE_SNOC_MAX_YIELD_TIME_US; hif_set_ce_service_max_yield_time(hif_ctx, ce_service_max_yield_time); } #else static void hdd_hif_set_ce_max_yield_time(struct hif_opaque_softc *hif_ctx, enum qdf_bus_type bus_type, uint32_t ce_service_max_yield_time) { hif_set_ce_service_max_yield_time(hif_ctx, ce_service_max_yield_time); } #endif /** * hdd_init_cds_hif_context() - API to set CDS HIF Context * @hif: HIF Context * * Return: success/failure */ static int hdd_init_cds_hif_context(void *hif) { QDF_STATUS status; status = cds_set_context(QDF_MODULE_ID_HIF, hif); if (status) return -ENOENT; return 0; } /** * hdd_deinit_cds_hif_context() - API to clear CDS HIF COntext * * Return: None */ static void hdd_deinit_cds_hif_context(void) { QDF_STATUS status; status = cds_set_context(QDF_MODULE_ID_HIF, NULL); if (status) hdd_err("Failed to reset CDS HIF Context"); } /** * to_bus_type() - Map PLD bus type to low level bus type * @bus_type: PLD bus type * * Map PLD bus type to low level bus type. * * Return: low level bus type. */ static enum qdf_bus_type to_bus_type(enum pld_bus_type bus_type) { switch (bus_type) { case PLD_BUS_TYPE_PCIE_FW_SIM: case PLD_BUS_TYPE_PCIE: return QDF_BUS_TYPE_PCI; case PLD_BUS_TYPE_SNOC_FW_SIM: case PLD_BUS_TYPE_SNOC: return QDF_BUS_TYPE_SNOC; case PLD_BUS_TYPE_SDIO: return QDF_BUS_TYPE_SDIO; case PLD_BUS_TYPE_USB: return QDF_BUS_TYPE_USB; case PLD_BUS_TYPE_IPCI_FW_SIM: case PLD_BUS_TYPE_IPCI: return QDF_BUS_TYPE_IPCI; default: return QDF_BUS_TYPE_NONE; } } int hdd_hif_open(struct device *dev, void *bdev, const struct hif_bus_id *bid, enum qdf_bus_type bus_type, bool reinit) { QDF_STATUS status; int ret = 0; struct hif_opaque_softc *hif_ctx; qdf_device_t qdf_ctx = cds_get_context(QDF_MODULE_ID_QDF_DEVICE); struct hif_driver_state_callbacks cbk; uint32_t mode = cds_get_conparam(); struct hdd_context *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); if (!hdd_ctx) return -EFAULT; hdd_hif_init_driver_state_callbacks(dev, &cbk); hif_ctx = hif_open(qdf_ctx, mode, bus_type, &cbk, hdd_ctx->psoc); if (!hif_ctx) { hdd_err("hif_open error"); return -ENOMEM; } ret = hdd_init_cds_hif_context(hif_ctx); if (ret) { hdd_err("Failed to set global HIF CDS Context err: %d", ret); goto err_hif_close; } status = hdd_hif_register_shutdown_notifier(hif_ctx); if (status != QDF_STATUS_SUCCESS) { hdd_err("Shutdown notifier register failed: %d", status); goto err_deinit_hif_context; } hdd_hif_set_attribute(hif_ctx); status = hif_enable(hif_ctx, dev, bdev, bid, bus_type, (reinit == true) ? HIF_ENABLE_TYPE_REINIT : HIF_ENABLE_TYPE_PROBE); if (!QDF_IS_STATUS_SUCCESS(status)) { hdd_err("hif_enable failed status: %d, reinit: %d", status, reinit); ret = qdf_status_to_os_return(status); goto err_deinit_hif_context; } else { cds_set_target_ready(true); ret = hdd_napi_create(); hdd_debug("hdd_napi_create returned: %d", ret); if (ret == 0) hdd_debug("NAPI: no instances are created"); else if (ret < 0) { hdd_err("NAPI creation error, rc: 0x%x, reinit: %d", ret, reinit); ret = -EFAULT; goto mark_target_not_ready; } else { hdd_napi_event(NAPI_EVT_INI_FILE, (void *)ucfg_dp_get_napi_enabled(hdd_ctx->psoc)); } } hdd_hif_set_ce_max_yield_time( hif_ctx, bus_type, cfg_get(hdd_ctx->psoc, CFG_DP_CE_SERVICE_MAX_YIELD_TIME)); ucfg_pmo_psoc_set_hif_handle(hdd_ctx->psoc, hif_ctx); ucfg_dp_set_hif_handle(hdd_ctx->psoc, hif_ctx); hif_set_ce_service_max_rx_ind_flush(hif_ctx, cfg_get(hdd_ctx->psoc, CFG_DP_CE_SERVICE_MAX_RX_IND_FLUSH)); return 0; mark_target_not_ready: cds_set_target_ready(false); err_deinit_hif_context: hdd_deinit_cds_hif_context(); err_hif_close: hif_close(hif_ctx); return ret; } void hdd_hif_close(struct hdd_context *hdd_ctx, void *hif_ctx) { if (!hdd_ctx) { hdd_err("hdd_ctx error"); return; } if (!hif_ctx) return; cds_set_target_ready(false); hif_disable(hif_ctx, HIF_DISABLE_TYPE_REMOVE); hdd_napi_destroy(true); hdd_deinit_cds_hif_context(); hif_close(hif_ctx); ucfg_pmo_psoc_set_hif_handle(hdd_ctx->psoc, NULL); } /** * hdd_init_qdf_ctx() - API to initialize global QDF Device structure * @dev: Device Pointer * @bdev: Bus Device pointer * @bus_type: Underlying bus type * @bid: Bus id passed by platform driver * * Return: 0 - success, < 0 - failure */ static int hdd_init_qdf_ctx(struct device *dev, void *bdev, enum qdf_bus_type bus_type, const struct hif_bus_id *bid) { qdf_device_t qdf_dev = cds_get_context(QDF_MODULE_ID_QDF_DEVICE); if (!qdf_dev) return -EINVAL; qdf_dev->dev = dev; qdf_dev->drv_hdl = bdev; qdf_dev->bus_type = bus_type; qdf_dev->bid = bid; qdf_dma_invalid_buf_list_init(); if (cds_smmu_mem_map_setup(qdf_dev, ucfg_ipa_is_ready()) != QDF_STATUS_SUCCESS) { hdd_err("cds_smmu_mem_map_setup() failed"); } return 0; } /** * hdd_deinit_qdf_ctx() - API to Deinitialize global QDF Device structure * @domain: Debug domain * * Return: 0 - success, < 0 - failure */ int hdd_deinit_qdf_ctx(uint8_t domain) { qdf_device_t qdf_dev = cds_get_context(QDF_MODULE_ID_QDF_DEVICE); if (!qdf_dev) return -EINVAL; qdf_dma_invalid_buf_free(qdf_dev->dev, domain); return 0; } /** * check_for_probe_defer() - API to check return value * @ret: Return Value * * Return: return -EPROBE_DEFER to platform driver if return value * is -ENOMEM. Platform driver will try to re-probe. */ #ifdef MODULE static int check_for_probe_defer(int ret) { return ret; } #else static int check_for_probe_defer(int ret) { if (ret == -ENOMEM) return -EPROBE_DEFER; return ret; } #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) static void hdd_abort_system_suspend(struct device *dev) { qdf_pm_system_wakeup(); } #else static void hdd_abort_system_suspend(struct device *dev) { } #endif int hdd_soc_idle_restart_lock(struct device *dev) { hdd_prevent_suspend(WIFI_POWER_EVENT_WAKELOCK_DRIVER_IDLE_RESTART); hdd_abort_system_suspend(dev); return 0; } void hdd_soc_idle_restart_unlock(void) { hdd_allow_suspend(WIFI_POWER_EVENT_WAKELOCK_DRIVER_IDLE_RESTART); } static void hdd_soc_load_lock(struct device *dev) { hdd_prevent_suspend(WIFI_POWER_EVENT_WAKELOCK_DRIVER_INIT); hdd_request_pm_qos(dev, DISABLE_KRAIT_IDLE_PS_VAL); } static void hdd_soc_load_unlock(struct device *dev) { hdd_remove_pm_qos(dev); hdd_allow_suspend(WIFI_POWER_EVENT_WAKELOCK_DRIVER_INIT); } #ifdef DP_MEM_PRE_ALLOC /** * hdd_init_dma_mask() - Set the DMA mask for dma memory pre-allocation * @dev: device handle * @bus_type: Bus type for which init is being done * * Return: 0 - success, non-zero on failure */ static int hdd_init_dma_mask(struct device *dev, enum qdf_bus_type bus_type) { return hif_init_dma_mask(dev, bus_type); } #else static inline int hdd_init_dma_mask(struct device *dev, enum qdf_bus_type bus_type) { return QDF_STATUS_SUCCESS; } #endif static int __hdd_soc_probe(struct device *dev, void *bdev, const struct hif_bus_id *bid, enum qdf_bus_type bus_type) { struct hdd_context *hdd_ctx; QDF_STATUS status; int errno; hdd_info("probing driver"); hdd_soc_load_lock(dev); cds_set_load_in_progress(true); cds_set_driver_in_bad_state(false); cds_set_recovery_in_progress(false); errno = hdd_init_qdf_ctx(dev, bdev, bus_type, bid); if (errno) goto unlock; errno = hdd_init_dma_mask(dev, bus_type); if (errno) goto unlock; hdd_ctx = hdd_context_create(dev); if (IS_ERR(hdd_ctx)) { errno = PTR_ERR(hdd_ctx); goto assert_fail_count; } status = ucfg_dp_prealloc_init((struct cdp_ctrl_objmgr_psoc *) hdd_ctx->psoc); if (status != QDF_STATUS_SUCCESS) { errno = qdf_status_to_os_return(status); goto dp_prealloc_fail; } errno = hdd_wlan_startup(hdd_ctx); if (errno) goto hdd_context_destroy; status = hdd_psoc_create_vdevs(hdd_ctx); if (QDF_IS_STATUS_ERROR(status)) { errno = qdf_status_to_os_return(status); goto wlan_exit; } probe_fail_cnt = 0; cds_set_driver_loaded(true); cds_set_load_in_progress(false); hdd_start_complete(0); hdd_thermal_mitigation_register(hdd_ctx, dev); hdd_set_sar_init_index(hdd_ctx); hdd_soc_load_unlock(dev); return 0; wlan_exit: hdd_wlan_exit(hdd_ctx); hdd_context_destroy: ucfg_dp_prealloc_deinit(); dp_prealloc_fail: hdd_context_destroy(hdd_ctx); assert_fail_count: probe_fail_cnt++; hdd_err("consecutive probe failures:%u", probe_fail_cnt); QDF_BUG(probe_fail_cnt < SSR_MAX_FAIL_CNT); unlock: cds_set_load_in_progress(false); hdd_soc_load_unlock(dev); return check_for_probe_defer(errno); } /** * hdd_soc_probe() - perform SoC probe * @dev: kernel device being probed * @bdev: bus device structure * @bid: bus identifier for shared busses * @bus_type: underlying bus type * * A SoC probe indicates new SoC hardware has become available and needs to be * initialized. * * Return: Errno */ static int hdd_soc_probe(struct device *dev, void *bdev, const struct hif_bus_id *bid, enum qdf_bus_type bus_type) { struct osif_psoc_sync *psoc_sync; int errno; hdd_info("probing driver"); errno = osif_psoc_sync_create_and_trans(&psoc_sync); if (errno) return errno; osif_psoc_sync_register(dev, psoc_sync); errno = __hdd_soc_probe(dev, bdev, bid, bus_type); if (errno) goto destroy_sync; osif_psoc_sync_trans_stop(psoc_sync); return 0; destroy_sync: osif_psoc_sync_unregister(dev); osif_psoc_sync_wait_for_ops(psoc_sync); osif_psoc_sync_trans_stop(psoc_sync); osif_psoc_sync_destroy(psoc_sync); return errno; } static int __hdd_soc_recovery_reinit(struct device *dev, void *bdev, const struct hif_bus_id *bid, enum qdf_bus_type bus_type) { int errno; struct hdd_context *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); hdd_info("re-probing driver"); if (!hdd_ctx) { hdd_err("hdd_ctx is null!"); return qdf_status_to_os_return(QDF_STATUS_E_RESOURCES); } hdd_soc_load_lock(dev); cds_set_driver_in_bad_state(false); errno = hdd_init_qdf_ctx(dev, bdev, bus_type, bid); if (errno) goto unlock; errno = hdd_wlan_re_init(); if (errno) { re_init_fail_cnt++; goto assert_fail_count; } re_init_fail_cnt = 0; /* * In case of SSR within SSR we have seen the race * where the reinit is successful and fw down is received * which sets the recovery in progress. Now as reinit is * successful we reset the recovery in progress here. * So check if FW is down then don't reset the recovery * in progress */ if (!qdf_is_fw_down()) { cds_set_recovery_in_progress(false); hdd_handle_cached_commands(); } if (!hdd_is_any_interface_open(hdd_ctx)) { hdd_debug("restarting idle shutdown timer"); hdd_psoc_idle_timer_start(hdd_ctx); } hdd_soc_load_unlock(dev); hdd_send_driver_ready_to_user(); return 0; assert_fail_count: hdd_err("consecutive reinit failures:%u", re_init_fail_cnt); QDF_BUG(re_init_fail_cnt < SSR_MAX_FAIL_CNT); unlock: cds_set_driver_in_bad_state(true); hdd_soc_load_unlock(dev); hdd_start_complete(errno); return check_for_probe_defer(errno); } /** * hdd_soc_recovery_reinit() - perform PDR/SSR SoC reinit * @dev: the kernel device being re-initialized * @bdev: bus device structure * @bid: bus identifier for shared busses * @bus_type: underlying bus type * * When communication with firmware breaks down, a SoC recovery process kicks in * with two phases: shutdown and reinit. * * SSR reinit is similar to a 'probe' but happens in response to an SSR * shutdown. The idea is to re-initialize the SoC to as close to its old, * pre-communications-breakdown configuration as possible. This is completely * transparent from a userspace point of view. * * Return: Errno */ static int hdd_soc_recovery_reinit(struct device *dev, void *bdev, const struct hif_bus_id *bid, enum qdf_bus_type bus_type) { struct osif_psoc_sync *psoc_sync; int errno; /* if driver is unloading, there is no need to do SSR */ if (qdf_is_driver_unloading()) { hdd_info("driver is unloading, avoid SSR"); return 0; } /* SSR transition is initiated at the beginning of soc shutdown */ errno = osif_psoc_sync_trans_resume(dev, &psoc_sync); QDF_BUG(!errno); if (errno) return errno; errno = __hdd_soc_recovery_reinit(dev, bdev, bid, bus_type); osif_psoc_sync_trans_stop(psoc_sync); hdd_start_complete(0); return errno; } static void __hdd_soc_remove(struct device *dev) { struct hdd_context *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); QDF_BUG(hdd_ctx); if (!hdd_ctx) return; pr_info("%s: Removing driver v%s\n", WLAN_MODULE_NAME, QWLAN_VERSIONSTR); qdf_rtpm_sync_resume(); cds_set_driver_loaded(false); cds_set_unload_in_progress(true); if (!hdd_wait_for_debugfs_threads_completion()) hdd_warn("Debugfs threads are still active attempting driver unload anyway"); if (hdd_get_conparam() == QDF_GLOBAL_EPPING_MODE) { hdd_wlan_stop_modules(hdd_ctx, false); qdf_nbuf_deinit_replenish_timer(); } else { hdd_thermal_mitigation_unregister(hdd_ctx, dev); hdd_wlan_exit(hdd_ctx); } hdd_context_destroy(hdd_ctx); cds_set_driver_in_bad_state(false); cds_set_unload_in_progress(false); ucfg_dp_prealloc_deinit(); pr_info("%s: Driver De-initialized\n", WLAN_MODULE_NAME); } /** * hdd_soc_remove() - perform SoC remove * @dev: the kernel device being removed * * A SoC remove indicates the attached SoC hardware is about to go away and * needs to be cleaned up. * * Return: void */ static void hdd_soc_remove(struct device *dev) { __hdd_soc_remove(dev); } /** * hdd_send_hang_data() - Send hang data to userspace * @data: Hang data * @data_len: Length of @data * * Return: None */ static void hdd_send_hang_data(uint8_t *data, size_t data_len) { enum qdf_hang_reason reason = QDF_REASON_UNSPECIFIED; struct hdd_context *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); if (!hdd_ctx) return; cds_get_recovery_reason(&reason); cds_reset_recovery_reason(); wlan_hdd_send_hang_reason_event(hdd_ctx, reason, data, data_len); } static void __hdd_soc_recovery_shutdown(void) { struct hdd_context *hdd_ctx; void *hif_ctx; /* recovery starts via firmware down indication; ensure we got one */ QDF_BUG(cds_is_driver_recovering()); hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); if (!hdd_ctx) return; /* * Perform SSR related cleanup if it has not already been done as a * part of receiving the uevent. */ if (!qdf_atomic_read(&is_recovery_cleanup_done)) hdd_soc_recovery_cleanup(); else qdf_atomic_set(&is_recovery_cleanup_done, 0); if (!hdd_wait_for_debugfs_threads_completion()) hdd_err("Debugfs threads are still pending, attempting SSR anyway"); hif_ctx = cds_get_context(QDF_MODULE_ID_HIF); if (!hif_ctx) return; /* mask the host controller interrupts */ hif_mask_interrupt_call(hif_ctx); if (!QDF_IS_EPPING_ENABLED(cds_get_conparam())) { hif_disable_isr(hif_ctx); hdd_wlan_shutdown(); } } /** * hdd_soc_recovery_shutdown() - perform PDR/SSR SoC shutdown * @dev: the device to shutdown * * When communication with firmware breaks down, a SoC recovery process kicks in * with two phases: shutdown and reinit. * * SSR shutdown is similar to a 'remove' but without communication with * firmware. The idea is to retain as much SoC configuration as possible, so it * can be re-initialized to the same state after a reset. This is completely * transparent from a userspace point of view. * * Return: void */ static void hdd_soc_recovery_shutdown(struct device *dev) { struct osif_psoc_sync *psoc_sync; int errno; /* if driver is unloading, there is no need to do SSR */ if (qdf_is_driver_unloading()) { hdd_info("driver is unloading, avoid SSR"); return; } errno = osif_psoc_sync_trans_start_wait(dev, &psoc_sync); if (errno) return; ucfg_dp_wait_complete_tasks(); osif_psoc_sync_wait_for_ops(psoc_sync); __hdd_soc_recovery_shutdown(); /* SSR transition is concluded at the end of soc re-init */ } /** * wlan_hdd_crash_shutdown() - wlan_hdd_crash_shutdown * * HDD crash shutdown function: This function is called by * platform driver's crash shutdown routine * * Return: void */ static void wlan_hdd_crash_shutdown(void) { QDF_STATUS ret; WMA_HANDLE wma_handle = cds_get_context(QDF_MODULE_ID_WMA); if (!wma_handle) return; /* * When kernel panic happen, if WiFi FW is still active * it may cause NOC errors/memory corruption, to avoid * this, inject a fw crash first. * send crash_inject to FW directly, because we are now * in an atomic context, and preempt has been disabled, * MCThread won't be scheduled at the moment, at the same * time, TargetFailure event won't be received after inject * crash due to the same reason. */ ret = wma_crash_inject(wma_handle, RECOVERY_SIM_ASSERT, 0); if (QDF_IS_STATUS_ERROR(ret)) { hdd_err("Failed to send crash inject:%d", ret); return; } hif_crash_shutdown(cds_get_context(QDF_MODULE_ID_HIF)); } /** * wlan_hdd_notify_handler() - wlan_hdd_notify_handler * * This function is called by the platform driver to notify the * COEX * * @state: state * * Return: void */ static void wlan_hdd_notify_handler(int state) { if (!QDF_IS_EPPING_ENABLED(cds_get_conparam())) { int ret; ret = hdd_wlan_notify_modem_power_state(state); if (ret < 0) hdd_err("Fail to send notify"); } } static int hdd_to_pmo_interface_pause(enum wow_interface_pause hdd_pause, enum pmo_wow_interface_pause *pmo_pause) { switch (hdd_pause) { case WOW_INTERFACE_PAUSE_DEFAULT: *pmo_pause = PMO_WOW_INTERFACE_PAUSE_DEFAULT; break; case WOW_INTERFACE_PAUSE_ENABLE: *pmo_pause = PMO_WOW_INTERFACE_PAUSE_ENABLE; break; case WOW_INTERFACE_PAUSE_DISABLE: *pmo_pause = PMO_WOW_INTERFACE_PAUSE_DISABLE; break; default: hdd_err("Invalid interface pause: %d", hdd_pause); return -EINVAL; } return 0; } static int hdd_to_pmo_resume_trigger(enum wow_resume_trigger hdd_trigger, enum pmo_wow_resume_trigger *pmo_trigger) { switch (hdd_trigger) { case WOW_RESUME_TRIGGER_DEFAULT: *pmo_trigger = PMO_WOW_RESUME_TRIGGER_DEFAULT; break; case WOW_RESUME_TRIGGER_HTC_WAKEUP: *pmo_trigger = PMO_WOW_RESUME_TRIGGER_HTC_WAKEUP; break; case WOW_RESUME_TRIGGER_GPIO: *pmo_trigger = PMO_WOW_RESUME_TRIGGER_GPIO; break; default: hdd_err("Invalid resume trigger: %d", hdd_trigger); return -EINVAL; } return 0; } static int hdd_to_pmo_wow_enable_params(struct wow_enable_params *in_params, struct pmo_wow_enable_params *out_params) { int err; /* unit-test suspend */ out_params->is_unit_test = in_params->is_unit_test; /* interface pause */ err = hdd_to_pmo_interface_pause(in_params->interface_pause, &out_params->interface_pause); if (err) return err; /* resume trigger */ err = hdd_to_pmo_resume_trigger(in_params->resume_trigger, &out_params->resume_trigger); if (err) return err; return 0; } void __wlan_hdd_trigger_cds_recovery(enum qdf_hang_reason reason, const char *func, const uint32_t line) { __cds_trigger_recovery(reason, func, line); } /** * __wlan_hdd_bus_suspend() - handles platform suspend * @wow_params: collection of wow enable override parameters * @type: WoW suspend type * * Does precondition validation. Ensures that a subsystem restart isn't in * progress. Ensures that no load or unload is in progress. Does: * data path suspend * component (pmo) suspend * hif (bus) suspend * * Return: 0 for success, -EFAULT for null pointers, * -EBUSY or -EAGAIN if another operation is in progress and * wlan will not be ready to suspend in time. */ static int __wlan_hdd_bus_suspend(struct wow_enable_params wow_params, enum qdf_suspend_type type) { int err; QDF_STATUS status; struct hdd_context *hdd_ctx; void *hif_ctx; void *dp_soc; struct pmo_wow_enable_params pmo_params; int pending; struct bbm_params param = {0}; hdd_info("starting bus suspend"); hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); if (!hdd_ctx) return -ENODEV; err = wlan_hdd_validate_context(hdd_ctx); if (0 != err) { if (pld_is_low_power_mode(hdd_ctx->parent_dev)) hdd_debug("low power mode (Deep Sleep/Hibernate)"); else return err; } /* If Wifi is off, return success for system suspend */ if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) { hdd_debug("Driver Module closed; skipping suspend"); return 0; } hif_ctx = cds_get_context(QDF_MODULE_ID_HIF); if (!hif_ctx) return -EINVAL; err = hdd_to_pmo_wow_enable_params(&wow_params, &pmo_params); if (err) { hdd_err("Invalid WoW enable parameters: %d", err); return err; } dp_soc = cds_get_context(QDF_MODULE_ID_SOC); err = qdf_status_to_os_return(ucfg_dp_bus_suspend(dp_soc, OL_TXRX_PDEV_ID)); if (err) { hdd_err("Failed cdp bus suspend: %d", err); return err; } if (ucfg_ipa_is_tx_pending(hdd_ctx->pdev)) { hdd_err("failed due to pending IPA TX comps"); err = -EBUSY; goto resume_dp; } err = hif_bus_early_suspend(hif_ctx); if (err) { hdd_err("Failed hif bus early suspend"); goto resume_dp; } status = ucfg_pmo_psoc_bus_suspend_req(hdd_ctx->psoc, type, &pmo_params); err = qdf_status_to_os_return(status); if (err) { hdd_err("Failed pmo bus suspend: %d", status); goto late_hif_resume; } hif_system_pm_set_state_suspended(hif_ctx); err = hif_bus_suspend(hif_ctx); if (err) { hdd_err("Failed hif bus suspend: %d", err); goto resume_pmo; } status = ucfg_pmo_core_txrx_suspend(hdd_ctx->psoc); err = qdf_status_to_os_return(status); if (err) { hdd_err("Failed to suspend TXRX: %d", err); goto resume_hif; } pending = cdp_rx_get_pending(cds_get_context(QDF_MODULE_ID_SOC)); if (pending) { hdd_debug("Prevent suspend, RX frame pending %d", pending); err = -EBUSY; goto resume_txrx; } if (hif_try_prevent_ep_vote_access(hif_ctx)) { hdd_debug("Prevent suspend, ep work pending"); err = -EBUSY; goto resume_txrx; } /* * Remove bus votes at the very end, after making sure there are no * pending bus transactions from WLAN SOC for TX/RX. */ param.policy = BBM_NON_PERSISTENT_POLICY; param.policy_info.flag = BBM_APPS_SUSPEND; ucfg_dp_bbm_apply_independent_policy(hdd_ctx->psoc, ¶m); hdd_info("bus suspend succeeded"); return 0; resume_txrx: status = ucfg_pmo_core_txrx_resume(hdd_ctx->psoc); if (QDF_IS_STATUS_ERROR(status)) { wlan_hdd_trigger_cds_recovery(QDF_RESUME_TIMEOUT); return qdf_status_to_os_return(status); } resume_hif: status = hif_bus_resume(hif_ctx); if (QDF_IS_STATUS_ERROR(status)) { wlan_hdd_trigger_cds_recovery(QDF_RESUME_TIMEOUT); return qdf_status_to_os_return(status); } resume_pmo: status = ucfg_pmo_psoc_bus_resume_req(hdd_ctx->psoc, type); if (QDF_IS_STATUS_ERROR(status)) { wlan_hdd_trigger_cds_recovery(QDF_RESUME_TIMEOUT); return qdf_status_to_os_return(status); } late_hif_resume: status = hif_bus_late_resume(hif_ctx); if (QDF_IS_STATUS_ERROR(status)) { wlan_hdd_trigger_cds_recovery(QDF_RESUME_TIMEOUT); return qdf_status_to_os_return(status); } resume_dp: status = ucfg_dp_bus_resume(dp_soc, OL_TXRX_PDEV_ID); if (QDF_IS_STATUS_ERROR(status)) { wlan_hdd_trigger_cds_recovery(QDF_RESUME_TIMEOUT); return qdf_status_to_os_return(status); } hif_system_pm_set_state_on(hif_ctx); return err; } int wlan_hdd_bus_suspend(void) { struct wow_enable_params default_params = {0}; return __wlan_hdd_bus_suspend(default_params, QDF_SYSTEM_SUSPEND); } #ifdef WLAN_SUSPEND_RESUME_TEST int wlan_hdd_unit_test_bus_suspend(struct wow_enable_params wow_params) { return __wlan_hdd_bus_suspend(wow_params, QDF_UNIT_TEST_WOW_SUSPEND); } #endif /** * wlan_hdd_bus_suspend_noirq() - handle .suspend_noirq callback * * This function is called by the platform driver to complete the * bus suspend callback when device interrupts are disabled by kernel. * Call HIF and WMA suspend_noirq callbacks to make sure there is no * wake up pending from FW before allowing suspend. * * Return: 0 for success and -EBUSY if FW is requesting wake up */ int wlan_hdd_bus_suspend_noirq(void) { struct hdd_context *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); void *hif_ctx; int errno; uint32_t pending_events; hdd_debug("start bus_suspend_noirq"); if (!hdd_ctx) return -ENODEV; /* If Wifi is off, return success for system suspend */ if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) { hdd_debug("Driver module closed; skip bus-noirq suspend"); return 0; } errno = wlan_hdd_validate_context(hdd_ctx); if (errno) return errno; hif_ctx = cds_get_context(QDF_MODULE_ID_HIF); if (!hif_ctx) return -EINVAL; errno = hif_bus_suspend_noirq(hif_ctx); if (errno) goto done; errno = ucfg_pmo_psoc_is_target_wake_up_received(hdd_ctx->psoc); if (errno == -EAGAIN) { hdd_err("Firmware attempting wakeup, try again"); wlan_hdd_inc_suspend_stats(hdd_ctx, SUSPEND_FAIL_INITIAL_WAKEUP); } if (errno) goto resume_hif_noirq; pending_events = wma_critical_events_in_flight(); if (pending_events) { hdd_err("%d critical event(s) in flight; try again", pending_events); errno = -EAGAIN; goto resume_hif_noirq; } hdd_ctx->suspend_resume_stats.suspends++; hdd_debug("bus_suspend_noirq done"); return 0; resume_hif_noirq: QDF_BUG(!hif_bus_resume_noirq(hif_ctx)); done: hdd_err("suspend_noirq failed, status: %d", errno); return errno; } /** * wlan_hdd_bus_resume() - handles platform resume * * @type: WoW suspend type * * Does precondition validation. Ensures that a subsystem restart isn't in * progress. Ensures that no load or unload is in progress. Ensures that * it has valid pointers for the required contexts. * Calls into hif to resume the bus operation. * Calls into wma to handshake with firmware and notify it that the bus is up. * Calls into ol_txrx for symmetry. * Failures are treated as catastrophic. * * return: error code or 0 for success */ int wlan_hdd_bus_resume(enum qdf_suspend_type type) { struct hdd_context *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); void *hif_ctx; int status; QDF_STATUS qdf_status; void *dp_soc; struct bbm_params param = {0}; if (cds_is_driver_recovering()) return 0; hdd_info("starting bus resume"); if (!hdd_ctx) return -ENODEV; /* If Wifi is off, return success for system resume */ if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) { hdd_debug("Driver Module closed; return success"); return 0; } status = wlan_hdd_validate_context(hdd_ctx); if (status) return status; hif_ctx = cds_get_context(QDF_MODULE_ID_HIF); if (!hif_ctx) return -EINVAL; /* * Add bus votes at the beginning, before making sure there are any * bus transactions from WLAN SOC for TX/RX. */ param.policy = BBM_NON_PERSISTENT_POLICY; param.policy_info.flag = BBM_APPS_RESUME; ucfg_dp_bbm_apply_independent_policy(hdd_ctx->psoc, ¶m); status = hif_bus_resume(hif_ctx); if (status) { hdd_err("Failed hif bus resume"); goto out; } hif_system_pm_set_state_resuming(hif_ctx); qdf_status = ucfg_pmo_psoc_bus_resume_req(hdd_ctx->psoc, type); status = qdf_status_to_os_return(qdf_status); if (status) { hdd_err("Failed pmo bus resume"); goto out; } qdf_status = ucfg_pmo_core_txrx_resume(hdd_ctx->psoc); status = qdf_status_to_os_return(qdf_status); if (status) { hdd_err("Failed to resume TXRX"); goto out; } hif_system_pm_set_state_on(hif_ctx); status = hif_bus_late_resume(hif_ctx); if (status) { hdd_err("Failed hif bus late resume"); goto out; } dp_soc = cds_get_context(QDF_MODULE_ID_SOC); qdf_status = ucfg_dp_bus_resume(dp_soc, OL_TXRX_PDEV_ID); status = qdf_status_to_os_return(qdf_status); if (status) { hdd_err("Failed cdp bus resume"); goto out; } hdd_info("bus resume succeeded"); return 0; out: hif_system_pm_set_state_suspended(hif_ctx); if (cds_is_driver_recovering() || cds_is_driver_in_bad_state() || cds_is_fw_down()) return 0; if (status != -ETIMEDOUT) QDF_BUG(false); return status; } /** * wlan_hdd_bus_resume_noirq(): handle bus resume no irq * * This function is called by the platform driver to do bus * resume no IRQ before calling resume callback. Call WMA and HIF * layers to complete the resume_noirq. * * Return: 0 for success and negative error code for failure */ int wlan_hdd_bus_resume_noirq(void) { struct hdd_context *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); void *hif_ctx; int status; QDF_STATUS qdf_status; hdd_debug("starting bus_resume_noirq"); if (cds_is_driver_recovering()) return 0; if (!hdd_ctx) return -ENODEV; /* If Wifi is off, return success for system resume */ if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) { hdd_debug("Driver Module closed return success"); return 0; } status = wlan_hdd_validate_context(hdd_ctx); if (status) return status; hif_ctx = cds_get_context(QDF_MODULE_ID_HIF); if (!hif_ctx) return -EINVAL; qdf_status = ucfg_pmo_psoc_clear_target_wake_up(hdd_ctx->psoc); QDF_BUG(!qdf_status); status = hif_bus_resume_noirq(hif_ctx); QDF_BUG(!status); hdd_debug("bus_resume_noirq done"); return status; } /** * wlan_hdd_bus_reset_resume() - resume wlan bus after reset * * This function is called to tell the driver that the device has been resumed * and it has also been reset. The driver should redo any necessary * initialization. It is mainly used by the USB bus * * Return: int 0 for success, non zero for failure */ static int wlan_hdd_bus_reset_resume(void) { struct hif_opaque_softc *scn = cds_get_context(QDF_MODULE_ID_HIF); if (!scn) return -EFAULT; return hif_bus_reset_resume(scn); } #ifdef FEATURE_RUNTIME_PM /** * hdd_pld_runtime_suspend_cb() - Runtime suspend callback from PMO * * Return: 0 on success or error value otherwise */ static int hdd_pld_runtime_suspend_cb(void) { qdf_device_t qdf_dev = cds_get_context(QDF_MODULE_ID_QDF_DEVICE); if (!qdf_dev) return -EINVAL; return pld_auto_suspend(qdf_dev->dev); } /** * wlan_hdd_runtime_suspend() - suspend the wlan bus without apps suspend * @dev: Driver device instance * * Each layer is responsible for its own suspend actions. wma_runtime_suspend * takes care of the parts of the 802.11 suspend that we want to do for runtime * suspend. * * Return: 0 or errno */ static int wlan_hdd_runtime_suspend(struct device *dev) { int err; QDF_STATUS status; struct hdd_context *hdd_ctx; qdf_time_t delta; hdd_debug("Starting runtime suspend"); hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); err = wlan_hdd_validate_context(hdd_ctx); if (err) return err; if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) { hdd_debug("Driver module closed skipping runtime suspend"); return 0; } if (!hdd_is_runtime_pm_enabled(hdd_ctx)) return 0; if (ucfg_scan_get_pdev_status(hdd_ctx->pdev) != SCAN_NOT_IN_PROGRESS) { hdd_debug("Scan in progress, ignore runtime suspend"); return -EBUSY; } if (ucfg_ipa_is_tx_pending(hdd_ctx->pdev)) { hdd_debug("IPA TX comps pending, ignore rtpm suspend"); return -EBUSY; } if (hdd_ctx->config->runtime_pm == hdd_runtime_pm_dynamic && wlan_hdd_is_cpu_pm_qos_in_progress(hdd_ctx)) { hdd_debug("PM QoS Latency constraint, ignore runtime suspend"); return -EBUSY; } status = ucfg_pmo_psoc_bus_runtime_suspend(hdd_ctx->psoc, hdd_pld_runtime_suspend_cb); err = qdf_status_to_os_return(status); hdd_ctx->runtime_suspend_done_time_stamp = qdf_get_log_timestamp_usecs(); delta = hdd_ctx->runtime_suspend_done_time_stamp - hdd_ctx->runtime_resume_start_time_stamp; if (hdd_ctx->runtime_suspend_done_time_stamp > hdd_ctx->runtime_resume_start_time_stamp) hdd_debug("Runtime suspend done result: %d total cxpc up time %lu microseconds", err, delta); if (status == QDF_STATUS_SUCCESS) ucfg_dp_bus_bw_compute_timer_stop(hdd_ctx->psoc); hdd_debug("Runtime suspend done result: %d", err); return err; } /** * hdd_pld_runtime_resume_cb() - Runtime resume callback from PMO * * Return: 0 on success or error value otherwise */ static int hdd_pld_runtime_resume_cb(void) { qdf_device_t qdf_dev = cds_get_context(QDF_MODULE_ID_QDF_DEVICE); if (!qdf_dev) return -EINVAL; return pld_auto_resume(qdf_dev->dev); } /** * wlan_hdd_runtime_resume() - resume the wlan bus from runtime suspend * @dev: Driver device instance * * Sets the runtime pm state and coordinates resume between hif wma and * ol_txrx. * * Return: success since failure is a bug */ static int wlan_hdd_runtime_resume(struct device *dev) { struct hdd_context *hdd_ctx; QDF_STATUS status; qdf_time_t delta; hdd_debug("Starting runtime resume"); hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); /* * In__hdd_soc_remove, runtime_sync_resume is called before setting * unload_in_progress flag. wlan_hdd_validate_context will cause * resume fail, if driver load/unload in-progress, so not doing * wlan_hdd_validate_context, have only SSR in progress check. */ if (!hdd_ctx) return 0; if (cds_is_driver_recovering()) { hdd_debug("Recovery in progress, state:0x%x", cds_get_driver_state()); return 0; } if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) { hdd_debug("Driver module closed skipping runtime resume"); return 0; } if (!hdd_is_runtime_pm_enabled(hdd_ctx)) return 0; hdd_ctx->runtime_resume_start_time_stamp = qdf_get_log_timestamp_usecs(); delta = hdd_ctx->runtime_resume_start_time_stamp - hdd_ctx->runtime_suspend_done_time_stamp; hdd_debug("Starting runtime resume total cxpc down time %lu microseconds", delta); status = ucfg_pmo_psoc_bus_runtime_resume(hdd_ctx->psoc, hdd_pld_runtime_resume_cb); if (status != QDF_STATUS_SUCCESS) { hdd_err("PMO Runtime resume failed: %d", status); } else { if (policy_mgr_get_connection_count(hdd_ctx->psoc)) ucfg_dp_bus_bw_compute_timer_try_start(hdd_ctx->psoc); } hdd_debug("Runtime resume done"); return 0; } #endif /** * wlan_hdd_pld_probe() - probe function registered to PLD * @dev: device * @pld_bus_type: PLD bus type * @bdev: bus device structure * @id: bus identifier for shared busses * * Return: 0 on success */ static int wlan_hdd_pld_probe(struct device *dev, enum pld_bus_type pld_bus_type, void *bdev, void *id) { enum qdf_bus_type bus_type = to_bus_type(pld_bus_type); if (bus_type == QDF_BUS_TYPE_NONE) { hdd_err("Invalid bus type %d->%d", pld_bus_type, bus_type); return -EINVAL; } qdf_ssr_driver_dump_register_region("hang_event_data", g_fw_host_hang_event, sizeof(g_fw_host_hang_event)); return hdd_soc_probe(dev, bdev, id, bus_type); } /** * wlan_hdd_pld_remove() - remove function registered to PLD * @dev: device to remove * @bus_type: PLD bus type * * Return: void */ static void wlan_hdd_pld_remove(struct device *dev, enum pld_bus_type bus_type) { hdd_enter(); hdd_soc_remove(dev); qdf_ssr_driver_dump_unregister_region("hang_event_data"); hdd_exit(); } static void hdd_soc_idle_shutdown_lock(struct device *dev) { hdd_prevent_suspend(WIFI_POWER_EVENT_WAKELOCK_DRIVER_IDLE_SHUTDOWN); hdd_abort_system_suspend(dev); } static void hdd_soc_idle_shutdown_unlock(void) { hdd_allow_suspend(WIFI_POWER_EVENT_WAKELOCK_DRIVER_IDLE_SHUTDOWN); } /** * wlan_hdd_pld_idle_shutdown() - wifi module idle shutdown after interface * inactivity timeout has triggered idle shutdown * @dev: device to remove * @bus_type: PLD bus type * * Return: 0 for success and negative error code for failure */ static int wlan_hdd_pld_idle_shutdown(struct device *dev, enum pld_bus_type bus_type) { int ret; hdd_soc_idle_shutdown_lock(dev); ret = hdd_psoc_idle_shutdown(dev); hdd_soc_idle_shutdown_unlock(); return ret; } /** * wlan_hdd_pld_idle_restart() - wifi module idle restart after idle shutdown * @dev: device to remove * @bus_type: PLD bus type * * Return: 0 for success and negative error code for failure */ static int wlan_hdd_pld_idle_restart(struct device *dev, enum pld_bus_type bus_type) { return hdd_psoc_idle_restart(dev); } /** * wlan_hdd_pld_shutdown() - shutdown function registered to PLD * @dev: device to shutdown * @bus_type: PLD bus type * * Return: void */ static void wlan_hdd_pld_shutdown(struct device *dev, enum pld_bus_type bus_type) { hdd_enter(); hdd_soc_recovery_shutdown(dev); hdd_exit(); } /** * wlan_hdd_pld_reinit() - reinit function registered to PLD * @dev: device * @pld_bus_type: PLD bus type * @bdev: bus device structure * @id: bus identifier for shared busses * * Return: 0 on success */ static int wlan_hdd_pld_reinit(struct device *dev, enum pld_bus_type pld_bus_type, void *bdev, void *id) { enum qdf_bus_type bus_type = to_bus_type(pld_bus_type); if (bus_type == QDF_BUS_TYPE_NONE) { hdd_err("Invalid bus type %d->%d", pld_bus_type, bus_type); return -EINVAL; } return hdd_soc_recovery_reinit(dev, bdev, id, bus_type); } /** * wlan_hdd_pld_crash_shutdown() - crash_shutdown function registered to PLD * @dev: device * @bus_type: PLD bus type * * Return: void */ static void wlan_hdd_pld_crash_shutdown(struct device *dev, enum pld_bus_type bus_type) { wlan_hdd_crash_shutdown(); } /** * wlan_hdd_pld_suspend() - suspend function registered to PLD * @dev: device * @bus_type: PLD bus type * @state: PM state * * Return: 0 on success */ static int wlan_hdd_pld_suspend(struct device *dev, enum pld_bus_type bus_type, pm_message_t state) { struct osif_psoc_sync *psoc_sync; int errno; struct hdd_context *hdd_ctx; hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD); if (!hdd_ctx) return -ENODEV; errno = wlan_hdd_validate_context(hdd_ctx); if (0 != errno) { if (pld_is_low_power_mode(hdd_ctx->parent_dev)) hdd_debug("low power mode (Deep Sleep/Hibernate)"); else return errno; } /* * Flush the idle shutdown before ops start.This is done here to avoid * the deadlock as idle shutdown waits for the dsc ops * to complete. */ hdd_psoc_idle_timer_stop(hdd_ctx); errno = osif_psoc_sync_op_start(dev, &psoc_sync); if (errno) return errno; errno = wlan_hdd_bus_suspend(); osif_psoc_sync_op_stop(psoc_sync); return errno; } /** * wlan_hdd_pld_resume() - resume function registered to PLD * @dev: device * @bus_type: PLD bus type * * Return: 0 on success */ static int wlan_hdd_pld_resume(struct device *dev, enum pld_bus_type bus_type) { struct osif_psoc_sync *psoc_sync; int errno; errno = osif_psoc_sync_op_start(dev, &psoc_sync); if (errno) return errno; errno = wlan_hdd_bus_resume(QDF_SYSTEM_SUSPEND); osif_psoc_sync_op_stop(psoc_sync); return errno; } /** * wlan_hdd_pld_suspend_noirq() - handle suspend no irq * @dev: device * @bus_type: PLD bus type * * Complete the actions started by suspend(). Carry out any * additional operations required for suspending the device that might be * racing with its driver's interrupt handler, which is guaranteed not to * run while suspend_noirq() is being executed. Make sure to resume device * if FW has sent initial wake up message and expecting APPS to wake up. * * Return: 0 on success */ static int wlan_hdd_pld_suspend_noirq(struct device *dev, enum pld_bus_type bus_type) { struct osif_psoc_sync *psoc_sync; int errno; errno = osif_psoc_sync_op_start(dev, &psoc_sync); if (errno) return errno; errno = wlan_hdd_bus_suspend_noirq(); osif_psoc_sync_op_stop(psoc_sync); return errno; } /** * wlan_hdd_pld_resume_noirq() - handle resume no irq * @dev: device * @bus_type: PLD bus type * * Prepare for the execution of resume() by carrying out any * operations required for resuming the device that might be racing with * its driver's interrupt handler, which is guaranteed not to run while * resume_noirq() is being executed. Make sure to clear target initial * wake up request such that next suspend can happen cleanly. * * Return: 0 on success */ static int wlan_hdd_pld_resume_noirq(struct device *dev, enum pld_bus_type bus_type) { struct osif_psoc_sync *psoc_sync; int errno; errno = osif_psoc_sync_op_start(dev, &psoc_sync); if (errno) return errno; errno = wlan_hdd_bus_resume_noirq(); osif_psoc_sync_op_stop(psoc_sync); return errno; } /** * wlan_hdd_pld_reset_resume() - reset resume function registered to PLD * @dev: device * @bus_type: PLD bus type * * Return: 0 on success */ static int wlan_hdd_pld_reset_resume(struct device *dev, enum pld_bus_type bus_type) { struct osif_psoc_sync *psoc_sync; int errno; errno = osif_psoc_sync_op_start(dev, &psoc_sync); if (errno) return errno; errno = wlan_hdd_bus_reset_resume(); osif_psoc_sync_op_stop(psoc_sync); return errno; } /** * wlan_hdd_pld_notify_handler() - notify_handler function registered to PLD * @dev: device * @bus_type: PLD bus type * @state: Modem power state * * Return: void */ static void wlan_hdd_pld_notify_handler(struct device *dev, enum pld_bus_type bus_type, int state) { wlan_hdd_notify_handler(state); } /** * wlan_hdd_pld_uevent() - platform uevent handler * @dev: device on which the uevent occurred * @event_data: uevent parameters * * Return: None */ static void wlan_hdd_pld_uevent(struct device *dev, struct pld_uevent_data *event_data) { struct qdf_notifer_data hang_evt_data; void *hif_ctx = cds_get_context(QDF_MODULE_ID_HIF); enum qdf_hang_reason reason = QDF_REASON_UNSPECIFIED; uint8_t bus_type; bus_type = pld_get_bus_type(dev); switch (event_data->uevent) { case PLD_SMMU_FAULT: qdf_set_smmu_fault_state(true); hdd_debug("Received smmu fault indication"); break; case PLD_FW_DOWN: hdd_debug("Received firmware down indication"); hdd_dump_log_buffer(NULL, NULL); cds_set_target_ready(false); cds_set_recovery_in_progress(true); hdd_init_start_completion(); /* Notify external threads currently waiting on firmware * by forcefully completing waiting events with a "reset" * status. This will cause the event to fail early instead * of timing out. */ qdf_complete_wait_events(); /* * In case of some platforms, uevent will come to the driver in * process context. In that case, it is safe to complete the * SSR cleanup activities in the same context. In case of * other platforms, it will be invoked in interrupt context. * Performing the cleanup in interrupt context is not ideal, * thus defer the cleanup to be done during * hdd_soc_recovery_shutdown */ if (qdf_in_interrupt() || bus_type == PLD_BUS_TYPE_PCIE) break; hdd_soc_recovery_cleanup(); qdf_atomic_set(&is_recovery_cleanup_done, 1); break; case PLD_FW_HANG_EVENT: hdd_info("Received firmware hang event"); cds_get_recovery_reason(&reason); if ((reason == QDF_REASON_UNSPECIFIED) && hif_ctx) { hif_display_ctrl_traffic_pipes_state(hif_ctx); hif_display_latest_desc_hist(hif_ctx); } qdf_mem_zero(&g_fw_host_hang_event, QDF_HANG_EVENT_DATA_SIZE); hang_evt_data.hang_data = g_fw_host_hang_event; hang_evt_data.offset = 0; qdf_hang_event_notifier_call(reason, &hang_evt_data); hang_evt_data.offset = QDF_WLAN_HANG_FW_OFFSET; if (event_data->hang_data.hang_event_data_len >= QDF_HANG_EVENT_DATA_SIZE / 2) event_data->hang_data.hang_event_data_len = QDF_HANG_EVENT_DATA_SIZE / 2; if (event_data->hang_data.hang_event_data_len) qdf_mem_copy((hang_evt_data.hang_data + hang_evt_data.offset), event_data->hang_data.hang_event_data, event_data->hang_data.hang_event_data_len); hdd_send_hang_data(hang_evt_data.hang_data, QDF_HANG_EVENT_DATA_SIZE); break; case PLD_BUS_EVENT: hdd_debug("Bus event received"); /* Currently only link_down taken care. * Need to extend event buffer to define more bus info, * if need later. */ if (event_data->bus_data.etype == PLD_BUS_EVENT_PCIE_LINK_DOWN) host_log_device_status(WLAN_STATUS_BUS_EXCEPTION); break; case PLD_SYS_REBOOT: hdd_info("Received system reboot"); cds_set_sys_rebooting(); break; default: /* other events intentionally not handled */ hdd_debug("Received uevent %d", event_data->uevent); break; } } #ifdef WLAN_FEATURE_SSR_DRIVER_DUMP static int wlan_hdd_pld_collect_driver_dump(struct device *dev, enum pld_bus_type bus_type, struct cnss_ssr_driver_dump_entry *input_array, size_t *num_entries_loaded) { QDF_STATUS status; status = qdf_ssr_driver_dump_retrieve_regions(input_array, num_entries_loaded); return qdf_status_to_os_return(status); } #endif #ifdef FEATURE_RUNTIME_PM /** * wlan_hdd_pld_runtime_suspend() - runtime suspend function registered to PLD * @dev: device * @bus_type: PLD bus type * * Return: 0 on success */ static int wlan_hdd_pld_runtime_suspend(struct device *dev, enum pld_bus_type bus_type) { struct osif_psoc_sync *psoc_sync; int errno; errno = osif_psoc_sync_op_start(dev, &psoc_sync); if (errno) goto out; errno = wlan_hdd_runtime_suspend(dev); osif_psoc_sync_op_stop(psoc_sync); out: /* If it returns other errno to kernel, it will treat * it as critical issue, so all the future runtime * PM api will return error, pm runtime can't be work * anymore. Such case found in SSR. */ if (errno && errno != -EAGAIN && errno != -EBUSY) errno = -EAGAIN; return errno; } /** * wlan_hdd_pld_runtime_resume() - runtime resume function registered to PLD * @dev: device * @bus_type: PLD bus type * * Return: 0 on success */ static int wlan_hdd_pld_runtime_resume(struct device *dev, enum pld_bus_type bus_type) { /* As opposite to suspend, Runtime PM resume can happen * synchronously during driver shutdown or idle shutown, * so remove PSOC sync protection here. */ return wlan_hdd_runtime_resume(dev); } #endif struct pld_driver_ops wlan_drv_ops = { .probe = wlan_hdd_pld_probe, .remove = wlan_hdd_pld_remove, .idle_shutdown = wlan_hdd_pld_idle_shutdown, .idle_restart = wlan_hdd_pld_idle_restart, .shutdown = wlan_hdd_pld_shutdown, .reinit = wlan_hdd_pld_reinit, .crash_shutdown = wlan_hdd_pld_crash_shutdown, .suspend = wlan_hdd_pld_suspend, .resume = wlan_hdd_pld_resume, .suspend_noirq = wlan_hdd_pld_suspend_noirq, .resume_noirq = wlan_hdd_pld_resume_noirq, .reset_resume = wlan_hdd_pld_reset_resume, .modem_status = wlan_hdd_pld_notify_handler, .uevent = wlan_hdd_pld_uevent, #ifdef WLAN_FEATURE_SSR_DRIVER_DUMP .collect_driver_dump = wlan_hdd_pld_collect_driver_dump, #endif #ifdef FEATURE_RUNTIME_PM .runtime_suspend = wlan_hdd_pld_runtime_suspend, .runtime_resume = wlan_hdd_pld_runtime_resume, #endif .set_curr_therm_cdev_state = wlan_hdd_pld_set_thermal_mitigation, }; int wlan_hdd_register_driver(void) { return pld_register_driver(&wlan_drv_ops); } void wlan_hdd_unregister_driver(void) { pld_unregister_driver(); }