* [PATCH ath-next v4 1/9] wifi: ath11k: free tx skbs through ieee80211_free_txskb()
2026-09-08 13:53 [PATCH ath-next v4 0/9] wifi: ath11k: airtime queue limits, fairness and a driver TXQ scheduler Julius Bairaktaris
@ 2026-09-08 13:53 ` Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 2/9] wifi: ath11k: enable airtime queue limits Julius Bairaktaris
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Julius Bairaktaris @ 2026-09-08 13:53 UTC (permalink / raw)
To: jjohnson
Cc: linux-wireless, ath11k, linux-kernel, toke, sven, quic_vthiagar,
john
ath11k_dp_tx_free_txbuf(), reached from the HTT tx completion handler for
the REINJ and INSPECT statuses, releases the frame with
dev_kfree_skb_any(). Every other tx completion path in dp_tx.c hands the
skb back to mac80211, and this one is the last that does not.
A frame freed that way keeps whatever state mac80211 was holding for it.
A data frame whose socket asked for tx status has a clone parked in
local->ack_status_frames and an index allocated from an idr bounded at
8191 entries; ieee80211_free_txskb() releases both through
ieee80211_report_used_skb(), and dev_kfree_skb_any() leaks them for the
lifetime of the hw. Commit 29d15589f084 ("wifi: ath11k: Cleanup mac80211
references on failure during tx_complete") converted three sibling paths
for exactly that reason and left this one.
On a driver that advertises NL80211_EXT_FEATURE_AQL, which the next
patch makes ath11k do, ieee80211_free_txskb() also returns the pending
airtime the frame was charged at dequeue, and dev_kfree_skb_any() does
not. The charge then stays on the access category for good, and once
local->aql_ac_pending_airtime[] reaches its limit
ieee80211_txq_schedule_airtime_check() stops every station on the radio
in that category.
Neither status was seen on the hardware below during the measurements
in this series, so the change is by inspection of the completion path.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices")
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
drivers/net/wireless/ath/ath11k/dp_tx.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/ath11k/dp_tx.c b/drivers/net/wireless/ath/ath11k/dp_tx.c
index cac970c92806..52c4a9924a31 100644
--- a/drivers/net/wireless/ath/ath11k/dp_tx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_tx.c
@@ -305,9 +305,10 @@ static void ath11k_dp_tx_free_txbuf(struct ath11k_base *ab, u8 mac_id,
skb_cb = ATH11K_SKB_CB(msdu);
dma_unmap_single(ab->dev, skb_cb->paddr, msdu->len, DMA_TO_DEVICE);
- dev_kfree_skb_any(msdu);
ar = ab->pdevs[mac_id].ar;
+ ieee80211_free_txskb(ar->hw, msdu);
+
if (atomic_dec_and_test(&ar->dp.num_tx_pending))
wake_up(&ar->dp.tx_empty_waitq);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH ath-next v4 2/9] wifi: ath11k: enable airtime queue limits
2026-09-08 13:53 [PATCH ath-next v4 0/9] wifi: ath11k: airtime queue limits, fairness and a driver TXQ scheduler Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 1/9] wifi: ath11k: free tx skbs through ieee80211_free_txskb() Julius Bairaktaris
@ 2026-09-08 13:53 ` Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 3/9] wifi: ath11k: report the pending tx MSDU count in soc_dp_stats Julius Bairaktaris
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Julius Bairaktaris @ 2026-09-08 13:53 UTC (permalink / raw)
To: jjohnson
Cc: linux-wireless, ath11k, linux-kernel, toke, sven, quic_vthiagar,
john
ath11k does not advertise NL80211_EXT_FEATURE_AQL, and every user of AQL in
mac80211 is gated on that bit: the airtime charge in
ieee80211_tx_dequeue(), ieee80211_txq_airtime_check(),
ieee80211_sta_update_pending_airtime(), and the per-station aql file in
debugfs, which is not even created.
That matters here more than it would elsewhere, because AQL is the only
brake. ath11k uses ieee80211_handle_wake_tx_queue(), whose
wake_tx_push_queue() drains a selected txq whole into the driver, and the
check at the top of ieee80211_tx_dequeue() is what stops that drain.
Without the feature bit nothing does, so mac80211 holds no backlog and
every queued byte of a saturated download sits in the hardware TX rings,
where there is no AQM and no flow separation.
Measured on an IPQ8074 AP with a TCP download forwarded from a wired
host to one 1x1 VHT80 station at 101 Mbit/s, sampled once a second from
that station's own aql file: BE pending airtime sits at the 12000 us
default high limit, 12024 us at the median, and at 1008 to 1024 us when
the limit is lowered to 500/1000 us. Pending airtime stands a frame
above the limit it is tested against because
ieee80211_txq_airtime_check() admits a frame while pending is still
below it. Without this patch the file does not exist and the limits are
never consulted.
The rate the estimator needs is already there: ath11k keeps a per-station
rate_info in arsta->last_txrate and passes it to ieee80211_tx_status_ext(),
which stores it as tx_stats.last_rate_info, and that is what
ieee80211_calc_expected_tx_airtime() reads. Where no rate is known yet the
estimator falls back to its 4 us floor, so a station's queue is charged the
minimum until the first HTT PPDU stats arrive and the per-AC limits of 5000
and 12000 us stay loose over that window. The current code charges nothing
and consults no limit at all, so that window changes nothing. A rate that
has gone stale low overcharges a station that has since sped up, bounded
by the same lag.
The estimate is only as fresh as arsta->last_txrate, which is updated from
the HTT PPDU stats when a descriptor is evicted from ar->ppdu_stats_info,
so the rate behind it can lag a rate change by up to
HTT_PPDU_DESC_MAX_DEPTH PPDUs.
ieee80211_sta_update_pending_airtime() returns the airtime but does not
reschedule the txq, and ath11k has no completion-side push, so a queue held
back by AQL is re-poked only by the next enqueue or queue wake.
The bit alone brakes the queue but does not refill it: the check at the top
of ieee80211_tx_dequeue() stops the drain, and nothing restarts it until
mac80211 next calls the wake_tx_queue callback. Later patches in this
series give the driver a scheduling round and run it from tx completion,
which is what makes a binding limit affordable.
ath10k and mt76 set the same feature bit.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
drivers/net/wireless/ath/ath11k/mac.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
index 2d55cdc4d165..38f641bbc53c 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -10592,6 +10592,7 @@ static int __ath11k_mac_register(struct ath11k *ar)
ar->hw->sta_data_size = sizeof(struct ath11k_sta);
wiphy_ext_feature_set(ar->hw->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST);
+ wiphy_ext_feature_set(ar->hw->wiphy, NL80211_EXT_FEATURE_AQL);
wiphy_ext_feature_set(ar->hw->wiphy, NL80211_EXT_FEATURE_STA_TX_PWR);
if (test_bit(WMI_TLV_SERVICE_BSS_COLOR_OFFLOAD,
ar->ab->wmi_ab.svc_map)) {
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH ath-next v4 3/9] wifi: ath11k: report the pending tx MSDU count in soc_dp_stats
2026-09-08 13:53 [PATCH ath-next v4 0/9] wifi: ath11k: airtime queue limits, fairness and a driver TXQ scheduler Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 1/9] wifi: ath11k: free tx skbs through ieee80211_free_txskb() Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 2/9] wifi: ath11k: enable airtime queue limits Julius Bairaktaris
@ 2026-09-08 13:53 ` Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 4/9] wifi: ath11k: report tx airtime and enable airtime fairness Julius Bairaktaris
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Julius Bairaktaris @ 2026-09-08 13:53 UTC (permalink / raw)
To: jjohnson
Cc: linux-wireless, ath11k, linux-kernel, toke, sven, quic_vthiagar,
john
ar->dp.num_tx_pending counts the MSDUs the hardware has not completed
yet, and is used to wait for the transmit path to drain. It is never
shown.
soc_dp_stats can therefore say when the transmit path overflowed - TCL ring
full, misc transmit failures - and not how deep it is at this instant. The
count added here is bounded by the tx descriptor idr, one entry short of
DP_TX_IDR_SIZE per ring, which is large enough that occupancy rather than
exhaustion is what characterises the path under load.
Print it per radio, beside the failure counters it complements.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
drivers/net/wireless/ath/ath11k/debugfs.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/net/wireless/ath/ath11k/debugfs.c b/drivers/net/wireless/ath/ath11k/debugfs.c
index 70922612d3fb..ffe1f466aefb 100644
--- a/drivers/net/wireless/ath/ath11k/debugfs.c
+++ b/drivers/net/wireless/ath/ath11k/debugfs.c
@@ -714,6 +714,16 @@ static ssize_t ath11k_debugfs_dump_soc_dp_stats(struct file *file,
"\nMisc Transmit Failures: %d\n",
atomic_read(&soc_stats->tx_err.misc_fail));
+ /* Descriptors the hardware has not completed. */
+ len += scnprintf(buf + len, size - len, "\nPending Tx MSDUs:\n");
+ for (i = 0; i < ab->num_radios; i++) {
+ struct ath11k *ar = ab->pdevs[i].ar;
+
+ if (ar)
+ len += scnprintf(buf + len, size - len, "radio%d: %d\n",
+ i, atomic_read(&ar->dp.num_tx_pending));
+ }
+
len += ath11k_debugfs_dump_soc_ring_bp_stats(ab, buf + len, size - len);
if (len > size)
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH ath-next v4 4/9] wifi: ath11k: report tx airtime and enable airtime fairness
2026-09-08 13:53 [PATCH ath-next v4 0/9] wifi: ath11k: airtime queue limits, fairness and a driver TXQ scheduler Julius Bairaktaris
` (2 preceding siblings ...)
2026-09-08 13:53 ` [PATCH ath-next v4 3/9] wifi: ath11k: report the pending tx MSDU count in soc_dp_stats Julius Bairaktaris
@ 2026-09-08 13:53 ` Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 5/9] wifi: ath11k: schedule TXQs from the driver Julius Bairaktaris
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Julius Bairaktaris @ 2026-09-08 13:53 UTC (permalink / raw)
To: jjohnson
Cc: linux-wireless, ath11k, linux-kernel, toke, sven, quic_vthiagar,
john
mac80211's airtime scheduler charges sta->airtime[ac].deficit from one
place, ieee80211_sta_register_airtime(). Unlike AQL there is no estimator
fallback, and ath11k never calls it, so the ATF feature bit cannot be
advertised on its own: the deficit would only ever be replenished, every
station would stay permanently in credit, and the DRR would be inert. The
bit is not a no-op in that state either, because it also moves txq
insertion to the head of the active list, which __ieee80211_schedule_txq()
documents as safe only because the DRR moves a station back once its
deficit goes negative.
fes_duration_us arrives with every PPDU's stats and is already
accumulated into arsta->tx_duration a few lines above. Register it, and
set the feature bit in the same change so the two cannot be separated.
The access category the airtime is charged to comes from the TID, and the
ack/BA status TLV that ath11k took it from is emitted only for a PPDU that
drew a response. A transmission that timed out, was filtered or was aborted
names its TID only in the completion and rate TLVs, both of which already
reach the driver and were read nowhere. Take the first of the three the
firmware supplied that names one of the sixteen QoS TIDs; a PPDU that names
none is charged to best effort, where ieee80211_sta_register_airtime()
also puts the management TID.
The call runs under ar->data_lock and ab->base_lock. mac80211 takes
local->active_txq_lock[ac] inside it and calls nothing back under that
lock, and ath10k registers from the same place under its data_lock.
Measured on an IPQ8074 AP with two stations on one radio, a 1x1 VHT
client and a 2x2 HE client, both receiving at once. Before, 'iw station
set <mac> airtime_weight' is refused with -EOPNOTSUPP and the per-station
airtime file reads zero. After, the weight is accepted and read back, TX
airtime accumulates, and the per-AC deficits move.
What the deficit can do with that is bounded by how the driver hands
frames to the hardware. Every station keeps its full airtime queue limit
in flight and the hardware picks among them, so the scheduling order
does not reach the air and a weight moves nothing on this driver by
itself. The bit is what makes the weight settable and the deficit real;
what acts on them is a mac80211 change posted separately, which scales
the limit by the weight for a driver that says it pushes everything.
fes_duration_us spans the frame exchange sequence, so it includes the
SIFS and the responding BlockAck, time the radio spends receiving; the
airtime registered is therefore larger than the airtime transmitted. It
is a per-PPDU field, so each user of a downlink MU PPDU is charged the
whole sequence, as arsta->tx_duration already accumulates it; the stats
carry no per-user duration. On the firmware tested here every PPDU
carried exactly one user, over 21051 PPDUs.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
drivers/net/wireless/ath/ath11k/dp_rx.c | 17 +++++++++++++++++
drivers/net/wireless/ath/ath11k/mac.c | 2 ++
2 files changed, 19 insertions(+)
diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c
index 33425707c084..68472e1e748a 100644
--- a/drivers/net/wireless/ath/ath11k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_rx.c
@@ -1377,6 +1377,7 @@ ath11k_update_per_peer_tx_stats(struct ath11k *ar,
u16 rate = 0, succ_pkts = 0;
u32 tx_duration = 0;
u8 tid = HTT_PPDU_STATS_NON_QOS_TID;
+ u8 airtime_tid;
bool is_ampdu = false;
if (!(usr_stats->tlv_flags & BIT(HTT_PPDU_STATS_TAG_USR_RATE)))
@@ -1485,6 +1486,22 @@ ath11k_update_per_peer_tx_stats(struct ath11k *ar,
arsta->txrate.bw = ath11k_mac_bw_to_mac80211_bw(bw);
arsta->tx_duration += tx_duration;
+
+ /* The ack/BA status TLV names the TID only for a PPDU that drew a
+ * response; fall back to the completion and rate TLVs, then to BE.
+ */
+ airtime_tid = tid;
+ if (airtime_tid >= IEEE80211_NUM_TIDS &&
+ usr_stats->tlv_flags & BIT(HTT_PPDU_STATS_TAG_USR_COMPLTN_COMMON))
+ airtime_tid = usr_stats->cmpltn_cmn.tid_num;
+ if (airtime_tid >= IEEE80211_NUM_TIDS)
+ airtime_tid = user_rate->tid_num;
+ if (airtime_tid >= IEEE80211_NUM_TIDS)
+ airtime_tid = 0;
+
+ if (tx_duration)
+ ieee80211_sta_register_airtime(sta, airtime_tid, tx_duration, 0);
+
memcpy(&arsta->last_txrate, &arsta->txrate, sizeof(struct rate_info));
/* PPDU stats reported for mgmt packet doesn't have valid tx bytes.
diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
index 38f641bbc53c..40a2b6d2f804 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -10593,6 +10593,8 @@ static int __ath11k_mac_register(struct ath11k *ar)
wiphy_ext_feature_set(ar->hw->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST);
wiphy_ext_feature_set(ar->hw->wiphy, NL80211_EXT_FEATURE_AQL);
+ wiphy_ext_feature_set(ar->hw->wiphy,
+ NL80211_EXT_FEATURE_AIRTIME_FAIRNESS);
wiphy_ext_feature_set(ar->hw->wiphy, NL80211_EXT_FEATURE_STA_TX_PWR);
if (test_bit(WMI_TLV_SERVICE_BSS_COLOR_OFFLOAD,
ar->ab->wmi_ab.svc_map)) {
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH ath-next v4 5/9] wifi: ath11k: schedule TXQs from the driver
2026-09-08 13:53 [PATCH ath-next v4 0/9] wifi: ath11k: airtime queue limits, fairness and a driver TXQ scheduler Julius Bairaktaris
` (3 preceding siblings ...)
2026-09-08 13:53 ` [PATCH ath-next v4 4/9] wifi: ath11k: report tx airtime and enable airtime fairness Julius Bairaktaris
@ 2026-09-08 13:53 ` Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 6/9] wifi: ath11k: run the TXQ scheduler on tx completion Julius Bairaktaris
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Julius Bairaktaris @ 2026-09-08 13:53 UTC (permalink / raw)
To: jjohnson
Cc: linux-wireless, ath11k, linux-kernel, toke, sven, quic_vthiagar,
john
ieee80211_handle_wake_tx_queue() has exactly one entry point, the
wake_tx_queue callback, so a round starts only where mac80211 calls that
callback: a frame arriving from the network stack, a block ack session
starting or stopping, a station leaving powersave, or a stopped hardware
queue restarting. Every round runs under
local->handle_wake_tx_queue_lock, which is shared by all four access
categories of the hardware.
That is the wrong shape for a driver whose only backpressure is the
airtime queue limit. When ieee80211_txq_airtime_check() refuses a
station, ieee80211_tx_dequeue() stops handing out frames and the
station's intermediate queue keeps its backlog until the next enqueue,
not until the airtime it is waiting on comes back from the hardware.
Returning that airtime does not reschedule anything.
Open-code the round in the driver so that a second context can drive it.
The walk is the same deficit round-robin over the active TXQs of one
access category that the generic handler performs. A selection ends
where the airtime queue limit binds, because ieee80211_tx_dequeue()
applies the limit itself, and drains the station's queue where it does
not, which is what the generic handler does today.
mac80211 documents that a driver must not run concurrent scheduling rounds,
and the next patch adds a second context that starts one. The round
therefore takes a per-category lock rather than the hardware-wide one.
ath10k and mt76 also drive the round from the driver; ath10k takes its
per-category lock on the wake path alone, and mt76 serialises instead by
running its rounds from one per-device tx worker.
A custom .wake_tx_queue with a tx-completion driven push was proposed for
ath11k once before [1]; the design was right and the measurement that
justifies it is in the next patch.
Link: https://lore.kernel.org/all/20230501130725.7171-1-quic_tamizhr@quicinc.com/ [1]
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
drivers/net/wireless/ath/ath11k/core.h | 5 +++
drivers/net/wireless/ath/ath11k/mac.c | 44 ++++++++++++++++++++++++--
2 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h
index a0d725923ef2..40895d276de5 100644
--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -709,6 +709,11 @@ struct ath11k {
*/
spinlock_t data_lock;
+ /* serialises one scheduling round per access category against the
+ * other contexts that drive it
+ */
+ spinlock_t txq_lock[IEEE80211_NUM_ACS];
+
struct list_head arvifs;
/* should never be NULL; needed for regular htt rx */
struct ieee80211_channel *rx_channel;
diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
index 40a2b6d2f804..342b38308468 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -6546,6 +6546,43 @@ static void ath11k_mac_op_tx(struct ieee80211_hw *hw,
}
}
+static void ath11k_mac_tx_push_txq(struct ath11k *ar, struct ieee80211_txq *txq)
+{
+ struct ieee80211_tx_control control = { .sta = txq->sta };
+ struct sk_buff *skb;
+
+ /* ieee80211_tx_dequeue() applies the airtime queue limit itself, so a
+ * selection ends where that limit binds, where the hardware queue is
+ * stopped, or when the queue empties, as the generic handler does
+ * today.
+ */
+ while ((skb = ieee80211_tx_dequeue(ar->hw, txq)))
+ ath11k_mac_op_tx(ar->hw, &control, skb);
+}
+
+static void ath11k_mac_schedule_txq(struct ath11k *ar, u8 ac)
+{
+ struct ieee80211_hw *hw = ar->hw;
+ struct ieee80211_txq *txq;
+
+ spin_lock_bh(&ar->txq_lock[ac]);
+
+ ieee80211_txq_schedule_start(hw, ac);
+ while ((txq = ieee80211_next_txq(hw, ac))) {
+ ath11k_mac_tx_push_txq(ar, txq);
+ ieee80211_return_txq(hw, txq, false);
+ }
+ ieee80211_txq_schedule_end(hw, ac);
+
+ spin_unlock_bh(&ar->txq_lock[ac]);
+}
+
+static void ath11k_mac_op_wake_tx_queue(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq)
+{
+ ath11k_mac_schedule_txq(hw->priv, txq->ac);
+}
+
void ath11k_mac_drain_tx(struct ath11k *ar)
{
/* make sure rcu-protected mac80211 tx path itself is drained */
@@ -10067,7 +10104,7 @@ static int ath11k_mac_op_sta_state(struct ieee80211_hw *hw,
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,
@@ -10757,7 +10794,7 @@ int ath11k_mac_allocate(struct ath11k_base *ab)
struct ath11k *ar;
struct ath11k_pdev *pdev;
int ret;
- int i;
+ int i, j;
if (test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags))
return 0;
@@ -10791,6 +10828,9 @@ int ath11k_mac_allocate(struct ath11k_base *ab)
pdev->ar = ar;
spin_lock_init(&ar->data_lock);
+
+ for (j = 0; j < IEEE80211_NUM_ACS; j++)
+ spin_lock_init(&ar->txq_lock[j]);
INIT_LIST_HEAD(&ar->arvifs);
INIT_LIST_HEAD(&ar->ppdu_stats_info);
mutex_init(&ar->conf_mutex);
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH ath-next v4 6/9] wifi: ath11k: run the TXQ scheduler on tx completion
2026-09-08 13:53 [PATCH ath-next v4 0/9] wifi: ath11k: airtime queue limits, fairness and a driver TXQ scheduler Julius Bairaktaris
` (4 preceding siblings ...)
2026-09-08 13:53 ` [PATCH ath-next v4 5/9] wifi: ath11k: schedule TXQs from the driver Julius Bairaktaris
@ 2026-09-08 13:53 ` Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 7/9] wifi: ath11k: charge received airtime to the station deficit Julius Bairaktaris
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Julius Bairaktaris @ 2026-09-08 13:53 UTC (permalink / raw)
To: jjohnson
Cc: linux-wireless, ath11k, linux-kernel, toke, sven, quic_vthiagar,
john
A station whose airtime queue limit is reached stops being served by
ieee80211_tx_dequeue(), and the airtime it is waiting on is only returned
when the hardware completes the frames already charged to it. ath11k starts
a scheduling round only where mac80211 calls the wake_tx_queue callback, so
once the limit binds, the backlog moves again only when one of those events
comes round: most often the next frame from the network stack. Under a
saturated download the arrivals keep the round running; a flow that has
just filled the intermediate queue, or one whose sender is waiting on the
acknowledgements behind that queue, stalls for as long as it takes the next
arrival to appear.
Start a round for each access category a reaped completion belonged to,
on that radio, at the point where the airtime has been returned. The
airtime queue limit then bounds a queue that is refilled as it drains,
rather than one that is refilled when the network stack happens to push.
The access category is the queue mapping mac80211 stamped on the frame.
Measured on an IPQ8074 AP with a TCP download forwarded from a wired
host to one 1x1 VHT80 station, BE aql_txq_limit 500/1000 us, 20 s runs,
three interleaved runs per arm, download rate and the RTT the sender's
TCP sees on that flow, mean and maximum:
Mbit/s RTT mean ms RTT max ms
with this patch 100.8, 101.5, 100.6 27, 26, 26 32, 31, 32
without 93.0, 99.1, 76.2 26, 27, 94 66, 36, 347
Without it the download moves only when the network stack pushes, and
a run in three stalls until the sender retransmits. At the default
limit, which binds on this flow as well, this patch reads 101 Mbit/s at
48 to 55 ms and the arm without it 80.7 to 101.1 Mbit/s at 54 to 79 ms.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
drivers/net/wireless/ath/ath11k/dp_tx.c | 14 +++++++++++++-
drivers/net/wireless/ath/ath11k/mac.c | 7 +++++++
drivers/net/wireless/ath/ath11k/mac.h | 1 +
3 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/ath11k/dp_tx.c b/drivers/net/wireless/ath/ath11k/dp_tx.c
index 52c4a9924a31..70d326fe6a1f 100644
--- a/drivers/net/wireless/ath/ath11k/dp_tx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_tx.c
@@ -694,9 +694,10 @@ void ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id)
struct sk_buff *msdu;
struct hal_tx_status ts = {};
struct dp_tx_ring *tx_ring = &dp->tx_ring[ring_id];
+ unsigned long push = 0;
u32 *desc;
u32 msdu_id;
- u8 mac_id;
+ u8 mac_id, i;
spin_lock_bh(&status_ring->lock);
@@ -756,12 +757,23 @@ void ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id)
spin_unlock(&tx_ring->tx_idr_lock);
ar = ab->pdevs[mac_id].ar;
+ __set_bit(mac_id * IEEE80211_NUM_ACS + skb_get_queue_mapping(msdu),
+ &push);
if (atomic_dec_and_test(&ar->dp.num_tx_pending))
wake_up(&ar->dp.tx_empty_waitq);
ath11k_dp_tx_complete_msdu(ar, msdu, &ts);
}
+
+ /* A completion returns the airtime the frame was charged, which is
+ * what an airtime-limited station in that access category is waiting
+ * on. The arrivals that would otherwise start a round are themselves
+ * queued behind that limit.
+ */
+ for_each_set_bit(i, &push, ab->num_radios * IEEE80211_NUM_ACS)
+ ath11k_mac_tx_push_pending(ab->pdevs[i / IEEE80211_NUM_ACS].ar,
+ i % IEEE80211_NUM_ACS);
}
int ath11k_dp_tx_send_reo_cmd(struct ath11k_base *ab, struct dp_rx_tid *rx_tid,
diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
index 342b38308468..ffb6b865c2df 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -6583,6 +6583,13 @@ static void ath11k_mac_op_wake_tx_queue(struct ieee80211_hw *hw,
ath11k_mac_schedule_txq(hw->priv, txq->ac);
}
+void ath11k_mac_tx_push_pending(struct ath11k *ar, u8 ac)
+{
+ rcu_read_lock();
+ ath11k_mac_schedule_txq(ar, ac);
+ rcu_read_unlock();
+}
+
void ath11k_mac_drain_tx(struct ath11k *ar)
{
/* make sure rcu-protected mac80211 tx path itself is drained */
diff --git a/drivers/net/wireless/ath/ath11k/mac.h b/drivers/net/wireless/ath/ath11k/mac.h
index 59f83c7175fd..d577c5ca249c 100644
--- a/drivers/net/wireless/ath/ath11k/mac.h
+++ b/drivers/net/wireless/ath/ath11k/mac.h
@@ -161,6 +161,7 @@ struct ath11k *ath11k_mac_get_ar_by_vdev_id(struct ath11k_base *ab, u32 vdev_id)
struct ath11k *ath11k_mac_get_ar_by_pdev_id(struct ath11k_base *ab, u32 pdev_id);
void ath11k_mac_drain_tx(struct ath11k *ar);
+void ath11k_mac_tx_push_pending(struct ath11k *ar, u8 ac);
void ath11k_mac_peer_cleanup_all(struct ath11k *ar);
int ath11k_mac_tx_mgmt_pending_free(int buf_id, void *skb, void *ctx);
u8 ath11k_mac_bw_to_mac80211_bw(u8 bw);
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH ath-next v4 7/9] wifi: ath11k: charge received airtime to the station deficit
2026-09-08 13:53 [PATCH ath-next v4 0/9] wifi: ath11k: airtime queue limits, fairness and a driver TXQ scheduler Julius Bairaktaris
` (5 preceding siblings ...)
2026-09-08 13:53 ` [PATCH ath-next v4 6/9] wifi: ath11k: run the TXQ scheduler on tx completion Julius Bairaktaris
@ 2026-09-08 13:53 ` Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 8/9] wifi: ath11k: stop a scheduling round when the hardware refuses a frame Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 9/9] wifi: ath11k: budget the tx completion handler Julius Bairaktaris
8 siblings, 0 replies; 10+ messages in thread
From: Julius Bairaktaris @ 2026-09-08 13:53 UTC (permalink / raw)
To: jjohnson
Cc: linux-wireless, ath11k, linux-kernel, toke, sven, quic_vthiagar,
john
mac80211's airtime scheduler is configured to account both directions:
local->airtime_flags is AIRTIME_USE_TX | AIRTIME_USE_RX, and
ieee80211_sta_register_airtime() adds the received airtime to the same
deficit the transmit side subtracts from. ath11k passes zero for it, so
an access point serving a station that mostly uploads charges that
station nothing for the medium it occupies, and the deficit that decides
whose queue is served next describes half the traffic.
Register the duration the hardware reports for each received PPDU. It
arrives in the RXPCU PPDU end TLV on the monitor status ring, which is
subscribed for every radio, and ath11k already accumulates it as the
station's rx_duration. Charging it once per PPDU matches what the
transmit side registers, whereas an estimate rebuilt per MSDU counts the
preamble and the interframe space once for every subframe of an
aggregate.
The PPDU end user stats TLV that names the TID is not in the default
monitor status filter, so the airtime is charged to best effort, the
access category the deficit arbitrates the bulk of the traffic in. The
call is made after ab->base_lock is dropped, under the rcu read lock the
loop holds, as the tx completion path does; base_lock is contended by
every tx completion. A PPDU from a peer with no station entry is not
charged to anyone.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
drivers/net/wireless/ath/ath11k/dp_rx.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c
index 68472e1e748a..a7b9bb295728 100644
--- a/drivers/net/wireless/ath/ath11k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_rx.c
@@ -5270,6 +5270,7 @@ int ath11k_dp_rx_process_mon_status(struct ath11k_base *ab, int mac_id,
struct sk_buff_head skb_list;
struct ath11k_peer *peer;
struct ath11k_sta *arsta;
+ struct ieee80211_sta *sta;
int num_buffs_reaped = 0;
u32 rx_buf_sz;
u16 log_type;
@@ -5324,6 +5325,7 @@ int ath11k_dp_rx_process_mon_status(struct ath11k_base *ab, int mac_id,
continue;
}
+ sta = NULL;
rcu_read_lock();
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find_by_id(ab, ppdu_info->peer_id);
@@ -5337,12 +5339,25 @@ int ath11k_dp_rx_process_mon_status(struct ath11k_base *ab, int mac_id,
arsta = ath11k_sta_to_arsta(peer->sta);
ath11k_dp_rx_update_peer_stats(arsta, ppdu_info);
+ sta = peer->sta;
if (ath11k_debugfs_is_pktlog_peer_valid(ar, peer->addr))
trace_ath11k_htt_rxdesc(ar, skb->data, log_type, rx_buf_sz);
next_skb:
spin_unlock_bh(&ab->base_lock);
+
+ /* The airtime a station spends transmitting is taken from
+ * the others, so its deficit is charged for it too, with the
+ * duration the hardware measured for the whole PPDU, as on
+ * the transmit side. The TLV that names the TID is not in
+ * the default monitor status filter, so the charge goes to
+ * BE. Outside base_lock, which tx completion contends for;
+ * sta is held by the rcu read lock.
+ */
+ if (sta && ppdu_info->rx_duration)
+ ieee80211_sta_register_airtime(sta, 0, 0,
+ ppdu_info->rx_duration);
rcu_read_unlock();
dev_kfree_skb_any(skb);
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH ath-next v4 8/9] wifi: ath11k: stop a scheduling round when the hardware refuses a frame
2026-09-08 13:53 [PATCH ath-next v4 0/9] wifi: ath11k: airtime queue limits, fairness and a driver TXQ scheduler Julius Bairaktaris
` (6 preceding siblings ...)
2026-09-08 13:53 ` [PATCH ath-next v4 7/9] wifi: ath11k: charge received airtime to the station deficit Julius Bairaktaris
@ 2026-09-08 13:53 ` Julius Bairaktaris
2026-09-08 13:53 ` [PATCH ath-next v4 9/9] wifi: ath11k: budget the tx completion handler Julius Bairaktaris
8 siblings, 0 replies; 10+ messages in thread
From: Julius Bairaktaris @ 2026-09-08 13:53 UTC (permalink / raw)
To: jjohnson
Cc: linux-wireless, ath11k, linux-kernel, toke, sven, quic_vthiagar,
john
ath11k_dp_tx() returns -ENOSPC when the MSDU idr is exhausted and -ENOMEM
when no TCL descriptor is free on any ring or a DMA mapping fails, and the
caller answers both by freeing the frame. Inside a scheduling round that is
a loop: the next iteration pulls the next frame out of the same flow queue
and drops that one too, so a transient shortage costs the head of an
FQ-CoDel queue rather than the tail of a hardware one.
Return the error from the transmit path and end the round on it. The
frame that met the shortage is freed as before; the ones behind it stay
queued. The round-robin over the remaining stations stops as well, since
a shortage that reaches this point is not specific to the station being
served.
ath11k_mac_mgmt_tx() returns -ENOSPC as well, for a full management
queue, and that ends the round the same way; the next tx completion on
the radio starts another.
ath11k_dp_tx() also rejects a frame the hardware can never accept, with
-EINVAL or -EOPNOTSUPP, and every frame with -ESHUTDOWN while a firmware
crash is being flushed; those describe the frame or the device and not the
ring, so the round continues past them.
ath10k and mt76 check for room before they dequeue and never hold a frame
at this point; ath11k learns of the shortage from ath11k_dp_tx() and
cannot.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
drivers/net/wireless/ath/ath11k/mac.c | 41 ++++++++++++++++++++-------
1 file changed, 30 insertions(+), 11 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
index ffb6b865c2df..12535926631b 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -6499,9 +6499,9 @@ static int ath11k_mac_mgmt_tx(struct ath11k *ar, struct sk_buff *skb,
return 0;
}
-static void ath11k_mac_op_tx(struct ieee80211_hw *hw,
- struct ieee80211_tx_control *control,
- struct sk_buff *skb)
+static int ath11k_mac_tx(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
{
struct ath11k_skb_cb *skb_cb = ATH11K_SKB_CB(skb);
struct ath11k *ar = hw->priv;
@@ -6533,7 +6533,7 @@ static void ath11k_mac_op_tx(struct ieee80211_hw *hw,
ret);
ieee80211_free_txskb(ar->hw, skb);
}
- return;
+ return ret;
}
if (control->sta)
@@ -6544,20 +6544,36 @@ static void ath11k_mac_op_tx(struct ieee80211_hw *hw,
ath11k_warn(ar->ab, "failed to transmit frame %d\n", ret);
ieee80211_free_txskb(ar->hw, skb);
}
+
+ return ret;
+}
+
+static void ath11k_mac_op_tx(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
+{
+ ath11k_mac_tx(hw, control, skb);
}
-static void ath11k_mac_tx_push_txq(struct ath11k *ar, struct ieee80211_txq *txq)
+static int ath11k_mac_tx_push_txq(struct ath11k *ar, struct ieee80211_txq *txq)
{
struct ieee80211_tx_control control = { .sta = txq->sta };
struct sk_buff *skb;
+ int ret;
/* ieee80211_tx_dequeue() applies the airtime queue limit itself, so a
- * selection ends where that limit binds, where the hardware queue is
- * stopped, or when the queue empties, as the generic handler does
- * today.
+ * selection ends where the airtime queue limit binds, where the
+ * hardware queue is stopped, or when the queue empties, as the generic
+ * handler does today, and additionally when the hardware refuses a
+ * frame.
*/
- while ((skb = ieee80211_tx_dequeue(ar->hw, txq)))
- ath11k_mac_op_tx(ar->hw, &control, skb);
+ while ((skb = ieee80211_tx_dequeue(ar->hw, txq))) {
+ ret = ath11k_mac_tx(ar->hw, &control, skb);
+ if (unlikely(ret == -ENOSPC || ret == -ENOMEM))
+ return ret;
+ }
+
+ return 0;
}
static void ath11k_mac_schedule_txq(struct ath11k *ar, u8 ac)
@@ -6569,8 +6585,11 @@ static void ath11k_mac_schedule_txq(struct ath11k *ar, u8 ac)
ieee80211_txq_schedule_start(hw, ac);
while ((txq = ieee80211_next_txq(hw, ac))) {
- ath11k_mac_tx_push_txq(ar, txq);
+ int ret = ath11k_mac_tx_push_txq(ar, txq);
+
ieee80211_return_txq(hw, txq, false);
+ if (unlikely(ret))
+ break;
}
ieee80211_txq_schedule_end(hw, ac);
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH ath-next v4 9/9] wifi: ath11k: budget the tx completion handler
2026-09-08 13:53 [PATCH ath-next v4 0/9] wifi: ath11k: airtime queue limits, fairness and a driver TXQ scheduler Julius Bairaktaris
` (7 preceding siblings ...)
2026-09-08 13:53 ` [PATCH ath-next v4 8/9] wifi: ath11k: stop a scheduling round when the hardware refuses a frame Julius Bairaktaris
@ 2026-09-08 13:53 ` Julius Bairaktaris
8 siblings, 0 replies; 10+ messages in thread
From: Julius Bairaktaris @ 2026-09-08 13:53 UTC (permalink / raw)
To: jjohnson
Cc: linux-wireless, ath11k, linux-kernel, toke, sven, quic_vthiagar,
john
ath11k_dp_tx_completion_handler() reaps every completion the release
ring holds before it returns, and ath11k_dp_service_srng() charges none
of that work against the NAPI budget the poll was given, unlike every
receive handler below it. With the scheduling round that now ends the
handler, one poll that finds the ring full runs one round for all of it.
Reap at most the budget's worth of completions per poll and return the
count, so the poll ends and the round runs once per budget of
completions rather than once per ring. The completions left in the
FIFO are reaped by the next poll, which NAPI schedules at once when the
budget was spent.
Measured on an IPQ8074 AP with a TCP download forwarded from a wired
host to one 1x1 VHT80 station, BE aql_txq_limit 500/1000 us, 20 s runs,
three interleaved runs per arm, download rate and the RTT the sender's
TCP sees on that flow, mean and maximum:
Mbit/s RTT mean ms RTT max ms
with this patch 100.8, 101.5, 100.6 27, 26, 26 32, 31, 32
without 74.7, 101.1, 100.6 120, 27, 26 1873, 31, 30
Without it a poll that finds the ring full reaps all of it before the
round runs, and one run in three stalls for close to two seconds. At
the default limit both arms read 101 Mbit/s at 47 to 55 ms. Two soaks
on the series, a 91 s TCP soak at 1033 Mbit/s aggregate over five
streams, about 86000 completions per second, and a 60 s soak of
200-byte UDP datagrams at 60906 packets per second, produced no
status_fifo-is-full warning and no TCL ring-full failure.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
drivers/net/wireless/ath/ath11k/dp.c | 10 ++++++++--
drivers/net/wireless/ath/ath11k/dp_tx.c | 11 +++++++++--
drivers/net/wireless/ath/ath11k/dp_tx.h | 3 ++-
3 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c
index be65d73b904b..ddb434c513e0 100644
--- a/drivers/net/wireless/ath/ath11k/dp.c
+++ b/drivers/net/wireless/ath/ath11k/dp.c
@@ -783,8 +783,14 @@ int ath11k_dp_service_srng(struct ath11k_base *ab,
for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) {
if (BIT(ab->hw_params.hal_params->tcl2wbm_rbm_map[i].wbm_ring_num) &
- ab->hw_params.ring_mask->tx[grp_id])
- ath11k_dp_tx_completion_handler(ab, i);
+ ab->hw_params.ring_mask->tx[grp_id]) {
+ work_done =
+ ath11k_dp_tx_completion_handler(ab, i, budget);
+ budget -= work_done;
+ tot_work_done += work_done;
+ if (budget <= 0)
+ goto done;
+ }
}
if (ab->hw_params.ring_mask->rx_err[grp_id]) {
diff --git a/drivers/net/wireless/ath/ath11k/dp_tx.c b/drivers/net/wireless/ath/ath11k/dp_tx.c
index 70d326fe6a1f..3997bd336942 100644
--- a/drivers/net/wireless/ath/ath11k/dp_tx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_tx.c
@@ -685,7 +685,8 @@ static inline void ath11k_dp_tx_status_parse(struct ath11k_base *ab,
ts->rate_stats = 0;
}
-void ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id)
+int ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id,
+ int budget)
{
struct ath11k *ar;
struct ath11k_dp *dp = &ab->dp;
@@ -695,6 +696,7 @@ void ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id)
struct hal_tx_status ts = {};
struct dp_tx_ring *tx_ring = &dp->tx_ring[ring_id];
unsigned long push = 0;
+ int done = 0;
u32 *desc;
u32 msdu_id;
u8 mac_id, i;
@@ -723,10 +725,13 @@ void ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id)
spin_unlock_bh(&status_ring->lock);
- while (ATH11K_TX_COMPL_NEXT(tx_ring->tx_status_tail) != tx_ring->tx_status_head) {
+ while (done < budget &&
+ ATH11K_TX_COMPL_NEXT(tx_ring->tx_status_tail) !=
+ tx_ring->tx_status_head) {
struct hal_wbm_release_ring *tx_status;
u32 desc_id;
+ done++;
tx_ring->tx_status_tail =
ATH11K_TX_COMPL_NEXT(tx_ring->tx_status_tail);
tx_status = &tx_ring->tx_status[tx_ring->tx_status_tail];
@@ -774,6 +779,8 @@ void ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id)
for_each_set_bit(i, &push, ab->num_radios * IEEE80211_NUM_ACS)
ath11k_mac_tx_push_pending(ab->pdevs[i / IEEE80211_NUM_ACS].ar,
i % IEEE80211_NUM_ACS);
+
+ return done;
}
int ath11k_dp_tx_send_reo_cmd(struct ath11k_base *ab, struct dp_rx_tid *rx_tid,
diff --git a/drivers/net/wireless/ath/ath11k/dp_tx.h b/drivers/net/wireless/ath/ath11k/dp_tx.h
index 9303b5ba6e01..b5296cda73e3 100644
--- a/drivers/net/wireless/ath/ath11k/dp_tx.h
+++ b/drivers/net/wireless/ath/ath11k/dp_tx.h
@@ -21,7 +21,8 @@ void ath11k_dp_tx_update_txcompl(struct ath11k *ar, struct hal_tx_status *ts);
int ath11k_dp_tx_htt_h2t_ver_req_msg(struct ath11k_base *ab);
int ath11k_dp_tx(struct ath11k *ar, struct ath11k_vif *arvif,
struct ath11k_sta *arsta, struct sk_buff *skb);
-void ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id);
+int ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id,
+ int budget);
int ath11k_dp_tx_send_reo_cmd(struct ath11k_base *ab, struct dp_rx_tid *rx_tid,
enum hal_reo_cmd_type type,
struct ath11k_hal_reo_cmd *cmd,
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread