* [PATCH wireless-next v3 0/7] wifi: rt2x00: improve the throughput of rt2800usb
@ 2026-11-12 16:45 Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 1/7] wifi: rt2x00: start the link tuner when an interface is added Ho Jie Feng
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Ho Jie Feng @ 2026-11-12 16:45 UTC (permalink / raw)
To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ho Jie Feng
This series improves the throughput of rt2800usb.
Implements
- Frequency offset calibration
- Better fallback rates
- Aggregation
- GRO
Tested on WNDA4100 RT3593, 5 GHz with iperf3
patch before after
TX RX TX RX
2 crystal calibration 27.7 31.6 28.5 69.7
3 MCS16 to MCS8 TX status 27.0 70.0 29.4 70.0
5 RX bulk aggregation 30.5 67.8 30.5 80.9
7 threaded NAPI with GRO 30.5 80.9 30.5 161.7
RT3070 did not have any visible change for both TX and RX.
Patch 5 added WARN_ON_ONCE to get_rx_seg_len, but it will always
trigger due to a bug due to USB data toggle. The fix will be sent
in another patch.
Changes in v3:
- Crystal calibration: save the frequency offset of the beacons in
rt2x00lib_rxdone_skb() instead of fill_rxdone, through rxdesc
- RX aggregation: enable it for usb_maxpacket >= 512
- GRO: Add locking and reference mt76 on how it is done
Changes since v2:
- Correct the link tuner start and stop conditions
- Extend fallback and MCS retry calculation to all devices with 3 TX chains
- Remove TX AMSDU and fast-xmit
Ho Jie Feng (7):
wifi: rt2x00: start the link tuner when an interface is added
wifi: rt2800: calibrate the crystal of RT3593 with the beacons
wifi: rt2800: make MCS 16 fall back to MCS 8 on devices with 3 TX
chains
wifi: rt2800: don't report an A-MPDU length to minstrel_ht
wifi: rt2800usb: enable RX bulk aggregation
wifi: rt2800: support A-MSDU in A-MPDU
wifi: rt2x00usb: use a threaded NAPI with GRO for RX
drivers/net/wireless/ralink/rt2x00/rt2800.h | 1 +
.../net/wireless/ralink/rt2x00/rt2800lib.c | 89 +++++++++-
.../net/wireless/ralink/rt2x00/rt2800lib.h | 1 +
.../net/wireless/ralink/rt2x00/rt2800usb.c | 33 +++-
.../net/wireless/ralink/rt2x00/rt2800usb.h | 6 +
drivers/net/wireless/ralink/rt2x00/rt2x00.h | 37 ++++-
.../net/wireless/ralink/rt2x00/rt2x00dev.c | 152 ++++++++++++++----
.../net/wireless/ralink/rt2x00/rt2x00lib.h | 2 +
.../net/wireless/ralink/rt2x00/rt2x00link.c | 10 ++
.../net/wireless/ralink/rt2x00/rt2x00mac.c | 13 ++
.../net/wireless/ralink/rt2x00/rt2x00mmio.c | 2 +-
.../net/wireless/ralink/rt2x00/rt2x00queue.h | 5 +
.../net/wireless/ralink/rt2x00/rt2x00usb.c | 62 +++++--
13 files changed, 365 insertions(+), 48 deletions(-)
base-commit: 3c311bf321442d4c4f452a1a08bc9efce242a1f9
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH wireless-next v3 1/7] wifi: rt2x00: start the link tuner when an interface is added
2026-11-12 16:45 [PATCH wireless-next v3 0/7] wifi: rt2x00: improve the throughput of rt2800usb Ho Jie Feng
@ 2026-11-12 16:45 ` Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 2/7] wifi: rt2800: calibrate the crystal of RT3593 with the beacons Ho Jie Feng
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ho Jie Feng @ 2026-11-12 16:45 UTC (permalink / raw)
To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ho Jie Feng
rt2x00lib_start() enables the radio before mac80211 adds the first
interface, so rt2x00link_start_tuner(), called from
rt2x00lib_enable_radio(), finds no station or AP interface and returns.
Start the link tuner again when the first interface is added, and stop
it when the last interface is removed.
Fixes: d96324703ffa ("rt2x00: merge agc and vco works with link tuner")
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
Acked-by: Stanislaw Gruszka <stf_xl@wp.pl>
---
drivers/net/wireless/ralink/rt2x00/rt2x00mac.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c b/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c
index 3bc0c1c906..86547ce81c 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c
@@ -264,6 +264,13 @@ int rt2x00mac_add_interface(struct ieee80211_hw *hw,
*/
rt2x00dev->packet_filter = 0;
+ /*
+ * Start the link tuner again if the radio is enabled before the
+ * first interface is added.
+ */
+ if (rt2x00dev->intf_ap_count + rt2x00dev->intf_sta_count == 1)
+ rt2x00link_start_tuner(rt2x00dev);
+
return 0;
}
EXPORT_SYMBOL_GPL(rt2x00mac_add_interface);
@@ -289,6 +296,12 @@ void rt2x00mac_remove_interface(struct ieee80211_hw *hw,
else
rt2x00dev->intf_sta_count--;
+ /*
+ * Stop the link tuner when the last interface is removed.
+ */
+ if (!rt2x00dev->intf_ap_count && !rt2x00dev->intf_sta_count)
+ rt2x00link_stop_tuner(rt2x00dev);
+
/*
* Release beacon entry so it is available for
* new interfaces again.
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH wireless-next v3 2/7] wifi: rt2800: calibrate the crystal of RT3593 with the beacons
2026-11-12 16:45 [PATCH wireless-next v3 0/7] wifi: rt2x00: improve the throughput of rt2800usb Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 1/7] wifi: rt2x00: start the link tuner when an interface is added Ho Jie Feng
@ 2026-11-12 16:45 ` Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 3/7] wifi: rt2800: make MCS 16 fall back to MCS 8 on devices with 3 TX chains Ho Jie Feng
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ho Jie Feng @ 2026-11-12 16:45 UTC (permalink / raw)
To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ho Jie Feng
On a Netgear WNDA4100 (RT3593), as the adapter warms up, the
frequency offset drifts enough to affect the link quality.
In certain cases it can cause the link speed to drop
to 1Mbit/s or below.
Add a frequency calibration step function to RT3593 to use
the frequency offset obtained from the beacon frames of the AP
for tuning.
Test results:
before after
TX 27.7 28.5
RX 31.6 69.7
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/ralink/rt2x00/rt2800.h | 1 +
.../net/wireless/ralink/rt2x00/rt2800lib.c | 77 +++++++++++++++++++
.../net/wireless/ralink/rt2x00/rt2800lib.h | 1 +
.../net/wireless/ralink/rt2x00/rt2800usb.c | 1 +
drivers/net/wireless/ralink/rt2x00/rt2x00.h | 13 ++++
.../net/wireless/ralink/rt2x00/rt2x00dev.c | 24 ++++++
.../net/wireless/ralink/rt2x00/rt2x00lib.h | 2 +
.../net/wireless/ralink/rt2x00/rt2x00link.c | 10 +++
.../net/wireless/ralink/rt2x00/rt2x00queue.h | 5 ++
9 files changed, 134 insertions(+)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800.h b/drivers/net/wireless/ralink/rt2x00/rt2800.h
index 8930589b49..b9f6de3a46 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800.h
@@ -3163,6 +3163,7 @@ enum rt2800_eeprom_word {
*/
#define RXWI_W3_SNR0 FIELD32(0x000000ff)
#define RXWI_W3_SNR1 FIELD32(0x0000ff00)
+#define RXWI_W3_FOFFSET FIELD32(0xff000000)
/*
* Macros for converting txpower from EEPROM to mac80211 value
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
index 4f7beca668..371b293bac 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
@@ -935,6 +935,16 @@ void rt2800_process_rxwi(struct queue_entry *entry,
* Convert descriptor AGC value to RSSI value.
*/
rxdesc->rssi = rt2800_agc_to_rssi(entry->queue->rt2x00dev, word);
+
+ /*
+ * The frequency offset is used for the frequency calibration.
+ */
+ if (rt2x00_rt(entry->queue->rt2x00dev, RT3593)) {
+ word = rt2x00_desc_read(rxwi, 3);
+ rxdesc->freq_offset = rt2x00_get_field32(word, RXWI_W3_FOFFSET);
+ rxdesc->dev_flags |= RXDONE_FREQ_OFFSET;
+ }
+
/*
* Remove RXWI descriptor from start of the buffer.
*/
@@ -2441,6 +2451,7 @@ static inline bool rt2800_clk_is_20mhz(struct rt2x00_dev *rt2x00dev)
}
#define FREQ_OFFSET_BOUND 0x5f
+#define FREQ_OFFSET_INVALID -128
static void rt2800_freq_cal_mode1(struct rt2x00_dev *rt2x00dev)
{
@@ -5631,6 +5642,72 @@ void rt2800_vco_calibration(struct rt2x00_dev *rt2x00dev)
}
EXPORT_SYMBOL_GPL(rt2800_vco_calibration);
+void rt2800_freq_calibration(struct rt2x00_dev *rt2x00dev)
+{
+ struct link *link = &rt2x00dev->link;
+ u8 activate_threshold, deactivate_threshold;
+ u8 freq_offset, rate_mode;
+ u16 eeprom;
+ s8 offset;
+
+ if (!rt2x00_rt(rt2x00dev, RT3593))
+ return;
+
+ if (!rt2x00dev->intf_associated) {
+ eeprom = rt2800_eeprom_read(rt2x00dev, EEPROM_FREQ);
+ rt2x00dev->freq_offset = rt2x00_get_field16(eeprom,
+ EEPROM_FREQ_OFFSET);
+ link->freq_cal_adjusting = false;
+ return;
+ }
+
+ spin_lock_bh(&link->bcn_lock);
+ offset = link->bcn_freq_offset;
+ rate_mode = link->bcn_rate_mode;
+ link->bcn_freq_offset = FREQ_OFFSET_INVALID;
+ spin_unlock_bh(&link->bcn_lock);
+
+ /* No beacons received */
+ if (offset == FREQ_OFFSET_INVALID)
+ return;
+
+ if (rate_mode == RATE_MODE_CCK) {
+ activate_threshold = 4;
+ deactivate_threshold = 2;
+ } else {
+ activate_threshold = 20;
+ deactivate_threshold = 10;
+ }
+
+ /* Hysteresis */
+ if (abs(offset) >= activate_threshold)
+ link->freq_cal_adjusting = true;
+ else if (abs(offset) <= deactivate_threshold)
+ link->freq_cal_adjusting = false;
+
+ if (!link->freq_cal_adjusting)
+ return;
+
+ freq_offset = rt2x00_get_field8(rt2x00dev->freq_offset, RFCSR17_CODE);
+ freq_offset = min_t(u8, freq_offset, FREQ_OFFSET_BOUND);
+
+ if (offset > 0) {
+ if (freq_offset > 0)
+ freq_offset--;
+ else
+ link->freq_cal_adjusting = false;
+ } else {
+ if (freq_offset < FREQ_OFFSET_BOUND)
+ freq_offset++;
+ else
+ link->freq_cal_adjusting = false;
+ }
+
+ rt2x00dev->freq_offset = freq_offset;
+ rt2800_freq_cal_mode1(rt2x00dev);
+}
+EXPORT_SYMBOL_GPL(rt2800_freq_calibration);
+
static void rt2800_config_retry_limit(struct rt2x00_dev *rt2x00dev,
struct rt2x00lib_conf *libconf)
{
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.h b/drivers/net/wireless/ralink/rt2x00/rt2800lib.h
index a3c3a751f5..1e31a61e95 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.h
@@ -241,6 +241,7 @@ void rt2800_link_tuner(struct rt2x00_dev *rt2x00dev, struct link_qual *qual,
const u32 count);
void rt2800_gain_calibration(struct rt2x00_dev *rt2x00dev);
void rt2800_vco_calibration(struct rt2x00_dev *rt2x00dev);
+void rt2800_freq_calibration(struct rt2x00_dev *rt2x00dev);
int rt2800_enable_radio(struct rt2x00_dev *rt2x00dev);
void rt2800_disable_radio(struct rt2x00_dev *rt2x00dev);
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800usb.c b/drivers/net/wireless/ralink/rt2x00/rt2800usb.c
index 15de3db124..387c07e225 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800usb.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800usb.c
@@ -693,6 +693,7 @@ static const struct rt2x00lib_ops rt2800usb_rt2x00_ops = {
.link_tuner = rt2800_link_tuner,
.gain_calibration = rt2800_gain_calibration,
.vco_calibration = rt2800_vco_calibration,
+ .freq_calibration = rt2800_freq_calibration,
.watchdog = rt2800_watchdog,
.start_queue = rt2800usb_start_queue,
.kick_queue = rt2x00usb_kick_queue,
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00.h b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
index 7d313e86d3..415ff253cb 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
@@ -319,6 +319,18 @@ struct link {
*/
struct ewma_rssi avg_rssi;
+ /*
+ * Controls interval of frequency calibration
+ */
+ bool freq_cal_adjusting;
+
+ /*
+ * Frequency offset and rate mode of the last beacon from the AP
+ */
+ spinlock_t bcn_lock; /* protects bcn_freq_offset, bcn_rate_mode */
+ s8 bcn_freq_offset;
+ u8 bcn_rate_mode;
+
/*
* Work structure for scheduling periodic link tuning.
*/
@@ -559,6 +571,7 @@ struct rt2x00lib_ops {
struct link_qual *qual, const u32 count);
void (*gain_calibration) (struct rt2x00_dev *rt2x00dev);
void (*vco_calibration) (struct rt2x00_dev *rt2x00dev);
+ void (*freq_calibration) (struct rt2x00_dev *rt2x00dev);
/*
* Data queue handlers.
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
index 4d94b7062f..7b23da0678 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
@@ -688,6 +688,25 @@ static void rt2x00lib_rxdone_check_ps(struct rt2x00_dev *rt2x00dev,
queue_work(rt2x00dev->workqueue, &rt2x00dev->sleep_work);
}
+static void rt2x00lib_rxdone_freq_offset(struct rt2x00_dev *rt2x00dev,
+ struct sk_buff *skb,
+ struct rxdone_entry_desc *rxdesc)
+{
+ struct ieee80211_hdr *hdr = (void *)skb->data;
+ struct link *link = &rt2x00dev->link;
+
+ if (!(rxdesc->dev_flags & RXDONE_FREQ_OFFSET) ||
+ !(rxdesc->dev_flags & RXDONE_MY_BSS) ||
+ (rxdesc->flags & RX_FLAG_FAILED_FCS_CRC) ||
+ !ieee80211_is_beacon(hdr->frame_control))
+ return;
+
+ spin_lock_bh(&link->bcn_lock);
+ link->bcn_freq_offset = rxdesc->freq_offset;
+ link->bcn_rate_mode = rxdesc->rate_mode;
+ spin_unlock_bh(&link->bcn_lock);
+}
+
static int rt2x00lib_rxdone_read_signal(struct rt2x00_dev *rt2x00dev,
struct rxdone_entry_desc *rxdesc)
{
@@ -825,6 +844,11 @@ void rt2x00lib_rxdone(struct queue_entry *entry, gfp_t gfp)
*/
rt2x00lib_rxdone_check_ba(rt2x00dev, entry->skb, &rxdesc);
+ /*
+ * Store frequency offset of the beacons for frequency calibration.
+ */
+ rt2x00lib_rxdone_freq_offset(rt2x00dev, entry->skb, &rxdesc);
+
/*
* Update extra components
*/
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00lib.h b/drivers/net/wireless/ralink/rt2x00/rt2x00lib.h
index 2f1385baa7..a6f516ebdb 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00lib.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00lib.h
@@ -22,6 +22,8 @@
#define LINK_TUNE_INTERVAL round_jiffies_relative(LINK_TUNE_SECONDS * HZ)
#define AGC_SECONDS 4
#define VCO_SECONDS 10
+#define FREQ_SECONDS 10
+#define FREQ_ADJUST_SECONDS 1
/*
* rt2x00_rate: Per rate device information
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00link.c b/drivers/net/wireless/ralink/rt2x00/rt2x00link.c
index fb23d409fb..d44a55b14f 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00link.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00link.c
@@ -343,6 +343,7 @@ static void rt2x00link_tuner(struct work_struct *work)
struct rt2x00_dev *rt2x00dev =
container_of(work, struct rt2x00_dev, link.work.work);
struct link *link = &rt2x00dev->link;
+ unsigned int freq_interval;
/*
* When the radio is shutting down we should
@@ -362,6 +363,14 @@ static void rt2x00link_tuner(struct work_struct *work)
(link->count % (AGC_SECONDS / LINK_TUNE_SECONDS)) == 0)
rt2x00dev->ops->lib->gain_calibration(rt2x00dev);
+ freq_interval = link->freq_cal_adjusting ?
+ FREQ_ADJUST_SECONDS : FREQ_SECONDS;
+
+ /* VCO calibration should be done after frequency calibration */
+ if (rt2x00dev->ops->lib->freq_calibration &&
+ (link->count % (freq_interval / LINK_TUNE_SECONDS)) == 0)
+ rt2x00dev->ops->lib->freq_calibration(rt2x00dev);
+
if (rt2x00dev->ops->lib->vco_calibration &&
rt2x00_has_cap_vco_recalibration(rt2x00dev) &&
(link->count % (VCO_SECONDS / LINK_TUNE_SECONDS)) == 0)
@@ -422,6 +431,7 @@ void rt2x00link_register(struct rt2x00_dev *rt2x00dev)
INIT_DELAYED_WORK(&link->work, rt2x00link_tuner);
INIT_DELAYED_WORK(&link->watchdog_work, rt2x00link_watchdog);
+ spin_lock_init(&link->bcn_lock);
if (link->watchdog_interval == 0)
link->watchdog_interval = WATCHDOG_INTERVAL;
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00queue.h b/drivers/net/wireless/ralink/rt2x00/rt2x00queue.h
index 23739dd0bc..4caf9261a5 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00queue.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00queue.h
@@ -129,6 +129,7 @@ static inline struct skb_frame_desc* get_skb_frame_desc(struct sk_buff *skb)
* @RXDONE_CRYPTO_IV: Driver provided IV/EIV data.
* @RXDONE_CRYPTO_ICV: Driver provided ICV data.
* @RXDONE_L2PAD: 802.11 payload has been padded to 4-byte boundary.
+ * @RXDONE_FREQ_OFFSET: Driver provided the frequency offset.
*/
enum rxdone_entry_desc_flags {
RXDONE_SIGNAL_PLCP = BIT(0),
@@ -138,6 +139,7 @@ enum rxdone_entry_desc_flags {
RXDONE_CRYPTO_IV = BIT(4),
RXDONE_CRYPTO_ICV = BIT(5),
RXDONE_L2PAD = BIT(6),
+ RXDONE_FREQ_OFFSET = BIT(7),
};
/**
@@ -164,6 +166,7 @@ enum rxdone_entry_desc_flags {
* @cipher_status: Decryption status.
* @iv: IV/EIV data used during decryption.
* @icv: ICV data used during decryption.
+ * @freq_offset: Frequency offset of the received frame.
*/
struct rxdone_entry_desc {
u64 timestamp;
@@ -181,6 +184,8 @@ struct rxdone_entry_desc {
__le32 iv[2];
__le32 icv;
+
+ s8 freq_offset;
};
/**
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH wireless-next v3 3/7] wifi: rt2800: make MCS 16 fall back to MCS 8 on devices with 3 TX chains
2026-11-12 16:45 [PATCH wireless-next v3 0/7] wifi: rt2x00: improve the throughput of rt2800usb Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 1/7] wifi: rt2x00: start the link tuner when an interface is added Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 2/7] wifi: rt2800: calibrate the crystal of RT3593 with the beacons Ho Jie Feng
@ 2026-11-12 16:45 ` Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 4/7] wifi: rt2800: don't report an A-MPDU length to minstrel_ht Ho Jie Feng
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ho Jie Feng @ 2026-11-12 16:45 UTC (permalink / raw)
To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ho Jie Feng
MCS 15 has worse reliability than MCS 16, since 16 and above
are 3 stream rates, and 15 is the top of the 2 stream rates.
Falling back from MCS 16 to MCS 15 does not make sense, so
make the fallback to MCS 8 on all devices with 3 TX chains,
like the Ralink driver does.
The retry chain is now MCS 16 to MCS 8, skipping 7 MCS rates
in between. Reflect that in the retry calculation code. This
also fixes the retry count on RT3883, which already used this
fallback.
Test results:
before after
TX 28.2 29.1
RX 70.4 70.2
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/ralink/rt2x00/rt2800lib.c | 10 +++++++++-
drivers/net/wireless/ralink/rt2x00/rt2x00.h | 5 +++++
drivers/net/wireless/ralink/rt2x00/rt2x00dev.c | 14 ++++++++++++--
3 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
index 371b293bac..3be4f4cbde 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
@@ -1096,6 +1096,14 @@ void rt2800_txdone_entry(struct queue_entry *entry, u32 status, __le32 *txwi,
*/
__set_bit(TXDONE_SUCCESS, &txdesc.flags);
txdesc.retry = ((mcs > real_mcs) ? mcs - real_mcs : 0);
+
+ /*
+ * On devices with 3 TX chains MCS16 falls back to MCS8, so a
+ * frame that was sent below MCS16 after a three stream rate
+ * skipped MCS9 to MCS15.
+ */
+ if (rt2x00_is_3t(rt2x00dev) && mcs >= 16 && real_mcs <= 8)
+ txdesc.retry -= 7;
} else {
/*
* Transmission failed. The number of retries is
@@ -6280,7 +6288,7 @@ static int rt2800_init_registers(struct rt2x00_dev *rt2x00dev)
reg = rt2x00_rt(rt2x00dev, RT5592) ? 0x00000082 : 0x00000002;
rt2800_register_write(rt2x00dev, TXOP_HLDR_ET, reg);
- if (rt2x00_rt(rt2x00dev, RT3883)) {
+ if (rt2x00_is_3t(rt2x00dev)) {
rt2800_register_write(rt2x00dev, TX_FBK_CFG_3S_0, 0x12111008);
rt2800_register_write(rt2x00dev, TX_FBK_CFG_3S_1, 0x16151413);
}
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00.h b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
index 415ff253cb..ace06ba73d 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
@@ -1200,6 +1200,11 @@ static inline bool rt2x00_is_soc(struct rt2x00_dev *rt2x00dev)
return rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_SOC);
}
+static inline bool rt2x00_is_3t(struct rt2x00_dev *rt2x00dev)
+{
+ return rt2x00dev->default_ant.tx_chain_num == 3;
+}
+
/* Helpers for capability flags */
static inline bool
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
index 7b23da0678..7c03fd07bc 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
@@ -329,10 +329,10 @@ static void rt2x00lib_fill_tx_status(struct rt2x00_dev *rt2x00dev,
* lowest rate was used.
*/
for (i = 0; i < retry_rates && i < IEEE80211_TX_MAX_RATES; i++) {
- tx_info->status.rates[i].idx = rate_idx - i;
+ tx_info->status.rates[i].idx = rate_idx;
tx_info->status.rates[i].flags = rate_flags;
- if (rate_idx - i == 0) {
+ if (rate_idx == 0) {
/*
* The lowest rate (index 0) was used until the
* number of max retries was reached.
@@ -340,8 +340,18 @@ static void rt2x00lib_fill_tx_status(struct rt2x00_dev *rt2x00dev,
tx_info->status.rates[i].count = retry_rates - i;
i++;
break;
+ } else if (rate_idx == 16) {
+ /*
+ * TX_FBK_CFG_3S_0 makes MCS16 fall back to MCS8 on
+ * devices with 3 TX chains, the decrement below shifts
+ * it back to 8.
+ */
+ if (rt2x00_is_3t(rt2x00dev) &&
+ (rate_flags & IEEE80211_TX_RC_MCS))
+ rate_idx = 9;
}
tx_info->status.rates[i].count = 1;
+ rate_idx--;
}
if (i < (IEEE80211_TX_MAX_RATES - 1))
tx_info->status.rates[i].idx = -1; /* terminate */
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH wireless-next v3 4/7] wifi: rt2800: don't report an A-MPDU length to minstrel_ht
2026-11-12 16:45 [PATCH wireless-next v3 0/7] wifi: rt2x00: improve the throughput of rt2800usb Ho Jie Feng
` (2 preceding siblings ...)
2026-11-12 16:45 ` [PATCH wireless-next v3 3/7] wifi: rt2800: make MCS 16 fall back to MCS 8 on devices with 3 TX chains Ho Jie Feng
@ 2026-11-12 16:45 ` Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 5/7] wifi: rt2800usb: enable RX bulk aggregation Ho Jie Feng
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Ho Jie Feng @ 2026-11-12 16:45 UTC (permalink / raw)
To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ho Jie Feng
Set IEEE80211_HW_TX_STATUS_NO_AMPDU_LEN, which was added for drivers
that can't report the proper AMPDU length and record this workaround
below the TODO comment.
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
Acked-by: Stanislaw Gruszka <stf_xl@wp.pl>
---
drivers/net/wireless/ralink/rt2x00/rt2800lib.c | 1 +
drivers/net/wireless/ralink/rt2x00/rt2x00dev.c | 2 ++
2 files changed, 3 insertions(+)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
index 3be4f4cbde..269d621f51 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
@@ -11837,6 +11837,7 @@ static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
ieee80211_hw_set(rt2x00dev->hw, PS_NULLFUNC_STACK);
ieee80211_hw_set(rt2x00dev->hw, SIGNAL_DBM);
ieee80211_hw_set(rt2x00dev->hw, SUPPORTS_PS);
+ ieee80211_hw_set(rt2x00dev->hw, TX_STATUS_NO_AMPDU_LEN);
/*
* Don't set IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING for USB devices
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
index 7c03fd07bc..baf5d039bc 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
@@ -374,6 +374,8 @@ static void rt2x00lib_fill_tx_status(struct rt2x00_dev *rt2x00dev,
* by the hw we could provide the real ampdu_len to mac80211
* which would allow the rc algorithm to better decide on
* which rates are suitable.
+ *
+ * Currently, we work around this by setting TX_STATUS_NO_AMPDU_LEN.
*/
if (test_bit(TXDONE_AMPDU, &txdesc->flags) ||
tx_info->flags & IEEE80211_TX_CTL_AMPDU) {
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH wireless-next v3 5/7] wifi: rt2800usb: enable RX bulk aggregation
2026-11-12 16:45 [PATCH wireless-next v3 0/7] wifi: rt2x00: improve the throughput of rt2800usb Ho Jie Feng
` (3 preceding siblings ...)
2026-11-12 16:45 ` [PATCH wireless-next v3 4/7] wifi: rt2800: don't report an A-MPDU length to minstrel_ht Ho Jie Feng
@ 2026-11-12 16:45 ` Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 6/7] wifi: rt2800: support A-MSDU in A-MPDU Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 7/7] wifi: rt2x00usb: use a threaded NAPI with GRO for RX Ho Jie Feng
6 siblings, 0 replies; 8+ messages in thread
From: Ho Jie Feng @ 2026-11-12 16:45 UTC (permalink / raw)
To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ho Jie Feng
Enable RX bulk aggregation on high speed USB, to receive more
frames per URB.
RX buffers are now 24KB, so reduce it to 16 buffers instead of 128,
so that they take 512 KB in total instead of 4 MB.
Test results:
before after
TX 30.5 30.5
RX 67.8 80.9
This code references the mt7601u driver.
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
.../net/wireless/ralink/rt2x00/rt2800usb.c | 32 +++++--
.../net/wireless/ralink/rt2x00/rt2800usb.h | 6 ++
drivers/net/wireless/ralink/rt2x00/rt2x00.h | 3 +-
.../net/wireless/ralink/rt2x00/rt2x00dev.c | 84 +++++++++++++------
.../net/wireless/ralink/rt2x00/rt2x00mmio.c | 2 +-
.../net/wireless/ralink/rt2x00/rt2x00usb.c | 5 +-
6 files changed, 100 insertions(+), 32 deletions(-)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800usb.c b/drivers/net/wireless/ralink/rt2x00/rt2800usb.c
index 387c07e225..be58d72105 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800usb.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800usb.c
@@ -301,15 +301,15 @@ static int rt2800usb_enable_radio(struct rt2x00_dev *rt2x00dev)
return -EIO;
rt2x00_set_field32(®, USB_DMA_CFG_PHY_CLEAR, 0);
- rt2x00_set_field32(®, USB_DMA_CFG_RX_BULK_AGG_EN, 0);
+ rt2x00_set_field32(®, USB_DMA_CFG_RX_BULK_AGG_EN,
+ rt2x00dev->rx->usb_maxpacket >= 512);
rt2x00_set_field32(®, USB_DMA_CFG_RX_BULK_AGG_TIMEOUT, 128);
/*
* Total room for RX frames in kilobytes, PBF might still exceed
* this limit so reduce the number to prevent errors.
*/
rt2x00_set_field32(®, USB_DMA_CFG_RX_BULK_AGG_LIMIT,
- ((rt2x00dev->rx->limit * DATA_FRAME_SIZE)
- / 1024) - 3);
+ (rt2x00dev->rx->data_size / 1024) - 4);
rt2x00_set_field32(®, USB_DMA_CFG_RX_BULK_EN, 1);
rt2x00_set_field32(®, USB_DMA_CFG_TX_BULK_EN, 1);
rt2x00usb_register_write(rt2x00dev, USB_DMA_CFG, reg);
@@ -478,6 +478,27 @@ static void rt2800usb_work_txdone(struct work_struct *work)
/*
* RX control handlers
*/
+static unsigned int rt2800usb_get_rx_seg_len(void *data, unsigned int len)
+{
+ unsigned int min_seg_len = RXINFO_DESC_SIZE + RXWI_DESC_SIZE_4WORDS +
+ RXD_DESC_SIZE;
+ unsigned int rx_pkt_len;
+ u32 word;
+
+ if (len < min_seg_len)
+ return 0;
+
+ word = rt2x00_desc_read(data, 0);
+ rx_pkt_len = rt2x00_get_field32(word, RXINFO_W0_USB_DMA_RX_PKT_LEN);
+
+ if (WARN_ON_ONCE(RXINFO_DESC_SIZE + rx_pkt_len + RXD_DESC_SIZE > len) ||
+ WARN_ON_ONCE(rx_pkt_len & 0x3) ||
+ WARN_ON_ONCE(rx_pkt_len < min_seg_len))
+ return 0;
+
+ return RXINFO_DESC_SIZE + rx_pkt_len + RXD_DESC_SIZE;
+}
+
static void rt2800usb_fill_rxdone(struct queue_entry *entry,
struct rxdone_entry_desc *rxdesc)
{
@@ -706,6 +727,7 @@ static const struct rt2x00lib_ops rt2800usb_rt2x00_ops = {
.clear_beacon = rt2800_clear_beacon,
.get_tx_data_len = rt2800usb_get_tx_data_len,
.fill_rxdone = rt2800usb_fill_rxdone,
+ .get_rx_seg_len = rt2800usb_get_rx_seg_len,
.config_shared_key = rt2800_config_shared_key,
.config_pairwise_key = rt2800_config_pairwise_key,
.config_filter = rt2800_config_filter,
@@ -725,8 +747,8 @@ static void rt2800usb_queue_init(struct data_queue *queue)
switch (queue->qid) {
case QID_RX:
- queue->limit = 128;
- queue->data_size = AGGREGATION_SIZE;
+ queue->limit = 16;
+ queue->data_size = MAX_RXBULK_SIZE;
queue->desc_size = RXINFO_DESC_SIZE;
queue->winfo_size = rxwi_size;
queue->priv_size = sizeof(struct queue_entry_priv_usb);
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800usb.h b/drivers/net/wireless/ralink/rt2x00/rt2800usb.h
index 9e180e9e41..2fae79072f 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800usb.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800usb.h
@@ -29,6 +29,12 @@
*/
#define TXINFO_DESC_SIZE (1 * sizeof(__le32))
#define RXINFO_DESC_SIZE (1 * sizeof(__le32))
+#define RXD_DESC_SIZE (1 * sizeof(__le32))
+
+/*
+ * RX buffer size
+ */
+#define MAX_RXBULK_SIZE 24576
/*
* TX Info structure
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00.h b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
index ace06ba73d..7b1a32d3e1 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
@@ -600,6 +600,7 @@ struct rt2x00lib_ops {
*/
void (*fill_rxdone) (struct queue_entry *entry,
struct rxdone_entry_desc *rxdesc);
+ unsigned int (*get_rx_seg_len) (void *data, unsigned int len);
/*
* Configuration handlers.
@@ -1460,7 +1461,7 @@ void rt2x00lib_txdone(struct queue_entry *entry,
void rt2x00lib_txdone_nomatch(struct queue_entry *entry,
struct txdone_entry_desc *txdesc);
void rt2x00lib_txdone_noinfo(struct queue_entry *entry, u32 status);
-void rt2x00lib_rxdone(struct queue_entry *entry, gfp_t gfp);
+void rt2x00lib_rxdone(struct queue_entry *entry, unsigned int len, gfp_t gfp);
/*
* mac80211 handlers.
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
index baf5d039bc..dc6a7d12b2 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
@@ -765,35 +765,14 @@ static int rt2x00lib_rxdone_read_signal(struct rt2x00_dev *rt2x00dev,
return 0;
}
-void rt2x00lib_rxdone(struct queue_entry *entry, gfp_t gfp)
+static void rt2x00lib_rxdone_skb(struct queue_entry *entry)
{
struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
struct rxdone_entry_desc rxdesc;
- struct sk_buff *skb;
struct ieee80211_rx_status *rx_status;
unsigned int header_length;
int rate_idx;
- if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) ||
- !test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
- goto submit_entry;
-
- if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags))
- goto submit_entry;
-
- /*
- * Allocate a new sk_buffer. If no new buffer available, drop the
- * received frame and reuse the existing buffer.
- */
- skb = rt2x00queue_alloc_rxskb(entry, gfp);
- if (!skb)
- goto submit_entry;
-
- /*
- * Unmap the skb.
- */
- rt2x00queue_unmap_skb(entry);
-
/*
* Extract the RXD details.
*/
@@ -809,7 +788,7 @@ void rt2x00lib_rxdone(struct queue_entry *entry, gfp_t gfp)
rt2x00_err(rt2x00dev, "Wrong frame size %d max %d\n",
rxdesc.size, entry->queue->data_size);
dev_kfree_skb(entry->skb);
- goto renew_skb;
+ return;
}
/*
@@ -893,8 +872,65 @@ void rt2x00lib_rxdone(struct queue_entry *entry, gfp_t gfp)
rx_status->antenna = rt2x00dev->link.ant.active.rx;
ieee80211_rx_ni(rt2x00dev->hw, entry->skb);
+}
+
+void rt2x00lib_rxdone(struct queue_entry *entry, unsigned int len, gfp_t gfp)
+{
+ struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
+ struct skb_frame_desc *skbdesc, *frame_skbdesc;
+ struct sk_buff *skb;
+ unsigned int seg_len;
+ u8 *data;
+
+ if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) ||
+ !test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
+ goto submit_entry;
+
+ if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags))
+ goto submit_entry;
+
+ if (rt2x00dev->ops->lib->get_rx_seg_len) {
+ skb = entry->skb;
+ skbdesc = get_skb_frame_desc(skb);
+ data = skb->data;
+
+ while ((seg_len = rt2x00dev->ops->lib->get_rx_seg_len(data, len))) {
+ entry->skb = __dev_alloc_skb(seg_len, gfp);
+ if (!entry->skb)
+ break;
+
+ skb_put_data(entry->skb, data, seg_len);
+
+ frame_skbdesc = get_skb_frame_desc(entry->skb);
+ memset(frame_skbdesc, 0, sizeof(*frame_skbdesc));
+ frame_skbdesc->desc = skbdesc->desc;
+ frame_skbdesc->desc_len = skbdesc->desc_len;
+
+ rt2x00lib_rxdone_skb(entry);
+
+ data += seg_len;
+ len -= seg_len;
+ }
+
+ entry->skb = skb;
+ goto submit_entry;
+ }
+
+ /*
+ * Allocate a new sk_buffer. If no new buffer available, drop the
+ * received frame and reuse the existing buffer.
+ */
+ skb = rt2x00queue_alloc_rxskb(entry, gfp);
+ if (!skb)
+ goto submit_entry;
+
+ /*
+ * Unmap the skb.
+ */
+ rt2x00queue_unmap_skb(entry);
+
+ rt2x00lib_rxdone_skb(entry);
-renew_skb:
/*
* Replace the skb with the freshly allocated one.
*/
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00mmio.c b/drivers/net/wireless/ralink/rt2x00/rt2x00mmio.c
index 93f76acf3d..a1d8b8d13d 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00mmio.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00mmio.c
@@ -78,7 +78,7 @@ bool rt2x00mmio_rxdone(struct rt2x00_dev *rt2x00dev)
/*
* Send the frame to rt2x00lib for further processing.
*/
- rt2x00lib_rxdone(entry, GFP_ATOMIC);
+ rt2x00lib_rxdone(entry, entry->skb->len, GFP_ATOMIC);
}
return !max_rx;
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
index c9fbc3710f..4519a20a7f 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
@@ -345,6 +345,7 @@ static void rt2x00usb_work_rxdone(struct work_struct *work)
{
struct rt2x00_dev *rt2x00dev =
container_of(work, struct rt2x00_dev, rxdone_work);
+ struct queue_entry_priv_usb *entry_priv;
struct queue_entry *entry;
struct skb_frame_desc *skbdesc;
u8 rxd[32];
@@ -365,7 +366,9 @@ static void rt2x00usb_work_rxdone(struct work_struct *work)
/*
* Send the frame to rt2x00lib for further processing.
*/
- rt2x00lib_rxdone(entry, GFP_KERNEL);
+ entry_priv = entry->priv_data;
+ rt2x00lib_rxdone(entry, entry_priv->urb->actual_length,
+ GFP_KERNEL);
}
}
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH wireless-next v3 6/7] wifi: rt2800: support A-MSDU in A-MPDU
2026-11-12 16:45 [PATCH wireless-next v3 0/7] wifi: rt2x00: improve the throughput of rt2800usb Ho Jie Feng
` (4 preceding siblings ...)
2026-11-12 16:45 ` [PATCH wireless-next v3 5/7] wifi: rt2800usb: enable RX bulk aggregation Ho Jie Feng
@ 2026-11-12 16:45 ` Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 7/7] wifi: rt2x00usb: use a threaded NAPI with GRO for RX Ho Jie Feng
6 siblings, 0 replies; 8+ messages in thread
From: Ho Jie Feng @ 2026-11-12 16:45 UTC (permalink / raw)
To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ho Jie Feng
Set SUPPORTS_AMSDU_IN_AMPDU, which can increase throughput.
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/ralink/rt2x00/rt2800lib.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
index 269d621f51..ac62b56280 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
@@ -11838,6 +11838,7 @@ static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
ieee80211_hw_set(rt2x00dev->hw, SIGNAL_DBM);
ieee80211_hw_set(rt2x00dev->hw, SUPPORTS_PS);
ieee80211_hw_set(rt2x00dev->hw, TX_STATUS_NO_AMPDU_LEN);
+ ieee80211_hw_set(rt2x00dev->hw, SUPPORTS_AMSDU_IN_AMPDU);
/*
* Don't set IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING for USB devices
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH wireless-next v3 7/7] wifi: rt2x00usb: use a threaded NAPI with GRO for RX
2026-11-12 16:45 [PATCH wireless-next v3 0/7] wifi: rt2x00: improve the throughput of rt2800usb Ho Jie Feng
` (5 preceding siblings ...)
2026-11-12 16:45 ` [PATCH wireless-next v3 6/7] wifi: rt2800: support A-MSDU in A-MPDU Ho Jie Feng
@ 2026-11-12 16:45 ` Ho Jie Feng
6 siblings, 0 replies; 8+ messages in thread
From: Ho Jie Feng @ 2026-11-12 16:45 UTC (permalink / raw)
To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ho Jie Feng
GRO makes sense now that RX aggregation is enabled, since multiple
frames arrive at once.
This code is referenced from commit 5323d3e50c20
("wifi: mt76: mt76u: use a threaded NAPI for the RX path")
The RX locking is referenced from commit 3298b1f866fb ("mt76: use
ieee80211_rx_list to pass frames to the network stack as a batch") and
commit 5b8ccdfb943f ("wifi: mt76: add missing locking to protect
against concurrent rx/status calls")
Test results:
before after
TX 30.5 30.5
RX 80.9 161.7
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/ralink/rt2x00/rt2x00.h | 16 ++++-
.../net/wireless/ralink/rt2x00/rt2x00dev.c | 28 ++++++++-
.../net/wireless/ralink/rt2x00/rt2x00usb.c | 59 +++++++++++++++----
3 files changed, 89 insertions(+), 14 deletions(-)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00.h b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
index 7b1a32d3e1..7d10bbe3ba 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
@@ -960,11 +960,22 @@ struct rt2x00_dev {
struct work_struct intf_work;
/**
- * Scheduled work for TX/RX done handling (USB devices)
+ * Scheduled work for TX done handling (USB devices)
*/
- struct work_struct rxdone_work;
struct work_struct txdone_work;
+ /*
+ * NAPI context for RX done handling (USB devices).
+ */
+ struct net_device *napi_dev;
+ struct napi_struct napi;
+ struct sk_buff_head rx_skb;
+
+ /*
+ * Serialize the RX in NAPI with the TX status reports to mac80211.
+ */
+ spinlock_t rx_lock;
+
/*
* Powersaving work
*/
@@ -1462,6 +1473,7 @@ void rt2x00lib_txdone_nomatch(struct queue_entry *entry,
struct txdone_entry_desc *txdesc);
void rt2x00lib_txdone_noinfo(struct queue_entry *entry, u32 status);
void rt2x00lib_rxdone(struct queue_entry *entry, unsigned int len, gfp_t gfp);
+void rt2x00lib_rx_complete(struct rt2x00_dev *rt2x00dev);
/*
* mac80211 handlers.
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
index dc6a7d12b2..aae96d082a 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
@@ -461,7 +461,9 @@ void rt2x00lib_txdone_nomatch(struct queue_entry *entry,
rt2x00lib_fill_tx_status(rt2x00dev, &txinfo, skbdesc, txdesc,
success);
+ spin_lock_bh(&rt2x00dev->rx_lock);
ieee80211_tx_status_noskb(rt2x00dev->hw, skbdesc->sta, &txinfo);
+ spin_unlock_bh(&rt2x00dev->rx_lock);
}
dev_kfree_skb_any(entry->skb);
@@ -545,10 +547,12 @@ void rt2x00lib_txdone(struct queue_entry *entry,
* send the status report back.
*/
if (!(skbdesc_flags & SKBDESC_NOT_MAC80211)) {
+ spin_lock_bh(&rt2x00dev->rx_lock);
if (rt2x00_has_cap_flag(rt2x00dev, REQUIRE_TASKLET_CONTEXT))
ieee80211_tx_status_skb(rt2x00dev->hw, entry->skb);
else
ieee80211_tx_status_ni(rt2x00dev->hw, entry->skb);
+ spin_unlock_bh(&rt2x00dev->rx_lock);
} else {
dev_kfree_skb_any(entry->skb);
}
@@ -871,7 +875,10 @@ static void rt2x00lib_rxdone_skb(struct queue_entry *entry)
rx_status->bw = rxdesc.bw;
rx_status->antenna = rt2x00dev->link.ant.active.rx;
- ieee80211_rx_ni(rt2x00dev->hw, entry->skb);
+ if (rt2x00dev->napi_dev)
+ __skb_queue_tail(&rt2x00dev->rx_skb, entry->skb);
+ else
+ ieee80211_rx_ni(rt2x00dev->hw, entry->skb);
}
void rt2x00lib_rxdone(struct queue_entry *entry, unsigned int len, gfp_t gfp)
@@ -945,6 +952,23 @@ void rt2x00lib_rxdone(struct queue_entry *entry, unsigned int len, gfp_t gfp)
}
EXPORT_SYMBOL_GPL(rt2x00lib_rxdone);
+void rt2x00lib_rx_complete(struct rt2x00_dev *rt2x00dev)
+{
+ struct sk_buff *skb, *tmp;
+ LIST_HEAD(list);
+
+ spin_lock(&rt2x00dev->rx_lock);
+ while ((skb = __skb_dequeue(&rt2x00dev->rx_skb)) != NULL)
+ ieee80211_rx_list(rt2x00dev->hw, NULL, skb, &list);
+ spin_unlock(&rt2x00dev->rx_lock);
+
+ list_for_each_entry_safe(skb, tmp, &list, list) {
+ skb_list_del_init(skb);
+ napi_gro_receive(&rt2x00dev->napi, skb);
+ }
+}
+EXPORT_SYMBOL_GPL(rt2x00lib_rx_complete);
+
/*
* Driver initialization handlers.
*/
@@ -1469,6 +1493,8 @@ int rt2x00lib_probe_dev(struct rt2x00_dev *rt2x00dev)
mutex_init(&rt2x00dev->conf_mutex);
INIT_LIST_HEAD(&rt2x00dev->bar_list);
spin_lock_init(&rt2x00dev->bar_list_lock);
+ spin_lock_init(&rt2x00dev->rx_lock);
+ skb_queue_head_init(&rt2x00dev->rx_skb);
hrtimer_setup(&rt2x00dev->txstatus_timer, hrtimer_dummy_timeout, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
index 4519a20a7f..4fe0d6f836 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
@@ -341,15 +341,16 @@ static bool rt2x00usb_kick_tx_entry(struct queue_entry *entry, void *data)
/*
* RX data handlers.
*/
-static void rt2x00usb_work_rxdone(struct work_struct *work)
+static int rt2x00usb_poll_rxdone(struct napi_struct *napi, int budget)
{
struct rt2x00_dev *rt2x00dev =
- container_of(work, struct rt2x00_dev, rxdone_work);
+ container_of(napi, struct rt2x00_dev, napi);
struct queue_entry_priv_usb *entry_priv;
struct queue_entry *entry;
struct skb_frame_desc *skbdesc;
u8 rxd[32];
+ rcu_read_lock();
while (!rt2x00queue_empty(rt2x00dev->rx)) {
entry = rt2x00queue_get_entry(rt2x00dev->rx, Q_INDEX_DONE);
@@ -368,8 +369,14 @@ static void rt2x00usb_work_rxdone(struct work_struct *work)
*/
entry_priv = entry->priv_data;
rt2x00lib_rxdone(entry, entry_priv->urb->actual_length,
- GFP_KERNEL);
+ GFP_ATOMIC);
}
+ rt2x00lib_rx_complete(rt2x00dev);
+ rcu_read_unlock();
+
+ napi_complete(napi);
+
+ return 0;
}
static void rt2x00usb_interrupt_rxdone(struct urb *urb)
@@ -396,7 +403,7 @@ static void rt2x00usb_interrupt_rxdone(struct urb *urb)
/*
* Schedule the delayed work for processing RX data
*/
- queue_work(rt2x00dev->workqueue, &rt2x00dev->rxdone_work);
+ napi_schedule(&rt2x00dev->napi);
}
static bool rt2x00usb_kick_rx_entry(struct queue_entry *entry, void *data)
@@ -478,7 +485,7 @@ static bool rt2x00usb_flush_entry(struct queue_entry *entry, void *data)
void rt2x00usb_flush_queue(struct data_queue *queue, bool drop)
{
- struct work_struct *completion;
+ bool is_tx;
unsigned int i;
if (drop)
@@ -486,17 +493,17 @@ void rt2x00usb_flush_queue(struct data_queue *queue, bool drop)
rt2x00usb_flush_entry);
/*
- * Obtain the queue completion handler
+ * Obtain the queue type
*/
switch (queue->qid) {
case QID_AC_VO:
case QID_AC_VI:
case QID_AC_BE:
case QID_AC_BK:
- completion = &queue->rt2x00dev->txdone_work;
+ is_tx = true;
break;
case QID_RX:
- completion = &queue->rt2x00dev->rxdone_work;
+ is_tx = false;
break;
default:
return;
@@ -515,7 +522,14 @@ void rt2x00usb_flush_queue(struct data_queue *queue, bool drop)
* Schedule the completion handler manually, when this
* worker function runs, it should cleanup the queue.
*/
- queue_work(queue->rt2x00dev->workqueue, completion);
+ if (is_tx) {
+ queue_work(queue->rt2x00dev->workqueue,
+ &queue->rt2x00dev->txdone_work);
+ } else {
+ local_bh_disable();
+ napi_schedule(&queue->rt2x00dev->napi);
+ local_bh_enable();
+ }
/*
* Wait for a little while to give the driver
@@ -750,7 +764,7 @@ void rt2x00usb_uninitialize(struct rt2x00_dev *rt2x00dev)
usb_kill_anchored_urbs(rt2x00dev->anchor);
hrtimer_cancel(&rt2x00dev->txstatus_timer);
- cancel_work_sync(&rt2x00dev->rxdone_work);
+ napi_synchronize(&rt2x00dev->napi);
cancel_work_sync(&rt2x00dev->txdone_work);
queue_for_each(rt2x00dev, queue)
@@ -758,6 +772,14 @@ void rt2x00usb_uninitialize(struct rt2x00_dev *rt2x00dev)
}
EXPORT_SYMBOL_GPL(rt2x00usb_uninitialize);
+static void rt2x00usb_free_napi(struct rt2x00_dev *rt2x00dev)
+{
+ napi_disable(&rt2x00dev->napi);
+ netif_napi_del(&rt2x00dev->napi);
+ free_netdev(rt2x00dev->napi_dev);
+ rt2x00dev->napi_dev = NULL;
+}
+
int rt2x00usb_probe(struct usb_interface *usb_intf,
const struct rt2x00_ops *ops)
{
@@ -801,9 +823,21 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_USB);
- INIT_WORK(&rt2x00dev->rxdone_work, rt2x00usb_work_rxdone);
INIT_WORK(&rt2x00dev->txdone_work, rt2x00usb_work_txdone);
+ rt2x00dev->napi_dev = alloc_netdev_dummy(0);
+ if (!rt2x00dev->napi_dev) {
+ retval = -ENOMEM;
+ goto exit_free_hw;
+ }
+
+ strscpy(rt2x00dev->napi_dev->name, wiphy_name(hw->wiphy),
+ sizeof(rt2x00dev->napi_dev->name));
+ netif_napi_add(rt2x00dev->napi_dev, &rt2x00dev->napi,
+ rt2x00usb_poll_rxdone);
+ netif_threaded_enable(rt2x00dev->napi_dev);
+ napi_enable(&rt2x00dev->napi);
+
init_usb_anchor(rt2x00dev->anchor);
retval = rt2x00lib_probe_dev(rt2x00dev);
@@ -814,6 +848,8 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
exit_free_anchor:
usb_kill_anchored_urbs(rt2x00dev->anchor);
+ rt2x00usb_free_napi(rt2x00dev);
+exit_free_hw:
ieee80211_free_hw(hw);
usb_set_intfdata(usb_intf, NULL);
@@ -830,6 +866,7 @@ void rt2x00usb_disconnect(struct usb_interface *usb_intf)
* Free all allocated data.
*/
rt2x00lib_remove_dev(rt2x00dev);
+ rt2x00usb_free_napi(rt2x00dev);
ieee80211_free_hw(hw);
usb_set_intfdata(usb_intf, NULL);
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-11-12 16:46 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-12 16:45 [PATCH wireless-next v3 0/7] wifi: rt2x00: improve the throughput of rt2800usb Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 1/7] wifi: rt2x00: start the link tuner when an interface is added Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 2/7] wifi: rt2800: calibrate the crystal of RT3593 with the beacons Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 3/7] wifi: rt2800: make MCS 16 fall back to MCS 8 on devices with 3 TX chains Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 4/7] wifi: rt2800: don't report an A-MPDU length to minstrel_ht Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 5/7] wifi: rt2800usb: enable RX bulk aggregation Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 6/7] wifi: rt2800: support A-MSDU in A-MPDU Ho Jie Feng
2026-11-12 16:45 ` [PATCH wireless-next v3 7/7] wifi: rt2x00usb: use a threaded NAPI with GRO for RX Ho Jie Feng
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®