Search Linux Wireless

Re: [PATCH v2 2/2] wifi: ath12k: add get_txpower mac ops

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 2/1/2025 1:07 AM, Rameshkumar Sundaram wrote:
> From: Aditya Kumar Singh <aditya.kumar.singh@xxxxxxxxxxxxxxxx>
> 
> 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@xxxxxxxxxxxxxxxx>
> Signed-off-by: Rameshkumar Sundaram <rameshkumar.sundaram@xxxxxxxxxxxxxxxx>
> ---
>  drivers/net/wireless/ath/ath12k/core.h |   1 +
>  drivers/net/wireless/ath/ath12k/mac.c  | 208 +++++++++++++++++--------
>  drivers/net/wireless/ath/ath12k/mac.h  |   3 +
>  3 files changed, 148 insertions(+), 64 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
> index 373e7baf379b..d278e4a8bb08 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 8cd33ea75590..efab885bdbb1 100644
> --- a/drivers/net/wireless/ath/ath12k/mac.c
> +++ b/drivers/net/wireless/ath/ath12k/mac.c
> @@ -4280,6 +4280,145 @@ static int ath12k_start_scan(struct ath12k *ar,
>  	return 0;
>  }
>  
> +int ath12k_mac_get_fw_stats(struct ath12k *ar,
> +			    struct ath12k_fw_stats_req_params *param)
> +{
> +	struct ath12k_base *ab = ar->ab;
> +	struct ath12k_hw *ah = ath12k_ar_to_ah(ar);
> +	unsigned long timeout, time_left;
> +	int ret;
> +
> +	guard(mutex)(&ah->hw_mutex);
> +
> +	if (ah->state != ATH12K_HW_STATE_ON)
> +		return -ENETDOWN;
> +
> +	/* FW stats can get split when exceeding the stats data buffer limit.
> +	 * In that case, since there is no end marking for the back-to-back
> +	 * received 'update stats' event, we keep a 3 seconds timeout in case,
> +	 * fw_stats_done is not marked yet
> +	 */
> +	timeout = jiffies + msecs_to_jiffies(3 * 1000);
> +	ath12k_fw_stats_reset(ar);
> +
> +	reinit_completion(&ar->fw_stats_complete);
> +
> +	ret = ath12k_wmi_send_stats_request_cmd(ar, param->stats_id,
> +						param->vdev_id, param->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",
> +		   param->pdev_id, param->vdev_id, param->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 -ETIMEDOUT;
> +	}
> +
> +	/* Firmware sends WMI_UPDATE_STATS_EVENTID back-to-back
> +	 * when stats data buffer limit is reached. fw_stats_complete
> +	 * is completed once host receives first event from firmware, but
> +	 * still end might not be marked in the TLV.
> +	 * Below loop is to confirm that firmware completed sending all the event
> +	 * and fw_stats_done is marked true when end is marked in the TLV.
> +	 */
> +	for (;;) {
> +		if (time_after(jiffies, timeout))
> +			break;
> +		spin_lock_bh(&ar->data_lock);
> +		if (ar->fw_stats.fw_stats_done) {
> +			spin_unlock_bh(&ar->data_lock);
> +			break;
> +		}
> +		spin_unlock_bh(&ar->data_lock);
> +	}
> +	return 0;
> +}
> +
> +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_req_params params = {};
> +	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;
> +
> +	params.pdev_id = ar->pdev->pdev_id;
> +	params.vdev_id = arvif->vdev_id;
> +	params.stats_id = WMI_REQUEST_PDEV_STAT;
> +	ret = ath12k_mac_get_fw_stats(ar, &params);
> +	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;
> +	}
> +
> +	/* tx power reported by firmware is in units of 0.5 dBm */
> +	ar->chan_tx_pwr = pdev->chan_tx_power / 2;
> +	spin_unlock_bh(&ar->data_lock);
> +	ar->last_tx_power_update = jiffies;
> +
> +send_tx_power:
> +	*dbm = ar->chan_tx_pwr;
> +	ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "txpower fetched from firmware %d dBm\n",
> +		   *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 +7572,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 +8778,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 +8807,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,68 +10251,6 @@ static int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx,
>  	return 0;
>  }
>  
> -int ath12k_mac_get_fw_stats(struct ath12k *ar,
> -			    struct ath12k_fw_stats_req_params *param)
> -{
> -	struct ath12k_base *ab = ar->ab;
> -	struct ath12k_hw *ah = ath12k_ar_to_ah(ar);
> -	unsigned long timeout, time_left;
> -	int ret;
> -
> -	guard(mutex)(&ah->hw_mutex);
> -
> -	if (ah->state != ATH12K_HW_STATE_ON)
> -		return -ENETDOWN;
> -
> -	/* FW stats can get split when exceeding the stats data buffer limit.
> -	 * In that case, since there is no end marking for the back-to-back
> -	 * received 'update stats' event, we keep a 3 seconds timeout in case,
> -	 * fw_stats_done is not marked yet
> -	 */
> -	timeout = jiffies + msecs_to_jiffies(3 * 1000);
> -	ath12k_fw_stats_reset(ar);
> -
> -	reinit_completion(&ar->fw_stats_complete);
> -
> -	ret = ath12k_wmi_send_stats_request_cmd(ar, param->stats_id,
> -						param->vdev_id, param->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",
> -		   param->pdev_id, param->vdev_id, param->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 -ETIMEDOUT;
> -	}
> -
> -	/* Firmware sends WMI_UPDATE_STATS_EVENTID back-to-back
> -	 * when stats data buffer limit is reached. fw_stats_complete
> -	 * is completed once host receives first event from firmware, but
> -	 * still end might not be marked in the TLV.
> -	 * Below loop is to confirm that firmware completed sending all the event
> -	 * and fw_stats_done is marked true when end is marked in the TLV.
> -	 */
> -	for (;;) {
> -		if (time_after(jiffies, timeout))
> -			break;
> -		spin_lock_bh(&ar->data_lock);
> -		if (ar->fw_stats.fw_stats_done) {
> -			spin_unlock_bh(&ar->data_lock);
> -			break;
> -		}
> -		spin_unlock_bh(&ar->data_lock);
> -	}
> -	return 0;
> -}
> -
>  static void ath12k_mac_op_sta_statistics(struct ieee80211_hw *hw,
>  					 struct ieee80211_vif *vif,
>  					 struct ieee80211_sta *sta,
> @@ -10463,6 +10543,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,
> @@ -11210,11 +11291,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 a0de0ddf175e..8435bdea904f 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		((u32)-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)

Series LGTM





[Index of Archives]     [Linux Host AP]     [ATH6KL]     [Linux Wireless Personal Area Network]     [Linux Bluetooth]     [Wireless Regulations]     [Linux Netdev]     [Kernel Newbies]     [Linux Kernel]     [IDE]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite Hiking]     [MIPS Linux]     [ARM Linux]     [Linux RAID]

  Powered by Linux