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
This commit is contained in:
Krunal Soni 2019-01-03 21:44:41 -08:00 committed by nshrivas
parent 85e1ac663c
commit f9ba53dbbc
7 changed files with 393 additions and 2 deletions

1
Kbuild
View File

@ -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 += \

View File

@ -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

View File

@ -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 @@ 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(&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 @@ 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,

View File

@ -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 @@ 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

View File

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

View File

@ -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;

View File

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