Commit 710a95f9 authored by Venkateswara Naralasetty's avatar Venkateswara Naralasetty Committed by Kalle Valo

wifi: ath11k: Add support to get power save duration for each client

Add support to get the following power save information through debugfs interface,

 * Current ps state of the peer
 * Time duration since the peer is in power save
 * Total duration of the peer spent in power save

Above information is helpful in debugging the issues with power save clients.

This patch also add trace log support for PS timekeeper to track the PS state
change of the peers alongs with the peer MAC address and timestamp.

Use the below commands to get the above power save information,

To know the time_since_station_in_power_save:
cat /sys/kernel/debug/ieee80211/phyX/netdev:wlanX/stations/
XX:XX:XX:XX:XX:XX/current_ps_duration

To know power_save_duration:
cat /sys/kernel/debug/ieee80211/phyX/netdev:wlanX/stations/
XX:XX:XX:XX:XX:XX/total_ps_duration

To reset the power_save_duration of all stations connected to AP:
echo 1 > /sys/kernel/debug/ieee80211/phyX/ath11k/reset_ps_duration

To enable/disable the ps_timekeeper:
echo Y > /sys/kernel/debug/ieee80211/phyX/ath11k/ps_timekeeper_enable
Y = 1 to enable and Y = 0 to disable.

To record PS timekeeer logs after enabling ps_timekeeper:
trace-cmd record -e ath11k_ps_timekeeper

Tested-on: Tested-on: IPQ8074 WLAN.HK.2.5.0.1-00991-QCAHKSWPL_SILICONZ-1
Signed-off-by: default avatarVenkateswara Naralasetty <quic_vnaralas@quicinc.com>
Signed-off-by: default avatarTamizh Chelvam Raja <quic_tamizhr@quicinc.com>
Signed-off-by: default avatarKalle Valo <quic_kvalo@quicinc.com>
Link: https://lore.kernel.org/r/20220725054601.14719-1-quic_tamizhr@quicinc.com
parent d673cb6f
......@@ -498,6 +498,13 @@ struct ath11k_sta {
bool use_4addr_set;
u16 tcl_metadata;
/* Protected with ar->data_lock */
enum ath11k_wmi_peer_ps_state peer_ps_state;
u64 ps_start_time;
u64 ps_start_jiffies;
u64 ps_total_duration;
bool peer_current_ps_valid;
};
#define ATH11K_MIN_5G_FREQ 4150
......@@ -710,6 +717,10 @@ struct ath11k {
struct ath11k_fw_stats fw_stats;
struct completion fw_stats_complete;
bool fw_stats_done;
/* protected by conf_mutex */
bool ps_state_enable;
bool ps_timekeeper_enable;
};
struct ath11k_band_cap {
......
......@@ -1369,6 +1369,193 @@ static const struct file_operations fops_dbr_debug = {
.llseek = default_llseek,
};
static ssize_t ath11k_write_ps_timekeeper_enable(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
ssize_t ret;
u8 ps_timekeeper_enable;
if (kstrtou8_from_user(user_buf, count, 0, &ps_timekeeper_enable))
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto exit;
}
if (!ar->ps_state_enable) {
ret = -EINVAL;
goto exit;
}
ar->ps_timekeeper_enable = !!ps_timekeeper_enable;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_ps_timekeeper_enable(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[32];
int len;
mutex_lock(&ar->conf_mutex);
len = scnprintf(buf, sizeof(buf), "%d\n", ar->ps_timekeeper_enable);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_ps_timekeeper_enable = {
.read = ath11k_read_ps_timekeeper_enable,
.write = ath11k_write_ps_timekeeper_enable,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static void ath11k_reset_peer_ps_duration(void *data,
struct ieee80211_sta *sta)
{
struct ath11k *ar = data;
struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv;
spin_lock_bh(&ar->data_lock);
arsta->ps_total_duration = 0;
spin_unlock_bh(&ar->data_lock);
}
static ssize_t ath11k_write_reset_ps_duration(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int ret;
u8 reset_ps_duration;
if (kstrtou8_from_user(user_buf, count, 0, &reset_ps_duration))
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto exit;
}
if (!ar->ps_state_enable) {
ret = -EINVAL;
goto exit;
}
ieee80211_iterate_stations_atomic(ar->hw,
ath11k_reset_peer_ps_duration,
ar);
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_reset_ps_duration = {
.write = ath11k_write_reset_ps_duration,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static void ath11k_peer_ps_state_disable(void *data,
struct ieee80211_sta *sta)
{
struct ath11k *ar = data;
struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv;
spin_lock_bh(&ar->data_lock);
arsta->peer_ps_state = WMI_PEER_PS_STATE_DISABLED;
arsta->ps_start_time = 0;
arsta->ps_total_duration = 0;
spin_unlock_bh(&ar->data_lock);
}
static ssize_t ath11k_write_ps_state_enable(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
struct ath11k_pdev *pdev = ar->pdev;
int ret;
u32 param;
u8 ps_state_enable;
if (kstrtou8_from_user(user_buf, count, 0, &ps_state_enable))
return -EINVAL;
mutex_lock(&ar->conf_mutex);
ps_state_enable = !!ps_state_enable;
if (ar->ps_state_enable == ps_state_enable) {
ret = count;
goto exit;
}
param = WMI_PDEV_PEER_STA_PS_STATECHG_ENABLE;
ret = ath11k_wmi_pdev_set_param(ar, param, ps_state_enable, pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to enable ps_state_enable: %d\n",
ret);
goto exit;
}
ar->ps_state_enable = ps_state_enable;
if (!ar->ps_state_enable) {
ar->ps_timekeeper_enable = false;
ieee80211_iterate_stations_atomic(ar->hw,
ath11k_peer_ps_state_disable,
ar);
}
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_ps_state_enable(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[32];
int len;
mutex_lock(&ar->conf_mutex);
len = scnprintf(buf, sizeof(buf), "%d\n", ar->ps_state_enable);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_ps_state_enable = {
.read = ath11k_read_ps_state_enable,
.write = ath11k_write_ps_state_enable,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
int ath11k_debugfs_register(struct ath11k *ar)
{
struct ath11k_base *ab = ar->ab;
......@@ -1415,6 +1602,20 @@ int ath11k_debugfs_register(struct ath11k *ar)
debugfs_create_file("enable_dbr_debug", 0200, ar->debug.debugfs_pdev,
ar, &fops_dbr_debug);
debugfs_create_file("ps_state_enable", 0600, ar->debug.debugfs_pdev, ar,
&fops_ps_state_enable);
if (test_bit(WMI_TLV_SERVICE_PEER_POWER_SAVE_DURATION_SUPPORT,
ar->ab->wmi_ab.svc_map)) {
debugfs_create_file("ps_timekeeper_enable", 0600,
ar->debug.debugfs_pdev, ar,
&fops_ps_timekeeper_enable);
debugfs_create_file("reset_ps_duration", 0200,
ar->debug.debugfs_pdev, ar,
&fops_reset_ps_duration);
}
return 0;
}
......
......@@ -751,6 +751,102 @@ static const struct file_operations fops_htt_peer_stats_reset = {
.llseek = default_llseek,
};
static ssize_t ath11k_dbg_sta_read_peer_ps_state(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ieee80211_sta *sta = file->private_data;
struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv;
struct ath11k *ar = arsta->arvif->ar;
char buf[20];
int len;
spin_lock_bh(&ar->data_lock);
len = scnprintf(buf, sizeof(buf), "%d\n", arsta->peer_ps_state);
spin_unlock_bh(&ar->data_lock);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_peer_ps_state = {
.open = simple_open,
.read = ath11k_dbg_sta_read_peer_ps_state,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_dbg_sta_read_current_ps_duration(struct file *file,
char __user *user_buf,
size_t count,
loff_t *ppos)
{
struct ieee80211_sta *sta = file->private_data;
struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv;
struct ath11k *ar = arsta->arvif->ar;
u64 time_since_station_in_power_save;
char buf[20];
int len;
spin_lock_bh(&ar->data_lock);
if (arsta->peer_ps_state == WMI_PEER_PS_STATE_ON &&
arsta->peer_current_ps_valid)
time_since_station_in_power_save = jiffies_to_msecs(jiffies
- arsta->ps_start_jiffies);
else
time_since_station_in_power_save = 0;
len = scnprintf(buf, sizeof(buf), "%llu\n",
time_since_station_in_power_save);
spin_unlock_bh(&ar->data_lock);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_current_ps_duration = {
.open = simple_open,
.read = ath11k_dbg_sta_read_current_ps_duration,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_dbg_sta_read_total_ps_duration(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ieee80211_sta *sta = file->private_data;
struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv;
struct ath11k *ar = arsta->arvif->ar;
char buf[20];
u64 power_save_duration;
int len;
spin_lock_bh(&ar->data_lock);
if (arsta->peer_ps_state == WMI_PEER_PS_STATE_ON &&
arsta->peer_current_ps_valid)
power_save_duration = jiffies_to_msecs(jiffies
- arsta->ps_start_jiffies)
+ arsta->ps_total_duration;
else
power_save_duration = arsta->ps_total_duration;
len = scnprintf(buf, sizeof(buf), "%llu\n", power_save_duration);
spin_unlock_bh(&ar->data_lock);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_total_ps_duration = {
.open = simple_open,
.read = ath11k_dbg_sta_read_total_ps_duration,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
void ath11k_debugfs_sta_op_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, struct dentry *dir)
{
......@@ -778,4 +874,15 @@ void ath11k_debugfs_sta_op_add(struct ieee80211_hw *hw, struct ieee80211_vif *vi
ar->ab->wmi_ab.svc_map))
debugfs_create_file("htt_peer_stats_reset", 0600, dir, sta,
&fops_htt_peer_stats_reset);
debugfs_create_file("peer_ps_state", 0400, dir, sta,
&fops_peer_ps_state);
if (test_bit(WMI_TLV_SERVICE_PEER_POWER_SAVE_DURATION_SUPPORT,
ar->ab->wmi_ab.svc_map)) {
debugfs_create_file("current_ps_duration", 0440, dir, sta,
&fops_current_ps_duration);
debugfs_create_file("total_ps_duration", 0440, dir, sta,
&fops_total_ps_duration);
}
}
......@@ -4529,6 +4529,7 @@ static int ath11k_mac_op_sta_state(struct ieee80211_hw *hw,
new_state == IEEE80211_STA_NONE) {
memset(arsta, 0, sizeof(*arsta));
arsta->arvif = arvif;
arsta->peer_ps_state = WMI_PEER_PS_STATE_DISABLED;
INIT_WORK(&arsta->update_wk, ath11k_sta_rc_update_wk);
INIT_WORK(&arsta->set_4addr_wk, ath11k_sta_set_4addr_wk);
......
......@@ -305,6 +305,34 @@ TRACE_EVENT(ath11k_wmi_diag,
)
);
TRACE_EVENT(ath11k_ps_timekeeper,
TP_PROTO(struct ath11k *ar, const void *peer_addr,
u32 peer_ps_timestamp, u8 peer_ps_state),
TP_ARGS(ar, peer_addr, peer_ps_timestamp, peer_ps_state),
TP_STRUCT__entry(__string(device, dev_name(ar->ab->dev))
__string(driver, dev_driver_string(ar->ab->dev))
__dynamic_array(u8, peer_addr, ETH_ALEN)
__field(u8, peer_ps_state)
__field(u32, peer_ps_timestamp)
),
TP_fast_assign(__assign_str(device, dev_name(ar->ab->dev));
__assign_str(driver, dev_driver_string(ar->ab->dev));
memcpy(__get_dynamic_array(peer_addr), peer_addr,
ETH_ALEN);
__entry->peer_ps_state = peer_ps_state;
__entry->peer_ps_timestamp = peer_ps_timestamp;
),
TP_printk("%s %s %u %u",
__get_str(driver),
__get_str(device),
__entry->peer_ps_state,
__entry->peer_ps_timestamp
)
);
#endif /* _TRACE_H_ || TRACE_HEADER_MULTI_READ*/
/* we don't want to use include/trace/events */
......
......@@ -6802,6 +6802,107 @@ static void ath11k_bcn_tx_status_event(struct ath11k_base *ab, struct sk_buff *s
rcu_read_unlock();
}
static void ath11k_wmi_event_peer_sta_ps_state_chg(struct ath11k_base *ab,
struct sk_buff *skb)
{
const struct wmi_peer_sta_ps_state_chg_event *ev;
struct ieee80211_sta *sta;
struct ath11k_peer *peer;
struct ath11k *ar;
struct ath11k_sta *arsta;
const void **tb;
enum ath11k_wmi_peer_ps_state peer_previous_ps_state;
int ret;
tb = ath11k_wmi_tlv_parse_alloc(ab, skb->data, skb->len, GFP_ATOMIC);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath11k_warn(ab, "failed to parse tlv: %d\n", ret);
return;
}
ev = tb[WMI_TAG_PEER_STA_PS_STATECHANGE_EVENT];
if (!ev) {
ath11k_warn(ab, "failed to fetch sta ps change ev");
kfree(tb);
return;
}
ath11k_dbg(ab, ATH11K_DBG_WMI,
"peer sta ps chnange ev addr %pM state %u sup_bitmap %x ps_valid %u ts %u\n",
ev->peer_macaddr.addr, ev->peer_ps_state,
ev->ps_supported_bitmap, ev->peer_ps_valid,
ev->peer_ps_timestamp);
rcu_read_lock();
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find_by_addr(ab, ev->peer_macaddr.addr);
if (!peer) {
spin_unlock_bh(&ab->base_lock);
ath11k_warn(ab, "peer not found %pM\n", ev->peer_macaddr.addr);
goto exit;
}
ar = ath11k_mac_get_ar_by_vdev_id(ab, peer->vdev_id);
if (!ar) {
spin_unlock_bh(&ab->base_lock);
ath11k_warn(ab, "invalid vdev id in peer sta ps state change ev %d",
peer->vdev_id);
goto exit;
}
sta = peer->sta;
spin_unlock_bh(&ab->base_lock);
if (!sta) {
ath11k_warn(ab, "failed to find station entry %pM\n",
ev->peer_macaddr.addr);
goto exit;
}
arsta = (struct ath11k_sta *)sta->drv_priv;
spin_lock_bh(&ar->data_lock);
peer_previous_ps_state = arsta->peer_ps_state;
arsta->peer_ps_state = ev->peer_ps_state;
arsta->peer_current_ps_valid = !!ev->peer_ps_valid;
if (test_bit(WMI_TLV_SERVICE_PEER_POWER_SAVE_DURATION_SUPPORT,
ar->ab->wmi_ab.svc_map)) {
if (!(ev->ps_supported_bitmap & WMI_PEER_PS_VALID) ||
!(ev->ps_supported_bitmap & WMI_PEER_PS_STATE_TIMESTAMP) ||
!ev->peer_ps_valid)
goto out;
if (arsta->peer_ps_state == WMI_PEER_PS_STATE_ON) {
arsta->ps_start_time = ev->peer_ps_timestamp;
arsta->ps_start_jiffies = jiffies;
} else if (arsta->peer_ps_state == WMI_PEER_PS_STATE_OFF &&
peer_previous_ps_state == WMI_PEER_PS_STATE_ON) {
arsta->ps_total_duration = arsta->ps_total_duration +
(ev->peer_ps_timestamp - arsta->ps_start_time);
}
if (ar->ps_timekeeper_enable)
trace_ath11k_ps_timekeeper(ar, ev->peer_macaddr.addr,
ev->peer_ps_timestamp,
arsta->peer_ps_state);
}
out:
spin_unlock_bh(&ar->data_lock);
exit:
rcu_read_unlock();
kfree(tb);
}
static void ath11k_vdev_stopped_event(struct ath11k_base *ab, struct sk_buff *skb)
{
struct ath11k *ar;
......@@ -8041,6 +8142,9 @@ static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb)
case WMI_DIAG_EVENTID:
ath11k_wmi_diag_event(ab, skb);
break;
case WMI_PEER_STA_PS_STATECHG_EVENTID:
ath11k_wmi_event_peer_sta_ps_state_chg(ab, skb);
break;
case WMI_GTK_OFFLOAD_STATUS_EVENTID:
ath11k_wmi_gtk_offload_status_event(ab, skb);
break;
......
......@@ -2090,6 +2090,7 @@ enum wmi_tlv_service {
WMI_TLV_SERVICE_PER_PEER_HTT_STATS_RESET = 213,
WMI_TLV_SERVICE_FREQINFO_IN_METADATA = 219,
WMI_TLV_SERVICE_EXT2_MSG = 220,
WMI_TLV_SERVICE_PEER_POWER_SAVE_DURATION_SUPPORT = 246,
WMI_TLV_SERVICE_SRG_SRP_SPATIAL_REUSE_SUPPORT = 249,
/* The second 128 bits */
......@@ -5369,6 +5370,26 @@ struct wmi_debug_log_config_cmd_fixed_param {
#define WMI_SERVICE_READY_TIMEOUT_HZ (5 * HZ)
#define WMI_SEND_TIMEOUT_HZ (3 * HZ)
enum ath11k_wmi_peer_ps_state {
WMI_PEER_PS_STATE_OFF,
WMI_PEER_PS_STATE_ON,
WMI_PEER_PS_STATE_DISABLED,
};
enum wmi_peer_ps_supported_bitmap {
/* Used to indicate that power save state change is valid */
WMI_PEER_PS_VALID = 0x1,
WMI_PEER_PS_STATE_TIMESTAMP = 0x2,
};
struct wmi_peer_sta_ps_state_chg_event {
struct wmi_mac_addr peer_macaddr;
u32 peer_ps_state;
u32 ps_supported_bitmap;
u32 peer_ps_valid;
u32 peer_ps_timestamp;
} __packed;
struct ath11k_wmi_base {
struct ath11k_base *ab;
struct ath11k_pdev_wmi wmi[MAX_RADIOS];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment