From: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
To: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>, jjohnson@kernel.org
Cc: ath11k@lists.infradead.org, ath12k@lists.infradead.org,
linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v5 4/4] wifi: ath12k: implement custom wake_tx_queue with flow control
Date: Sat, 8 Aug 2026 12:29:40 -0700 [thread overview]
Message-ID: <0b72231d-59f8-49bf-82a0-76d2c7862729@oss.qualcomm.com> (raw)
In-Reply-To: <20260807105843.20203-5-jtornosm@redhat.com>
On 8/7/2026 3:58 AM, Jose Ignacio Tornos Martinez wrote:
> Under heavy traffic, ath12k can hang and experiences -ENOMEM errors
> ("failed to transmit frame -12") when the hardware TCL ring fills up.
> This issue is more commonly observed in VMs with PCIe passthrough but
> also occurs on bare metal systems.
>
> Implement a custom wake_tx_queue operation that:
>
> 1. Checks hardware ring space before dequeuing packets from mac80211
> 2. Uses per-ring locking (wake_tx_lock with spin_lock_bh) to serialize
> concurrent wake_tx_queue calls targeting the same ring and to ensure
> bottom halves are disabled as required by ieee80211_tx_dequeue()
> 3. Syncs with hardware state to get accurate free slot count
> 4. Resolves the target TCL ring once using get_ring_selector(txq->ac),
> which selects the ring based on the access category
> 5. Returns early during firmware crash in the same way as other
> tx paths
>
> This approach follows the pattern used in the iwlwifi driver, adapted
> for ath12k's hardware ring architecture.
>
> This prevents hangs, eliminates -ENOMEM errors, and improves throughput
> by optimizing resource usage and preventing unnecessary packet drops.
Tested-on missing
>
> Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
> ---
> v5: Remove an unused variable
> v4: https://lore.kernel.org/all/20260724053121.15759-5-jtornosm@redhat.com/
>
> drivers/net/wireless/ath/ath12k/dp.c | 1 +
> drivers/net/wireless/ath/ath12k/dp.h | 2 +
> drivers/net/wireless/ath/ath12k/hal.c | 1 +
> drivers/net/wireless/ath/ath12k/wifi7/hw.c | 76 +++++++++++++++++++++-
> 4 files changed, 79 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c
> index af5f11fc1d84..3d46cfbf0a1c 100644
> --- a/drivers/net/wireless/ath/ath12k/dp.c
> +++ b/drivers/net/wireless/ath/ath12k/dp.c
> @@ -1539,6 +1539,7 @@ static int ath12k_dp_setup(struct ath12k_base *ab)
> }
>
> for (i = 0; i < ab->hw_params->max_tx_ring; i++) {
> + spin_lock_init(&dp->tx_ring[i].wake_tx_lock);
> dp->tx_ring[i].tcl_data_ring_id = i;
>
> dp->tx_ring[i].tx_status_head = 0;
> diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h
> index f8cfc7bb29dd..68d2020be9b8 100644
> --- a/drivers/net/wireless/ath/ath12k/dp.h
> +++ b/drivers/net/wireless/ath/ath12k/dp.h
> @@ -58,6 +58,8 @@ struct dp_tx_ring {
> u8 tcl_data_ring_id;
> struct dp_srng tcl_data_ring;
> struct dp_srng tcl_comp_ring;
> + /* Serializes wake_tx_queue operations for this ring */
> + spinlock_t wake_tx_lock;
> struct hal_wbm_completion_ring_tx *tx_status;
> int tx_status_head;
> int tx_status_tail;
> diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c
> index a164563fff28..071cb5d30931 100644
> --- a/drivers/net/wireless/ath/ath12k/hal.c
> +++ b/drivers/net/wireless/ath/ath12k/hal.c
> @@ -390,6 +390,7 @@ int ath12k_hal_srng_src_num_free(struct ath12k_base *ab, struct hal_srng *srng,
> else
> return ((srng->ring_size - hp + tp) / srng->entry_size) - 1;
> }
> +EXPORT_SYMBOL_GPL(ath12k_hal_srng_src_num_free);
NAK, must be EXPORT_SYMBOL()
The ath12k code was contributed under the license:
// SPDX-License-Identifier: BSD-3-Clause-Clear
>
> void *ath12k_hal_srng_src_next_peek(struct ath12k_base *ab,
> struct hal_srng *srng)
> diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
> index 7436cf70925a..af0a3ba805dd 100644
> --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
> +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
> @@ -1100,9 +1100,82 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
> }
> }
>
> +static void ath12k_wifi7_mac_op_wake_tx_queue(struct ieee80211_hw *hw,
> + struct ieee80211_txq *txq)
> +{
> + struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(txq->vif);
> + struct ieee80211_tx_control control = {
> + .sta = txq->sta,
> + };
> + struct ieee80211_vif *vif = txq->vif;
> + struct ath12k_link_vif *arvif;
> + struct dp_tx_ring *tx_ring;
> + struct hal_srng *tcl_ring;
> + struct ath12k_sta *ahsta;
> + struct ath12k_dp *dp;
> + struct sk_buff *skb;
> + struct ath12k *ar;
> + u8 ring_selector;
> + int num_free;
> + u8 ring_id;
> + u8 link_id;
> +
> + if (ieee80211_vif_is_mld(vif) && txq->sta) {
> + ahsta = ath12k_sta_to_ahsta(txq->sta);
> + link_id = ahsta->assoc_link_id;
> + } else {
> + link_id = ahvif->deflink.link_id;
> + }
> +
> + rcu_read_lock();
does this lock really need to be held for the entire function including the tx
loop?
> +
> + arvif = rcu_dereference(ahvif->link[link_id]);
> + if (!arvif || !arvif->ar) {
> + rcu_read_unlock();
> + return;
> + }
> +
> + ar = arvif->ar;
> + dp = ar->ab->dp;
or is it sufficient to cache these values and then rcu_read_unlock() here?
my LLM reviewer seems to think that would be sufficient
note the spinlock in the while loop will cause the RCU read lock to be held
when invoking mac_op_tx(), satisfying the RCU lock requirement there.
> +
> + ring_selector = dp->hw_params->hw_ops->get_ring_selector(txq->ac);
> + ring_id = ring_selector % dp->hw_params->max_tx_ring;
> + tx_ring = &dp->tx_ring[ring_id];
> + tcl_ring = &dp->hal->srng_list[tx_ring->tcl_data_ring.ring_id];
> +
> + while (1) {
> + if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH,
> + &ar->ab->dev_flags)))
> + break;
> +
> + spin_lock_bh(&tx_ring->wake_tx_lock);
using guard(spinlock_bh)(&tx_ring->wake_tx_lock) would eliminate the need for
all the spin_unlock_bh(&tx_ring->wake_tx_lock) calls
> +
> + spin_lock(&tcl_ring->lock);
seems this should be spin_lock_bh() to align with all other callers in the driver.
> + num_free = ath12k_hal_srng_src_num_free(ar->ab, tcl_ring, true);
> + spin_unlock(&tcl_ring->lock);
> +
> + if (num_free == 0) {
> + spin_unlock_bh(&tx_ring->wake_tx_lock);
> + break;
> + }
> +
> + skb = ieee80211_tx_dequeue(hw, txq);
> + if (!skb) {
> + spin_unlock_bh(&tx_ring->wake_tx_lock);
> + break;
> + }
> +
> + ath12k_wifi7_mac_op_tx(hw, &control, skb);
> +
> + spin_unlock_bh(&tx_ring->wake_tx_lock);
> + }
> +
> + rcu_read_unlock();
> +}
> +
> static const struct ieee80211_ops ath12k_ops_wifi7 = {
> .tx = ath12k_wifi7_mac_op_tx,
> - .wake_tx_queue = ieee80211_handle_wake_tx_queue,
> + .wake_tx_queue = ath12k_wifi7_mac_op_wake_tx_queue,
> .start = ath12k_mac_op_start,
> .stop = ath12k_mac_op_stop,
> .reconfig_complete = ath12k_mac_op_reconfig_complete,
next prev parent reply other threads:[~2026-08-08 19:29 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-07 10:58 [PATCH v5 0/4] ath11k/ath12k: implement TX " Jose Ignacio Tornos Martinez
2026-08-07 10:58 ` [PATCH v5 1/4] wifi: ath11k: use queue mapping for WCN6750 ring selection Jose Ignacio Tornos Martinez
2026-08-07 10:58 ` [PATCH v5 2/4] wifi: ath11k/ath12k: remove skb parameter from get_ring_selector Jose Ignacio Tornos Martinez
2026-08-07 10:58 ` [PATCH v5 3/4] wifi: ath11k: implement custom wake_tx_queue with flow control Jose Ignacio Tornos Martinez
2026-08-08 18:42 ` Jeff Johnson
2026-08-11 10:42 ` Jose Ignacio Tornos Martinez
2026-08-07 10:58 ` [PATCH v5 4/4] wifi: ath12k: " Jose Ignacio Tornos Martinez
2026-08-08 19:29 ` Jeff Johnson [this message]
2026-08-11 11:14 ` Jose Ignacio Tornos Martinez
2026-08-13 16:23 ` Jeff Johnson
2026-09-17 6:32 ` Jose Ignacio Tornos Martinez
2026-08-13 17:25 ` Jeff Johnson
2026-09-17 6:40 ` Jose Ignacio Tornos Martinez
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=0b72231d-59f8-49bf-82a0-76d2c7862729@oss.qualcomm.com \
--to=jeff.johnson@oss.qualcomm.com \
--cc=ath11k@lists.infradead.org \
--cc=ath12k@lists.infradead.org \
--cc=jjohnson@kernel.org \
--cc=jtornosm@redhat.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-wireless@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®