qcacld-3.0: Implement iwpriv cmd to get WLM stats from FW

As per new requirements to debug game latency related issues,
implement an iwpriv command which:
1) sends a bitmask to FW's Wireless Latency Manager module (WLM).
2) receives from WLM a measurement header along with measurement data.
3) converts both the header and data to a hexadecimal encoded string.
4) returns the hexadecimal encoded string to userspace

Change-Id: Ic79c4b757fe2d4e806306750250e3c102745c486
CRs-Fixed: 2388920
diff --git a/Kbuild b/Kbuild
index 2d98488..79f84fd 100644
--- a/Kbuild
+++ b/Kbuild
@@ -1991,6 +1991,7 @@
 cppflags-$(CONFIG_UNIT_TEST) += -DWLAN_UNIT_TEST
 cppflags-$(CONFIG_WLAN_DEBUG_CRASH_INJECT) += -DCONFIG_WLAN_DEBUG_CRASH_INJECT
 cppflags-$(CONFIG_FEATURE_UNIT_TEST_SUSPEND) += -DWLAN_SUSPEND_RESUME_TEST
+cppflags-$(CONFIG_FEATURE_WLM_STATS) += -DFEATURE_WLM_STATS
 
 ifeq ($(CONFIG_LEAK_DETECTION), y)
 cppflags-y += \
diff --git a/configs/default_defconfig b/configs/default_defconfig
index c8bb8a5..4ce5d0b 100644
--- a/configs/default_defconfig
+++ b/configs/default_defconfig
@@ -668,6 +668,7 @@
 ifeq ($(CONFIG_UNIT_TEST), y)
 	CONFIG_DSC_TEST := y
 	CONFIG_QDF_TEST := y
+	CONFIG_FEATURE_WLM_STATS := y
 endif
 
 # enable unit-test suspend for napier builds
diff --git a/core/hdd/src/wlan_hdd_wext.c b/core/hdd/src/wlan_hdd_wext.c
index 2a14910..e5af478 100644
--- a/core/hdd/src/wlan_hdd_wext.c
+++ b/core/hdd/src/wlan_hdd_wext.c
@@ -2685,7 +2685,28 @@
 #define WLAN_PRIV_SET_NONE_GET_THREE_INT   (SIOCIWFIRSTPRIV + 15)
 #define WE_GET_TSF      1
 /* (SIOCIWFIRSTPRIV + 16) is currently unused */
-/* (SIOCIWFIRSTPRIV + 17) is currently unused */
+
+#ifdef FEATURE_WLM_STATS
+/*
+ * <ioctl>
+ *
+ * get_wlm_stats - Get stats from FW for game latency
+ *
+ * @INPUT: BITMASK inform of decimal number
+ *
+ * @OUTPUT: HEX string given by FW
+ *
+ * This IOCTL is used to get game latency related STATS from FW
+ *
+ * @E.g.: iwpriv wlan0 get_wlm_stats 1
+ *
+ * Usage: internal
+ *
+ * </ioctl>
+ */
+#define WLAN_GET_WLM_STATS       (SIOCIWFIRSTPRIV + 17)
+#endif
+
 /* (SIOCIWFIRSTPRIV + 19) is currently unused */
 
 #define WLAN_PRIV_SET_FTIES             (SIOCIWFIRSTPRIV + 20)
@@ -3688,6 +3709,166 @@
 	return ret;
 }
 
+#ifdef FEATURE_WLM_STATS
+static void wlan_get_wlm_stats_cb(void *cookie, const char *data)
+{
+	struct osif_request *request;
+	char *priv;
+
+	request = osif_request_get(cookie);
+	if (!request) {
+		hdd_err("Obsolete request");
+		return;
+	}
+	priv = osif_request_priv(request);
+	strlcpy(priv, data, WE_MAX_STR_LEN);
+	osif_request_complete(request);
+	osif_request_put(request);
+}
+
+static int wlan_get_wlm_stats(struct hdd_adapter *adapter, uint32_t bitmask,
+			      char *response)
+{
+	struct osif_request *request;
+	void *cookie;
+	int errno;
+	char *priv;
+	static const struct osif_request_params params = {
+			.priv_size = WE_MAX_STR_LEN,
+			.timeout_ms = 2000,
+	};
+
+	if (!adapter) {
+		hdd_err("NULL argument");
+		return -EINVAL;
+	}
+	request = osif_request_alloc(&params);
+	if (!request) {
+		hdd_err("Request allocation failure");
+		return -ENOMEM;
+	}
+	cookie = osif_request_cookie(request);
+	errno = wma_wlm_stats_req(adapter->session_id, bitmask,
+				  params.priv_size,
+				  wlan_get_wlm_stats_cb, cookie);
+	if (errno) {
+		hdd_err("Request failed be sent, %d", errno);
+		goto cleanup;
+	}
+	errno = osif_request_wait_for_response(request);
+	if (errno) {
+		hdd_err("Timeout happened, can't complete the req");
+		goto cleanup;
+	}
+	priv = osif_request_priv(request);
+	strlcpy(response, priv, params.priv_size);
+
+cleanup:
+	osif_request_put(request);
+
+	return errno;
+}
+
+/*
+ * Due to a limitation in iwpriv the "get_wlm_stats" ioctl is defined
+ * to take as input a variable-length string as opposed to taking a
+ * single integer "bitmask" value. Hence we must have a buffer large
+ * enough to hold a string representing the largest possible
+ * value. MAX_INT = 2,147,483,647 which can be fit in 10 chars.
+ * Round up to 12 to hold the trailing NUL and be a multiple of 4.
+ */
+#define WLM_USER_DATA_SIZE 12
+
+static int __iw_get_wlm_stats(struct net_device *dev,
+			      struct iw_request_info *info,
+			      union iwreq_data *wrqu, char *extra)
+{
+	struct iw_point priv_data;
+	char user_data[WLM_USER_DATA_SIZE] = {0};
+	uint32_t bitmask = 0;
+	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
+	struct hdd_context *hdd_ctx;
+	int errno;
+
+	hdd_enter_dev(dev);
+
+	hdd_ctx = WLAN_HDD_GET_CTX(adapter);
+	errno = wlan_hdd_validate_context(hdd_ctx);
+	if (errno)
+		return errno;
+
+	if (!capable(CAP_NET_ADMIN)) {
+		hdd_err("permission check failed");
+		return -EPERM;
+	}
+
+	/*
+	 * Since this is GETTER iwpriv ioctl, driver needs to
+	 * copy SET data from user space to kernel space.
+	 * Helper function to get iwreq_data with compat handling.
+	 */
+	if (hdd_priv_get_data(&priv_data, wrqu))
+		return -EINVAL;
+
+	/*
+	 * priv_data.pointer  should be pointing to data given
+	 * to iwpriv command.
+	 *
+	 * For example "iwpriv wlan0 get_wlm_stats 1234"
+	 *
+	 * priv_data.pointer should be pointing to "1234"
+	 * priv_data.length should be zero as this GETTER iwpriv ioctl
+	 */
+	if (!priv_data.pointer) {
+		hdd_err("NULL data pointer");
+		return -EINVAL;
+	}
+
+	/*
+	 * ideally driver should have used priv_data.length to copy
+	 * data from priv_data.pointer but this iwpriv IOCTL has been
+	 * declared as GETTER in nature which makes length field zero
+	 * for input arguments but priv_data.pointer still points to
+	 * user's input argument (just doesn't pass the length of the
+	 * argument)
+	 */
+	if (copy_from_user(user_data, priv_data.pointer,
+			   sizeof(user_data) - 1)) {
+		hdd_err("failed to copy data from user buffer");
+		return -EFAULT;
+	}
+
+	/*
+	 * user data is given in ascii, convert ascii to integer
+	 */
+	if (kstrtou32(user_data, 0, &bitmask)) {
+		hdd_err("failed to parse input %s", user_data);
+		return -EFAULT;
+	}
+
+	if (wlan_get_wlm_stats(adapter, bitmask, extra)) {
+		hdd_err("returning failure");
+		return -EFAULT;
+	}
+	wrqu->data.length = strlen(extra) + 1;
+
+	return 0;
+}
+
+static int iw_get_wlm_stats(struct net_device *dev,
+			    struct iw_request_info *info,
+			    union iwreq_data *wrqu, char *extra)
+{
+	int ret;
+
+	cds_ssr_protect(__func__);
+	ret = __iw_get_wlm_stats(dev, info, wrqu, extra);
+	cds_ssr_unprotect(__func__);
+
+	return ret;
+}
+#endif /* FEATURE_WLM_STATS */
+
 int wlan_hdd_update_phymode(struct hdd_adapter *adapter, int new_phymode)
 {
 	struct net_device *net = adapter->dev;
@@ -9601,6 +9782,9 @@
 	[WLAN_PRIV_SET_MCBC_FILTER - SIOCIWFIRSTPRIV] =
 		iw_set_dynamic_mcbc_filter,
 	[WLAN_GET_LINK_SPEED - SIOCIWFIRSTPRIV] = iw_get_linkspeed,
+#ifdef FEATURE_WLM_STATS
+	[WLAN_GET_WLM_STATS - SIOCIWFIRSTPRIV] = iw_get_wlm_stats,
+#endif
 	[WLAN_PRIV_SET_TWO_INT_GET_NONE - SIOCIWFIRSTPRIV] =
 		iw_set_two_ints_getnone,
 	[WLAN_SET_DOT11P_CHANNEL_SCHED - SIOCIWFIRSTPRIV] =
@@ -10672,6 +10856,13 @@
 	 IW_PRIV_TYPE_CHAR | 5,
 	 "getLinkSpeed"},
 
+#ifdef FEATURE_WLM_STATS
+	{WLAN_GET_WLM_STATS,
+	 IW_PRIV_TYPE_CHAR | WE_MAX_STR_LEN,
+	 IW_PRIV_TYPE_CHAR | WE_MAX_STR_LEN,
+	 "get_wlm_stats"},
+#endif
+
 	/* handlers for main ioctl */
 	{WLAN_PRIV_SET_TWO_INT_GET_NONE,
 	 IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
diff --git a/core/wma/inc/wma.h b/core/wma/inc/wma.h
index e1a6921..cb15dbf 100644
--- a/core/wma/inc/wma.h
+++ b/core/wma/inc/wma.h
@@ -43,6 +43,7 @@
 #include "wlan_objmgr_psoc_obj.h"
 #include <cdp_txrx_handle.h>
 #include <wlan_policy_mgr_api.h>
+#include "wma_api.h"
 
 /* Platform specific configuration for max. no. of fragments */
 #define QCA_OL_11AC_TX_MAX_FRAGS            2
@@ -951,6 +952,20 @@
 	uint8_t channel_list[MAX_NUM_CHAN];
 };
 
+#ifdef FEATURE_WLM_STATS
+/**
+ * struct wma_wlm_stats_data - Data required to be used to send WLM req
+ * @wlm_stats_max_size: Buffer size provided by userspace
+ * @wlm_stats_cookie: Cookie to retrieve WLM req data
+ * @wlm_stats_callback: Callback to be used to send WLM response
+ */
+struct wma_wlm_stats_data {
+	uint32_t wlm_stats_max_size;
+	void *wlm_stats_cookie;
+	wma_wlm_stats_cb wlm_stats_callback;
+};
+#endif
+
 /**
  * struct t_wma_handle - wma context
  * @wmi_handle: wmi handle
@@ -1075,6 +1090,7 @@
  * @rcpi_enabled: Is RCPI enabled?
  * @link_stats_results: Structure for handing link stats from firmware
  * @tx_fail_cnt: Number of TX failures
+ * @wlm_data: Data required for WLM req and resp handling
  * @he_cap: 802.11ax capabilities
  * @bandcapability: band capability configured through ini
  * @tx_bfee_8ss_enabled: Is Tx Beamformee support for 8x8 enabled?
@@ -1208,6 +1224,9 @@
 	bool rcpi_enabled;
 	tSirLLStatsResults *link_stats_results;
 	uint64_t tx_fail_cnt;
+#ifdef FEATURE_WLM_STATS
+	struct wma_wlm_stats_data wlm_data;
+#endif
 #ifdef WLAN_FEATURE_11AX
 	struct he_capability he_cap;
 #endif
diff --git a/core/wma/inc/wma_api.h b/core/wma/inc/wma_api.h
index c5e23cd..7257a4a 100644
--- a/core/wma/inc/wma_api.h
+++ b/core/wma/inc/wma_api.h
@@ -574,4 +574,46 @@
 void wma_set_channel_switch_in_progress(struct wma_txrx_node *iface);
 
 #endif
-#endif
+
+#ifdef FEATURE_WLM_STATS
+/**
+ * typedef wma_wlm_stats_cb() - Callback function for WLM stats
+ * @cookie: Cookie provided by client during callback registration
+ * @data: Hex ASCII representation of the WLM stats
+ */
+typedef void (*wma_wlm_stats_cb)(void *cookie, const char *data);
+
+/**
+ * wma_wlm_stats_req() - Send a req to WLAN Latency Manager in FW
+ * @vdev_id: vdev id to be sent to FW's WLM
+ * @bitmask: A bitmask which is requested by user to be sent to FW's WLM
+ * @max_size: Size of user's buffer to store the response
+ * @cb: A callback to be called to once response is available
+ * @cookie: A cookie to be used by callback to retrieve the context of req
+ *
+ * This API is used to send a message to WLAN latency manager component
+ * in FW to retrieve some latency related data and send it to user space.
+ * Driver is just a pass-through for user to interract with FW.
+ *
+ * Return: 0 on success and non-zero for error
+ */
+int wma_wlm_stats_req(int vdev_id, uint32_t bitmask, uint32_t max_size,
+		      wma_wlm_stats_cb cb, void *cookie);
+
+/**
+ * wma_wlm_stats_rsp() - Handler to handle the response from FW's WLM component
+ * @wma_ctx: WMA context
+ * @event: WMI TLV event data
+ * @len: WMI TLV length
+ *
+ * This API is registered with WMI component in order to handle the response
+ * coming from FW's WLM correspondence to WLM REQ to FW. This API takes the
+ * data coming as HEX stream and write in to CHAR buffer as HEX CHAR stream
+ * and send this char buffer to user space through callback.
+ *
+ * Return: 0 on success and non-zero for error
+ */
+int wma_wlm_stats_rsp(void *wma_ctx, uint8_t *event, uint32_t len);
+#endif /* FEATURE_WLM_STATS */
+
+#endif /* WMA_API_H */
diff --git a/core/wma/src/wma_main.c b/core/wma/src/wma_main.c
index 9a4b0f9..6435c0f 100644
--- a/core/wma/src/wma_main.c
+++ b/core/wma/src/wma_main.c
@@ -3107,6 +3107,20 @@
 }
 #endif /* WLAN_FEATURE_MOTION_DETECTION */
 
+#ifdef FEATURE_WLM_STATS
+static void wma_register_wlm_stats_events(tp_wma_handle wma_handle)
+{
+	wmi_unified_register_event_handler(wma_handle->wmi_handle,
+					   wmi_wlm_stats_event_id,
+					   wma_wlm_stats_rsp,
+					   WMA_RX_SERIALIZER_CTX);
+}
+#else /* FEATURE_WLM_STATS */
+static void wma_register_wlm_stats_events(tp_wma_handle wma_handle)
+{
+}
+#endif /* FEATURE_WLM_STATS */
+
 struct wlan_objmgr_psoc *wma_get_psoc_from_scn_handle(void *scn_handle)
 {
 	tp_wma_handle wma_handle;
@@ -3681,6 +3695,7 @@
 
 	wma_register_apf_events(wma_handle);
 	wma_register_md_events(wma_handle);
+	wma_register_wlm_stats_events(wma_handle);
 
 	return QDF_STATUS_SUCCESS;
 
diff --git a/core/wma/src/wma_utils.c b/core/wma/src/wma_utils.c
index 3ca616e..cb1f0b1 100644
--- a/core/wma/src/wma_utils.c
+++ b/core/wma/src/wma_utils.c
@@ -5021,3 +5021,125 @@
 	iface->is_channel_switch = true;
 }
 #endif
+
+#ifdef FEATURE_WLM_STATS
+int wma_wlm_stats_req(int vdev_id, uint32_t bitmask, uint32_t max_size,
+		      wma_wlm_stats_cb cb, void *cookie)
+{
+	tp_wma_handle wma_handle = cds_get_context(QDF_MODULE_ID_WMA);
+	wmi_unified_t wmi_handle;
+	wmi_buf_t wmi_buf;
+	uint32_t buf_len, tlv_tag, tlv_len;
+	wmi_request_wlm_stats_cmd_fixed_param *cmd;
+	QDF_STATUS status;
+
+	if (!wma_handle) {
+		wma_err("Invalid wma handle");
+		return -EINVAL;
+	}
+
+	wmi_handle = wma_handle->wmi_handle;
+	if (!wmi_handle) {
+		wma_err("Invalid wmi handle for wlm_stats_event_handler");
+		return -EINVAL;
+	}
+
+	if (!wmi_service_enabled(wmi_handle, wmi_service_wlm_stats_support)) {
+		wma_err("Feature not supported by firmware");
+		return -ENOTSUPP;
+	}
+
+	wma_handle->wlm_data.wlm_stats_cookie = cookie;
+	wma_handle->wlm_data.wlm_stats_callback = cb;
+	wma_handle->wlm_data.wlm_stats_max_size = max_size;
+
+	buf_len = sizeof(*cmd);
+	wmi_buf = wmi_buf_alloc(wma_handle->wmi_handle, buf_len);
+	if (!wmi_buf)
+		return -EINVAL;
+
+	cmd = (void *)wmi_buf_data(wmi_buf);
+
+	tlv_tag = WMITLV_TAG_STRUC_wmi_request_wlm_stats_cmd_fixed_param;
+	tlv_len =
+		WMITLV_GET_STRUCT_TLVLEN(wmi_request_wlm_stats_cmd_fixed_param);
+	WMITLV_SET_HDR(&cmd->tlv_header, tlv_tag, tlv_len);
+
+	cmd->vdev_id = vdev_id;
+	cmd->request_bitmask = bitmask;
+	status = wmi_unified_cmd_send(wma_handle->wmi_handle, wmi_buf, buf_len,
+				      WMI_REQUEST_WLM_STATS_CMDID);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		wmi_buf_free(wmi_buf);
+		return -EINVAL;
+	}
+	/* info logging per test team request */
+	wma_info("---->sent request for vdev:%d", vdev_id);
+
+	return 0;
+}
+
+int wma_wlm_stats_rsp(void *wma_ctx, uint8_t *event, uint32_t evt_len)
+{
+	WMI_WLM_STATS_EVENTID_param_tlvs *param_tlvs;
+	wmi_wlm_stats_event_fixed_param *param;
+	tp_wma_handle wma_handle = wma_ctx;
+	char *data;
+	void *cookie;
+	uint32_t *raw_data;
+	uint32_t len, buffer_size, raw_data_num, i;
+
+	if (!wma_handle) {
+		wma_err("Invalid wma handle");
+		return -EINVAL;
+	}
+	if (!wma_handle->wlm_data.wlm_stats_callback) {
+		wma_err("No callback registered");
+		return -EINVAL;
+	}
+
+	param_tlvs = (WMI_WLM_STATS_EVENTID_param_tlvs *)event;
+	param = param_tlvs->fixed_param;
+	if (!param) {
+		wma_err("Fix size param is not present, something is wrong");
+		return -EINVAL;
+	}
+
+	/* info logging per test team request */
+	wma_info("---->Received response for vdev:%d", param->vdev_id);
+
+	raw_data = param_tlvs->data;
+	raw_data_num = param_tlvs->num_data;
+
+	len = 0;
+	buffer_size = wma_handle->wlm_data.wlm_stats_max_size;
+	data = qdf_mem_malloc(buffer_size);
+	if (!data)
+		return -ENOMEM;
+
+	len += qdf_scnprintf(data + len, buffer_size - len, "\n%x ",
+			     param->request_bitmask);
+	len += qdf_scnprintf(data + len, buffer_size - len, "%x ",
+			     param->vdev_id);
+	len += qdf_scnprintf(data + len, buffer_size - len, "%x ",
+			     param->timestamp);
+	len += qdf_scnprintf(data + len, buffer_size - len, "%x ",
+			     param->req_interval);
+	if (!raw_data)
+		goto send_data;
+
+	len += qdf_scnprintf(data + len, buffer_size - len, "\ndata:\n");
+
+	for (i = 0; i < raw_data_num; i++)
+		len += qdf_scnprintf(data + len, buffer_size - len, "%x ",
+				     *raw_data++);
+
+send_data:
+	cookie = wma_handle->wlm_data.wlm_stats_cookie;
+	wma_handle->wlm_data.wlm_stats_callback(cookie, data);
+
+	qdf_mem_free(data);
+
+	return 0;
+}
+#endif /* FEATURE_WLM_STATS */