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 */