* [PATCH 0/2] ath11k/ath12k: implement TX flow control
@ 2026-07-10 15:54 Jose Ignacio Tornos Martinez
2026-07-10 15:54 ` [PATCH 1/2] wifi: ath11k: implement custom wake_tx_queue with " Jose Ignacio Tornos Martinez
2026-07-10 15:54 ` [PATCH 2/2] wifi: ath12k: " Jose Ignacio Tornos Martinez
0 siblings, 2 replies; 8+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-10 15:54 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
This series implements custom wake_tx_queue operations for ath11k and
ath12k drivers to prevent hardware ring overflow issues under heavy
traffic.
Without proper flow control, both drivers experience -ENOMEM errors
("failed to transmit frame -12") when the hardware TCL ring fills up.
Additionally, ath12k can hang under sustained high throughput. These
issues are more commonly observed in VMs with PCIe passthrough but
also occur on bare metal systems.
The implementation follows the pattern used in the iwlwifi driver,
checking hardware ring space before dequeuing packets from mac80211.
Per-packet locking serializes ring access while allowing TX completions
to run, preventing deadlocks.
Testing shows stable operation with eliminated -ENOMEM errors, no hangs,
and improved throughput under heavy traffic conditions.
Jose Ignacio Tornos Martinez (2):
wifi: ath11k: implement custom wake_tx_queue with flow control
wifi: ath12k: implement custom wake_tx_queue with flow control
drivers/net/wireless/ath/ath11k/dp.c | 1 +
drivers/net/wireless/ath/ath11k/dp.h | 2 +
drivers/net/wireless/ath/ath11k/mac.c | 49 ++++++++++++++++++++-
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 | 50 +++++++++++++++++++++-
7 files changed, 104 insertions(+), 2 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/2] wifi: ath11k: implement custom wake_tx_queue with flow control
2026-07-10 15:54 [PATCH 0/2] ath11k/ath12k: implement TX flow control Jose Ignacio Tornos Martinez
@ 2026-07-10 15:54 ` Jose Ignacio Tornos Martinez
2026-07-11 15:33 ` Zhi-Jun You
2026-07-10 15:54 ` [PATCH 2/2] wifi: ath12k: " Jose Ignacio Tornos Martinez
1 sibling, 1 reply; 8+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-10 15:54 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
Under heavy traffic, ath11k experiences frequent -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. It is particularly problematic on
devices with a single shared TCL ring where all traffic classes
compete for the same 512 descriptor slots.
Implement a custom wake_tx_queue operation that:
1. Checks hardware ring space before dequeuing packets from mac80211
2. Uses per-packet locking to serialize ring access and prevent races
3. Syncs with hardware state to get accurate free slot count
4. 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 ath11k's hardware ring architecture.
This eliminates -ENOMEM errors and improves throughput by optimizing
resource usage and preventing unnecessary packet drops.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
drivers/net/wireless/ath/ath11k/dp.c | 1 +
drivers/net/wireless/ath/ath11k/dp.h | 2 ++
drivers/net/wireless/ath/ath11k/mac.c | 49 ++++++++++++++++++++++++++-
3 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c
index f389b97acbdd..2e5978ec2b05 100644
--- a/drivers/net/wireless/ath/ath11k/dp.c
+++ b/drivers/net/wireless/ath/ath11k/dp.c
@@ -1087,6 +1087,7 @@ int ath11k_dp_alloc(struct ath11k_base *ab)
for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) {
idr_init(&dp->tx_ring[i].txbuf_idr);
spin_lock_init(&dp->tx_ring[i].tx_idr_lock);
+ 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/ath11k/dp.h b/drivers/net/wireless/ath/ath11k/dp.h
index 84f66839f0c6..6d99501aa269 100644
--- a/drivers/net/wireless/ath/ath11k/dp.h
+++ b/drivers/net/wireless/ath/ath11k/dp.h
@@ -87,6 +87,8 @@ struct dp_tx_ring {
struct idr txbuf_idr;
/* Protects txbuf_idr and num_pending */
spinlock_t tx_idr_lock;
+ /* Serializes wake_tx_queue operations for this ring */
+ spinlock_t wake_tx_lock;
struct hal_wbm_release_ring *tx_status;
int tx_status_head;
int tx_status_tail;
diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
index 2d55cdc4d165..48a9e2b45e3d 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -10065,9 +10065,56 @@ static int ath11k_mac_op_sta_state(struct ieee80211_hw *hw,
return ret;
}
+static void ath11k_mac_op_wake_tx_queue(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq)
+{
+ struct ieee80211_tx_control control = {
+ .sta = txq->sta,
+ };
+ struct ath11k *ar = hw->priv;
+ struct dp_tx_ring *tx_ring;
+ struct hal_srng *tcl_ring;
+ struct sk_buff *skb;
+ int num_free;
+ u8 ring_id;
+
+ if (!ar)
+ return;
+
+ if (unlikely(test_bit(ATH11K_FLAG_CRASH_FLUSH, &ar->ab->dev_flags)))
+ return;
+
+ ring_id = txq->ac % ar->ab->hw_params.hal_params->num_tx_rings;
+ tx_ring = &ar->ab->dp.tx_ring[ring_id];
+ tcl_ring = &ar->ab->hal.srng_list[tx_ring->tcl_data_ring.ring_id];
+
+ while (1) {
+ spin_lock_bh(&tx_ring->wake_tx_lock);
+
+ spin_lock(&tcl_ring->lock);
+ num_free = ath11k_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;
+ }
+
+ ath11k_mac_op_tx(hw, &control, skb);
+
+ spin_unlock_bh(&tx_ring->wake_tx_lock);
+ }
+}
+
static const struct ieee80211_ops ath11k_ops = {
.tx = ath11k_mac_op_tx,
- .wake_tx_queue = ieee80211_handle_wake_tx_queue,
+ .wake_tx_queue = ath11k_mac_op_wake_tx_queue,
.start = ath11k_mac_op_start,
.stop = ath11k_mac_op_stop,
.reconfig_complete = ath11k_mac_op_reconfig_complete,
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 2/2] wifi: ath12k: implement custom wake_tx_queue with flow control
2026-07-10 15:54 [PATCH 0/2] ath11k/ath12k: implement TX flow control Jose Ignacio Tornos Martinez
2026-07-10 15:54 ` [PATCH 1/2] wifi: ath11k: implement custom wake_tx_queue with " Jose Ignacio Tornos Martinez
@ 2026-07-10 15:54 ` Jose Ignacio Tornos Martinez
2026-07-13 16:27 ` Jeff Johnson
2026-07-13 17:05 ` Tamizh Raja
1 sibling, 2 replies; 8+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-10 15:54 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
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-packet locking to serialize ring access and prevent races
3. Syncs with hardware state to get accurate free slot count
4. 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.
Signed-off-by: Jose Ignacio Tornos Martinez <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 | 50 +++++++++++++++++++++-
4 files changed, 53 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..c1c656e4550b 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(ath12k_hal_srng_src_num_free);
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 d9fdd2fc8298..e3a6f9cdee24 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -1100,9 +1100,57 @@ 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_hw *ah = ath12k_hw_to_ah(hw);
+ struct ieee80211_tx_control control = {
+ .sta = txq->sta,
+ };
+ struct ath12k *ar = ah->radio;
+ struct dp_tx_ring *tx_ring;
+ struct hal_srng *tcl_ring;
+ struct ath12k_dp *dp;
+ struct sk_buff *skb;
+ int num_free;
+
+ if (!ar)
+ return;
+
+ if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH, &ar->ab->dev_flags)))
+ return;
+
+ dp = ar->ab->dp;
+ tx_ring = &dp->tx_ring[txq->ac % dp->hw_params->max_tx_ring];
+ tcl_ring = &dp->hal->srng_list[tx_ring->tcl_data_ring.ring_id];
+
+ while (1) {
+ spin_lock_bh(&tx_ring->wake_tx_lock);
+
+ spin_lock(&tcl_ring->lock);
+ 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);
+ }
+}
+
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,
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 1/2] wifi: ath11k: implement custom wake_tx_queue with flow control
2026-07-10 15:54 ` [PATCH 1/2] wifi: ath11k: implement custom wake_tx_queue with " Jose Ignacio Tornos Martinez
@ 2026-07-11 15:33 ` Zhi-Jun You
2026-07-13 15:01 ` Jose Ignacio Tornos Martinez
0 siblings, 1 reply; 8+ messages in thread
From: Zhi-Jun You @ 2026-07-11 15:33 UTC (permalink / raw)
To: jtornosm; +Cc: ath11k, ath12k, jjohnson, linux-kernel, linux-wireless, hujy652
Hi Jose,
In wake_tx_queue:
ring_id = txq->ac % ar->ab->hw_params.hal_params->num_tx_rings;
In ath11k_dp_tx which is called by ath11k_mac_op_tx:
ring_selector = ab->hw_params.hw_ops->get_ring_selector(skb);
ti.ring_id = ring_selector % num_tx_rings;
Are you sure ring_id will be the same?
Also mgmt frames use a different path.
Best regards,
Zhi-Jun
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] wifi: ath11k: implement custom wake_tx_queue with flow control
2026-07-11 15:33 ` Zhi-Jun You
@ 2026-07-13 15:01 ` Jose Ignacio Tornos Martinez
2026-07-14 2:38 ` Zhi-Jun You
0 siblings, 1 reply; 8+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-07-13 15:01 UTC (permalink / raw)
To: hujy652; +Cc: ath11k, ath12k, jjohnson, jtornosm, linux-kernel, linux-wireless
Hi Zhi-Jun,
Thank you for the review.
> In wake_tx_queue:
> ring_id = txq->ac % ar->ab->hw_params.hal_params->num_tx_rings;
>
>In ath11k_dp_tx which is called by ath11k_mac_op_tx:
>ring_selector = ab->hw_params.hw_ops->get_ring_selector(skb);
> ti.ring_id = ring_selector % num_tx_rings;
>
> Are you sure ring_id will be the same?
You're right — txq->ac % num_tx_rings doesn't match
get_ring_selector(skb) % num_tx_rings on all platforms.
In v1 the ring identification was only correct for platforms where the ring
selector happens to coincide with the AC, but not for platforms.
I will send a v2 trying to solve this globally for all the platforms.
> Also mgmt frames use a different path.
Correct — management frames go through WMI, not the TCL data rings, so they
are not affected. The flow control applies to data frames, which are the
ones causing the problem since they use different TCL rings depending on the
platform's ring selector.
Best regards,
Jose Ignacio
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] wifi: ath12k: implement custom wake_tx_queue with flow control
2026-07-10 15:54 ` [PATCH 2/2] wifi: ath12k: " Jose Ignacio Tornos Martinez
@ 2026-07-13 16:27 ` Jeff Johnson
2026-07-13 17:05 ` Tamizh Raja
1 sibling, 0 replies; 8+ messages in thread
From: Jeff Johnson @ 2026-07-13 16:27 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez, jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel
On 7/10/2026 8:54 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-packet locking to serialize ring access and prevent races
> 3. Syncs with hardware state to get accurate free slot count
> 4. 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.
>
> Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
Just based upon the description I doubt this will be accepted. The ath12k
datapath team is striving to have a lockless fastpath, so adding new locks is
problematic. I've BCC'ed the internal developers list so that the team knows
to give more constructive feedback.
/jeff
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] wifi: ath12k: implement custom wake_tx_queue with flow control
2026-07-10 15:54 ` [PATCH 2/2] wifi: ath12k: " Jose Ignacio Tornos Martinez
2026-07-13 16:27 ` Jeff Johnson
@ 2026-07-13 17:05 ` Tamizh Raja
1 sibling, 0 replies; 8+ messages in thread
From: Tamizh Raja @ 2026-07-13 17:05 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez
Cc: jjohnson, ath11k, ath12k, linux-wireless, linux-kernel
On Fri, Jul 10, 2026 at 9:25 PM Jose Ignacio Tornos Martinez
<jtornosm@redhat.com> 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-packet locking to serialize ring access and prevent races
> 3. Syncs with hardware state to get accurate free slot count
> 4. 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.
>
> Signed-off-by: Jose Ignacio Tornos Martinez <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 | 50 +++++++++++++++++++++-
> 4 files changed, 53 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..c1c656e4550b 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(ath12k_hal_srng_src_num_free);
>
> 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 d9fdd2fc8298..e3a6f9cdee24 100644
> --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
> +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
> @@ -1100,9 +1100,57 @@ 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_hw *ah = ath12k_hw_to_ah(hw);
> + struct ieee80211_tx_control control = {
> + .sta = txq->sta,
> + };
> + struct ath12k *ar = ah->radio;
This assignment is wrong and all traffic is incorrectly steered to
radio[0] as ah->radio is a flexible array, not a pointer
> + struct dp_tx_ring *tx_ring;
> + struct hal_srng *tcl_ring;
> + struct ath12k_dp *dp;
> + struct sk_buff *skb;
> + int num_free;
> +
> + if (!ar)
> + return;
> +
> + if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH, &ar->ab->dev_flags)))
> + return;
> +
> + dp = ar->ab->dp;
> + tx_ring = &dp->tx_ring[txq->ac % dp->hw_params->max_tx_ring];
> + tcl_ring = &dp->hal->srng_list[tx_ring->tcl_data_ring.ring_id];
tx_ring/tcl_ring selection should be corrected.
> +
> + while (1) {
> + spin_lock_bh(&tx_ring->wake_tx_lock);
Do we need this spin_lock_bh?
> +
> + spin_lock(&tcl_ring->lock);
> + num_free = ath12k_hal_srng_src_num_free(ar->ab, tcl_ring, true);
> + spin_unlock(&tcl_ring->lock);
> +
Do we need this check and spin_lock here? already ath12k_wifi7_dp_tx()
has this lock and fetches the next entry. Can we check those return
value here and break the loop?
> + 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);
> + }
> +}
> +
> 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,
> --
> 2.54.0
>
>
--
- Tamizh.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] wifi: ath11k: implement custom wake_tx_queue with flow control
2026-07-13 15:01 ` Jose Ignacio Tornos Martinez
@ 2026-07-14 2:38 ` Zhi-Jun You
0 siblings, 0 replies; 8+ messages in thread
From: Zhi-Jun You @ 2026-07-14 2:38 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez
Cc: ath11k, ath12k, jjohnson, linux-kernel, linux-wireless
On Mon, Jul 13, 2026 at 11:02 PM Jose Ignacio Tornos Martinez
<jtornosm@redhat.com> wrote:
>
> Hi Zhi-Jun,
>
> Thank you for the review.
>
> > In wake_tx_queue:
> > ring_id = txq->ac % ar->ab->hw_params.hal_params->num_tx_rings;
> >
> >In ath11k_dp_tx which is called by ath11k_mac_op_tx:
> >ring_selector = ab->hw_params.hw_ops->get_ring_selector(skb);
> > ti.ring_id = ring_selector % num_tx_rings;
> >
> > Are you sure ring_id will be the same?
> You're right — txq->ac % num_tx_rings doesn't match
> get_ring_selector(skb) % num_tx_rings on all platforms.
> In v1 the ring identification was only correct for platforms where the ring
> selector happens to coincide with the AC, but not for platforms.
>
> I will send a v2 trying to solve this globally for all the platforms.
>
> > Also mgmt frames use a different path.
> Correct — management frames go through WMI, not the TCL data rings, so they
> are not affected. The flow control applies to data frames, which are the
> ones causing the problem since they use different TCL rings depending on the
> platform's ring selector.
>
> Best regards,
> Jose Ignacio
>
Hi Jose,
While you are reworking it would you mind looking at the previous attempt?
It tried to follow the behaviour in ath10k but lacks throttling mechanism.
https://lore.kernel.org/linux-wireless/20230501130725.7171-1-quic_tamizhr@quicinc.com/
Best regards,
Zhi-Jun
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-07-14 2:38 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 15:54 [PATCH 0/2] ath11k/ath12k: implement TX flow control Jose Ignacio Tornos Martinez
2026-07-10 15:54 ` [PATCH 1/2] wifi: ath11k: implement custom wake_tx_queue with " Jose Ignacio Tornos Martinez
2026-07-11 15:33 ` Zhi-Jun You
2026-07-13 15:01 ` Jose Ignacio Tornos Martinez
2026-07-14 2:38 ` Zhi-Jun You
2026-07-10 15:54 ` [PATCH 2/2] wifi: ath12k: " Jose Ignacio Tornos Martinez
2026-07-13 16:27 ` Jeff Johnson
2026-07-13 17:05 ` Tamizh Raja
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox