diff --git a/Kbuild b/Kbuild
index 2d98488ae0..79f84fdd3c 100644
--- a/Kbuild
+++ b/Kbuild
@@ -1991,6 +1991,7 @@ endif
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 c8bb8a54f2..4ce5d0b88f 100644
--- a/configs/default_defconfig
+++ b/configs/default_defconfig
@@ -668,6 +668,7 @@ endif
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 2a14910a75..e5af478ac0 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
+/*
+ *
+ *
+ * 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
+ *
+ *
+ */
+#define WLAN_GET_WLM_STATS (SIOCIWFIRSTPRIV + 17)
+#endif
+
/* (SIOCIWFIRSTPRIV + 19) is currently unused */
#define WLAN_PRIV_SET_FTIES (SIOCIWFIRSTPRIV + 20)
@@ -3688,6 +3709,166 @@ static int iw_get_linkspeed(struct net_device *dev,
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(¶ms);
+ 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 @@ static const iw_handler we_private[] = {
[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 @@ static const struct iw_priv_args we_private_args[] = {
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 e1a6921247..cb15dbfab2 100644
--- a/core/wma/inc/wma.h
+++ b/core/wma/inc/wma.h
@@ -43,6 +43,7 @@
#include "wlan_objmgr_psoc_obj.h"
#include
#include
+#include "wma_api.h"
/* Platform specific configuration for max. no. of fragments */
#define QCA_OL_11AC_TX_MAX_FRAGS 2
@@ -951,6 +952,20 @@ struct wma_valid_channels {
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 @@ struct wma_valid_channels {
* @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 @@ typedef struct {
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 c5e23cd95c..7257a4a30a 100644
--- a/core/wma/inc/wma_api.h
+++ b/core/wma/inc/wma_api.h
@@ -574,4 +574,46 @@ void wma_set_hidden_ssid_restart_in_progress(struct wma_txrx_node *iface,
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 9a4b0f9751..6435c0fe2d 100644
--- a/core/wma/src/wma_main.c
+++ b/core/wma/src/wma_main.c
@@ -3107,6 +3107,20 @@ static void wma_register_md_events(tp_wma_handle wma_handle)
}
#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 @@ QDF_STATUS wma_open(struct wlan_objmgr_psoc *psoc,
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 3ca616e338..cb1f0b12b0 100644
--- a/core/wma/src/wma_utils.c
+++ b/core/wma/src/wma_utils.c
@@ -5021,3 +5021,125 @@ void wma_set_channel_switch_in_progress(struct wma_txrx_node *iface)
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 */