/* * Copyright (c) 2019 The Linux Foundation. All rights reserved. * Copyright (c) 2022 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: wlan_hdd_fw_state.c * * The implementation for getting firmware state */ #include "osif_sync.h" #include "qca_vendor.h" #include "wlan_hdd_fw_state.h" #include "wlan_hdd_main.h" #include "wlan_osif_request_manager.h" #include "wmi_unified_param.h" struct fw_state { bool fw_active; }; /** * hdd_get_fw_state_cb() - Callback function to get fw state * @context: opaque context originally passed to SME. HDD always passes * a cookie for the request context * * This function receives the response/data from the lower layer and * checks to see if the thread is still waiting then post the results to * upper layer, if the request has timed out then ignore. * * Return: None */ static void hdd_get_fw_state_cb(void *context) { struct osif_request *request; struct fw_state *priv; hdd_enter(); request = osif_request_get(context); if (!request) { hdd_err("Obsolete request"); return; } priv = osif_request_priv(request); priv->fw_active = true; osif_request_complete(request); osif_request_put(request); } /** * hdd_post_get_fw_state_rsp - send rsp to user space * @hdd_ctx: pointer to hdd context * @state: true for fw active, false for fw error state * * Return: 0 for success, non-zero for failure */ static int hdd_post_get_fw_state_rsp(struct hdd_context *hdd_ctx, bool state) { struct sk_buff *skb; enum qca_wlan_vendor_attr_fw_state fw_state; skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(hdd_ctx->wiphy, sizeof(uint8_t) + NLA_HDRLEN + NLMSG_HDRLEN); if (!skb) { hdd_err("wlan_cfg80211_vendor_event_alloc failed"); return -ENOMEM; } if (state) fw_state = QCA_WLAN_VENDOR_ATTR_FW_STATE_ACTIVE; else fw_state = QCA_WLAN_VENDOR_ATTR_FW_STATE_ERROR; if (nla_put_u8(skb, QCA_WLAN_VENDOR_ATTR_FW_STATE, (uint8_t)fw_state)) { hdd_err("put fail"); goto nla_put_failure; } wlan_cfg80211_vendor_cmd_reply(skb); return 0; nla_put_failure: wlan_cfg80211_vendor_free_skb(skb); return -EINVAL; } /** * __wlan_hdd_cfg80211_get_fw_state() - get fw state * @wiphy: pointer to wireless wiphy structure. * @wdev: pointer to wireless_dev structure. * @data: Pointer to the data to be passed via vendor interface * @data_len:Length of the data to be passed * * This function sends a request to fw and waits on a timer to * invoke the callback. if the callback is invoked then true * will be returned or otherwise fail status will be returned. * * Return: 0 on success; error number otherwise. **/ static int __wlan_hdd_cfg80211_get_fw_state(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int data_len) { struct hdd_context *hdd_ctx = wiphy_priv(wiphy); mac_handle_t mac_handle; QDF_STATUS status; int retval; void *cookie; struct osif_request *request; struct fw_state *priv; static const struct osif_request_params params = { .priv_size = sizeof(*priv), .timeout_ms = WLAN_WAIT_TIME_STATS, }; bool state = false; hdd_enter_dev(wdev->netdev); retval = wlan_hdd_validate_context(hdd_ctx); if (retval) return retval; if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) { hdd_err("Command not allowed in FTM mode"); return -EPERM; } request = osif_request_alloc(¶ms); if (!request) { hdd_err("Request allocation failure"); return -ENOMEM; } cookie = osif_request_cookie(request); mac_handle = hdd_ctx->mac_handle; status = sme_get_fw_state(mac_handle, hdd_get_fw_state_cb, cookie); if (QDF_STATUS_SUCCESS != status) { hdd_err("Unable to get fw state"); retval = qdf_status_to_os_return(status); } else { retval = osif_request_wait_for_response(request); if (retval) { hdd_err("Target response timed out"); state = false; } else { priv = osif_request_priv(request); state = priv->fw_active; } } retval = hdd_post_get_fw_state_rsp(hdd_ctx, state); if (retval) hdd_err("Failed to post fw state"); osif_request_put(request); hdd_exit(); return retval; } /** * wlan_hdd_cfg80211_get_fw_state() - get fw state * @wiphy: wiphy pointer * @wdev: pointer to struct wireless_dev * @data: pointer to incoming NL vendor data * @data_len: length of @data * * Return: 0 on success; error number otherwise. */ int wlan_hdd_cfg80211_get_fw_state(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int data_len) { struct osif_psoc_sync *psoc_sync; int errno; errno = osif_psoc_sync_op_start(wiphy_dev(wiphy), &psoc_sync); if (errno) return errno; errno = __wlan_hdd_cfg80211_get_fw_state(wiphy, wdev, data, data_len); osif_psoc_sync_op_stop(psoc_sync); return errno; }