Browse Source

qcacld-3.0: add support for transmit latency stats

Add support for per-link transmit latency statistics

Change-Id: If5f2f7785e45379491f085690b7fc9ea109e1148
CRs-Fixed: 3597015
Yu Wang 1 year ago
parent
commit
33b2ada4cb

+ 6 - 0
Kbuild

@@ -4130,6 +4130,12 @@ ccflags-y += -DWLAN_FEATURE_TSF_UPLINK_DELAY
 CONFIG_WLAN_TSF_AUTO_REPORT := y
 endif
 
+# Enable tx latency stats feature
+ifeq ($(CONFIG_WLAN_TX_LATENCY_STATS), y)
+ccflags-y += -DWLAN_FEATURE_TX_LATENCY_STATS
+CONFIG_WLAN_TSF_AUTO_REPORT := y
+endif
+
 # Enable TSF auto report feature
 ccflags-$(CONFIG_WLAN_TSF_AUTO_REPORT) += -DWLAN_FEATURE_TSF_AUTO_REPORT
 

+ 5 - 0
Kconfig

@@ -1638,6 +1638,11 @@ config WLAN_TSF_UPLINK_DELAY
 	depends on WLAN_TSF_AUTO_REPORT
 	default n
 
+config WLAN_TX_LATENCY_STATS
+	bool "Enable WLAN_TX_LATENCY_STATS"
+	depends on WLAN_TSF_AUTO_REPORT
+	default n
+
 config WLAN_TWT_CONVERGED
 	bool "Enable WLAN_TWT_CONVERGED"
 	default n

+ 4 - 0
configs/config_to_feature.h

@@ -1336,6 +1336,10 @@
 #define WLAN_FEATURE_TSF_AUTO_REPORT (1)
 #endif
 
+#ifdef CONFIG_WLAN_TX_LATENCY_STATS
+#define WLAN_FEATURE_TX_LATENCY_STATS (1)
+#endif
+
 #ifdef CONFIG_WLAN_TSF_UPLINK_DELAY
 #define WLAN_FEATURE_TSF_UPLINK_DELAY (1)
 #endif

+ 4 - 0
core/hdd/inc/wlan_hdd_main.h

@@ -1284,6 +1284,7 @@ struct wlan_hdd_tx_power {
  * @deflink: Default link pointing to the 0th index of the linkinfo array
  * @link_info: Data structure to hold link specific information
  * @tx_power: Structure to hold connection tx Power info
+ * @tx_latency_cfg: configuration for per-link transmit latency statistics
  */
 struct hdd_adapter {
 	uint32_t magic;
@@ -1474,6 +1475,9 @@ struct hdd_adapter {
 	struct wlan_hdd_link_info *deflink;
 	struct wlan_hdd_link_info link_info[WLAN_MAX_ML_BSS_LINKS];
 	struct wlan_hdd_tx_power tx_power;
+#ifdef WLAN_FEATURE_TX_LATENCY_STATS
+	struct cdp_tx_latency_config tx_latency_cfg;
+#endif
 };
 
 #define WLAN_HDD_GET_STATION_CTX_PTR(link_info) (&(link_info)->session.station)

+ 2 - 0
core/hdd/src/wlan_hdd_cfg80211.c

@@ -2171,6 +2171,7 @@ static const struct nl80211_vendor_cmd_info wlan_hdd_cfg80211_vendor_events[] =
 		.subcmd = QCA_NL80211_VENDOR_SUBCMD_AUDIO_TRANSPORT_SWITCH,
 	},
 
+	FEATURE_TX_LATENCY_STATS_EVENTS
 };
 
 /**
@@ -20575,6 +20576,7 @@ const struct wiphy_vendor_command hdd_wiphy_vendor_commands[] = {
 	FEATURE_ML_LINK_STATE_COMMANDS
 	FEATURE_AFC_VENDOR_COMMANDS
 	FEATURE_LL_LT_SAP_VENDOR_COMMANDS
+	FEATURE_TX_LATENCY_STATS_COMMANDS
 };
 
 struct hdd_context *hdd_cfg80211_wiphy_alloc(void)

+ 3 - 0
core/hdd/src/wlan_hdd_main.c

@@ -5786,6 +5786,7 @@ status_ret:
 	/* Update FW WoW pattern with new MAC address */
 	ucfg_pmo_del_wow_pattern(vdev);
 	ucfg_pmo_register_wow_default_patterns(vdev);
+	hdd_tx_latency_restore_config(link_info);
 
 allow_suspend:
 	hdd_allow_suspend(WIFI_POWER_EVENT_WAKELOCK_DYN_MAC_ADDR_UPDATE);
@@ -15389,6 +15390,8 @@ static int hdd_pre_enable_configure(struct hdd_context *hdd_ctx)
 	uint8_t index = 0;
 
 	cdp_register_pause_cb(soc, wlan_hdd_txrx_pause_cb);
+	hdd_tx_latency_register_cb(soc);
+
 	/* Register HL netdev flow control callback */
 	cdp_hl_fc_register(soc, OL_TXRX_PDEV_ID, wlan_hdd_txrx_pause_cb);
 	/* Register rx mic error indication handler */

+ 2 - 0
core/hdd/src/wlan_hdd_softap_tx_rx.c

@@ -57,6 +57,7 @@
 #include <os_if_dp.h>
 #include <cfg_ucfg_api.h>
 #include <wlan_twt_ucfg_ext_api.h>
+#include "wlan_hdd_stats.h"
 
 /* Preprocessor definitions and constants */
 #undef QCA_HDD_SAP_DUMP_SK_BUFF
@@ -210,6 +211,7 @@ static void __hdd_softap_hard_start_xmit(struct sk_buff *skb,
 	QDF_STATUS status;
 
 	osif_dp_mark_pkt_type(skb);
+	hdd_tx_latency_record_ingress_ts(adapter, skb);
 
 	/* Get TL AC corresponding to Qdisc queue index/AC. */
 	ac = hdd_qdisc_ac_to_tl_ac[skb->queue_mapping];

+ 872 - 0
core/hdd/src/wlan_hdd_stats.c

@@ -10537,3 +10537,875 @@ int wlan_hdd_cfg80211_get_roam_stats(struct wiphy *wiphy,
 	return errno;
 }
 #endif
+
+#ifdef WLAN_FEATURE_TX_LATENCY_STATS
+#define TX_LATENCY_BUCKET_DISTRIBUTION_LEN \
+	(sizeof(uint32_t) * CDP_TX_LATENCY_TYPE_MAX)
+
+#define TX_LATENCY_ATTR(_name) QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ ## _name
+
+static const struct nla_policy
+tx_latency_bucket_policy[TX_LATENCY_ATTR(BUCKET_MAX) + 1] = {
+	[TX_LATENCY_ATTR(BUCKET_TYPE)] = {.type = NLA_U8},
+	[TX_LATENCY_ATTR(BUCKET_GRANULARITY)] = {.type = NLA_U32},
+	[TX_LATENCY_ATTR(BUCKET_AVERAGE)] = {.type = NLA_U32},
+	[TX_LATENCY_ATTR(BUCKET_DISTRIBUTION)] = {
+		.type = NLA_BINARY, .len = TX_LATENCY_BUCKET_DISTRIBUTION_LEN},
+};
+
+static const struct nla_policy
+tx_latency_link_policy[TX_LATENCY_ATTR(LINK_MAX) + 1] = {
+	[TX_LATENCY_ATTR(LINK_MAC_REMOTE)] = {
+		.type = NLA_BINARY, .len = QDF_MAC_ADDR_SIZE},
+	[TX_LATENCY_ATTR(LINK_STAT_BUCKETS)] =
+		VENDOR_NLA_POLICY_NESTED_ARRAY(tx_latency_bucket_policy),
+};
+
+const struct nla_policy
+tx_latency_policy[TX_LATENCY_ATTR(MAX) + 1] = {
+	[TX_LATENCY_ATTR(ACTION)] = {.type = NLA_U32},
+	[TX_LATENCY_ATTR(PERIODIC_REPORT)] = {.type = NLA_FLAG},
+	[TX_LATENCY_ATTR(PERIOD)] = {.type = NLA_U32 },
+	[TX_LATENCY_ATTR(BUCKETS)] =
+		VENDOR_NLA_POLICY_NESTED_ARRAY(tx_latency_bucket_policy),
+	[TX_LATENCY_ATTR(LINKS)] =
+		VENDOR_NLA_POLICY_NESTED_ARRAY(tx_latency_link_policy),
+};
+
+/**
+ * struct tx_latency_link_node - Link info of remote peer
+ * @node: list node for membership in the link list
+ * @vdev_id: Unique value to identify VDEV
+ * @mac_remote: link MAC address of the remote peer
+ */
+struct tx_latency_link_node {
+	qdf_list_node_t node;
+	uint8_t vdev_id;
+	struct qdf_mac_addr mac_remote;
+};
+
+/**
+ * hdd_tx_latency_set_for_link() - set tx latency stats config for a link
+ * @link_info: link specific information
+ * @config: pointer to tx latency stats config
+ *
+ * Return: QDF_STATUS
+ */
+static inline QDF_STATUS
+hdd_tx_latency_set_for_link(struct wlan_hdd_link_info *link_info,
+			    struct cdp_tx_latency_config *config)
+{
+	QDF_STATUS status;
+	void *soc = cds_get_context(QDF_MODULE_ID_SOC);
+
+	if (!soc)
+		return QDF_STATUS_E_INVAL;
+
+	if (wlan_hdd_validate_vdev_id(link_info->vdev_id))
+		return QDF_STATUS_SUCCESS;
+
+	status = cdp_host_tx_latency_stats_config(soc,
+						  link_info->vdev_id,
+						  config);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		hdd_err_rl("failed to %s for vdev id %d, status %d",
+			   config->enable ? "enable" : "disable",
+			   link_info->vdev_id, status);
+		return status;
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * hdd_tx_latency_restore_config() - restore tx latency stats config for a link
+ * @link_info: link specific information
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS
+hdd_tx_latency_restore_config(struct wlan_hdd_link_info *link_info)
+{
+	QDF_STATUS status;
+	void *soc = cds_get_context(QDF_MODULE_ID_SOC);
+	struct cdp_tx_latency_config *config;
+
+	if (!soc)
+		return QDF_STATUS_E_INVAL;
+
+	if (wlan_hdd_validate_vdev_id(link_info->vdev_id))
+		return QDF_STATUS_SUCCESS;
+
+	config = &link_info->adapter->tx_latency_cfg;
+	status = cdp_host_tx_latency_stats_config(soc,
+						  link_info->vdev_id,
+						  config);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		hdd_err_rl("failed to %s for vdev id %d, status %d",
+			   config->enable ? "enable" : "disable",
+			   link_info->vdev_id, status);
+		return status;
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * hdd_tx_latency_set() - restore tx latency stats config for a link
+ * @adapter: pointer to hdd vdev/net_device context
+ * @config: pointer to tx latency stats config
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+hdd_tx_latency_set(struct hdd_adapter *adapter,
+		   struct cdp_tx_latency_config *config)
+{
+	int ret;
+	struct wlan_hdd_link_info *link_info;
+	QDF_STATUS status = QDF_STATUS_E_NOENT;
+
+	ret = hdd_set_tsf_auto_report(adapter, config->enable,
+				      HDD_TSF_AUTO_RPT_SOURCE_TX_LATENCY);
+	if (ret) {
+		hdd_err_rl("failed to %s tsf auto report, ret %d",
+			   config->enable ? "enable" : "disable", ret);
+		return ret;
+	}
+
+	hdd_adapter_for_each_link_info(adapter, link_info) {
+		status = hdd_tx_latency_set_for_link(link_info, config);
+		if (QDF_IS_STATUS_ERROR(status))
+			break;
+	}
+
+	/* restore TSF auto report config on failure */
+	if (QDF_IS_STATUS_ERROR(status))
+		hdd_set_tsf_auto_report(adapter, !config->enable,
+					HDD_TSF_AUTO_RPT_SOURCE_TX_LATENCY);
+	else
+		qdf_mem_copy(&adapter->tx_latency_cfg, config,
+			     sizeof(*config));
+	hdd_debug("enable %d status %d", config->enable, status);
+	return qdf_status_to_os_return(status);
+}
+
+/**
+ * hdd_tx_latency_fill_link_stats() - fill tx latency statistics info skb
+ * @skb: skb to be filled
+ * @latency: per link tx latency statistics
+ * @idx: index of the nested attribute
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+hdd_tx_latency_fill_link_stats(struct sk_buff *skb,
+			       struct cdp_tx_latency *latency, int idx)
+{
+	struct nlattr *link, *link_stat_buckets, *link_stat_bucket;
+	uint32_t type;
+	int ret = 0;
+
+	link = nla_nest_start(skb, idx);
+	if (!link) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	if (nla_put(skb, TX_LATENCY_ATTR(LINK_MAC_REMOTE),
+		    QDF_MAC_ADDR_SIZE, latency->mac_remote.bytes)) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	hdd_debug_rl("idx %d link mac " QDF_MAC_ADDR_FMT,
+		     idx, QDF_MAC_ADDR_REF(latency->mac_remote.bytes));
+	link_stat_buckets =
+		nla_nest_start(skb, TX_LATENCY_ATTR(LINK_STAT_BUCKETS));
+	for (type = 0; type < CDP_TX_LATENCY_TYPE_MAX; type++) {
+		link_stat_bucket = nla_nest_start(skb, type);
+		if (!link_stat_bucket) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		if (nla_put_u8(skb, TX_LATENCY_ATTR(BUCKET_TYPE), type)) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		if (nla_put_u32(skb, TX_LATENCY_ATTR(BUCKET_GRANULARITY),
+				latency->stats[type].granularity)) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		if (nla_put_u32(skb, TX_LATENCY_ATTR(BUCKET_AVERAGE),
+				latency->stats[type].average)) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		if (nla_put(skb, TX_LATENCY_ATTR(BUCKET_DISTRIBUTION),
+			    TX_LATENCY_BUCKET_DISTRIBUTION_LEN,
+			    latency->stats[type].distribution)) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		nla_nest_end(skb, link_stat_bucket);
+		hdd_debug_rl("	type %u granularity %u average %u",
+			     type, latency->stats[type].granularity,
+			     latency->stats[type].average);
+	}
+
+	nla_nest_end(skb, link_stat_buckets);
+	nla_nest_end(skb, link);
+
+err:
+	if (ret)
+		hdd_err("failed for link " QDF_MAC_ADDR_FMT " ret: %d",
+			QDF_MAC_ADDR_REF(latency->mac_remote.bytes), ret);
+	return ret;
+}
+
+/**
+ * hdd_tx_latency_get_skb_len() - get required skb length for vendor command
+ * response/async event
+ * @num: required number of entries
+ *
+ * Return: the required skb length
+ */
+static uint32_t hdd_tx_latency_get_skb_len(uint32_t num)
+{
+	int32_t peer_stat_sz = 0, per_bucket_len = 0, len;
+
+	if (!num)
+		return 0;
+
+	/* QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE */
+	per_bucket_len += nla_total_size(sizeof(uint8_t));
+	/* QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_GRANULARITY */
+	per_bucket_len += nla_total_size(sizeof(uint32_t));
+	/* QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_DISTRIBUTION */
+	per_bucket_len += nla_total_size(TX_LATENCY_BUCKET_DISTRIBUTION_LEN);
+	/* Nested attr */
+	per_bucket_len = nla_total_size(per_bucket_len);
+
+	/* QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_MAC_REMOTE */
+	peer_stat_sz += nla_total_size(QDF_MAC_ADDR_SIZE);
+	/* QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_STAT_BUCKETS */
+	peer_stat_sz +=
+		nla_total_size(per_bucket_len * CDP_TX_LATENCY_TYPE_MAX);
+	/* Nested attr */
+	peer_stat_sz = nla_total_size(peer_stat_sz);
+
+	/* QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS */
+	len = nla_total_size(peer_stat_sz * num);
+	len += NLMSG_HDRLEN;
+	return len;
+}
+
+/**
+ * hdd_tx_latency_link_list_free() - free all the nodes in the list
+ * @list: list of the nodes for link info
+ *
+ * Return: None
+ */
+static void hdd_tx_latency_link_list_free(qdf_list_t *list)
+{
+	struct tx_latency_link_node *entry, *next;
+
+	qdf_list_for_each_del(list, entry, next, node) {
+		qdf_list_remove_node(list, &entry->node);
+		qdf_mem_free(entry);
+	}
+}
+
+/**
+ * hdd_tx_latency_link_list_add() - add a new node to the list for tx latency
+ * links
+ * @list: list of the nodes for link info
+ * @vdev_id: Unique value to identify VDEV
+ * @mac: link mac address of the remote peer
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+hdd_tx_latency_link_list_add(qdf_list_t *list, uint8_t vdev_id, uint8_t *mac)
+{
+	struct tx_latency_link_node *link;
+
+	link = (struct tx_latency_link_node *)qdf_mem_malloc(sizeof(*link));
+	if (!link)
+		return -ENOMEM;
+
+	qdf_mem_copy(link->mac_remote.bytes, mac, QDF_MAC_ADDR_SIZE);
+	link->vdev_id = vdev_id;
+	qdf_list_insert_back(list, &link->node);
+	return 0;
+}
+
+/**
+ * hdd_tx_latency_get_links_from_attr() - parse information of the links from
+ * attribute QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS
+ * @adapter: pointer to hdd vdev/net_device context
+ * @links_attr: pointer to attribute QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS
+ * @list: list of the nodes for link info
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+hdd_tx_latency_get_links_from_attr(struct hdd_adapter *adapter,
+				   struct nlattr *links_attr,
+				   qdf_list_t *list)
+{
+	struct nlattr *attr, *link_mac_remote_attr;
+	struct nlattr *tb[TX_LATENCY_ATTR(LINK_MAX) + 1];
+	int ret = 0, rem;
+	uint8_t vdev_id, *mac;
+
+	if (!links_attr || !list)
+		return -EINVAL;
+
+	/* links for MLO STA are attached to different vdevs */
+	vdev_id = (adapter->device_mode == QDF_STA_MODE ?
+		   CDP_VDEV_ALL : adapter->deflink->vdev_id);
+
+	nla_for_each_nested(attr, links_attr, rem) {
+		ret = wlan_cfg80211_nla_parse(tb, TX_LATENCY_ATTR(LINK_MAX),
+					      nla_data(attr), nla_len(attr),
+					      tx_latency_link_policy);
+		if (ret) {
+			hdd_err("Attribute parse failed, ret %d", ret);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		link_mac_remote_attr = tb[TX_LATENCY_ATTR(LINK_MAC_REMOTE)];
+		if (!link_mac_remote_attr) {
+			hdd_err("Missing link mac remote attribute");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (nla_len(link_mac_remote_attr) < QDF_MAC_ADDR_SIZE) {
+			hdd_err("Attribute link mac remote is invalid");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		mac = (uint8_t *)nla_data(link_mac_remote_attr);
+		ret = hdd_tx_latency_link_list_add(list, vdev_id, mac);
+		if (ret)
+			goto out;
+	}
+
+out:
+	if (ret)
+		hdd_tx_latency_link_list_free(list);
+
+	return ret;
+}
+
+/**
+ * hdd_tx_latency_get_links_for_sap() - get all the active links for SAP mode
+ * @adapter: pointer to hdd vdev/net_device context
+ * @list: list of the nodes for link info
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+hdd_tx_latency_get_links_for_sap(struct hdd_adapter *adapter, qdf_list_t *list)
+{
+	struct hdd_station_info *sta, *tmp = NULL;
+	int ret = 0;
+
+	hdd_for_each_sta_ref_safe(adapter->sta_info_list, sta, tmp,
+				  STA_INFO_SOFTAP_GET_STA_INFO) {
+		if (QDF_IS_ADDR_BROADCAST(sta->sta_mac.bytes)) {
+			hdd_put_sta_info_ref(&adapter->sta_info_list,
+					     &sta, true,
+					     STA_INFO_SOFTAP_GET_STA_INFO);
+			continue;
+		}
+
+		ret = hdd_tx_latency_link_list_add(list,
+						   adapter->deflink->vdev_id,
+						   sta->sta_mac.bytes);
+		hdd_put_sta_info_ref(&adapter->sta_info_list, &sta, true,
+				     STA_INFO_SOFTAP_GET_STA_INFO);
+		if (ret)
+			goto out;
+	}
+
+out:
+	if (ret)
+		hdd_tx_latency_link_list_free(list);
+
+	return ret;
+}
+
+/**
+ * hdd_tx_latency_get_links_for_sta() - get all the active links for station
+ * mode
+ * @adapter: pointer to hdd vdev/net_device context
+ * @list: list of the nodes for link info
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+hdd_tx_latency_get_links_for_sta(struct hdd_adapter *adapter, qdf_list_t *list)
+{
+	struct wlan_hdd_link_info *link_info;
+	struct hdd_station_ctx *ctx;
+	int ret = 0;
+
+	hdd_adapter_for_each_active_link_info(adapter, link_info) {
+		if (wlan_hdd_validate_vdev_id(link_info->vdev_id))
+			continue;
+
+		ctx = WLAN_HDD_GET_STATION_CTX_PTR(link_info);
+		if (!hdd_cm_is_vdev_associated(link_info))
+			continue;
+
+		ret = hdd_tx_latency_link_list_add(list, link_info->vdev_id,
+						   ctx->conn_info.bssid.bytes);
+		if (ret)
+			goto out;
+	}
+
+out:
+	if (ret)
+		hdd_tx_latency_link_list_free(list);
+
+	return ret;
+}
+
+/**
+ * hdd_tx_latency_get_links() - get all the active links
+ * @adapter: pointer to hdd vdev/net_device context
+ * @links_attr: pointer to attribute QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS
+ * @list: list of the nodes for link info
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+hdd_tx_latency_get_links(struct hdd_adapter *adapter,
+			 struct nlattr *links_attr, qdf_list_t *list)
+{
+	if (!list)
+		return -EINVAL;
+
+	if (links_attr)
+		return hdd_tx_latency_get_links_from_attr(adapter,
+							  links_attr, list);
+
+	if (adapter->device_mode == QDF_SAP_MODE ||
+	    adapter->device_mode == QDF_P2P_GO_MODE)
+		return hdd_tx_latency_get_links_for_sap(adapter, list);
+	else if (adapter->device_mode == QDF_STA_MODE ||
+		 adapter->device_mode == QDF_P2P_CLIENT_MODE)
+		return hdd_tx_latency_get_links_for_sta(adapter, list);
+	else
+		return -ENOTSUPP;
+}
+
+/**
+ * hdd_tx_latency_populate_links() - get per link tx latency stats and fill
+ * into skb
+ * @soc: pointer to soc context
+ * @skb: skb for vendor command response/async event
+ * @list: list of the nodes for link info
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static inline int
+hdd_tx_latency_populate_links(void *soc, struct sk_buff *skb, qdf_list_t *list)
+{
+	struct nlattr *links;
+	struct tx_latency_link_node *entry, *next;
+	struct cdp_tx_latency latency = {0};
+	int ret, idx = 0;
+	uint8_t *mac;
+	QDF_STATUS status;
+
+	links = nla_nest_start(skb, TX_LATENCY_ATTR(LINKS));
+	if (!links)
+		return -ENOMEM;
+
+	qdf_list_for_each_del(list, entry, next, node) {
+		qdf_list_remove_node(list, &entry->node);
+		mac = entry->mac_remote.bytes;
+		status = cdp_host_tx_latency_stats_fetch(soc, entry->vdev_id,
+							 mac, &latency);
+		if (QDF_IS_STATUS_ERROR(status)) {
+			qdf_mem_free(entry);
+			return qdf_status_to_os_return(status);
+		}
+
+		ret = hdd_tx_latency_fill_link_stats(skb, &latency, idx);
+		qdf_mem_free(entry);
+		if (ret)
+			return ret;
+
+		idx++;
+	}
+
+	nla_nest_end(skb, links);
+	return 0;
+}
+
+/**
+ * hdd_tx_latency_get() - get per link tx latency stats
+ * @wiphy: pointer to wiphy
+ * @adapter: pointer to hdd vdev/net_device context
+ * @links_attr: pointer to attribute QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+hdd_tx_latency_get(struct wiphy *wiphy,
+		   struct hdd_adapter *adapter, struct nlattr *links_attr)
+{
+	int ret;
+	void *soc = cds_get_context(QDF_MODULE_ID_SOC);
+	struct sk_buff *reply_skb = NULL;
+	uint32_t skb_len, links_num = 0;
+	qdf_list_t links_list;
+
+	if (!soc)
+		return -EINVAL;
+
+	qdf_list_create(&links_list, 0);
+	ret = hdd_tx_latency_get_links(adapter, links_attr, &links_list);
+	if (ret)
+		goto out;
+
+	links_num = qdf_list_size(&links_list);
+	if (!links_num) {
+		hdd_err_rl("no valid peers");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	skb_len = hdd_tx_latency_get_skb_len(links_num);
+	reply_skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(wiphy, skb_len);
+	if (!reply_skb) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = hdd_tx_latency_populate_links(soc, reply_skb, &links_list);
+	if (ret)
+		goto free_skb;
+
+	ret = wlan_cfg80211_vendor_cmd_reply(reply_skb);
+	/* skb has been consumed regardless of the return value */
+	goto out;
+
+free_skb:
+	wlan_cfg80211_vendor_free_skb(reply_skb);
+	hdd_tx_latency_link_list_free(&links_list);
+out:
+	qdf_list_destroy(&links_list);
+	hdd_debug_rl("get stats with ret %d", ret);
+	return ret;
+}
+
+/**
+ * hdd_tx_latency_enable() - enable per link tx latency stats
+ * @adapter: pointer to hdd vdev/net_device context
+ * @period: statistical period for transmit latency
+ * @periodic_report: whether driver needs to report transmit latency
+ * statistics at the end of each period
+ * @buckets_attr: pointer to attribute QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKETS
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+hdd_tx_latency_enable(struct hdd_adapter *adapter, uint32_t period,
+		      bool periodic_report, struct nlattr *buckets_attr)
+{
+	struct nlattr *tb[TX_LATENCY_ATTR(BUCKET_MAX) + 1];
+	struct nlattr *attr, *bucket_type_attr, *bucket_granularity_attr;
+	int rem, ret;
+	uint8_t bucket_type;
+	struct cdp_tx_latency_config config = {0};
+
+	nla_for_each_nested(attr, buckets_attr, rem) {
+		ret = wlan_cfg80211_nla_parse(tb, TX_LATENCY_ATTR(BUCKET_MAX),
+					      nla_data(attr), nla_len(attr),
+					      tx_latency_bucket_policy);
+		if (ret) {
+			hdd_err_rl("Attribute parse failed, ret %d", ret);
+			return -EINVAL;
+		}
+
+		bucket_type_attr = tb[TX_LATENCY_ATTR(BUCKET_TYPE)];
+		if (!bucket_type_attr) {
+			hdd_err_rl("Missing bucket type attribute");
+			return -EINVAL;
+		}
+
+		bucket_granularity_attr =
+			tb[TX_LATENCY_ATTR(BUCKET_GRANULARITY)];
+		if (!bucket_granularity_attr) {
+			hdd_err_rl("Missing bucket granularity attribute");
+			return -EINVAL;
+		}
+
+		bucket_type = nla_get_u8(bucket_type_attr);
+		if (bucket_type >= CDP_TX_LATENCY_TYPE_MAX) {
+			hdd_err_rl("Invalid bucket type %u", bucket_type);
+			return -EINVAL;
+		}
+
+		config.granularity[bucket_type] =
+			nla_get_u32(bucket_granularity_attr);
+		if (!config.granularity[bucket_type]) {
+			hdd_err_rl("Invalid granularity for type %d",
+				   bucket_type);
+			return -EINVAL;
+		}
+	}
+
+	for (rem = 0; rem < CDP_TX_LATENCY_TYPE_MAX; rem++) {
+		if (config.granularity[rem])
+			continue;
+
+		hdd_err_rl("Invalid granularity for type %d", rem);
+		return -EINVAL;
+	}
+
+	config.enable = true;
+	config.report = periodic_report;
+	config.period = period;
+	return hdd_tx_latency_set(adapter, &config);
+}
+
+/**
+ * hdd_tx_latency_disable() - disable per link tx latency stats
+ * @adapter: pointer to hdd vdev/net_device context
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int hdd_tx_latency_disable(struct hdd_adapter *adapter)
+{
+	struct cdp_tx_latency_config config = {0};
+
+	return hdd_tx_latency_set(adapter, &config);
+}
+
+/**
+ * __wlan_hdd_cfg80211_tx_latency - configure/retrieve per-link transmit
+ * latency statistics
+ * @wiphy: wiphy handle
+ * @wdev: wdev handle
+ * @data: user layer input
+ * @data_len: length of user layer input
+ *
+ * this function is called in ssr protected environment.
+ *
+ * return: 0 success, none zero for failure
+ */
+static int
+__wlan_hdd_cfg80211_tx_latency(struct wiphy *wiphy, struct wireless_dev *wdev,
+			       const void *data, int data_len)
+{
+	int ret;
+	uint32_t action, period;
+	struct nlattr *period_attr, *buckets_attr, *links_attr;
+
+	struct net_device *dev = wdev->netdev;
+	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
+	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
+	struct nlattr *tb[TX_LATENCY_ATTR(MAX) + 1];
+	bool periodic_report;
+
+	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
+		hdd_warn("command not allowed in ftm mode");
+		return -EPERM;
+	}
+
+	ret = wlan_hdd_validate_context(hdd_ctx);
+	if (ret)
+		return -EINVAL;
+
+	if (wlan_cfg80211_nla_parse(tb, TX_LATENCY_ATTR(MAX),
+				    data, data_len,
+				    tx_latency_policy)) {
+		hdd_err_rl("invalid attribute");
+		return -EINVAL;
+	}
+
+	if (!tb[TX_LATENCY_ATTR(ACTION)]) {
+		hdd_err_rl("no attr action");
+		return -EINVAL;
+	}
+
+	action = nla_get_u32(tb[TX_LATENCY_ATTR(ACTION)]);
+	switch (action) {
+	case QCA_WLAN_VENDOR_TX_LATENCY_ACTION_DISABLE:
+		if (!adapter->tx_latency_cfg.enable) {
+			ret = 0;
+			break;
+		}
+
+		ret = hdd_tx_latency_disable(adapter);
+		break;
+	case QCA_WLAN_VENDOR_TX_LATENCY_ACTION_ENABLE:
+		period_attr = tb[TX_LATENCY_ATTR(PERIOD)];
+		if (!period_attr) {
+			hdd_err_rl("no attr period");
+			return -EINVAL;
+		}
+
+		buckets_attr = tb[TX_LATENCY_ATTR(BUCKETS)];
+		if (!buckets_attr) {
+			hdd_err_rl("no attr buckets");
+			return -EINVAL;
+		}
+
+		period = nla_get_u32(period_attr);
+		if (!period) {
+			hdd_err_rl("invalid period");
+			return -EINVAL;
+		}
+
+		periodic_report =
+			nla_get_flag(tb[TX_LATENCY_ATTR(PERIODIC_REPORT)]);
+		ret = hdd_tx_latency_enable(adapter, period,
+					    periodic_report, buckets_attr);
+		break;
+	case QCA_WLAN_VENDOR_TX_LATENCY_ACTION_GET:
+		if (!adapter->tx_latency_cfg.enable) {
+			hdd_err_rl("please enable the feature first");
+			ret = -EINVAL;
+			break;
+		}
+
+		links_attr = tb[TX_LATENCY_ATTR(LINKS)];
+		ret = hdd_tx_latency_get(wiphy, adapter, links_attr);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+/**
+ * wlan_hdd_cfg80211_tx_latency - configure/retrieve per-link transmit latency
+ * statistics
+ * @wiphy: wiphy handle
+ * @wdev: wdev handle
+ * @data: user layer input
+ * @data_len: length of user layer input
+ *
+ * return: 0 success, einval failure
+ */
+int wlan_hdd_cfg80211_tx_latency(struct wiphy *wiphy,
+				 struct wireless_dev *wdev,
+				 const void *data, int data_len)
+{
+	int errno;
+	struct osif_vdev_sync *vdev_sync;
+
+	errno = osif_vdev_sync_op_start(wdev->netdev, &vdev_sync);
+	if (errno)
+		return errno;
+
+	errno = __wlan_hdd_cfg80211_tx_latency(wiphy, wdev, data, data_len);
+	osif_vdev_sync_op_stop(vdev_sync);
+	return errno;
+}
+
+/**
+ * hdd_tx_latency_stats_cb() - callback function for transmit latency stats
+ * @vdev_id: Unique value to identify VDEV
+ * @stats_list: list of the nodes for per-link transmit latency statistics
+ *
+ * Return: QDF_STATUS
+ */
+static QDF_STATUS
+hdd_tx_latency_stats_cb(uint8_t vdev_id, qdf_list_t *stats_list)
+{
+	uint32_t len, stats_cnt;
+	struct sk_buff *vendor_event;
+	struct hdd_context *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
+	struct wlan_hdd_link_info *link_info;
+	struct cdp_tx_latency *entry, *next;
+	struct nlattr *links;
+	int ret, idx = 0, flags = cds_get_gfp_flags();
+	int event_idx = QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY_INDEX;
+
+	if (!hdd_ctx) {
+		hdd_err("HDD context is NULL");
+		return QDF_STATUS_E_FAULT;
+	}
+
+	if (!stats_list || qdf_list_empty(stats_list)) {
+		hdd_err("invalid stats list");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	link_info = hdd_get_link_info_by_vdev(hdd_ctx, vdev_id);
+	if (!link_info) {
+		hdd_err("adapter NULL for vdev id %d", vdev_id);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	stats_cnt = qdf_list_size(stats_list);
+	len = hdd_tx_latency_get_skb_len(stats_cnt);
+	hdd_debug_rl("vdev id %d stats cnt %d", vdev_id, stats_cnt);
+	vendor_event =
+		wlan_cfg80211_vendor_event_alloc(hdd_ctx->wiphy,
+						 &link_info->adapter->wdev,
+						 len, event_idx, flags);
+	if (!vendor_event) {
+		hdd_err("event alloc failed vdev id %d, len %d",
+			vdev_id, len);
+		return QDF_STATUS_E_NOMEM;
+	}
+
+	links = nla_nest_start(vendor_event, TX_LATENCY_ATTR(LINKS));
+	if (!links) {
+		wlan_cfg80211_vendor_free_skb(vendor_event);
+		hdd_err("failed to put peers");
+		return QDF_STATUS_E_NOMEM;
+	}
+
+	qdf_list_for_each_del(stats_list, entry, next, node) {
+		qdf_list_remove_node(stats_list, &entry->node);
+		ret = hdd_tx_latency_fill_link_stats(vendor_event, entry, idx);
+		qdf_mem_free(entry);
+		if (ret) {
+			hdd_err("failed to populate stats for idx %d", idx);
+			wlan_cfg80211_vendor_free_skb(vendor_event);
+			return QDF_STATUS_E_NOMEM;
+		}
+
+		idx++;
+	}
+
+	nla_nest_end(vendor_event, links);
+	wlan_cfg80211_vendor_event(vendor_event, flags);
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * hdd_tx_latency_register_cb() - register callback function for transmit
+ * latency stats
+ * @soc: pointer to soc context
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS hdd_tx_latency_register_cb(void *soc)
+{
+	hdd_debug("Register tx latency callback");
+	return cdp_host_tx_latency_stats_register_cb(soc,
+						     hdd_tx_latency_stats_cb);
+}
+#endif

+ 91 - 0
core/hdd/src/wlan_hdd_stats.h

@@ -704,4 +704,95 @@ int wlan_hdd_cfg80211_get_roam_stats(struct wiphy *wiphy,
 #define FEATURE_ROAM_STATS_COMMANDS
 #define FEATURE_ROAM_STATS_EVENTS
 #endif
+
+#ifdef WLAN_FEATURE_TX_LATENCY_STATS
+/**
+ * hdd_tx_latency_register_cb() - register callback function for transmit
+ * latency stats
+ * @soc: pointer to soc context
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS hdd_tx_latency_register_cb(void *soc);
+
+/**
+ * hdd_tx_latency_restore_config() - restore tx latency stats config for a link
+ * @link_info: link specific information
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS
+hdd_tx_latency_restore_config(struct wlan_hdd_link_info *link_info);
+
+/**
+ * wlan_hdd_cfg80211_tx_latency - configure/retrieve per-link transmit latency
+ * statistics
+ * @wiphy: wiphy handle
+ * @wdev: wdev handle
+ * @data: user layer input
+ * @data_len: length of user layer input
+ *
+ * return: 0 success, einval failure
+ */
+int wlan_hdd_cfg80211_tx_latency(struct wiphy *wiphy,
+				 struct wireless_dev *wdev,
+				 const void *data, int data_len);
+
+/**
+ * hdd_tx_latency_record_ingress_ts() - Record driver ingress timestamp in CB
+ * @adapter: pointer to hdd vdev/net_device context
+ * @skb: sk buff
+ *
+ * Return: None
+ */
+static inline void
+hdd_tx_latency_record_ingress_ts(struct hdd_adapter *adapter,
+				 struct sk_buff *skb)
+{
+	if (adapter->tx_latency_cfg.enable)
+		qdf_nbuf_set_tx_ts(skb);
+}
+
+extern const struct nla_policy
+	tx_latency_policy[QCA_WLAN_VENDOR_ATTR_TX_LATENCY_MAX + 1];
+
+#define FEATURE_TX_LATENCY_STATS_COMMANDS	\
+	{	\
+	.info.vendor_id = QCA_NL80211_VENDOR_ID,	\
+	.info.subcmd = QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY,	\
+	.flags = WIPHY_VENDOR_CMD_NEED_WDEV |	\
+		 WIPHY_VENDOR_CMD_NEED_NETDEV |	\
+		 WIPHY_VENDOR_CMD_NEED_RUNNING,	\
+	.doit = wlan_hdd_cfg80211_tx_latency,	\
+	vendor_command_policy(tx_latency_policy,	\
+			      QCA_WLAN_VENDOR_ATTR_TX_LATENCY_MAX)	\
+	},	\
+
+#define FEATURE_TX_LATENCY_STATS_EVENTS	\
+	[QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY_INDEX] = {	\
+		.vendor_id = QCA_NL80211_VENDOR_ID,	\
+		.subcmd = QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY,	\
+	},	\
+
+#else
+static inline QDF_STATUS hdd_tx_latency_register_cb(void *soc)
+{
+	return QDF_STATUS_SUCCESS;
+}
+
+static inline QDF_STATUS
+hdd_tx_latency_restore_config(struct wlan_hdd_link_info *link_info)
+{
+	return QDF_STATUS_SUCCESS;
+}
+
+static inline void
+hdd_tx_latency_record_ingress_ts(struct hdd_adapter *adapter,
+				 struct sk_buff *skb)
+{
+}
+
+#define FEATURE_TX_LATENCY_STATS_COMMANDS
+#define FEATURE_TX_LATENCY_STATS_EVENTS
+#endif
 #endif /* end #if !defined(WLAN_HDD_STATS_H) */

+ 3 - 2
core/hdd/src/wlan_hdd_tsf.c

@@ -3015,7 +3015,8 @@ hdd_set_tsf_auto_report(struct hdd_adapter *adapter, bool ena,
 
 	enabled = !!adapter->tsf.auto_rpt_src;
 	if (enabled == ena) {
-		hdd_info_rl("current %d and no action is required", enabled);
+		hdd_debug_rl("source %d current %d and no action is required",
+			     source, enabled);
 		goto set_src;
 	}
 
@@ -3024,7 +3025,7 @@ hdd_set_tsf_auto_report(struct hdd_adapter *adapter, bool ena,
 				  (int)GEN_PARAM_TSF_AUTO_REPORT_DISABLE,
 				  ena, GEN_CMD);
 	if (ret) {
-		hdd_err_rl("tsf auto report %d failed: %d", ena, ret);
+		hdd_err_rl("source %d enable %d failed: %d", source, ena, ret);
 		ret = -EINPROGRESS;
 		goto out;
 	}

+ 2 - 0
core/hdd/src/wlan_hdd_tx_rx.c

@@ -59,6 +59,7 @@
 #include "wlan_dp_ucfg_api.h"
 #include "os_if_dp.h"
 #include "wlan_ipa_ucfg_api.h"
+#include "wlan_hdd_stats.h"
 
 #ifdef TX_MULTIQ_PER_AC
 #if defined(QCA_LL_TX_FLOW_CONTROL_V2) || defined(QCA_LL_PDEV_TX_FLOW_CONTROL)
@@ -518,6 +519,7 @@ static void __hdd_hard_start_xmit(struct sk_buff *skb,
 		return;
 
 	osif_dp_mark_pkt_type(skb);
+	hdd_tx_latency_record_ingress_ts(adapter, skb);
 
 	/* Get TL AC corresponding to Qdisc queue index/AC. */
 	ac = hdd_qdisc_ac_to_tl_ac[skb->queue_mapping];