/* * Copyright (c) 2019-2021 The Linux Foundation. All rights reserved. * Copyright (c) 2023 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. */ /** * DOC: target interface APIs for fw offload * */ #include "qdf_mem.h" #include "target_if.h" #include "qdf_status.h" #include "wmi_unified_api.h" #include "wmi_unified_priv.h" #include "wmi_unified_param.h" #include "wlan_objmgr_psoc_obj.h" #include "wlan_utility.h" #include "wlan_defs.h" #include "wlan_fwol_public_structs.h" #include "wlan_fw_offload_main.h" #include "target_if_fwol.h" #ifdef WLAN_FEATURE_ELNA /** * target_if_fwol_set_elna_bypass() - send set eLNA bypass request to FW * @psoc: pointer to PSOC object * @req: set eLNA bypass request * * Return: QDF_STATUS_SUCCESS on success */ static QDF_STATUS target_if_fwol_set_elna_bypass(struct wlan_objmgr_psoc *psoc, struct set_elna_bypass_request *req) { QDF_STATUS status; wmi_unified_t wmi_handle = get_wmi_unified_hdl_from_psoc(psoc); if (!wmi_handle) { target_if_err("Invalid wmi_handle"); return QDF_STATUS_E_INVAL; } status = wmi_unified_send_set_elna_bypass_cmd(wmi_handle, req); if (status) target_if_err("Failed to set eLNA bypass %d", status); return status; } /** * target_if_fwol_get_elna_bypass() - send get eLNA bypass request to FW * @psoc: pointer to PSOC object * @req: get eLNA bypass request * * Return: QDF_STATUS_SUCCESS on success */ static QDF_STATUS target_if_fwol_get_elna_bypass(struct wlan_objmgr_psoc *psoc, struct get_elna_bypass_request *req) { QDF_STATUS status; wmi_unified_t wmi_handle = get_wmi_unified_hdl_from_psoc(psoc); if (!wmi_handle) { target_if_err("Invalid wmi_handle"); return QDF_STATUS_E_INVAL; } status = wmi_unified_send_get_elna_bypass_cmd(wmi_handle, req); if (status) target_if_err("Failed to set eLNA bypass %d", status); return status; } /** * target_if_fwol_get_elna_bypass_resp() - handler for get eLNA bypass response * @scn: scn handle * @event_buf: pointer to the event buffer * @len: length of the buffer * * Return: 0 on success */ static int target_if_fwol_get_elna_bypass_resp(ol_scn_t scn, uint8_t *event_buf, uint32_t len) { QDF_STATUS status; struct get_elna_bypass_response resp; struct wlan_objmgr_psoc *psoc; wmi_unified_t wmi_handle; struct wlan_fwol_psoc_obj *fwol_obj; struct wlan_fwol_rx_ops *rx_ops; target_if_debug("scn:%pK, data:%pK, datalen:%d", scn, event_buf, len); if (!scn || !event_buf) { target_if_err("scn: 0x%pK, data: 0x%pK", scn, event_buf); return -EINVAL; } psoc = target_if_get_psoc_from_scn_hdl(scn); if (!psoc) { target_if_err("null psoc"); return -EINVAL; } wmi_handle = get_wmi_unified_hdl_from_psoc(psoc); if (!wmi_handle) { target_if_err("Invalid wmi_handle"); return -EINVAL; } fwol_obj = fwol_get_psoc_obj(psoc); if (!fwol_obj) { target_if_err("Failed to get FWOL Obj"); return -EINVAL; } rx_ops = &fwol_obj->rx_ops; if (rx_ops->get_elna_bypass_resp) { status = wmi_extract_get_elna_bypass_resp(wmi_handle, event_buf, &resp); if (QDF_IS_STATUS_ERROR(status)) { target_if_err("Failed to extract eLNA bypass"); return -EINVAL; } status = rx_ops->get_elna_bypass_resp(psoc, &resp); if (status != QDF_STATUS_SUCCESS) { target_if_err("get_elna_bypass_resp failed."); return -EINVAL; } } else { target_if_fatal("No get_elna_bypass_resp callback"); return -EINVAL; } return 0; }; static void target_if_fwol_register_elna_event_handler(struct wlan_objmgr_psoc *psoc, void *arg) { QDF_STATUS rc; rc = wmi_unified_register_event(get_wmi_unified_hdl_from_psoc(psoc), wmi_get_elna_bypass_event_id, target_if_fwol_get_elna_bypass_resp); if (QDF_IS_STATUS_ERROR(rc)) target_if_debug("Failed to register get eLNA bypass event cb"); } static void target_if_fwol_unregister_elna_event_handler(struct wlan_objmgr_psoc *psoc, void *arg) { QDF_STATUS rc; rc = wmi_unified_unregister_event_handler( get_wmi_unified_hdl_from_psoc(psoc), wmi_get_elna_bypass_event_id); if (QDF_IS_STATUS_ERROR(rc)) target_if_debug("Failed to unregister get eLNA bypass event cb"); } static void target_if_fwol_register_elna_tx_ops(struct wlan_fwol_tx_ops *tx_ops) { tx_ops->set_elna_bypass = target_if_fwol_set_elna_bypass; tx_ops->get_elna_bypass = target_if_fwol_get_elna_bypass; } #else static void target_if_fwol_register_elna_event_handler(struct wlan_objmgr_psoc *psoc, void *arg) { } static void target_if_fwol_unregister_elna_event_handler(struct wlan_objmgr_psoc *psoc, void *arg) { } static void target_if_fwol_register_elna_tx_ops(struct wlan_fwol_tx_ops *tx_ops) { } #endif /* WLAN_FEATURE_ELNA */ #ifdef WLAN_SEND_DSCP_UP_MAP_TO_FW /** * target_if_fwol_send_dscp_up_map_to_fw() - send dscp up map to FW * @psoc: pointer to PSOC object * @dscp_to_up_map: DSCP to UP map array * * Return: QDF_STATUS_SUCCESS on success */ static QDF_STATUS target_if_fwol_send_dscp_up_map_to_fw(struct wlan_objmgr_psoc *psoc, uint32_t *dscp_to_up_map) { QDF_STATUS status; wmi_unified_t wmi_handle = get_wmi_unified_hdl_from_psoc(psoc); if (!wmi_handle) { target_if_err("Invalid wmi_handle"); return QDF_STATUS_E_INVAL; } status = wmi_unified_send_dscp_tip_map_cmd(wmi_handle, dscp_to_up_map); if (status) target_if_err("Failed to send dscp_up_map_to_fw %d", status); return status; } static void target_if_fwol_register_dscp_up_tx_ops(struct wlan_fwol_tx_ops *tx_ops) { tx_ops->send_dscp_up_map_to_fw = target_if_fwol_send_dscp_up_map_to_fw; } #else static void target_if_fwol_register_dscp_up_tx_ops(struct wlan_fwol_tx_ops *tx_ops) { } #endif #ifdef THERMAL_STATS_SUPPORT /** * target_if_fwol_get_thermal_stats() - send get thermal stats request to FW * @psoc: pointer to PSOC object * @req_type: get thermal stats request type * @therm_stats_offset: thermal temp stats offset for each temp range * * Return: QDF_STATUS_SUCCESS on success */ static QDF_STATUS target_if_fwol_get_thermal_stats(struct wlan_objmgr_psoc *psoc, enum thermal_stats_request_type req_type, uint8_t therm_stats_offset) { QDF_STATUS status; wmi_unified_t wmi_handle = get_wmi_unified_hdl_from_psoc(psoc); if (!wmi_handle) { target_if_err("Invalid wmi_handle"); return QDF_STATUS_E_INVAL; } status = wmi_unified_send_get_thermal_stats_cmd(wmi_handle, req_type, therm_stats_offset); if (status) target_if_err("Failed to send get thermal stats cmd %d", status); return status; } static void target_if_fwol_register_thermal_stats_tx_ops(struct wlan_fwol_tx_ops *tx_ops) { tx_ops->get_thermal_stats = target_if_fwol_get_thermal_stats; } static QDF_STATUS target_if_fwol_handle_thermal_lvl_stats_evt(struct wlan_objmgr_psoc *psoc, struct wlan_fwol_rx_ops *rx_ops, struct thermal_throttle_info *info) { QDF_STATUS status = QDF_STATUS_E_FAILURE; if (rx_ops->get_thermal_stats_resp && info->therm_throt_levels) status = rx_ops->get_thermal_stats_resp(psoc, info); return status; } static bool target_if_fwol_is_thermal_stats_enable(struct wlan_fwol_psoc_obj *fwol_obj) { return (fwol_obj->capability_info.fw_thermal_stats_cap && fwol_obj->cfg.thermal_temp_cfg.therm_stats_offset); } #else static void target_if_fwol_register_thermal_stats_tx_ops(struct wlan_fwol_tx_ops *tx_ops) { } #ifdef FW_THERMAL_THROTTLE_SUPPORT static QDF_STATUS target_if_fwol_handle_thermal_lvl_stats_evt(struct wlan_objmgr_psoc *psoc, struct wlan_fwol_rx_ops *rx_ops, struct thermal_throttle_info *info) { return QDF_STATUS_E_NOSUPPORT; } static bool target_if_fwol_is_thermal_stats_enable(struct wlan_fwol_psoc_obj *fwol_obj) { return false; } #endif #endif #if defined FW_THERMAL_THROTTLE_SUPPORT || defined THERMAL_STATS_SUPPORT QDF_STATUS target_if_fwol_notify_thermal_throttle(struct wlan_objmgr_psoc *psoc, struct thermal_throttle_info *info) { struct wlan_fwol_psoc_obj *fwol_obj; struct wlan_fwol_rx_ops *rx_ops; QDF_STATUS status; fwol_obj = fwol_get_psoc_obj(psoc); if (!fwol_obj) { target_if_err("Failed to get FWOL Obj"); return QDF_STATUS_E_INVAL; } rx_ops = &fwol_obj->rx_ops; if (!rx_ops) { target_if_err("rx_ops Null"); return QDF_STATUS_E_INVAL; } if (!info) { target_if_err("info Null"); return QDF_STATUS_E_INVAL; } if (rx_ops->notify_thermal_throttle_handler) { if (info->level == THERMAL_UNKNOWN) { target_if_debug("Invalid thermal target lvl"); return QDF_STATUS_E_INVAL; } status = rx_ops->notify_thermal_throttle_handler(psoc, info); if (QDF_IS_STATUS_ERROR(status)) { target_if_debug("notify thermal_throttle failed."); return QDF_STATUS_E_INVAL; } } else { target_if_debug("No notify thermal_throttle callback"); return QDF_STATUS_E_INVAL; } return status; } /** * target_if_fwol_thermal_throttle_event_handler() - handler for thermal * throttle event * @scn: scn handle * @event_buf: pointer to the event buffer * @len: length of the buffer * * Return: 0 on success */ static int target_if_fwol_thermal_throttle_event_handler(ol_scn_t scn, uint8_t *event_buf, uint32_t len) { QDF_STATUS status = QDF_STATUS_E_FAILURE; struct thermal_throttle_info info = {0}; struct wlan_objmgr_psoc *psoc; wmi_unified_t wmi_handle; struct wlan_fwol_psoc_obj *fwol_obj; struct wlan_fwol_rx_ops *rx_ops; target_if_debug("scn:%pK, data:%pK, datalen:%d", scn, event_buf, len); if (!scn || !event_buf) { target_if_err("scn: 0x%pK, data: 0x%pK", scn, event_buf); return -EINVAL; } psoc = target_if_get_psoc_from_scn_hdl(scn); if (!psoc) { target_if_err("null psoc"); return -EINVAL; } wmi_handle = get_wmi_unified_hdl_from_psoc(psoc); if (!wmi_handle) { target_if_err("Invalid wmi_handle"); return -EINVAL; } fwol_obj = fwol_get_psoc_obj(psoc); if (!fwol_obj) { target_if_err("Failed to get FWOL Obj"); return -EINVAL; } status = wmi_extract_thermal_stats(wmi_handle, event_buf, &info.temperature, &info.level, &info.therm_throt_levels, info.level_info, &info.pdev_id); if (QDF_IS_STATUS_ERROR(status)) { target_if_debug("Failed to convert thermal target level"); return -EINVAL; } rx_ops = &fwol_obj->rx_ops; if (!rx_ops) { target_if_debug("rx_ops Null"); return -EINVAL; } status = target_if_fwol_handle_thermal_lvl_stats_evt(psoc, rx_ops, &info); if (QDF_IS_STATUS_ERROR(status)) target_if_debug("thermal stats level response failed."); if (rx_ops->notify_thermal_throttle_handler) { if (info.level == THERMAL_UNKNOWN) { target_if_debug("Failed to convert thermal target lvl"); return -EINVAL; } status = rx_ops->notify_thermal_throttle_handler(psoc, &info); if (QDF_IS_STATUS_ERROR(status)) { target_if_debug("notify thermal_throttle failed."); return -EINVAL; } } else { target_if_debug("No notify thermal_throttle callback"); return -EINVAL; } return 0; } /** * target_if_fwol_register_thermal_throttle_handler() - Register handler for * thermal throttle stats firmware event * @psoc: psoc object * * Return: void */ static void target_if_fwol_register_thermal_throttle_handler(struct wlan_objmgr_psoc *psoc) { QDF_STATUS status; struct wlan_fwol_psoc_obj *fwol_obj; fwol_obj = fwol_get_psoc_obj(psoc); if (!fwol_obj) { target_if_err("Failed to get FWOL Obj"); return; } if (!fwol_obj->cfg.thermal_temp_cfg.thermal_mitigation_enable && !target_if_fwol_is_thermal_stats_enable(fwol_obj)) { target_if_debug("thermal mitigation or stats offload not enabled"); return; } status = wmi_unified_register_event_handler( get_wmi_unified_hdl_from_psoc(psoc), wmi_tt_stats_event_id, target_if_fwol_thermal_throttle_event_handler, WMI_RX_SERIALIZER_CTX); if (QDF_IS_STATUS_ERROR(status)) target_if_debug("Failed to register thermal stats event cb"); } /** * target_if_fwol_unregister_thermal_throttle_handler() - Unregister handler for * thermal throttle stats firmware event * @psoc: psoc object * * Return: void */ static void target_if_fwol_unregister_thermal_throttle_handler( struct wlan_objmgr_psoc *psoc) { QDF_STATUS status; status = wmi_unified_unregister_event_handler( get_wmi_unified_hdl_from_psoc(psoc), wmi_tt_stats_event_id); if (QDF_IS_STATUS_ERROR(status)) target_if_debug("Failed to unregister thermal stats event cb"); } #else static void target_if_fwol_register_thermal_throttle_handler(struct wlan_objmgr_psoc *psoc) { } static void target_if_fwol_unregister_thermal_throttle_handler( struct wlan_objmgr_psoc *psoc) { } #endif #ifdef WLAN_FEATURE_MDNS_OFFLOAD /** * target_if_fwol_set_mdns_config() - Set mdns Config to FW * @psoc: pointer to PSOC object * @mdns_info: pointer to mdns config info * * Return: QDF_STATUS_SUCCESS on success */ static QDF_STATUS target_if_fwol_set_mdns_config(struct wlan_objmgr_psoc *psoc, struct mdns_config_info *mdns_info) { QDF_STATUS status; wmi_unified_t wmi_handle = get_wmi_unified_hdl_from_psoc(psoc); if (!wmi_handle) { target_if_err("Invalid wmi_handle"); return QDF_STATUS_E_INVAL; } status = wmi_unified_send_set_mdns_config_cmd(wmi_handle, mdns_info); if (QDF_IS_STATUS_ERROR(status)) target_if_err("Failed to set mDNS Config %d", status); return status; } static void target_if_fwol_register_mdns_tx_ops(struct wlan_fwol_tx_ops *tx_ops) { tx_ops->set_mdns_config = target_if_fwol_set_mdns_config; } #else static void target_if_fwol_register_mdns_tx_ops(struct wlan_fwol_tx_ops *tx_ops) { } #endif /* WLAN_FEATURE_MDNS_OFFLOAD */ QDF_STATUS target_if_fwol_register_event_handler(struct wlan_objmgr_psoc *psoc, void *arg) { target_if_fwol_register_elna_event_handler(psoc, arg); target_if_fwol_register_thermal_throttle_handler(psoc); return QDF_STATUS_SUCCESS; } QDF_STATUS target_if_fwol_unregister_event_handler(struct wlan_objmgr_psoc *psoc, void *arg) { target_if_fwol_unregister_thermal_throttle_handler(psoc); target_if_fwol_unregister_elna_event_handler(psoc, arg); return QDF_STATUS_SUCCESS; } QDF_STATUS target_if_fwol_register_tx_ops(struct wlan_fwol_tx_ops *tx_ops) { target_if_fwol_register_elna_tx_ops(tx_ops); target_if_fwol_register_dscp_up_tx_ops(tx_ops); target_if_fwol_register_mdns_tx_ops(tx_ops); target_if_fwol_register_thermal_stats_tx_ops(tx_ops); tx_ops->reg_evt_handler = target_if_fwol_register_event_handler; tx_ops->unreg_evt_handler = target_if_fwol_unregister_event_handler; return QDF_STATUS_SUCCESS; }