Message ID | 20250127172202.1410429-3-rameshkumar.sundaram@oss.qualcomm.com |
---|---|
State | New |
Headers | show |
Series | wifi: ath12k: add support for get_txpower mac ops | expand |
On 1/27/2025 10:52 PM, Rameshkumar Sundaram wrote: > From: Aditya Kumar Singh <aditya.kumar.singh@oss.qualcomm.com> > > Driver does not support get_txpower mac ops because of which > cfg80211 returns vif->bss_conf.txpower to user space. bss_conf.txpower > gets its value from ieee80211_channel->max_reg_power. However, the final > txpower is dependent on few other parameters apart from max regulatory > supported power. It is the firmware which knows about all these > parameters and considers the minimum for each packet transmission. > > All ath12k firmware reports the final TX power in firmware pdev stats > which falls under fw_stats. add get_txpower mac ops to get the TX power > from firmware leveraging fw_stats and return it accordingly. > > While at it, there is a possibility that repeated stats request WMI > commands are queued to FW if mac80211/userspace does get tx power back > to back(in Multiple BSS cases). This could potentially consume the WMI > queue completely. Hence limit this by fetching the power only for every > 5 seconds and reusing the value until the refresh timeout or when there > is a change in channel. > > Also remove init_completion(&ar->fw_stats_complete) in > ath12k_mac_hw_register() as ath12k_fw_stats_init() takes care of > it for each ar. > > Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1 > Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 > > Signed-off-by: Aditya Kumar Singh <aditya.kumar.singh@oss.qualcomm.com> > Signed-off-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com> > --- > drivers/net/wireless/ath/ath12k/core.h | 1 + > drivers/net/wireless/ath/ath12k/mac.c | 155 +++++++++++++++++++------ > drivers/net/wireless/ath/ath12k/mac.h | 3 + > 3 files changed, 123 insertions(+), 36 deletions(-) > > diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h > index e4f51ad6a59f..42da19870713 100644 > --- a/drivers/net/wireless/ath/ath12k/core.h > +++ b/drivers/net/wireless/ath/ath12k/core.h > @@ -731,6 +731,7 @@ struct ath12k { > u32 mlo_setup_status; > u8 ftm_msgref; > struct ath12k_fw_stats fw_stats; > + unsigned long last_tx_power_update; > }; > > struct ath12k_hw { > diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c > index 4fb7e235be66..54fe3a2c9c0b 100644 > --- a/drivers/net/wireless/ath/ath12k/mac.c > +++ b/drivers/net/wireless/ath/ath12k/mac.c > @@ -4280,6 +4280,120 @@ static int ath12k_start_scan(struct ath12k *ar, > return 0; > } > > +static int ath12k_mac_get_fw_stats(struct ath12k *ar, u32 pdev_id, > + u32 vdev_id, u32 stats_id) > +{ > + struct ath12k_base *ab = ar->ab; > + struct ath12k_hw *ah = ath12k_ar_to_ah(ar); > + unsigned long time_left; > + int ret; > + > + guard(mutex)(&ah->hw_mutex); > + > + if (ah->state != ATH12K_HW_STATE_ON) > + return -ENETDOWN; > + > + spin_lock_bh(&ar->data_lock); > + ar->fw_stats.fw_stats_done = false; > + ath12k_fw_stats_free(&ar->fw_stats); > + spin_unlock_bh(&ar->data_lock); rename ath12k_debugfs_fw_stats_reset and reuse instead of the above 4 lines > + reinit_completion(&ar->fw_stats_complete); > + > + ret = ath12k_wmi_send_stats_request_cmd(ar, stats_id, vdev_id, pdev_id); > + > + if (ret) { > + ath12k_warn(ab, "failed to request fw stats: stats id %u ret %d\n", > + stats_id, ret); > + return ret; > + } > + > + ath12k_dbg(ab, ATH12K_DBG_WMI, > + "get fw stat pdev id %d vdev id %d stats id 0x%x\n", > + pdev_id, vdev_id, stats_id); > + > + time_left = wait_for_completion_timeout(&ar->fw_stats_complete, 1 * HZ); > + > + if (!time_left) > + ath12k_warn(ab, "time out while waiting for get fw stats\n"); > + suggestion is to create a separate function and move some of the common code in ath12k_mac_get_fw_stats and ath12k_debugfs_fw_stats_request > + return ret; > +} > + > +static int ath12k_mac_op_get_txpower(struct ieee80211_hw *hw, > + struct ieee80211_vif *vif, > + unsigned int link_id, > + int *dbm) > +{ > + struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(vif); > + struct ath12k_fw_stats_pdev *pdev; > + struct ath12k_hw *ah = hw->priv; > + struct ath12k_link_vif *arvif; > + struct ath12k_base *ab; > + struct ath12k *ar; > + int ret; > + > + /* Final Tx power is minimum of Target Power, CTL power, Regulatory > + * Power, PSD EIRP Power. We just know the Regulatory power from the > + * regulatory rules obtained. FW knows all these power and sets the min > + * of these. Hence, we request the FW pdev stats in which FW reports > + * the minimum of all vdev's channel Tx power. > + */ > + lockdep_assert_wiphy(hw->wiphy); > + > + arvif = wiphy_dereference(ah->hw->wiphy, ahvif->link[link_id]); > + if (!arvif || !arvif->ar) > + return -EINVAL; > + > + ar = arvif->ar; > + ab = ar->ab; > + if (ah->state != ATH12K_HW_STATE_ON) > + goto err_fallback; > + > + if (test_bit(ATH12K_FLAG_CAC_RUNNING, &ar->dev_flags)) > + return -EAGAIN; > + > + /* Limit the requests to Firmware for fetching the tx power */ > + if (ar->chan_tx_pwr != ATH12K_PDEV_TX_POWER_INVALID && > + time_before(jiffies, > + msecs_to_jiffies(ATH12K_PDEV_TX_POWER_REFRESH_TIME_MSECS) + > + ar->last_tx_power_update)) > + goto send_tx_power; > + > + ret = ath12k_mac_get_fw_stats(ar, ar->pdev->pdev_id, arvif->vdev_id, > + WMI_REQUEST_PDEV_STAT); > + if (ret) { > + ath12k_warn(ab, "failed to request fw pdev stats: %d\n", ret); > + goto err_fallback; > + } > + > + spin_lock_bh(&ar->data_lock); > + pdev = list_first_entry_or_null(&ar->fw_stats.pdevs, > + struct ath12k_fw_stats_pdev, list); > + if (!pdev) { > + spin_unlock_bh(&ar->data_lock); > + goto err_fallback; > + } > + > + ar->chan_tx_pwr = pdev->chan_tx_power; It is better to divide and store > + spin_unlock_bh(&ar->data_lock); > + ar->last_tx_power_update = jiffies; > + > +send_tx_power: > + /* tx power reported by firmware is in units of 0.5 dBm */ > + *dbm = ar->chan_tx_pwr / 2; based on the above comment, we dont need to do divide everytime here during repeated calls > + ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "txpower from firmware %d, reported %d dBm\n", > + ar->chan_tx_pwr, *dbm); > + return 0; > + > +err_fallback: > + /* We didn't get txpower from FW. Hence, relying on vif->bss_conf.txpower */ > + *dbm = vif->bss_conf.txpower; > + ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "txpower from firmware NaN, reported %d dBm\n", > + *dbm); > + return 0; > +} > + > static u8 > ath12k_mac_find_link_id_by_ar(struct ath12k_vif *ahvif, struct ath12k *ar) > { > @@ -7433,6 +7547,7 @@ static int ath12k_mac_start(struct ath12k *ar) > ar->num_created_vdevs = 0; > ar->num_peers = 0; > ar->allocated_vdev_map = 0; > + ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID; ar->chan_tx_pwr type u32..and assigning signed value. fix it. > > /* Configure monitor status ring with default rx_filter to get rx status > * such as rssi, rx_duration. > @@ -8638,6 +8753,7 @@ static int ath12k_mac_op_add_chanctx(struct ieee80211_hw *hw, > */ > ar->rx_channel = ctx->def.chan; > spin_unlock_bh(&ar->data_lock); > + ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID; > > return 0; > } > @@ -8666,6 +8782,7 @@ static void ath12k_mac_op_remove_chanctx(struct ieee80211_hw *hw, > */ > ar->rx_channel = NULL; > spin_unlock_bh(&ar->data_lock); > + ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID; > } > > static enum wmi_phy_mode > @@ -10109,40 +10226,6 @@ static int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx, > return 0; > } > > -static int ath12k_mac_get_fw_stats(struct ath12k *ar, u32 pdev_id, > - u32 vdev_id, u32 stats_id) > -{ > - struct ath12k_base *ab = ar->ab; > - struct ath12k_hw *ah = ath12k_ar_to_ah(ar); > - unsigned long time_left; > - int ret; > - > - guard(mutex)(&ah->hw_mutex); > - > - if (ah->state != ATH12K_HW_STATE_ON) > - return -ENETDOWN; > - > - reinit_completion(&ar->fw_stats_complete); > - > - ret = ath12k_wmi_send_stats_request_cmd(ar, stats_id, vdev_id, pdev_id); > - > - if (ret) { > - ath12k_warn(ab, "failed to request fw stats: %d\n", ret); > - return ret; > - } > - > - ath12k_dbg(ab, ATH12K_DBG_WMI, > - "get fw stat pdev id %d vdev id %d stats id 0x%x\n", > - pdev_id, vdev_id, stats_id); > - > - time_left = wait_for_completion_timeout(&ar->fw_stats_complete, 1 * HZ); > - > - if (!time_left) > - ath12k_warn(ab, "time out while waiting for get fw stats\n"); > - > - return ret; > -} > - > static void ath12k_mac_op_sta_statistics(struct ieee80211_hw *hw, > struct ieee80211_vif *vif, > struct ieee80211_sta *sta, > @@ -10431,6 +10514,7 @@ static const struct ieee80211_ops ath12k_ops = { > .assign_vif_chanctx = ath12k_mac_op_assign_vif_chanctx, > .unassign_vif_chanctx = ath12k_mac_op_unassign_vif_chanctx, > .switch_vif_chanctx = ath12k_mac_op_switch_vif_chanctx, > + .get_txpower = ath12k_mac_op_get_txpower, > .set_rts_threshold = ath12k_mac_op_set_rts_threshold, > .set_frag_threshold = ath12k_mac_op_set_frag_threshold, > .set_bitrate_mask = ath12k_mac_op_set_bitrate_mask, > @@ -11178,11 +11262,10 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah) > goto err_unregister_hw; > } > > + ath12k_fw_stats_init(ar); > ath12k_debugfs_register(ar); > } > > - init_completion(&ar->fw_stats_complete); > - > return 0; > > err_unregister_hw: > diff --git a/drivers/net/wireless/ath/ath12k/mac.h b/drivers/net/wireless/ath/ath12k/mac.h > index 1acaf3f68292..af0d3c6a2a6c 100644 > --- a/drivers/net/wireless/ath/ath12k/mac.h > +++ b/drivers/net/wireless/ath/ath12k/mac.h > @@ -33,6 +33,9 @@ struct ath12k_generic_iter { > #define ATH12K_KEEPALIVE_MAX_IDLE 3895 > #define ATH12K_KEEPALIVE_MAX_UNRESPONSIVE 3900 > > +#define ATH12K_PDEV_TX_POWER_INVALID (-1) > +#define ATH12K_PDEV_TX_POWER_REFRESH_TIME_MSECS 5000 /* msecs */ > + > /* FIXME: should these be in ieee80211.h? */ > #define IEEE80211_VHT_MCS_SUPPORT_0_11_MASK GENMASK(23, 16) > #define IEEE80211_DISABLE_VHT_MCS_SUPPORT_0_11 BIT(24)
On 1/30/2025 11:55 AM, Mahendran P wrote: > On 1/27/2025 10:52 PM, Rameshkumar Sundaram wrote: >> From: Aditya Kumar Singh <aditya.kumar.singh@oss.qualcomm.com> >> >> Driver does not support get_txpower mac ops because of which >> cfg80211 returns vif->bss_conf.txpower to user space. bss_conf.txpower >> gets its value from ieee80211_channel->max_reg_power. However, the final >> txpower is dependent on few other parameters apart from max regulatory >> supported power. It is the firmware which knows about all these >> parameters and considers the minimum for each packet transmission. >> >> All ath12k firmware reports the final TX power in firmware pdev stats >> which falls under fw_stats. add get_txpower mac ops to get the TX power >> from firmware leveraging fw_stats and return it accordingly. >> >> While at it, there is a possibility that repeated stats request WMI >> commands are queued to FW if mac80211/userspace does get tx power back >> to back(in Multiple BSS cases). This could potentially consume the WMI >> queue completely. Hence limit this by fetching the power only for every >> 5 seconds and reusing the value until the refresh timeout or when there >> is a change in channel. >> >> Also remove init_completion(&ar->fw_stats_complete) in >> ath12k_mac_hw_register() as ath12k_fw_stats_init() takes care of >> it for each ar. >> >> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1 >> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 >> >> Signed-off-by: Aditya Kumar Singh <aditya.kumar.singh@oss.qualcomm.com> >> Signed-off-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com> >> --- >> drivers/net/wireless/ath/ath12k/core.h | 1 + >> drivers/net/wireless/ath/ath12k/mac.c | 155 +++++++++++++++++++------ >> drivers/net/wireless/ath/ath12k/mac.h | 3 + >> 3 files changed, 123 insertions(+), 36 deletions(-) >> >> diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h >> index e4f51ad6a59f..42da19870713 100644 >> --- a/drivers/net/wireless/ath/ath12k/core.h >> +++ b/drivers/net/wireless/ath/ath12k/core.h >> @@ -731,6 +731,7 @@ struct ath12k { >> u32 mlo_setup_status; >> u8 ftm_msgref; >> struct ath12k_fw_stats fw_stats; >> + unsigned long last_tx_power_update; >> }; >> >> struct ath12k_hw { >> diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c >> index 4fb7e235be66..54fe3a2c9c0b 100644 >> --- a/drivers/net/wireless/ath/ath12k/mac.c >> +++ b/drivers/net/wireless/ath/ath12k/mac.c >> @@ -4280,6 +4280,120 @@ static int ath12k_start_scan(struct ath12k *ar, >> return 0; >> } >> >> +static int ath12k_mac_get_fw_stats(struct ath12k *ar, u32 pdev_id, >> + u32 vdev_id, u32 stats_id) >> +{ >> + struct ath12k_base *ab = ar->ab; >> + struct ath12k_hw *ah = ath12k_ar_to_ah(ar); >> + unsigned long time_left; >> + int ret; >> + >> + guard(mutex)(&ah->hw_mutex); >> + >> + if (ah->state != ATH12K_HW_STATE_ON) >> + return -ENETDOWN; >> + >> + spin_lock_bh(&ar->data_lock); >> + ar->fw_stats.fw_stats_done = false; >> + ath12k_fw_stats_free(&ar->fw_stats); >> + spin_unlock_bh(&ar->data_lock); > rename ath12k_debugfs_fw_stats_reset and reuse instead of the above 4 lines Sure, will do it in next version > >> + reinit_completion(&ar->fw_stats_complete); >> + >> + ret = ath12k_wmi_send_stats_request_cmd(ar, stats_id, vdev_id, pdev_id); >> + >> + if (ret) { >> + ath12k_warn(ab, "failed to request fw stats: stats id %u ret %d\n", >> + stats_id, ret); >> + return ret; >> + } >> + >> + ath12k_dbg(ab, ATH12K_DBG_WMI, >> + "get fw stat pdev id %d vdev id %d stats id 0x%x\n", >> + pdev_id, vdev_id, stats_id); >> + >> + time_left = wait_for_completion_timeout(&ar->fw_stats_complete, 1 * HZ); >> + >> + if (!time_left) >> + ath12k_warn(ab, "time out while waiting for get fw stats\n"); >> + > suggestion is to create a separate function and move some of the common code in ath12k_mac_get_fw_stats and ath12k_debugfs_fw_stats_request Will move the wait logic from ath12k_debugfs_fw_stats_request() to ath12k_mac_get_fw_stats() and use the same on both places. > >> + return ret; >> +} >> + >> +static int ath12k_mac_op_get_txpower(struct ieee80211_hw *hw, >> + struct ieee80211_vif *vif, >> + unsigned int link_id, >> + int *dbm) >> +{ >> + struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(vif); >> + struct ath12k_fw_stats_pdev *pdev; >> + struct ath12k_hw *ah = hw->priv; >> + struct ath12k_link_vif *arvif; >> + struct ath12k_base *ab; >> + struct ath12k *ar; >> + int ret; >> + >> + /* Final Tx power is minimum of Target Power, CTL power, Regulatory >> + * Power, PSD EIRP Power. We just know the Regulatory power from the >> + * regulatory rules obtained. FW knows all these power and sets the min >> + * of these. Hence, we request the FW pdev stats in which FW reports >> + * the minimum of all vdev's channel Tx power. >> + */ >> + lockdep_assert_wiphy(hw->wiphy); >> + >> + arvif = wiphy_dereference(ah->hw->wiphy, ahvif->link[link_id]); >> + if (!arvif || !arvif->ar) >> + return -EINVAL; >> + >> + ar = arvif->ar; >> + ab = ar->ab; >> + if (ah->state != ATH12K_HW_STATE_ON) >> + goto err_fallback; >> + >> + if (test_bit(ATH12K_FLAG_CAC_RUNNING, &ar->dev_flags)) >> + return -EAGAIN; >> + >> + /* Limit the requests to Firmware for fetching the tx power */ >> + if (ar->chan_tx_pwr != ATH12K_PDEV_TX_POWER_INVALID && >> + time_before(jiffies, >> + msecs_to_jiffies(ATH12K_PDEV_TX_POWER_REFRESH_TIME_MSECS) + >> + ar->last_tx_power_update)) >> + goto send_tx_power; >> + >> + ret = ath12k_mac_get_fw_stats(ar, ar->pdev->pdev_id, arvif->vdev_id, >> + WMI_REQUEST_PDEV_STAT); >> + if (ret) { >> + ath12k_warn(ab, "failed to request fw pdev stats: %d\n", ret); >> + goto err_fallback; >> + } >> + >> + spin_lock_bh(&ar->data_lock); >> + pdev = list_first_entry_or_null(&ar->fw_stats.pdevs, >> + struct ath12k_fw_stats_pdev, list); >> + if (!pdev) { >> + spin_unlock_bh(&ar->data_lock); >> + goto err_fallback; >> + } >> + >> + ar->chan_tx_pwr = pdev->chan_tx_power; > It is better to divide and store > >> + spin_unlock_bh(&ar->data_lock); >> + ar->last_tx_power_update = jiffies; >> + >> +send_tx_power: >> + /* tx power reported by firmware is in units of 0.5 dBm */ >> + *dbm = ar->chan_tx_pwr / 2; > based on the above comment, we dont need to do divide everytime here during repeated calls Thanks for the suggestion, will do it in next version > >> + ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "txpower from firmware %d, reported %d dBm\n", >> + ar->chan_tx_pwr, *dbm); >> + return 0; >> + >> +err_fallback: >> + /* We didn't get txpower from FW. Hence, relying on vif->bss_conf.txpower */ >> + *dbm = vif->bss_conf.txpower; >> + ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "txpower from firmware NaN, reported %d dBm\n", >> + *dbm); >> + return 0; >> +} >> + >> static u8 >> ath12k_mac_find_link_id_by_ar(struct ath12k_vif *ahvif, struct ath12k *ar) >> { >> @@ -7433,6 +7547,7 @@ static int ath12k_mac_start(struct ath12k *ar) >> ar->num_created_vdevs = 0; >> ar->num_peers = 0; >> ar->allocated_vdev_map = 0; >> + ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID; > ar->chan_tx_pwr type u32..and assigning signed value. fix it. Thanks for pointing out, will fix it by assigning u32 max for ATH12K_PDEV_TX_POWER_INVALID > >> >> /* Configure monitor status ring with default rx_filter to get rx status >> * such as rssi, rx_duration. >> @@ -8638,6 +8753,7 @@ static int ath12k_mac_op_add_chanctx(struct ieee80211_hw *hw, >> */ >> ar->rx_channel = ctx->def.chan; >> spin_unlock_bh(&ar->data_lock); >> + ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID; >> >> return 0; >> } >> @@ -8666,6 +8782,7 @@ static void ath12k_mac_op_remove_chanctx(struct ieee80211_hw *hw, >>
diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index e4f51ad6a59f..42da19870713 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -731,6 +731,7 @@ struct ath12k { u32 mlo_setup_status; u8 ftm_msgref; struct ath12k_fw_stats fw_stats; + unsigned long last_tx_power_update; }; struct ath12k_hw { diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 4fb7e235be66..54fe3a2c9c0b 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -4280,6 +4280,120 @@ static int ath12k_start_scan(struct ath12k *ar, return 0; } +static int ath12k_mac_get_fw_stats(struct ath12k *ar, u32 pdev_id, + u32 vdev_id, u32 stats_id) +{ + struct ath12k_base *ab = ar->ab; + struct ath12k_hw *ah = ath12k_ar_to_ah(ar); + unsigned long time_left; + int ret; + + guard(mutex)(&ah->hw_mutex); + + if (ah->state != ATH12K_HW_STATE_ON) + return -ENETDOWN; + + spin_lock_bh(&ar->data_lock); + ar->fw_stats.fw_stats_done = false; + ath12k_fw_stats_free(&ar->fw_stats); + spin_unlock_bh(&ar->data_lock); + + reinit_completion(&ar->fw_stats_complete); + + ret = ath12k_wmi_send_stats_request_cmd(ar, stats_id, vdev_id, pdev_id); + + if (ret) { + ath12k_warn(ab, "failed to request fw stats: stats id %u ret %d\n", + stats_id, ret); + return ret; + } + + ath12k_dbg(ab, ATH12K_DBG_WMI, + "get fw stat pdev id %d vdev id %d stats id 0x%x\n", + pdev_id, vdev_id, stats_id); + + time_left = wait_for_completion_timeout(&ar->fw_stats_complete, 1 * HZ); + + if (!time_left) + ath12k_warn(ab, "time out while waiting for get fw stats\n"); + + return ret; +} + +static int ath12k_mac_op_get_txpower(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int link_id, + int *dbm) +{ + struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(vif); + struct ath12k_fw_stats_pdev *pdev; + struct ath12k_hw *ah = hw->priv; + struct ath12k_link_vif *arvif; + struct ath12k_base *ab; + struct ath12k *ar; + int ret; + + /* Final Tx power is minimum of Target Power, CTL power, Regulatory + * Power, PSD EIRP Power. We just know the Regulatory power from the + * regulatory rules obtained. FW knows all these power and sets the min + * of these. Hence, we request the FW pdev stats in which FW reports + * the minimum of all vdev's channel Tx power. + */ + lockdep_assert_wiphy(hw->wiphy); + + arvif = wiphy_dereference(ah->hw->wiphy, ahvif->link[link_id]); + if (!arvif || !arvif->ar) + return -EINVAL; + + ar = arvif->ar; + ab = ar->ab; + if (ah->state != ATH12K_HW_STATE_ON) + goto err_fallback; + + if (test_bit(ATH12K_FLAG_CAC_RUNNING, &ar->dev_flags)) + return -EAGAIN; + + /* Limit the requests to Firmware for fetching the tx power */ + if (ar->chan_tx_pwr != ATH12K_PDEV_TX_POWER_INVALID && + time_before(jiffies, + msecs_to_jiffies(ATH12K_PDEV_TX_POWER_REFRESH_TIME_MSECS) + + ar->last_tx_power_update)) + goto send_tx_power; + + ret = ath12k_mac_get_fw_stats(ar, ar->pdev->pdev_id, arvif->vdev_id, + WMI_REQUEST_PDEV_STAT); + if (ret) { + ath12k_warn(ab, "failed to request fw pdev stats: %d\n", ret); + goto err_fallback; + } + + spin_lock_bh(&ar->data_lock); + pdev = list_first_entry_or_null(&ar->fw_stats.pdevs, + struct ath12k_fw_stats_pdev, list); + if (!pdev) { + spin_unlock_bh(&ar->data_lock); + goto err_fallback; + } + + ar->chan_tx_pwr = pdev->chan_tx_power; + spin_unlock_bh(&ar->data_lock); + ar->last_tx_power_update = jiffies; + +send_tx_power: + /* tx power reported by firmware is in units of 0.5 dBm */ + *dbm = ar->chan_tx_pwr / 2; + ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "txpower from firmware %d, reported %d dBm\n", + ar->chan_tx_pwr, *dbm); + return 0; + +err_fallback: + /* We didn't get txpower from FW. Hence, relying on vif->bss_conf.txpower */ + *dbm = vif->bss_conf.txpower; + ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "txpower from firmware NaN, reported %d dBm\n", + *dbm); + return 0; +} + static u8 ath12k_mac_find_link_id_by_ar(struct ath12k_vif *ahvif, struct ath12k *ar) { @@ -7433,6 +7547,7 @@ static int ath12k_mac_start(struct ath12k *ar) ar->num_created_vdevs = 0; ar->num_peers = 0; ar->allocated_vdev_map = 0; + ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID; /* Configure monitor status ring with default rx_filter to get rx status * such as rssi, rx_duration. @@ -8638,6 +8753,7 @@ static int ath12k_mac_op_add_chanctx(struct ieee80211_hw *hw, */ ar->rx_channel = ctx->def.chan; spin_unlock_bh(&ar->data_lock); + ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID; return 0; } @@ -8666,6 +8782,7 @@ static void ath12k_mac_op_remove_chanctx(struct ieee80211_hw *hw, */ ar->rx_channel = NULL; spin_unlock_bh(&ar->data_lock); + ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID; } static enum wmi_phy_mode @@ -10109,40 +10226,6 @@ static int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx, return 0; } -static int ath12k_mac_get_fw_stats(struct ath12k *ar, u32 pdev_id, - u32 vdev_id, u32 stats_id) -{ - struct ath12k_base *ab = ar->ab; - struct ath12k_hw *ah = ath12k_ar_to_ah(ar); - unsigned long time_left; - int ret; - - guard(mutex)(&ah->hw_mutex); - - if (ah->state != ATH12K_HW_STATE_ON) - return -ENETDOWN; - - reinit_completion(&ar->fw_stats_complete); - - ret = ath12k_wmi_send_stats_request_cmd(ar, stats_id, vdev_id, pdev_id); - - if (ret) { - ath12k_warn(ab, "failed to request fw stats: %d\n", ret); - return ret; - } - - ath12k_dbg(ab, ATH12K_DBG_WMI, - "get fw stat pdev id %d vdev id %d stats id 0x%x\n", - pdev_id, vdev_id, stats_id); - - time_left = wait_for_completion_timeout(&ar->fw_stats_complete, 1 * HZ); - - if (!time_left) - ath12k_warn(ab, "time out while waiting for get fw stats\n"); - - return ret; -} - static void ath12k_mac_op_sta_statistics(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, @@ -10431,6 +10514,7 @@ static const struct ieee80211_ops ath12k_ops = { .assign_vif_chanctx = ath12k_mac_op_assign_vif_chanctx, .unassign_vif_chanctx = ath12k_mac_op_unassign_vif_chanctx, .switch_vif_chanctx = ath12k_mac_op_switch_vif_chanctx, + .get_txpower = ath12k_mac_op_get_txpower, .set_rts_threshold = ath12k_mac_op_set_rts_threshold, .set_frag_threshold = ath12k_mac_op_set_frag_threshold, .set_bitrate_mask = ath12k_mac_op_set_bitrate_mask, @@ -11178,11 +11262,10 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah) goto err_unregister_hw; } + ath12k_fw_stats_init(ar); ath12k_debugfs_register(ar); } - init_completion(&ar->fw_stats_complete); - return 0; err_unregister_hw: diff --git a/drivers/net/wireless/ath/ath12k/mac.h b/drivers/net/wireless/ath/ath12k/mac.h index 1acaf3f68292..af0d3c6a2a6c 100644 --- a/drivers/net/wireless/ath/ath12k/mac.h +++ b/drivers/net/wireless/ath/ath12k/mac.h @@ -33,6 +33,9 @@ struct ath12k_generic_iter { #define ATH12K_KEEPALIVE_MAX_IDLE 3895 #define ATH12K_KEEPALIVE_MAX_UNRESPONSIVE 3900 +#define ATH12K_PDEV_TX_POWER_INVALID (-1) +#define ATH12K_PDEV_TX_POWER_REFRESH_TIME_MSECS 5000 /* msecs */ + /* FIXME: should these be in ieee80211.h? */ #define IEEE80211_VHT_MCS_SUPPORT_0_11_MASK GENMASK(23, 16) #define IEEE80211_DISABLE_VHT_MCS_SUPPORT_0_11 BIT(24)