1 /*
2  * Copyright (c) 2019 The Linux Foundation. All rights reserved.
3  * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
4  *
5  * Permission to use, copy, modify, and/or distribute this software for
6  * any purpose with or without fee is hereby granted, provided that the
7  * above copyright notice and this permission notice appear in all
8  * copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13  * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17  * PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 /**
21  * DOC: wlan_hdd_fw_state.c
22  *
23  * The implementation for getting firmware state
24  */
25 
26 #include "osif_sync.h"
27 #include "qca_vendor.h"
28 #include "wlan_hdd_fw_state.h"
29 #include "wlan_hdd_main.h"
30 #include "wlan_osif_request_manager.h"
31 #include "wmi_unified_param.h"
32 
33 struct fw_state {
34 	bool fw_active;
35 };
36 
37 /**
38  * hdd_get_fw_state_cb() - Callback function to get fw state
39  * @context: opaque context originally passed to SME. HDD always passes
40  * a cookie for the request context
41  *
42  * This function receives the response/data from the lower layer and
43  * checks to see if the thread is still waiting then post the results to
44  * upper layer, if the request has timed out then ignore.
45  *
46  * Return: None
47  */
hdd_get_fw_state_cb(void * context)48 static void hdd_get_fw_state_cb(void *context)
49 {
50 	struct osif_request *request;
51 	struct fw_state *priv;
52 
53 	hdd_enter();
54 
55 	request = osif_request_get(context);
56 	if (!request) {
57 		hdd_err("Obsolete request");
58 		return;
59 	}
60 
61 	priv = osif_request_priv(request);
62 	priv->fw_active = true;
63 	osif_request_complete(request);
64 	osif_request_put(request);
65 }
66 
67 /**
68  * hdd_post_get_fw_state_rsp - send rsp to user space
69  * @hdd_ctx: pointer to hdd context
70  * @state: true for fw active, false for fw error state
71  *
72  * Return: 0 for success, non-zero for failure
73  */
hdd_post_get_fw_state_rsp(struct hdd_context * hdd_ctx,bool state)74 static int hdd_post_get_fw_state_rsp(struct hdd_context *hdd_ctx,
75 				     bool state)
76 {
77 	struct sk_buff *skb;
78 	enum qca_wlan_vendor_attr_fw_state fw_state;
79 
80 	skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(hdd_ctx->wiphy,
81 						       sizeof(uint8_t) +
82 						       NLA_HDRLEN +
83 						       NLMSG_HDRLEN);
84 	if (!skb) {
85 		hdd_err("wlan_cfg80211_vendor_event_alloc failed");
86 		return -ENOMEM;
87 	}
88 
89 	if (state)
90 		fw_state = QCA_WLAN_VENDOR_ATTR_FW_STATE_ACTIVE;
91 	else
92 		fw_state = QCA_WLAN_VENDOR_ATTR_FW_STATE_ERROR;
93 
94 	if (nla_put_u8(skb, QCA_WLAN_VENDOR_ATTR_FW_STATE,
95 		       (uint8_t)fw_state)) {
96 		hdd_err("put fail");
97 		goto nla_put_failure;
98 	}
99 
100 	wlan_cfg80211_vendor_cmd_reply(skb);
101 	return 0;
102 
103 nla_put_failure:
104 	wlan_cfg80211_vendor_free_skb(skb);
105 	return -EINVAL;
106 }
107 
108 /**
109  * __wlan_hdd_cfg80211_get_fw_state() - get fw state
110  * @wiphy: pointer to wireless wiphy structure.
111  * @wdev: pointer to wireless_dev structure.
112  * @data: Pointer to the data to be passed via vendor interface
113  * @data_len:Length of the data to be passed
114  *
115  * This function sends a request to fw and waits on a timer to
116  * invoke the callback. if the callback is invoked then true
117  * will be returned or otherwise fail status will be returned.
118  *
119  * Return: 0 on success; error number otherwise.
120  **/
__wlan_hdd_cfg80211_get_fw_state(struct wiphy * wiphy,struct wireless_dev * wdev,const void * data,int data_len)121 static int __wlan_hdd_cfg80211_get_fw_state(struct wiphy *wiphy,
122 					    struct wireless_dev *wdev,
123 					    const void *data,
124 					    int data_len)
125 {
126 	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
127 	mac_handle_t mac_handle;
128 	QDF_STATUS status;
129 	int retval;
130 	void *cookie;
131 	struct osif_request *request;
132 	struct fw_state *priv;
133 	static const struct osif_request_params params = {
134 		.priv_size = sizeof(*priv),
135 		.timeout_ms = WLAN_WAIT_TIME_STATS,
136 	};
137 	bool state = false;
138 
139 	hdd_enter_dev(wdev->netdev);
140 
141 	retval = wlan_hdd_validate_context(hdd_ctx);
142 	if (retval)
143 		return retval;
144 
145 	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
146 		hdd_err("Command not allowed in FTM mode");
147 		return -EPERM;
148 	}
149 
150 	request = osif_request_alloc(&params);
151 	if (!request) {
152 		hdd_err("Request allocation failure");
153 		return -ENOMEM;
154 	}
155 	cookie = osif_request_cookie(request);
156 
157 	mac_handle = hdd_ctx->mac_handle;
158 	status = sme_get_fw_state(mac_handle,
159 				  hdd_get_fw_state_cb,
160 				  cookie);
161 	if (QDF_STATUS_SUCCESS != status) {
162 		hdd_err("Unable to get fw state");
163 		retval = qdf_status_to_os_return(status);
164 	} else {
165 		retval = osif_request_wait_for_response(request);
166 		if (retval) {
167 			hdd_err("Target response timed out");
168 			state = false;
169 		} else {
170 			priv = osif_request_priv(request);
171 			state = priv->fw_active;
172 		}
173 	}
174 	retval = hdd_post_get_fw_state_rsp(hdd_ctx, state);
175 	if (retval)
176 		hdd_err("Failed to post fw state");
177 
178 	osif_request_put(request);
179 
180 	hdd_exit();
181 	return retval;
182 }
183 
184 /**
185  * wlan_hdd_cfg80211_get_fw_state() - get fw state
186  * @wiphy: wiphy pointer
187  * @wdev: pointer to struct wireless_dev
188  * @data: pointer to incoming NL vendor data
189  * @data_len: length of @data
190  *
191  * Return: 0 on success; error number otherwise.
192  */
wlan_hdd_cfg80211_get_fw_state(struct wiphy * wiphy,struct wireless_dev * wdev,const void * data,int data_len)193 int wlan_hdd_cfg80211_get_fw_state(struct wiphy *wiphy,
194 				   struct wireless_dev *wdev,
195 				   const void *data,
196 				   int data_len)
197 {
198 	struct osif_psoc_sync *psoc_sync;
199 	int errno;
200 
201 	errno = osif_psoc_sync_op_start(wiphy_dev(wiphy), &psoc_sync);
202 	if (errno)
203 		return errno;
204 
205 	errno = __wlan_hdd_cfg80211_get_fw_state(wiphy, wdev, data, data_len);
206 
207 	osif_psoc_sync_op_stop(psoc_sync);
208 
209 	return errno;
210 }
211