Linux wireless drivers development
 help / color / mirror / Atom feed
* Re: [PATCH v8 5/9] block: implement NVMEM provider
From: Loic Poulain @ 2026-07-24  9:10 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Srinivas Kandagatla, Andrew Lunn, Heiner Kallweit,
	Russell King, Saravana Kannan, Christian Marangi, linux-mmc,
	devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Bartosz Golaszewski
In-Reply-To: <amLwz6WuHYUwKKJo@infradead.org>

Hi Christoph,

On Fri, Jul 24, 2026 at 6:57 AM Christoph Hellwig <hch@infradead.org> wrote:
>
> On Fri, Jul 03, 2026 at 03:45:18PM +0200, Loic Poulain wrote:
> > +       Allow block devices (or partitions) to act as NVMEM providers,
>
> Based on the code below I don't think you actually support partitions.

Right, I was planning to add partition support in a follow-up series.
But since this is really supposed to be a block-agnostic feature,
whether it's a disk or a partition, I'll include partition in v8.

>
> > +static int blk_nvmem_reg_read(void *priv, unsigned int from, void *val, size_t bytes)
>
> Overly long line.
>
> > +{
> > +     dev_t devt = (dev_t)(uintptr_t)priv;
> > +     size_t bytes_left = bytes;
> > +     loff_t pos = from;
> > +     int ret = 0;
> > +
> > +     struct file *bdev_file __free(fput) =
> > +             bdev_file_open_by_dev(devt, BLK_OPEN_READ, NULL, NULL);
>
> Please try to avoid all that magic autfree stuff that makes the code
> impossible to read about.

Ack, I've received mixed feedback on this. However, given that there
aren't any particularly complex or numerous exit paths, I guess the
explicit release will make the code clearer.

> > +     if (IS_ERR(bdev_file))
> > +             return PTR_ERR(bdev_file);
> > +
> > +     while (bytes_left) {
>
> .. and just use __kernel_read instead of badly reimplementing it.

Ack.

>
> > +     config.name = dev_name(dev);
> > +     config.owner = THIS_MODULE;
> > +     config.priv = (void *)(uintptr_t)dev->devt;
>
> Why is this storing the dev_t and not the block device itself?

It was originally intended to avoid races with device removal, but now
that we're synchronized with the block subsystem, that's no longer
strictly necessary.

>
> > +
> > +             blk_nvmem_add(disk->part0);
>
> Hmm, and you're always doing it for the whole device.  So the intefaces
> really should be based on a gendisk, not a block_device.

I will add it for partitions as well.

>
> Also once you register the whole devices as nvmem provider, we really
> should keep it from being used a block device, shouldn't we?


I'm not sure, the whole concept is block backed NVMEM, so we still
need to add that block as base layer, though we could discuss
suppressing uevent for it. Preventing block usage would make the
feature much more intrusive than other NVMEM providers like MTD, where
the underlying device stays accessible. And we may genuinely still
want to write the disk (e.g. for factory provisioning), updating the
very data the cells point to, etc. There's also the layering aspect,
if a disk (e.g. mmcblk0) has logical partitions on top (mmcblk0pX), we
still register/expose the whole disk mmcblk0 as a block device
regardless. So if a disk contains NVMEM cells, I think it should still
be exposed as a block device as well.

Regards,
Loic

^ permalink raw reply

* RE: [PATCH v2] wifi: rtlwifi: pci: fix error path in rtl_pci_probe()
From: Ping-Ke Shih @ 2026-07-24  9:07 UTC (permalink / raw)
  To: Abdun Nihaal; +Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <20260723120118.145383-1-nihaal@cse.iitm.ac.in>

Abdun Nihaal <nihaal@cse.iitm.ac.in> wrote:
> In the last error path in rtl_pci_probe(), the cleanup functions are
> skipped due to a wrong goto label. Moreover, the successful call to
> rtl_init_rfkill(), ieee80211_register_hw(), rtl_debug_add_one() have to
> be reverted. Fix this issue by updating the labels and adding the
> relevant cleanup functions to the last error path.
> 
> Fixes: 0c8173385e54 ("rtl8192ce: Add new driver")
> Signed-off-by: Abdun Nihaal <nihaal@cse.iitm.ac.in>

Acked-by: Ping-Ke Shih <pkshih@realtek.com>



^ permalink raw reply

* RE: [PATCH v2] wifi: rtlwifi: rtl8192du: Fix possible memory leak in rtl92du_init_sw_vars()
From: Ping-Ke Shih @ 2026-07-24  9:06 UTC (permalink / raw)
  To: Abdun Nihaal
  Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org,
	rtl8821cerfe2@gmail.com, stable@vger.kernel.org
In-Reply-To: <20260723114539.136986-1-nihaal@cse.iitm.ac.in>

Abdun Nihaal <nihaal@cse.iitm.ac.in> wrote:
> The memory allocated inside rtl92du_init_shared_data() is not freed in
> any of the subsequent error paths in rtl92du_init_sw_vars().
> Fix that by adding a call to rtl92du_deinit_shared_data() in the error
> path.
> 
> Fixes: b5dc8873b6ff ("wifi: rtlwifi: Add rtl8192du/sw.c")
> Cc: stable@vger.kernel.org
> Signed-off-by: Abdun Nihaal <nihaal@cse.iitm.ac.in>

Acked-by: Ping-Ke Shih <pkshih@realtek.com>



^ permalink raw reply

* Re: [PATCH v4 1/4] wifi: ath11k: use queue mapping for WCN6750 ring selection
From: Tamizh Raja @ 2026-07-24  9:04 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez
  Cc: jjohnson, ath11k, ath12k, linux-wireless, linux-kernel
In-Reply-To: <20260724053121.15759-2-jtornosm@redhat.com>

On Fri, Jul 24, 2026 at 11:01 AM Jose Ignacio Tornos Martinez
<jtornosm@redhat.com> wrote:
>
> WCN6750 selects the TCL ring using skb_get_hash() to distribute
> flows across its 3 TX rings, as introduced in commit 7636c9a6e7d7
> ("wifi: ath11k: Add multi TX ring support for WCN6750"). The goal
> was to prevent out-of-order packet delivery that could occur with
> smp_processor_id()-based selection, where packets of the same flow
> could end up on different rings depending on CPU scheduling.
>
> Switch to skb_get_queue_mapping() instead, which returns the AC
> (access category) assigned by mac80211 in ieee80211_select_queue().
> This provides the same ordering guarantee: packets of the same TID
> always map to the same AC and therefore always land on the same
> ring, preventing reordering.
>
> Using queue mapping for ring selection also provides QoS-aware
> distribution, where each traffic class gets a deterministic ring
> assignment, rather than the random distribution that flow hashing
> produces. With 3 rings and 4 ACs (VO=0, VI=1, BE=2, BK=3), the
> mapping becomes: VO and BK share ring 0, VI uses ring 1, and BE
> uses ring 2.

Combining VO and BK is fine? since VO is a high priority. @baochen any
thought on this?
>
> This matches the approach already used by ath12k for WCN7850, which
> has the same ring count and uses skb_get_queue_mapping() for its
> ring selector.
>
> This change also removes the dependency on skb_get_hash(), which
> is relevant for a subsequent patch that removes the skb parameter
> from the get_ring_selector hw_ops callback entirely, enabling
> TX flow control in wake_tx_queue without requiring a complex peek
> to inspect frames before dequeue.
>
> Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
> ---
> v4: new patch
>
>  drivers/net/wireless/ath/ath11k/hw.c | 8 +-------
>  1 file changed, 1 insertion(+), 7 deletions(-)
>
> diff --git a/drivers/net/wireless/ath/ath11k/hw.c b/drivers/net/wireless/ath/ath11k/hw.c
> index 93f9a03c48dc..d679e39dce03 100644
> --- a/drivers/net/wireless/ath/ath11k/hw.c
> +++ b/drivers/net/wireless/ath/ath11k/hw.c
> @@ -891,13 +891,7 @@ static u32 ath11k_hw_ipq8074_get_tcl_ring_selector(struct sk_buff *skb)
>
>  static u32 ath11k_hw_wcn6750_get_tcl_ring_selector(struct sk_buff *skb)
>  {
> -       /* Select the TCL ring based on the flow hash of the SKB instead
> -        * of CPU ID. Since applications pumping the traffic can be scheduled
> -        * on multiple CPUs, there is a chance that packets of the same flow
> -        * could end on different TCL rings, this could sometimes results in
> -        * an out of order arrival of the packets at the receiver.
> -        */
> -       return skb_get_hash(skb);
> +       return skb_get_queue_mapping(skb);
>  }
>
>  const struct ath11k_hw_ops ipq8074_ops = {
> --
> 2.54.0
>
>


-- 
- Tamizh.

^ permalink raw reply

* Re: [PATCH ath-next] wifi: ath12k: skip MLO multicast links during crash recovery in Tx path
From: Rameshkumar Sundaram @ 2026-07-24  8:58 UTC (permalink / raw)
  To: Pavankumar Nandeshwar, ath12k; +Cc: linux-wireless
In-Reply-To: <20260723054653.2794550-1-pavankumar.nandeshwar@oss.qualcomm.com>

On 7/23/2026 11:16 AM, Pavankumar Nandeshwar wrote:
> In ath12k_wifi7_mac_op_tx(), the MLO multicast broadcast path iterates
> over all active links and copies the original skb for transmission on
> each link. When firmware crash recovery is underway (ATH12K_FLAG_CRASH_FLUSH
> set), the per-link copy is allocated and partially processed before
> ath12k_wifi7_dp_tx() eventually rejects it with -ESHUTDOWN.
> 
> This wastes GFP_ATOMIC memory and produces spurious "failed to transmit
> frame" warnings for every active MLO link during the recovery window.
> The unicast and non-MLO paths are unaffected: they call ath12k_wifi7_dp_tx()
> directly, which already guards against the flag at its entry.
> 
> Skip any link whose associated ath12k_base has ATH12K_FLAG_CRASH_FLUSH set
> before performing the skb_copy(), matching the behaviour of
> ath12k_wifi7_dp_tx() but avoiding the unnecessary allocation entirely.
> 
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3
> 
> Signed-off-by: Pavankumar Nandeshwar <pavankumar.nandeshwar@oss.qualcomm.com>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* [PATCH] wifi: mt76: mt7925: add MT7928 debugfs txpower_sku with power table selection
From: JB Tsai @ 2026-07-24  8:52 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
	litien.chang, Charlie-cy.Wu, jb.tsai

From: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>

Add MT7928-specific debugfs interface for txpower_sku that supports
multiple power table selection. MT7928 extends the power structure with
additional OFDM bandwidth variants (OFDM20/40/80/160) compared to MT7925.

Key changes:
- Add struct mt7928_txpwr with OFDM20/40/80/160 support
- Implement mt7928_txpwr() handler for MT7928-specific power display
- Add txpower_tbl debugfs control for selecting power tables:
  * 0: STA_SISO
  * 1: STA_CDD
  * 2: STA_MIMO
  * 3: P2P_SISO
  * 4: P2P_CDD
  * 5: P2P_MIMO
- Extend mt7925_txpwr_req with power_tbl field for FW communication
- Add mt7928_txpwr_event structure for handling FW responses
- Store txpwr_tbl in mt792x_dev for per-device power table tracking

Usage:
  echo 0 > /sys/kernel/debug/ieee80211/phy0/mt76/txpower_tbl
  cat /sys/kernel/debug/ieee80211/phy0/mt76/txpower_sku

Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
 .../wireless/mediatek/mt76/mt7925/debugfs.c   | 151 +++++++++++++++++-
 .../net/wireless/mediatek/mt76/mt7925/mcu.c   |  27 ++++
 .../net/wireless/mediatek/mt76/mt7925/mcu.h   |  20 ++-
 .../wireless/mediatek/mt76/mt7925/mt7925.h    |  39 +++++
 drivers/net/wireless/mediatek/mt76/mt792x.h   |   1 +
 5 files changed, 235 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index e2498659c884..3db564d63c15 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -178,6 +178,147 @@ mt7925_txpwr(struct seq_file *s, void *data)
 	return ret;
 }
 
+static inline void
+mt7928_eht_txpwr(struct seq_file *s, struct mt7928_txpwr *txpwr, u8 band_idx)
+{
+	seq_printf(s, "%-22s  %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+		   " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+		   "mcs6", "mcs7", "mcs8", "mcs9", "mcs10", "mcs11",
+		   "mcs12", "mcs13", "mcs14", "mcs15");
+	mt7925_print_txpwr_entry(EHT26, eht26, band_idx);
+	mt7925_print_txpwr_entry(EHT52, eht52, band_idx);
+	mt7925_print_txpwr_entry(EHT106, eht106, band_idx);
+	mt7925_print_txpwr_entry(EHT242, eht242, band_idx);
+	mt7925_print_txpwr_entry(EHT484, eht484, band_idx);
+
+	mt7925_print_txpwr_entry(EHT996, eht996, band_idx);
+	mt7925_print_txpwr_entry(EHT996x2, eht996x2, band_idx);
+	mt7925_print_txpwr_entry(EHT996x4, eht996x4, band_idx);
+	mt7925_print_txpwr_entry(EHT26_52, eht26_52, band_idx);
+	mt7925_print_txpwr_entry(EHT26_106, eht26_106, band_idx);
+	mt7925_print_txpwr_entry(EHT484_242, eht484_242, band_idx);
+	mt7925_print_txpwr_entry(EHT996_484, eht996_484, band_idx);
+	mt7925_print_txpwr_entry(EHT996_484_242, eht996_484_242, band_idx);
+	mt7925_print_txpwr_entry(EHT996x2_484, eht996x2_484, band_idx);
+	mt7925_print_txpwr_entry(EHT996x3, eht996x3, band_idx);
+	mt7925_print_txpwr_entry(EHT996x3_484, eht996x3_484, band_idx);
+}
+
+static int
+mt7928_txpwr(struct seq_file *s, void *data)
+{
+	struct mt792x_dev *dev = dev_get_drvdata(s->private);
+	struct mt7928_txpwr *txpwr = NULL;
+	u8 band_idx = dev->mphy.band_idx;
+	int ret = 0;
+
+	txpwr = devm_kmalloc(dev->mt76.dev, sizeof(*txpwr), GFP_KERNEL);
+
+	if (!txpwr)
+		return -ENOMEM;
+
+	mt792x_mutex_acquire(dev);
+	ret = mt7928_get_txpwr_info(dev, band_idx, dev->txpwr_tbl, txpwr);
+	mt792x_mutex_release(dev);
+
+	if (ret)
+		goto out;
+
+	/* Display power table name based on the index */
+	switch (dev->txpwr_tbl) {
+	case 0:
+		seq_printf(s, "Power Table: %d (STA_SISO)\n", dev->txpwr_tbl);
+		break;
+	case 1:
+		seq_printf(s, "Power Table: %d (STA_CDD)\n", dev->txpwr_tbl);
+		break;
+	case 2:
+		seq_printf(s, "Power Table: %d (STA_MIMO)\n", dev->txpwr_tbl);
+		break;
+	case 3:
+		seq_printf(s, "Power Table: %d (P2P_SISO)\n", dev->txpwr_tbl);
+		break;
+	case 4:
+		seq_printf(s, "Power Table: %d (P2P_CDD)\n", dev->txpwr_tbl);
+		break;
+	case 5:
+		seq_printf(s, "Power Table: %d (P2P_MIMO)\n", dev->txpwr_tbl);
+		break;
+	default:
+		seq_printf(s, "Power Table: %d (Unknown)\n", dev->txpwr_tbl);
+		break;
+	}
+
+	seq_printf(s, "%-22s  %6s %6s %6s %6s\n",
+		   " ", "1m", "2m", "5m", "11m");
+	mt7925_print_txpwr_entry(CCK, cck, band_idx);
+
+	seq_printf(s, "%-22s  %6s %6s %6s %6s %6s %6s %6s %6s\n",
+		   " ", "6m", "9m", "12m", "18m", "24m", "36m",
+		   "48m", "54m");
+	mt7925_print_txpwr_entry(OFDM20, ofdm20, band_idx);
+	mt7925_print_txpwr_entry(OFDM40, ofdm40, band_idx);
+	mt7925_print_txpwr_entry(OFDM80, ofdm80, band_idx);
+	mt7925_print_txpwr_entry(OFDM160, ofdm160, band_idx);
+
+	seq_printf(s, "%-22s  %6s %6s %6s %6s %6s %6s %6s %6s\n",
+		   " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+		   "mcs6", "mcs7");
+	mt7925_print_txpwr_entry(HT20, ht20, band_idx);
+
+	seq_printf(s, "%-22s  %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+		   " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+		   "mcs6", "mcs7", "mcs32");
+	mt7925_print_txpwr_entry(HT40, ht40, band_idx);
+
+	seq_printf(s, "%-22s  %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+		   " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+		   "mcs6", "mcs7", "mcs8", "mcs9", "mcs10", "mcs11");
+	mt7925_print_txpwr_entry(VHT20, vht20, band_idx);
+	mt7925_print_txpwr_entry(VHT40, vht40, band_idx);
+
+	mt7925_print_txpwr_entry(VHT80, vht80, band_idx);
+	mt7925_print_txpwr_entry(VHT160, vht160, band_idx);
+
+	mt7925_print_txpwr_entry(HE26, he26, band_idx);
+	mt7925_print_txpwr_entry(HE52, he52, band_idx);
+	mt7925_print_txpwr_entry(HE106, he106, band_idx);
+	mt7925_print_txpwr_entry(HE242, he242, band_idx);
+	mt7925_print_txpwr_entry(HE484, he484, band_idx);
+
+	mt7925_print_txpwr_entry(HE996, he996, band_idx);
+	mt7925_print_txpwr_entry(HE996x2, he996x2, band_idx);
+
+	mt7928_eht_txpwr(s, txpwr, band_idx);
+
+out:
+	devm_kfree(dev->mt76.dev, txpwr);
+	return ret;
+}
+
+static int
+mt7928_txpwr_tbl_set(void *data, u64 val)
+{
+	struct mt792x_dev *dev = data;
+
+	dev->txpwr_tbl = (u8)val;
+
+	return 0;
+}
+
+static int
+mt7928_txpwr_tbl_get(void *data, u64 *val)
+{
+	struct mt792x_dev *dev = data;
+
+	*val = dev->txpwr_tbl;
+
+	return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_txpwr_tbl, mt7928_txpwr_tbl_get,
+			 mt7928_txpwr_tbl_set, "%lld\n");
+
 static int
 mt7925_pm_set(void *data, u64 val)
 {
@@ -303,8 +444,14 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
 
 	debugfs_create_devm_seqfile(dev->mt76.dev, "acq", dir,
 				    mt792x_queues_acq);
-	debugfs_create_devm_seqfile(dev->mt76.dev, "txpower_sku", dir,
-				    mt7925_txpwr);
+	if (is_mt7928(&dev->mt76)) {
+		debugfs_create_devm_seqfile(dev->mt76.dev, "txpower_sku", dir,
+					    mt7928_txpwr);
+		debugfs_create_file("txpower_tbl", 0600, dir, dev, &fops_txpwr_tbl);
+	} else {
+		debugfs_create_devm_seqfile(dev->mt76.dev, "txpower_sku", dir,
+					    mt7925_txpwr);
+	}
 	debugfs_create_file("tx_stats", 0400, dir, dev, &mt792x_tx_stats_fops);
 	debugfs_create_file("fw_debug", 0600, dir, dev, &fops_fw_debug);
 	debugfs_create_file("runtime-pm", 0600, dir, dev, &fops_pm);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 37502d8a0e7d..7162eca8e23e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -2211,6 +2211,33 @@ int mt7925_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx, struct mt7925_txp
 	return 0;
 }
 
+int mt7928_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx, u8 power_tbl,
+			  struct mt7928_txpwr *txpwr)
+{
+	struct mt7928_txpwr_event *event;
+	struct mt7925_txpwr_req req = {
+		.tag = cpu_to_le16(TX_POWER_SHOW_INFO),
+		.len = cpu_to_le16(sizeof(req) - 4),
+		.catg = TXPOWER_ALL_RATE_POWER_INFO,
+		.band_idx = band_idx,
+		.power_tbl = power_tbl,
+	};
+	struct sk_buff *skb;
+	int ret;
+
+	ret = mt76_mcu_send_and_get_msg(&dev->mt76, MCU_UNI_CMD(TXPOWER),
+					&req, sizeof(req), true, &skb);
+	if (ret)
+		return ret;
+
+	event = (struct mt7928_txpwr_event *)skb->data;
+	memcpy(txpwr, &event->txpwr, sizeof(*txpwr));
+
+	dev_kfree_skb(skb);
+
+	return 0;
+}
+
 int mt7925_mcu_set_sniffer(struct mt792x_dev *dev, struct ieee80211_vif *vif,
 			   bool enable)
 {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
index 1613c4765186..754db84200d8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
@@ -69,7 +69,7 @@ struct mt7925_txpwr_req {
 	u8 format_id;
 	u8 catg;
 	u8 band_idx;
-	u8 _rsv1;
+	u8 power_tbl;
 } __packed;
 
 struct mt7925_txpwr_event {
@@ -90,6 +90,24 @@ struct mt7925_txpwr_event {
 	u8 rsv1;
 } __packed;
 
+struct mt7928_txpwr_event {
+	u8 rsv[4];
+	__le16 tag;
+	__le16 len;
+
+	u8 catg;
+	u8 band_idx;
+	u8 ch_band;
+	u8 format; /* 0:Legacy, 1:HE */
+
+	/* Rate power info */
+	struct mt7928_txpwr txpwr;
+
+	s8 pwr_max;
+	s8 pwr_min;
+	u8 rsv1;
+} __packed;
+
 enum {
 	TM_SWITCH_MODE,
 	TM_SET_AT_CMD,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
index e3fef41cbb33..b06f17b2fd8c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
@@ -260,6 +260,43 @@ struct mt7925_txpwr {
 	s8 eht996x3_484[16][2];
 };
 
+struct mt7928_txpwr {
+	s8 cck[4][2];
+	s8 ofdm20[8][2];
+	s8 ofdm40[8][2];
+	s8 ofdm80[8][2];
+	s8 ofdm160[8][2];
+	s8 ht20[8][2];
+	s8 ht40[9][2];
+	s8 vht20[12][2];
+	s8 vht40[12][2];
+	s8 vht80[12][2];
+	s8 vht160[12][2];
+	s8 he26[12][2];
+	s8 he52[12][2];
+	s8 he106[12][2];
+	s8 he242[12][2];
+	s8 he484[12][2];
+	s8 he996[12][2];
+	s8 he996x2[12][2];
+	s8 eht26[16][2];
+	s8 eht52[16][2];
+	s8 eht106[16][2];
+	s8 eht242[16][2];
+	s8 eht484[16][2];
+	s8 eht996[16][2];
+	s8 eht996x2[16][2];
+	s8 eht996x4[16][2];
+	s8 eht26_52[16][2];
+	s8 eht26_106[16][2];
+	s8 eht484_242[16][2];
+	s8 eht996_484[16][2];
+	s8 eht996_484_242[16][2];
+	s8 eht996x2_484[16][2];
+	s8 eht996x3[16][2];
+	s8 eht996x3_484[16][2];
+};
+
 static inline u8 mt7927_band_idx(enum nl80211_band band)
 {
 	switch (band) {
@@ -339,6 +376,8 @@ int mt7925_mcu_uni_bss_ps(struct mt792x_dev *dev,
 void mt7925_coredump_work(struct work_struct *work);
 int mt7925_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx,
 			  struct mt7925_txpwr *txpwr);
+int mt7928_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx, u8 power_tbl,
+			  struct mt7928_txpwr *txpwr);
 void mt7925_mac_set_fixed_rate_table(struct mt792x_dev *dev,
 				     u8 tbl_idx, u16 rate_idx);
 void mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 5a1ba1cfc5a3..fbe1793e5598 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -281,6 +281,7 @@ struct mt792x_dev {
 
 	u8 fw_debug;
 	u8 fw_features;
+	u8 txpwr_tbl;
 
 	struct mt76_connac_pm pm;
 	struct mt76_connac_coredump coredump;
-- 
2.18.0


^ permalink raw reply related

* [PATCH] wifi: mt76: connac3: use __le32 for mt7925_regd_rule fields
From: JB Tsai @ 2026-07-24  8:51 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
	litien.chang, Charlie-cy.Wu, jb.tsai

From: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>

The mt7925_regd_rule structure is parsed directly from the firmware
regulatory event, whose fields are little-endian. Declaring start_freq,
end_freq, max_bw, eirp and flags as u32 misrepresents this wire format
and triggers sparse endianness warnings on big-endian aware builds.

Change these fields to __le32 to reflect the firmware byte order.

Fixes: 4ce639ea518f ("wifi: mt76: mt7925: add regulatory wiphy self manager support")
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/regd.h | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h
index 2feacf42dc22..373992558974 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h
@@ -13,11 +13,11 @@ struct mt7925_regd_rule_header {
 };
 
 struct mt7925_regd_rule {
-	u32 start_freq;
-	u32 end_freq;
-	u32 max_bw;
-	u32 eirp;
-	u32 flags;
+	__le32 start_freq;
+	__le32 end_freq;
+	__le32 max_bw;
+	__le32 eirp;
+	__le32 flags;
 	u8 rsv[12];
 };
 
-- 
2.18.0


^ permalink raw reply related

* [PATCH] wifi: mt76: connac3: refactor CLC header type validation logic
From: JB Tsai @ 2026-07-24  8:51 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
	litien.chang, Charlie-cy.Wu, jb.tsai

From: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>

Separate the hardware encapsulation type validation into two distinct
checks based on CLC index type. For BE_CTRL and REGD indices, validate
against the t2 header type, while other indices should validate against
the t0 header type. This ensures proper CLC filtering for different
calibration data structures.

Fixes: 4ce639ea518f ("wifi: mt76: mt7925: add regulatory wiphy self manager support")
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index a12b939b6123..37502d8a0e7d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -899,8 +899,11 @@ static int mt7925_load_clc(struct mt792x_dev *dev, const char *fw_name)
 			continue;
 
 		/* header content sanity */
-		if ((clc->idx == MT792x_CLC_BE_CTRL &&
-		     u8_get_bits(clc->t2.type, MT_EE_HW_TYPE_ENCAP) != hw_encap) ||
+		if ((clc->idx == MT792x_CLC_BE_CTRL || clc->idx == MT792x_CLC_REGD) &&
+		    u8_get_bits(clc->t2.type, MT_EE_HW_TYPE_ENCAP) != hw_encap)
+			continue;
+
+		if (clc->idx != MT792x_CLC_BE_CTRL && clc->idx != MT792x_CLC_REGD &&
 		    u8_get_bits(clc->t0.type, MT_EE_HW_TYPE_ENCAP) != hw_encap)
 			continue;
 
-- 
2.18.0


^ permalink raw reply related

* [PATCH] wifi: mt76: connac3: implement 6GHz power type detection and dynamic CLC filtering
From: JB Tsai @ 2026-07-24  8:51 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
	litien.chang, Charlie-cy.Wu, jb.tsai

From: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>

This patch enhances 6GHz regulatory handling by implementing proper
power type detection and selective CLC (Country Location Configuration)
table filtering.

Changes in main.c:
1. Add mt7925_regd_set_6ghz_power_type() with multi-source channel
   detection fallback mechanism:
   - Primary: link_conf->chanreq.oper.chan (per-link BSS config)
   - Secondary: vif->bss_conf.chanreq.oper.chan (default link)
   - Tertiary: phy->mt76->chandef.chan (PHY layer)
2. Move mt7925_calc_vif_num() and mt7925_regd_set_6ghz_power_type()
   before mt7925_mac_link_sta_add() to avoid implicit declaration
3. Call mt7925_regd_set_6ghz_power_type() in station add/remove paths

Changes in mcu.c:
1. Add power_type_change parameter to __mt7925_mcu_set_clc() and
   related functions to distinguish between regulatory updates and
   power type changes
2. Implement CLC table filtering logic:
   - When power_type_change=false (from mt7925_mcu_regd_update):
     Send 2/5G ('-'), 6G LPI ('0'), and 6G VLP ('2') tables
   - When power_type_change=true (from mt7925_regd_set_6ghz_power_type):
     Send only the specific 6G power table matching current power_type
3. Map power_type to CLC type codes:
   MT_AP_LPI -> '0', MT_AP_SP -> '1', MT_AP_VLP -> '2'
4. Add env_6g field to CLC request structure to pass power type to FW

This fixes the issue where vif->bss_conf.chanreq.oper.chan was NULL
during early connection stages, and enables proper 6GHz power type
switching based on regulatory requirements.

Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/main.c  | 84 +++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/mcu.c   | 46 ++++++++--
 .../wireless/mediatek/mt76/mt7925/mt7925.h    |  4 +-
 .../net/wireless/mediatek/mt76/mt7925/regd.c  | 10 +--
 4 files changed, 133 insertions(+), 11 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index a9059866b701..5f6ad84da70a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -884,6 +884,86 @@ mt7925_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 	return mvif->basic_rates_idx;
 }
 
+static void
+mt7925_calc_vif_num(void *priv, u8 *mac, struct ieee80211_vif *vif)
+{
+	u32 *num = priv;
+
+	if (!priv)
+		return;
+
+	switch (vif->type) {
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_P2P_CLIENT:
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_P2P_GO:
+		*num += 1;
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+mt7925_regd_set_6ghz_power_type(struct ieee80211_vif *vif,
+				struct ieee80211_bss_conf *link_conf,
+				bool is_add)
+{
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+	struct mt792x_phy *phy = mvif->phy;
+	struct mt792x_dev *dev = phy->dev;
+	struct ieee80211_channel *chan = NULL;
+	u32 valid_vif_num = 0;
+	enum mt792x_reg_power_type old_power_type;
+	bool power_type_changed = false;
+
+	old_power_type = phy->power_type;
+
+	/* Try to get channel from link_conf first, then vif->bss_conf, then phy */
+	if (link_conf && link_conf->chanreq.oper.chan)
+		chan = link_conf->chanreq.oper.chan;
+	else if (vif->bss_conf.chanreq.oper.chan)
+		chan = vif->bss_conf.chanreq.oper.chan;
+	else if (phy->mt76->chandef.chan)
+		chan = phy->mt76->chandef.chan;
+
+	ieee80211_iterate_active_interfaces(mt76_hw(dev),
+					    IEEE80211_IFACE_ITER_RESUME_ALL,
+					    mt7925_calc_vif_num, &valid_vif_num);
+
+	if (valid_vif_num > 1) {
+		phy->power_type = MT_AP_DEFAULT;
+		goto out;
+	}
+
+	if (!is_add)
+		vif->bss_conf.power_type = IEEE80211_REG_UNSET_AP;
+
+	switch (vif->bss_conf.power_type) {
+	case IEEE80211_REG_SP_AP:
+		phy->power_type = MT_AP_SP;
+		break;
+	case IEEE80211_REG_VLP_AP:
+		phy->power_type = MT_AP_VLP;
+		break;
+	case IEEE80211_REG_LPI_AP:
+		phy->power_type = MT_AP_LPI;
+		break;
+	case IEEE80211_REG_UNSET_AP:
+		phy->power_type = MT_AP_UNSET;
+		break;
+	default:
+		phy->power_type = MT_AP_DEFAULT;
+		break;
+	}
+
+out:
+	power_type_changed = (old_power_type != phy->power_type);
+
+	if (power_type_changed && chan && chan->band == NL80211_BAND_6GHZ)
+		mt7925_mcu_apply_regd(dev, dev->mt76.alpha2, dev->country_ie_env, true);
+}
+
 static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
 				   struct ieee80211_vif *vif,
 				   struct ieee80211_link_sta *link_sta,
@@ -1006,6 +1086,8 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
 			goto out_pm;
 	}
 
+	mt7925_regd_set_6ghz_power_type(vif, link_conf, true);
+
 	mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
 
 	return 0;
@@ -1279,6 +1361,8 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev,
 	mt76_wcid_cleanup(mdev, wcid);
 	mt76_wcid_mask_clear(mdev->wcid_mask, idx);
 
+	mt7925_regd_set_6ghz_power_type(vif, link_conf, false);
+
 	mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
 }
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 9678ab50dbe6..a12b939b6123 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -3496,7 +3496,7 @@ EXPORT_SYMBOL_GPL(mt7925_mcu_set_channel_domain);
 static int
 __mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
 		     enum environment_cap env_cap,
-		     struct mt7925_clc *clc, u8 idx)
+		     struct mt7925_clc *clc, u8 idx, bool power_type_change)
 {
 	struct mt7925_clc_segment *seg;
 	struct sk_buff *skb;
@@ -3514,19 +3514,38 @@ __mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
 		u8 pad1;
 		u8 alpha2[2];
 		u8 type[2];
-		u8 rsvd[64];
+		u8 env_6g;
+		u8 rsvd[63];
 	} __packed req = {
 		.tag = cpu_to_le16(0x3),
 
 		.idx = idx,
 		.env = env_cap,
+		.env_6g = dev->phy.power_type,
 	};
 	int ret, valid_cnt = 0;
 	u8 *pos, *last_pos;
+	u8 target_6g_type;
 
 	if (!clc)
 		return 0;
 
+	/* Determine target 6G power type based on phy->power_type */
+	switch (dev->phy.power_type) {
+	case MT_AP_LPI:
+		target_6g_type = '0';
+		break;
+	case MT_AP_SP:
+		target_6g_type = '1';
+		break;
+	case MT_AP_VLP:
+		target_6g_type = '2';
+		break;
+	default:
+		target_6g_type = '0'; /* default to LPI */
+		break;
+	}
+
 	req.ver = clc->ver;
 	pos = clc->data + sizeof(*seg) * clc->t0.nr_seg;
 	last_pos = clc->data + le32_to_cpu(*(__le32 *)(clc->data + 4));
@@ -3538,6 +3557,23 @@ __mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
 		    rule->alpha2[1] != alpha2[1])
 			continue;
 
+		/* Filter power tables based on call source:
+		 * power_type_change == false: from mt7925_mcu_regd_update()
+		 *   - Send 2/5G ('-') and 6G LPI ('0') and 6G VLP ('2')
+		 * power_type_change == true: from mt7925_regd_set_6ghz_power_type()
+		 *   - Send only the specific 6G power table based on power_type
+		 */
+		if (power_type_change) {
+			/* Only send the specific 6G power table */
+			if (rule->type[0] != target_6g_type)
+				continue;
+		} else {
+			/* Send 2/5G and 6G LPI and VLP only */
+			if (rule->type[0] != '-' && rule->type[0] != '0' &&
+			    rule->type[0] != '2')
+				continue;
+		}
+
 		seg = (struct mt7925_clc_segment *)clc->data
 			  + rule->seg_idx - 1;
 
@@ -3571,7 +3607,7 @@ __mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
 }
 
 int mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
-		       enum environment_cap env_cap)
+		       enum environment_cap env_cap, bool power_type_change)
 {
 	struct mt792x_phy *phy = (struct mt792x_phy *)&dev->phy;
 	int i, ret;
@@ -3586,13 +3622,13 @@ int mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
 			continue;
 
 		ret = __mt7925_mcu_set_clc(dev, alpha2, env_cap,
-					   phy->clc[i], i);
+					   phy->clc[i], i, power_type_change);
 
 		/* If no country found, set "00" as default */
 		if (ret == -ENOENT)
 			ret = __mt7925_mcu_set_clc(dev, "00",
 						   ENVIRON_INDOOR,
-						   phy->clc[i], i);
+						   phy->clc[i], i, power_type_change);
 		if (ret < 0)
 			return ret;
 	}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
index 321e732347f2..e3fef41cbb33 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
@@ -385,7 +385,9 @@ int mt7925_set_tx_sar_pwr(struct ieee80211_hw *hw,
 
 int mt7925_mcu_regval(struct mt792x_dev *dev, u32 regidx, u32 *val, bool set);
 int mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
-		       enum environment_cap env_cap);
+		       enum environment_cap env_cap, bool power_type_change);
+int mt7925_mcu_apply_regd(struct mt792x_dev *dev, u8 *alpha2,
+			  enum environment_cap env, bool power_type_change);
 int mt7925_mcu_set_mlo_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf,
 			   u16 sel_links, int duration, u8 token_id);
 int mt7925_mcu_set_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
index 3eb3f172dba4..a039ec3ee47b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
@@ -137,14 +137,14 @@ mt7925_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev)
 	}
 }
 
-static int mt7925_mcu_apply_regd(struct mt792x_dev *dev, u8 *alpha2,
-				  enum environment_cap env)
+int mt7925_mcu_apply_regd(struct mt792x_dev *dev, u8 *alpha2,
+			  enum environment_cap env, bool power_type_change)
 {
 	struct ieee80211_hw *hw = mt76_hw(dev);
 	struct wiphy *wiphy = hw->wiphy;
 	int ret;
 
-	ret = mt7925_mcu_set_clc(dev, alpha2, env);
+	ret = mt7925_mcu_set_clc(dev, alpha2, env, power_type_change);
 	if (ret < 0)
 		return ret;
 
@@ -167,7 +167,7 @@ int mt7925_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2,
 
 	mt792x_mutex_acquire(dev);
 	if (dev->regd_change)
-		ret = mt7925_mcu_apply_regd(dev, alpha2, country_ie_env);
+		ret = mt7925_mcu_apply_regd(dev, alpha2, country_ie_env, false);
 	mt792x_mutex_release(dev);
 	dev->regd_change = false;
 	dev->regd_in_progress = false;
@@ -407,7 +407,7 @@ int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2)
 	} else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) {
 		return regulatory_hint(wiphy, alpha2);
 	} else {
-		return mt7925_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR);
+		return mt7925_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR, false);
 	}
 }
 EXPORT_SYMBOL_GPL(mt7925_regd_change);
-- 
2.18.0


^ permalink raw reply related

* [PATCH] wifi: mt76: connac3: add fixedrate and autorate debugfs for rate control
From: JB Tsai @ 2026-07-24  8:50 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
	litien.chang, Charlie-cy.Wu, jb.tsai

From: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>

Add two debugfs interfaces to manually control the per-STA rate
adaptation via the UNI_CMD_RA firmware command:

- fixedrate: lock a STA to a fixed TX rate (SET_FIXED_RATE tag). The
  value is a packed hex field describing the full PHY rate:
    0x[WCID(2)][Mode][BW][MCS][Nss][SGI][Preamble]
	[STBC][LDPC][SPE_EN][HeLtf]
- autorate: re-enable/disable firmware rate adaptation for a STA
  (SET_AUTO_RATE tag):
    0x[WCID(2)][En][Mode]

Key changes:
- Add mt7925_mcu_set_fixed_rate() sending the SET_FIXED_RATE(0x0F) TLV
- Add mt7925_mcu_set_auto_rate() sending the SET_AUTO_RATE(0x11) TLV
- Add RA command TLV structures (mt7925_ra_hdr,
  mt7925_ra_fixed_rate_tlv/_v1, mt7925_ra_auto_rate_tlv) and the
  UNI_CMD_RA tag enum
- Register fixedrate/autorate debugfs files and store the last value
  in mt792x_dev for read back
- Translate the LDPC input to the FW's "on" value (7)

Note: EHT modes require HeLtf=1, otherwise the firmware drops all TX.

Usage:
  echo 0x1D372000101 > /sys/kernel/debug/ieee80211/phyX/mt76/fixedrate
  echo 0x110 > /sys/kernel/debug/ieee80211/phyX/mt76/autorate

Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
 .../wireless/mediatek/mt76/mt7925/debugfs.c   | 97 +++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/mcu.c   | 40 ++++++++
 .../net/wireless/mediatek/mt76/mt7925/mcu.h   | 42 ++++++++
 drivers/net/wireless/mediatek/mt76/mt792x.h   |  3 +
 4 files changed, 182 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index 3db564d63c15..4a043855a049 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -427,6 +427,101 @@ static int mt7925_chip_reset(void *data, u64 val)
 
 DEFINE_DEBUGFS_ATTRIBUTE(fops_reset, NULL, mt7925_chip_reset, "%lld\n");
 
+/* fixedrate input format (12 hex digits):
+ * 0x[WCID(2)][Mode][BW][MCS][Nss][SGI][Preamble][STBC][LDPC][SPE_EN][HeLtf]
+ * The RA tag is fixed to SET_FIXED_RATE internally.
+ * WCID is 2 nibbles, all others are 1 nibble.
+ *
+ * [WCID]    Wireless Client ID
+ * [Mode]    CCK=0, OFDM=1, HT=2, GF=3, VHT=4, PLR=5, HE_SU=8, HE_ER_SU=9,
+ *           HE_TRIG=10, HE_MU=11, EHT_ER=13, EHT_TB=14, EHT_SU and EHT_MU=15
+ * [BW]      BW20=0, BW40=1, BW80=2, BW160=3, BW320=4
+ * [MCS]     CCK=0~3, OFDM=0~7, HT=0~32, VHT=0~9, HE=0~11, EHT=0~13, EHT_ER=14~15
+ * [Nss]     1~8
+ * [GI]      HT/VHT: 0=Long, 1=Short; HE/EHT: 0=0.8us, 1=1.6us, 2=3.2us
+ * [Preamble] Long=0, Short=other
+ * [STBC]    Enable=1, Disable=0
+ * [LDPC]    BCC=0, LDPC=1 (driver sends the FW's "on" value 7)
+ * [SPE_EN]  spatial extension index
+ * [HeLtf]   1X=0, 2X=1, 4X=2 (NOTE: EHT modes require HeLtf=1)
+ */
+static int
+mt7925_fixedrate_set(void *data, u64 val)
+{
+	struct mt792x_dev *dev = data;
+	struct mt7925_ra_fixed_rate_v1 rate = {};
+
+	dev->fixed_rate = val;
+
+	rate.wlan_idx       = cpu_to_le16((val >> 40) & 0xff);
+	rate.phy_mode       = (val >> 36) & 0xf;
+	rate.bw             = (val >> 32) & 0xf;
+	rate.mcs            = (val >> 28) & 0xf;
+	rate.nss            = (val >> 24) & 0xf;
+	rate.short_gi       = cpu_to_le16((val >> 20) & 0xf);
+	rate.short_preamble = (val >> 16) & 0xf;
+	rate.stbc           = (val >> 12) & 0xf;
+	rate.ecc            = ((val >> 8) & 0xf) ? 7 : 0;
+	rate.spe            = (val >> 4) & 0xf;
+	rate.he_ltf         = cpu_to_le16((val >> 0) & 0xf);
+
+	mt792x_mutex_acquire(dev);
+	mt7925_mcu_set_fixed_rate(dev, &rate);
+	mt792x_mutex_release(dev);
+
+	return 0;
+}
+
+static int
+mt7925_fixedrate_get(void *data, u64 *val)
+{
+	struct mt792x_dev *dev = data;
+
+	*val = dev->fixed_rate;
+
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(fops_fixedrate, mt7925_fixedrate_get,
+			 mt7925_fixedrate_set, "0x%llx\n");
+
+/* autorate input format (6 hex digits):
+ * 0x[Tag(2)][WCID(2)][En][Mode]
+ * The RA tag is fixed to SET_AUTO_RATE internally.
+ * WCID is 2 nibbles, En and Mode are 1 nibble each.
+ *
+ * [WCID] Wireless Client ID
+ * [En]   Auto rate: Enable=1, Disable=0
+ * [Mode] don't care (RA picks the rate)
+ */
+static int
+mt7925_autorate_set(void *data, u64 val)
+{
+	struct mt792x_dev *dev = data;
+	u16 wlan_idx = (val >> 8) & 0xff;
+	bool enable = (val >> 4) & 0xf;
+	u8 mode = val & 0xf;
+
+	dev->auto_rate = val;
+
+	mt792x_mutex_acquire(dev);
+	mt7925_mcu_set_auto_rate(dev, wlan_idx, enable, mode);
+	mt792x_mutex_release(dev);
+
+	return 0;
+}
+
+static int
+mt7925_autorate_get(void *data, u64 *val)
+{
+	struct mt792x_dev *dev = data;
+
+	*val = dev->auto_rate;
+
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(fops_autorate, mt7925_autorate_get,
+			 mt7925_autorate_set, "0x%llx\n");
+
 int mt7925_init_debugfs(struct mt792x_dev *dev)
 {
 	struct dentry *dir;
@@ -461,6 +556,8 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
 	debugfs_create_devm_seqfile(dev->mt76.dev, "runtime_pm_stats", dir,
 				    mt792x_pm_stats);
 	debugfs_create_file("deep-sleep", 0600, dir, dev, &fops_ds);
+	debugfs_create_file("fixedrate", 0600, dir, dev, &fops_fixedrate);
+	debugfs_create_file("autorate", 0600, dir, dev, &fops_autorate);
 
 	return 0;
 }
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 7162eca8e23e..b8062eb91ba5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -4113,3 +4113,43 @@ int mt7925_mcu_set_rssimonitor(struct mt792x_dev *dev, struct ieee80211_vif *vif
 	return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(RSSI_MONITOR), &req,
 				 sizeof(req), false);
 }
+
+int mt7925_mcu_set_fixed_rate(struct mt792x_dev *dev,
+			      struct mt7925_ra_fixed_rate_v1 *rate)
+{
+	struct {
+		struct mt7925_ra_hdr hdr;
+		struct mt7925_ra_fixed_rate_tlv tlv;
+		struct mt7925_ra_fixed_rate_v1 v1;
+	} __packed req = {
+		.tlv = {
+			.tag = cpu_to_le16(UNI_CMD_RA_TAG_SET_FIXED_RATE),
+			.len = cpu_to_le16(sizeof(req.tlv) + sizeof(req.v1)),
+			.version = cpu_to_le16(0),
+		},
+		.v1 = *rate,
+	};
+
+	return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(RA), &req,
+				 sizeof(req), true);
+}
+
+int mt7925_mcu_set_auto_rate(struct mt792x_dev *dev, u16 wlan_idx,
+			     bool enable, u8 mode)
+{
+	struct {
+		struct mt7925_ra_hdr hdr;
+		struct mt7925_ra_auto_rate_tlv tlv;
+	} __packed req = {
+		.tlv = {
+			.tag = cpu_to_le16(UNI_CMD_RA_TAG_SET_AUTO_RATE),
+			.len = cpu_to_le16(sizeof(req.tlv)),
+			.wlan_idx = cpu_to_le16(wlan_idx),
+			.auto_rate_en = enable,
+			.mode = mode,
+		},
+	};
+
+	return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(RA), &req,
+				 sizeof(req), true);
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
index 754db84200d8..855613f0b29d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
@@ -61,6 +61,44 @@ struct mt7925_mcu_ant_id_config {
 	u8 ant_id[4];
 } __packed;
 
+enum {
+	UNI_CMD_RA_TAG_SET_FIXED_RATE = 0x0F,
+	UNI_CMD_RA_TAG_SET_AUTO_RATE = 0x11,
+};
+
+struct mt7925_ra_hdr {
+	u8 _rsv[4];
+} __packed;
+
+struct mt7925_ra_fixed_rate_tlv {
+	__le16 tag;
+	__le16 len;
+	__le16 version;
+} __packed;
+
+struct mt7925_ra_fixed_rate_v1 {
+	__le16 wlan_idx;
+	u8 phy_mode;
+	u8 stbc;
+	__le16 short_gi;
+	u8 bw;
+	u8 ecc;
+	u8 mcs;
+	u8 nss;
+	__le16 he_ltf;
+	u8 spe;
+	u8 short_preamble;
+	__le16 _rsv;
+} __packed;
+
+struct mt7925_ra_auto_rate_tlv {
+	__le16 tag;
+	__le16 len;
+	__le16 wlan_idx;
+	u8 auto_rate_en;
+	u8 mode;
+} __packed;
+
 struct mt7925_txpwr_req {
 	u8 _rsv[4];
 	__le16 tag;
@@ -758,4 +796,8 @@ int mt7925_mcu_update_arp_filter(struct mt76_dev *dev,
 int
 mt7925_mcu_uni_bss_bcnft(struct mt792x_dev *dev,
 			 struct ieee80211_bss_conf *link_conf, bool enable);
+int mt7925_mcu_set_fixed_rate(struct mt792x_dev *dev,
+			      struct mt7925_ra_fixed_rate_v1 *rate);
+int mt7925_mcu_set_auto_rate(struct mt792x_dev *dev, u16 wlan_idx,
+			     bool enable, u8 mode);
 #endif
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index fbe1793e5598..eecd0b1a5d0e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -283,6 +283,9 @@ struct mt792x_dev {
 	u8 fw_features;
 	u8 txpwr_tbl;
 
+	u64 fixed_rate;
+	u64 auto_rate;
+
 	struct mt76_connac_pm pm;
 	struct mt76_connac_coredump coredump;
 	const struct mt792x_hif_ops *hif_ops;
-- 
2.18.0


^ permalink raw reply related

* [PATCH] wifi: mt76: mt792x: skip MTCL country list lookup for default BE mode
From: JB Tsai @ 2026-07-24  8:48 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
	litien.chang, Charlie-cy.Wu, jb.tsai

From: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>

In the v3 MTCL country list parsing, a mode_be value of 0x02 indicates
that 11BE enablement should follow the driver default instead of being
gated by the per-country BE bitmap.

Handle this case explicitly: when cl->mode_be == 0x02, skip the BE/EU
country list matching and return the driver default configuration
directly, avoiding an unnecessary lookup.

Fixes: 7cebc2300de2 ("wifi: mt76: mt792x: extend MTCL of APCI to version3 for EHT control")
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c | 11 +++++++----
 drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h |  7 +++++++
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c
index 946dd7956e4a..868feadbff61 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c
@@ -388,7 +388,7 @@ mt792x_acpi_get_mtcl_map_v3(int row, int column, struct mt792x_asar_cl_v3 *cl)
 	u32 config = 0;
 	u8 mode_be = 0;
 
-	mode_be = (cl->mode_be > 0x02) ? 0 : cl->mode_be;
+	mode_be = (cl->mode_be > MT792X_ACPI_MTCL_MODE_DEFAULT) ? 0 : cl->mode_be;
 
 	if (cl->version > 2 && cl->clbe[row] & BIT(column))
 		config |= (mode_be & 0x3) << 4;
@@ -402,10 +402,10 @@ mt792x_acpi_get_mtcl_map(int row, int column, struct mt792x_asar_cl *cl)
 	u32 config = 0;
 	u8 mode_6g, mode_5g9;
 
-	mode_6g = (cl->mode_6g > 0x02) ? 0 : cl->mode_6g;
-	mode_5g9 = (cl->mode_5g9 > 0x01) ? 0 : cl->mode_5g9;
+	mode_6g = (cl->mode_6g > MT792X_ACPI_MTCL_MODE_DEFAULT) ? 0 : cl->mode_6g;
+	mode_5g9 = (cl->mode_5g9 > MT792X_ACPI_MTCL_MODE_ENABLE) ? 0 : cl->mode_5g9;
 
-	if ((cl->cl6g[row] & BIT(column)) || cl->mode_6g == 0x02)
+	if ((cl->cl6g[row] & BIT(column)) || cl->mode_6g == MT792X_ACPI_MTCL_MODE_DEFAULT)
 		config |= (mode_6g & 0x3) << 2;
 	if (cl->version > 1 && cl->cl5g9[row] & BIT(column))
 		config |= (mode_5g9 & 0x3);
@@ -426,6 +426,9 @@ mt792x_acpi_parse_mtcl_tbl_v3(struct mt792x_phy *phy, char *alpha2)
 	if (!cl)
 		return MT792X_ACPI_MTCL_INVALID;
 
+	if (cl->mode_be == MT792X_ACPI_MTCL_MODE_DEFAULT)
+		goto out;
+
 	for (i = 0; i < ARRAY_SIZE(cc_list_be); i++) {
 		col = 7 - i % 8;
 		row = i / 8;
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h
index 474033073831..110f68438d90 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h
@@ -17,6 +17,13 @@
 
 #define MT792X_ACPI_MTCL_INVALID	0xffffffff
 
+/* MTCL per-band control mode (mode_6g/mode_5g9/mode_be) */
+enum {
+	MT792X_ACPI_MTCL_MODE_DISABLE	= 0x00,	/* force disable */
+	MT792X_ACPI_MTCL_MODE_ENABLE	= 0x01,	/* enable if driver & BIOS support */
+	MT792X_ACPI_MTCL_MODE_DEFAULT	= 0x02,	/* supporting depends on driver setting */
+};
+
 struct mt792x_asar_dyn_limit {
 	u8 idx;
 	u8 frp[5];
-- 
2.18.0


^ permalink raw reply related

* [PATCH] wifi: mt76: connac2: add CLC regulatory domain support
From: JB Tsai @ 2026-07-24  8:47 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
	litien.chang, Charlie-cy.Wu, jb.tsai

From: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>

Add MT7921_CLC_REGD index definition and corresponding hardware
encapsulation type check in CLC loading flow to support regulatory
domain configuration via CLC (Country/Local Configuration) data.

Fixes: 058f274fb55f ("wifi: mt76: mt7921: add regulatory wiphy self manager support")
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7921/mcu.c    | 4 ++++
 drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h | 1 +
 2 files changed, 5 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
index 2e0769d18f87..c6af7f00ca59 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
@@ -476,6 +476,10 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name)
 		    u8_get_bits(clc->type, MT_EE_HW_TYPE_ENCAP) != hw_encap)
 			continue;
 
+		if (clc->idx == MT7921_CLC_REGD &&
+		    u8_get_bits(clc->type, MT_EE_HW_TYPE_ENCAP) != hw_encap)
+			continue;
+
 		phy->clc[clc->idx] = devm_kmemdup(mdev->dev, clc,
 						  le32_to_cpu(clc->len),
 						  GFP_KERNEL);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
index fb8654b7c5e7..f5628fb6bfbf 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
@@ -136,6 +136,7 @@ struct mt7921_dma_layout {
 enum {
 	MT7921_CLC_POWER,
 	MT7921_CLC_CHAN,
+	MT7921_CLC_REGD = 3,
 	MT7921_CLC_MAX_NUM,
 };
 
-- 
2.18.0


^ permalink raw reply related

* Re: [PATCH] wifi: mac80211: reject station association if AP is not started
From: Johannes Berg @ 2026-07-24  7:15 UTC (permalink / raw)
  To: Slawomir Stepien
  Cc: syzbot, syzkaller-bugs, linux-wireless, linux-kernel, syzbot
In-Reply-To: <amMLPgO8Yv1wjMGa@nr200>

On Fri, 2026-07-24 at 08:50 +0200, Slawomir Stepien wrote:
> On lip 23, 2026 11:14, Johannes Berg wrote:
> > On Thu, 2026-07-23 at 08:11 +0000, syzbot wrote:
> > > From: Slawomir Stepien <sst@poczta.fm>
> > 
> > I'm a bit confused? Did syzbot just pick up another patch?
> 
> Hi Johannes!
> 
> No, this issue has been found by syzbot while doing fuzzing testing.

Yeah, but the email? It's "From: syzbot <...>" and actually was
delivered to me by sea.source.kernel.org, whereas your other mail was
delivered to me by smtpo49.interia.pl, so I'm confused as to who sent
the mail and how :)

johannes

^ permalink raw reply

* Re: [PATCH] wifi: mac80211: reject station association if AP is not started
From: Slawomir Stepien @ 2026-07-24  6:50 UTC (permalink / raw)
  To: Johannes Berg
  Cc: syzbot, syzkaller-bugs, linux-wireless, linux-kernel, syzbot
In-Reply-To: <c9fa1c083ef9878240c2ee2fb223413153d93a68.camel@sipsolutions.net>

On lip 23, 2026 11:14, Johannes Berg wrote:
> On Thu, 2026-07-23 at 08:11 +0000, syzbot wrote:
> > From: Slawomir Stepien <sst@poczta.fm>
> 
> I'm a bit confused? Did syzbot just pick up another patch?

Hi Johannes!

No, this issue has been found by syzbot while doing fuzzing testing.

> > dereferences the NULL chanctx_conf, triggering a WARN_ON:
> 
> That can't be right?

Yeah, right. I will rephrase it in v2.

> > Fix this by rejecting the addition or modification of a station to the
> > associated state if the AP has not been started (chanctx_conf is NULL).
> 
> That's a proxy for "AP is started", but not a good one, I think we
> actually have a flag or so somewhere? Maybe even reject it in cfg80211?

I haven't seen a good place for rejection, but let me double check.

> > Exempt Multi-Link Operation (MLO) interfaces from this check, as they
> > handle chanctx_conf per-link rather than globally on the VIF.
> 
> Feels like that's only necessary because the proxy is bad.
> 
> Also maybe a similar issue arises if an MLO AP is only half-started, and
> stations are added to all links, or so?

Not sure about MLO, but your suggestion sounds logical and such case might be the same as this one.
I will take a look at this too.

-- 
Slawomir Stepien

^ permalink raw reply

* Re: [PATCH ath-next 0/2] wifi: ath: Fix buffer overreads in CSA switch count event processing
From: Rameshkumar Sundaram @ 2026-07-24  6:40 UTC (permalink / raw)
  To: Jeff Johnson, Jeff Johnson; +Cc: linux-wireless, ath12k, linux-kernel, ath11k
In-Reply-To: <20260723-ath12k_wmi_process_csa_switch_count_event-cleanup-v1-0-c01ef10bc6a3@oss.qualcomm.com>

On 7/24/2026 7:01 AM, Jeff Johnson wrote:
> Both ath11k and ath12k fail to properly sanitize the CSA switch count
> event.
> 
> ---
> Jeff Johnson (2):
>        wifi: ath12k: fix overreads in ath12k_wmi_process_csa_switch_count_event()
>        wifi: ath11k: fix overreads in ath11k_wmi_process_csa_switch_count_event()
> 
>   drivers/net/wireless/ath/ath11k/wmi.c | 21 +++++++++++++++++++--
>   drivers/net/wireless/ath/ath12k/wmi.c | 16 ++++++++++++++++
>   2 files changed, 35 insertions(+), 2 deletions(-)
> ---
> base-commit: 0ceb105245b5e57f803bdc0e79dc077fc06ff384
> change-id: 20260716-ath12k_wmi_process_csa_switch_count_event-cleanup-7fb191b7d7d3
> 
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH wireless-next] wifi: rt2x00: Use flexible array for hw_mode_spec channels
From: Stanislaw Gruszka @ 2026-07-24  6:29 UTC (permalink / raw)
  To: Rosen Penev
  Cc: linux-wireless, Kees Cook, Gustavo A. R. Silva, open list,
	open list:KERNEL HARDENING (not covered by other areas):Keyword:b__counted_by(_le|_be|_ptr)?b
In-Reply-To: <CAKxU2N_LbndYqrGDQ__sAjj87mj-J8Hs-RWeD90eRcGK1dDAcw@mail.gmail.com>

On Thu, Jul 23, 2026 at 12:26:41PM -0700, Rosen Penev wrote:
> On Thu, Jul 23, 2026 at 12:53 AM Stanislaw Gruszka <stf_xl@wp.pl> wrote:
> >
> > Hi,
> >
> > On Sun, Jun 28, 2026 at 08:31:56PM -0700, Rosen Penev wrote:
> > > struct hw_mode_spec stores channel information that is allocated by the
> > > individual rt2x00 drivers during hardware probing.  The channel info
> > > array has the same lifetime as the hardware mode specification, but it
> > > is currently allocated separately and then freed through a separate
> > > pointer.
> > >
> > > Make struct rt2x00_dev hold a pointer to the hardware mode
> > > specification, store the channel info as a flexible array member, and
> > > allocate both pieces together with kzalloc_flex().
> > >
> > > Assisted-by: Codex:GPT-5.5
> > > Signed-off-by: Rosen Penev <rosenp@gmail.com>
> > > ---
> > >  .../net/wireless/ralink/rt2x00/rt2400pci.c    | 24 +++---
> > >  .../net/wireless/ralink/rt2x00/rt2500pci.c    | 55 ++++++++------
> > >  .../net/wireless/ralink/rt2x00/rt2500usb.c    | 55 ++++++++------
> > >  .../net/wireless/ralink/rt2x00/rt2800lib.c    | 74 ++++++++++---------
> > >  drivers/net/wireless/ralink/rt2x00/rt2x00.h   |  4 +-
> > >  .../net/wireless/ralink/rt2x00/rt2x00config.c | 12 +--
> > >  .../net/wireless/ralink/rt2x00/rt2x00dev.c    |  4 +-
> > >  drivers/net/wireless/ralink/rt2x00/rt61pci.c  | 42 ++++++-----
> > >  drivers/net/wireless/ralink/rt2x00/rt73usb.c  | 50 +++++++------
> > >  9 files changed, 177 insertions(+), 143 deletions(-)
> >
> > This result in extra code. I do not see benefit of avoiding
> > kzalloc calls at cost of adding more lines of code.
> On runtime there's extra UBSAN analysis. Reducing allocations is always good.

No, not always. 

Sanitizes are for catching bugs, not for performance. 
Adding complexity to the driver to run faster with them
is not good idea.

Also, this is on initialization path, which done usually
once per system boot, so visible gains will be something
between zero an non-existing.

Regards
Stanislaw

^ permalink raw reply

* RE: [PATCH] wifi: rtl818x: initialize eeprom_93cx6 struct to zero
From: Ping-Ke Shih @ 2026-07-24  5:34 UTC (permalink / raw)
  To: stf_xl@wp.pl, linux-wireless@vger.kernel.org
In-Reply-To: <20260723110640.8588-1-stf_xl@wp.pl>

stf_xl@wp.pl <stf_xl@wp.pl> wrote:
> From: Stanislaw Gruszka <stf_xl@wp.pl>
> 
> Commit 7738a7ab9d12 ("misc: eeprom: eeprom_93cx6: Add quirk for extra
> read clock cycle") added extra 'quirk' field to struct eeprom_93cx6.
> 
> Many existing users of eeprom_93cx6, including rtl818x drivers, allocate
> the structure on the stack without initializing all fields. As a result,
> the added quirk field has an undefined value and can randomly cause
> reading wrong data from the EEPROM.
> 
> Fix by initializing the structures with {}.
> 
> Fixes: 7738a7ab9d12 ("misc: eeprom: eeprom_93cx6: Add quirk for extra read clock cycle")
> Cc: stable@kernel.org # v6.13+
> Signed-off-by: Stanislaw Gruszka <stf_xl@wp.pl>

Reviewed-by: Ping-Ke Shih <pkshih@realtek.com>



^ permalink raw reply

* [PATCH v4 4/4] wifi: ath12k: implement custom wake_tx_queue with flow control
From: Jose Ignacio Tornos Martinez @ 2026-07-24  5:31 UTC (permalink / raw)
  To: jjohnson
  Cc: ath11k, ath12k, linux-wireless, linux-kernel,
	Jose Ignacio Tornos Martinez
In-Reply-To: <20260724053121.15759-1-jtornosm@redhat.com>

Under heavy traffic, ath12k can hang and experiences -ENOMEM errors
("failed to transmit frame -12") when the hardware TCL ring fills up.
This issue is more commonly observed in VMs with PCIe passthrough but
also occurs on bare metal systems.

Implement a custom wake_tx_queue operation that:

1. Checks hardware ring space before dequeuing packets from mac80211
2. Uses per-ring locking (wake_tx_lock with spin_lock_bh) to serialize
   concurrent wake_tx_queue calls targeting the same ring and to ensure
   bottom halves are disabled as required by ieee80211_tx_dequeue()
3. Syncs with hardware state to get accurate free slot count
4. Resolves the target TCL ring once using get_ring_selector(txq->ac),
   which selects the ring based on the access category
5. Returns early during firmware crash in the same way as other
   tx paths

This approach follows the pattern used in the iwlwifi driver, adapted
for ath12k's hardware ring architecture.

This prevents hangs, eliminates -ENOMEM errors, and improves throughput
by optimizing resource usage and preventing unnecessary packet drops.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v4: Adapt to use get_ring_selector(txq->ac) instead of peek API
    due to concerns raised by Johannes Berg.
v3: https://lore.kernel.org/all/20260720070852.206495-4-jtornosm@redhat.com/

 drivers/net/wireless/ath/ath12k/dp.c       |  1 +
 drivers/net/wireless/ath/ath12k/dp.h       |  2 +
 drivers/net/wireless/ath/ath12k/hal.c      |  1 +
 drivers/net/wireless/ath/ath12k/wifi7/hw.c | 76 +++++++++++++++++++++-
 4 files changed, 79 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c
index af5f11fc1d84..3d46cfbf0a1c 100644
--- a/drivers/net/wireless/ath/ath12k/dp.c
+++ b/drivers/net/wireless/ath/ath12k/dp.c
@@ -1539,6 +1539,7 @@ static int ath12k_dp_setup(struct ath12k_base *ab)
 	}
 
 	for (i = 0; i < ab->hw_params->max_tx_ring; i++) {
+		spin_lock_init(&dp->tx_ring[i].wake_tx_lock);
 		dp->tx_ring[i].tcl_data_ring_id = i;
 
 		dp->tx_ring[i].tx_status_head = 0;
diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h
index f8cfc7bb29dd..68d2020be9b8 100644
--- a/drivers/net/wireless/ath/ath12k/dp.h
+++ b/drivers/net/wireless/ath/ath12k/dp.h
@@ -58,6 +58,8 @@ struct dp_tx_ring {
 	u8 tcl_data_ring_id;
 	struct dp_srng tcl_data_ring;
 	struct dp_srng tcl_comp_ring;
+	/* Serializes wake_tx_queue operations for this ring */
+	spinlock_t wake_tx_lock;
 	struct hal_wbm_completion_ring_tx *tx_status;
 	int tx_status_head;
 	int tx_status_tail;
diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c
index a164563fff28..071cb5d30931 100644
--- a/drivers/net/wireless/ath/ath12k/hal.c
+++ b/drivers/net/wireless/ath/ath12k/hal.c
@@ -390,6 +390,7 @@ int ath12k_hal_srng_src_num_free(struct ath12k_base *ab, struct hal_srng *srng,
 	else
 		return ((srng->ring_size - hp + tp) / srng->entry_size) - 1;
 }
+EXPORT_SYMBOL_GPL(ath12k_hal_srng_src_num_free);
 
 void *ath12k_hal_srng_src_next_peek(struct ath12k_base *ab,
 				    struct hal_srng *srng)
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index 7436cf70925a..af0a3ba805dd 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -1100,9 +1100,83 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
 	}
 }
 
+static void ath12k_wifi7_mac_op_wake_tx_queue(struct ieee80211_hw *hw,
+					      struct ieee80211_txq *txq)
+{
+	struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(txq->vif);
+	struct ath12k_hw *ah = ath12k_hw_to_ah(hw);
+	struct ieee80211_tx_control control = {
+		.sta = txq->sta,
+	};
+	struct ieee80211_vif *vif = txq->vif;
+	struct ath12k_link_vif *arvif;
+	struct dp_tx_ring *tx_ring;
+	struct hal_srng *tcl_ring;
+	struct ath12k_sta *ahsta;
+	struct ath12k_dp *dp;
+	struct sk_buff *skb;
+	struct ath12k *ar;
+	u8 ring_selector;
+	int num_free;
+	u8 ring_id;
+	u8 link_id;
+
+	if (ieee80211_vif_is_mld(vif) && txq->sta) {
+		ahsta = ath12k_sta_to_ahsta(txq->sta);
+		link_id = ahsta->assoc_link_id;
+	} else {
+		link_id = ahvif->deflink.link_id;
+	}
+
+	rcu_read_lock();
+
+	arvif = rcu_dereference(ahvif->link[link_id]);
+	if (!arvif || !arvif->ar) {
+		rcu_read_unlock();
+		return;
+	}
+
+	ar = arvif->ar;
+	dp = ar->ab->dp;
+
+	ring_selector = dp->hw_params->hw_ops->get_ring_selector(txq->ac);
+	ring_id = ring_selector % dp->hw_params->max_tx_ring;
+	tx_ring = &dp->tx_ring[ring_id];
+	tcl_ring = &dp->hal->srng_list[tx_ring->tcl_data_ring.ring_id];
+
+	while (1) {
+		if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH,
+				      &ar->ab->dev_flags)))
+			break;
+
+		spin_lock_bh(&tx_ring->wake_tx_lock);
+
+		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);
+	}
+
+	rcu_read_unlock();
+}
+
 static const struct ieee80211_ops ath12k_ops_wifi7 = {
 	.tx				= ath12k_wifi7_mac_op_tx,
-	.wake_tx_queue			= ieee80211_handle_wake_tx_queue,
+	.wake_tx_queue			= ath12k_wifi7_mac_op_wake_tx_queue,
 	.start                          = ath12k_mac_op_start,
 	.stop                           = ath12k_mac_op_stop,
 	.reconfig_complete              = ath12k_mac_op_reconfig_complete,
-- 
2.54.0


^ permalink raw reply related

* [PATCH v4 3/4] wifi: ath11k: implement custom wake_tx_queue with flow control
From: Jose Ignacio Tornos Martinez @ 2026-07-24  5:31 UTC (permalink / raw)
  To: jjohnson
  Cc: ath11k, ath12k, linux-wireless, linux-kernel,
	Jose Ignacio Tornos Martinez
In-Reply-To: <20260724053121.15759-1-jtornosm@redhat.com>

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-ring locking (wake_tx_lock with spin_lock_bh) to serialize
   concurrent wake_tx_queue calls targeting the same ring and to ensure
   bottom halves are disabled as required by ieee80211_tx_dequeue()
3. Syncs with hardware state to get accurate free slot count
4. Resolves the target TCL ring using get_ring_selector(txq->ac),
   which selects the ring based on the access category
5. Returns early during firmware crash in the same way as other
   tx paths

This approach follows the pattern used in the iwlwifi driver, adapted
for 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>
---
v4: Adapt to use get_ring_selector(txq->ac) instead of peek API
    due to concerns raised by Johannes Berg.
v3: https://lore.kernel.org/all/20260720070852.206495-3-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 | 52 ++++++++++++++++++++++++++-
 3 files changed, 54 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..9813f7923c34 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -10065,9 +10065,59 @@ 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;
+	u32 ring_selector;
+	int num_free;
+	u8 ring_id;
+
+	if (!ar)
+		return;
+
+	ring_selector = ar->ab->hw_params.hw_ops->get_ring_selector(txq->ac);
+	ring_id = ring_selector % 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) {
+		if (unlikely(test_bit(ATH11K_FLAG_CRASH_FLUSH,
+				      &ar->ab->dev_flags)))
+			break;
+
+		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

* [PATCH v4 2/4] wifi: ath11k/ath12k: remove skb parameter from get_ring_selector
From: Jose Ignacio Tornos Martinez @ 2026-07-24  5:31 UTC (permalink / raw)
  To: jjohnson
  Cc: ath11k, ath12k, linux-wireless, linux-kernel,
	Jose Ignacio Tornos Martinez
In-Reply-To: <20260724053121.15759-1-jtornosm@redhat.com>

Change the get_ring_selector hw_ops callback signature from
get_ring_selector(struct sk_buff *skb) to get_ring_selector(u8 ac),
removing the dependency on the skb.

After the previous patch switched WCN6750 from skb_get_hash() to
skb_get_queue_mapping(), all get_ring_selector implementations fall
into two categories:
 - smp_processor_id(): ignores the skb entirely
 - skb_get_queue_mapping(): only reads the AC value from the skb

Since the AC is the only information extracted from the skb, pass it
directly as a u8 parameter instead. Callers in the dp_tx path now
pass skb_get_queue_mapping(skb), which preserves the exact same
behavior.

This change enables a subsequent patch to call get_ring_selector()
from wake_tx_queue using txq->ac, without needing access to the skb.
mac80211 guarantees that skb_get_queue_mapping(skb) == txq->ac
for any skb dequeued from a given txq, since both values are
derived from the same skb->priority through the same AC mapping.

No functional change.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v4: new patch

 drivers/net/wireless/ath/ath11k/dp_tx.c       |  2 +-
 drivers/net/wireless/ath/ath11k/hw.c          | 16 ++++------------
 drivers/net/wireless/ath/ath11k/hw.h          |  2 +-
 drivers/net/wireless/ath/ath12k/hw.h          |  2 +-
 drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c |  2 +-
 drivers/net/wireless/ath/ath12k/wifi7/hw.c    |  6 +++---
 6 files changed, 11 insertions(+), 19 deletions(-)

diff --git a/drivers/net/wireless/ath/ath11k/dp_tx.c b/drivers/net/wireless/ath/ath11k/dp_tx.c
index cac970c92806..083aca8ff355 100644
--- a/drivers/net/wireless/ath/ath11k/dp_tx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_tx.c
@@ -108,7 +108,7 @@ int ath11k_dp_tx(struct ath11k *ar, struct ath11k_vif *arvif,
 
 	pool_id = skb_get_queue_mapping(skb) & (ATH11K_HW_MAX_QUEUES - 1);
 
-	ring_selector = ab->hw_params.hw_ops->get_ring_selector(skb);
+	ring_selector = ab->hw_params.hw_ops->get_ring_selector(skb_get_queue_mapping(skb));
 
 tcl_ring_sel:
 	tcl_ring_retry = false;
diff --git a/drivers/net/wireless/ath/ath11k/hw.c b/drivers/net/wireless/ath/ath11k/hw.c
index d679e39dce03..8a28c1d4c17b 100644
--- a/drivers/net/wireless/ath/ath11k/hw.c
+++ b/drivers/net/wireless/ath/ath11k/hw.c
@@ -876,22 +876,14 @@ static bool ath11k_hw_wcn6855_rx_desc_get_ldpc_support(struct hal_rx_desc *desc)
 			 __le32_to_cpu(desc->u.wcn6855.msdu_start.info2));
 }
 
-static u32 ath11k_hw_ipq8074_get_tcl_ring_selector(struct sk_buff *skb)
-{
-	/* Let the default ring selection be based on current processor
-	 * number, where one of the 3 tcl rings are selected based on
-	 * the smp_processor_id(). In case that ring
-	 * is full/busy, we resort to other available rings.
-	 * If all rings are full, we drop the packet.
-	 *
-	 * TODO: Add throttling logic when all rings are full
-	 */
+static u32 ath11k_hw_ipq8074_get_tcl_ring_selector(u8 ac)
+{
 	return smp_processor_id();
 }
 
-static u32 ath11k_hw_wcn6750_get_tcl_ring_selector(struct sk_buff *skb)
+static u32 ath11k_hw_wcn6750_get_tcl_ring_selector(u8 ac)
 {
-	return skb_get_queue_mapping(skb);
+	return ac;
 }
 
 const struct ath11k_hw_ops ipq8074_ops = {
diff --git a/drivers/net/wireless/ath/ath11k/hw.h b/drivers/net/wireless/ath/ath11k/hw.h
index 4996536fbd14..b6bc8b72d812 100644
--- a/drivers/net/wireless/ath/ath11k/hw.h
+++ b/drivers/net/wireless/ath/ath11k/hw.h
@@ -273,7 +273,7 @@ struct ath11k_hw_ops {
 	u16 (*mpdu_info_get_peerid)(struct hal_rx_mpdu_info *mpdu_info);
 	bool (*rx_desc_mac_addr2_valid)(struct hal_rx_desc *desc);
 	u8* (*rx_desc_mpdu_start_addr2)(struct hal_rx_desc *desc);
-	u32 (*get_ring_selector)(struct sk_buff *skb);
+	u32 (*get_ring_selector)(u8 ac);
 };
 
 extern const struct ath11k_hw_ops ipq8074_ops;
diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h
index 86fb8b719613..65caa7a2349f 100644
--- a/drivers/net/wireless/ath/ath12k/hw.h
+++ b/drivers/net/wireless/ath/ath12k/hw.h
@@ -243,7 +243,7 @@ struct ath12k_hw_ops {
 	int (*mac_id_to_pdev_id)(const struct ath12k_hw_params *hw, int mac_id);
 	int (*mac_id_to_srng_id)(const struct ath12k_hw_params *hw, int mac_id);
 	int (*rxdma_ring_sel_config)(struct ath12k_base *ab);
-	u8 (*get_ring_selector)(struct sk_buff *skb);
+	u8 (*get_ring_selector)(u8 ac);
 	bool (*dp_srng_is_tx_comp_ring)(int ring_num);
 	bool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif,
 				       struct ieee80211_mgmt *mgmt);
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c
index d2749de44553..74359e8eea47 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c
@@ -111,7 +111,7 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a
 	 * If all rings are full, we drop the packet.
 	 * TODO: Add throttling logic when all rings are full
 	 */
-	ring_selector = dp->hw_params->hw_ops->get_ring_selector(skb);
+	ring_selector = dp->hw_params->hw_ops->get_ring_selector(skb_get_queue_mapping(skb));
 
 tcl_ring_sel:
 	tcl_ring_retry = false;
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index d9fdd2fc8298..7436cf70925a 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -49,7 +49,7 @@ ath12k_wifi7_hw_mac_id_to_srng_id_qcn9274(const struct ath12k_hw_params *hw,
 	return 0;
 }
 
-static u8 ath12k_wifi7_hw_get_ring_selector_qcn9274(struct sk_buff *skb)
+static u8 ath12k_wifi7_hw_get_ring_selector_qcn9274(u8 ac)
 {
 	return smp_processor_id();
 }
@@ -83,9 +83,9 @@ ath12k_wifi7_hw_mac_id_to_srng_id_wcn7850(const struct ath12k_hw_params *hw,
 	return mac_id;
 }
 
-static u8 ath12k_wifi7_hw_get_ring_selector_wcn7850(struct sk_buff *skb)
+static u8 ath12k_wifi7_hw_get_ring_selector_wcn7850(u8 ac)
 {
-	return skb_get_queue_mapping(skb);
+	return ac;
 }
 
 static bool ath12k_wifi7_dp_srng_is_comp_ring_wcn7850(int ring_num)
-- 
2.54.0


^ permalink raw reply related

* [PATCH v4 1/4] wifi: ath11k: use queue mapping for WCN6750 ring selection
From: Jose Ignacio Tornos Martinez @ 2026-07-24  5:31 UTC (permalink / raw)
  To: jjohnson
  Cc: ath11k, ath12k, linux-wireless, linux-kernel,
	Jose Ignacio Tornos Martinez
In-Reply-To: <20260724053121.15759-1-jtornosm@redhat.com>

WCN6750 selects the TCL ring using skb_get_hash() to distribute
flows across its 3 TX rings, as introduced in commit 7636c9a6e7d7
("wifi: ath11k: Add multi TX ring support for WCN6750"). The goal
was to prevent out-of-order packet delivery that could occur with
smp_processor_id()-based selection, where packets of the same flow
could end up on different rings depending on CPU scheduling.

Switch to skb_get_queue_mapping() instead, which returns the AC
(access category) assigned by mac80211 in ieee80211_select_queue().
This provides the same ordering guarantee: packets of the same TID
always map to the same AC and therefore always land on the same
ring, preventing reordering.

Using queue mapping for ring selection also provides QoS-aware
distribution, where each traffic class gets a deterministic ring
assignment, rather than the random distribution that flow hashing
produces. With 3 rings and 4 ACs (VO=0, VI=1, BE=2, BK=3), the
mapping becomes: VO and BK share ring 0, VI uses ring 1, and BE
uses ring 2.

This matches the approach already used by ath12k for WCN7850, which
has the same ring count and uses skb_get_queue_mapping() for its
ring selector.

This change also removes the dependency on skb_get_hash(), which
is relevant for a subsequent patch that removes the skb parameter
from the get_ring_selector hw_ops callback entirely, enabling
TX flow control in wake_tx_queue without requiring a complex peek
to inspect frames before dequeue.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v4: new patch

 drivers/net/wireless/ath/ath11k/hw.c | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/drivers/net/wireless/ath/ath11k/hw.c b/drivers/net/wireless/ath/ath11k/hw.c
index 93f9a03c48dc..d679e39dce03 100644
--- a/drivers/net/wireless/ath/ath11k/hw.c
+++ b/drivers/net/wireless/ath/ath11k/hw.c
@@ -891,13 +891,7 @@ static u32 ath11k_hw_ipq8074_get_tcl_ring_selector(struct sk_buff *skb)
 
 static u32 ath11k_hw_wcn6750_get_tcl_ring_selector(struct sk_buff *skb)
 {
-	/* Select the TCL ring based on the flow hash of the SKB instead
-	 * of CPU ID. Since applications pumping the traffic can be scheduled
-	 * on multiple CPUs, there is a chance that packets of the same flow
-	 * could end on different TCL rings, this could sometimes results in
-	 * an out of order arrival of the packets at the receiver.
-	 */
-	return skb_get_hash(skb);
+	return skb_get_queue_mapping(skb);
 }
 
 const struct ath11k_hw_ops ipq8074_ops = {
-- 
2.54.0


^ permalink raw reply related

* [PATCH v4 0/4] ath11k/ath12k: implement TX flow control
From: Jose Ignacio Tornos Martinez @ 2026-07-24  5:31 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,
adapted for ath11k/ath12k hardware ring architecture, checking
hardware ring space before dequeuing packets from mac80211.

Testing shows stable operation with eliminated -ENOMEM errors, no hangs,
and improved throughput under heavy traffic conditions.

Jose Ignacio Tornos Martinez (4):
  wifi: ath11k: use queue mapping for WCN6750 ring selection
  wifi: ath11k/ath12k: remove skb parameter from get_ring_selector
  wifi: ath11k: implement custom wake_tx_queue with flow control
  wifi: ath12k: implement custom wake_tx_queue with flow control

v4: Address feedback from Johannes Berg:
  - Drop the ieee80211_tx_peek() mac80211 patch entirely due to the
    commented concerns.
  - Redesign as a consequence of dropping the peek API:
    - Patch 1: switch WCN6750 from skb_get_hash() to skb_get_queue_mapping()
      as a preparatory step, since the AC value is all that is needed.
      This aligns with the approach already used by ath12k WCN7850
      (which has the same ring count).
    - Patch 2: Instead of peeking the skb to call get_ring_selector(skb),
      pass txq->ac directly via get_ring_selector(u8 ac). This is safe
      because mac80211 guarantees skb_get_queue_mapping(skb) == txq->ac for
      any skb dequeued from a given txq.
    - Patch 3 and 4: adapt tx flow control to the new approach.
v3: https://lore.kernel.org/all/20260720070852.206495-1-jtornosm@redhat.com/
--
2.49.0


^ permalink raw reply

* Re: [PATCH ath-next 0/2] wifi: ath: Fix buffer overreads in CSA switch count event processing
From: Baochen Qiang @ 2026-07-24  5:28 UTC (permalink / raw)
  To: Jeff Johnson, Jeff Johnson; +Cc: linux-wireless, ath12k, linux-kernel, ath11k
In-Reply-To: <20260723-ath12k_wmi_process_csa_switch_count_event-cleanup-v1-0-c01ef10bc6a3@oss.qualcomm.com>



On 7/24/2026 9:31 AM, Jeff Johnson wrote:
> Both ath11k and ath12k fail to properly sanitize the CSA switch count
> event.
> 
> ---
> Jeff Johnson (2):
>       wifi: ath12k: fix overreads in ath12k_wmi_process_csa_switch_count_event()
>       wifi: ath11k: fix overreads in ath11k_wmi_process_csa_switch_count_event()
> 
>  drivers/net/wireless/ath/ath11k/wmi.c | 21 +++++++++++++++++++--
>  drivers/net/wireless/ath/ath12k/wmi.c | 16 ++++++++++++++++
>  2 files changed, 35 insertions(+), 2 deletions(-)
> ---
> base-commit: 0ceb105245b5e57f803bdc0e79dc077fc06ff384
> change-id: 20260716-ath12k_wmi_process_csa_switch_count_event-cleanup-7fb191b7d7d3
> 
> 

Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH v8 5/9] block: implement NVMEM provider
From: Christoph Hellwig @ 2026-07-24  4:57 UTC (permalink / raw)
  To: Loic Poulain
  Cc: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Srinivas Kandagatla, Andrew Lunn, Heiner Kallweit,
	Russell King, Saravana Kannan, Christian Marangi, linux-mmc,
	devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Bartosz Golaszewski
In-Reply-To: <20260703-block-as-nvmem-v8-5-98ae32bfc49a@oss.qualcomm.com>

On Fri, Jul 03, 2026 at 03:45:18PM +0200, Loic Poulain wrote:
> +	  Allow block devices (or partitions) to act as NVMEM providers,

Based on the code below I don't think you actually support partitions.

> +static int blk_nvmem_reg_read(void *priv, unsigned int from, void *val, size_t bytes)

Overly long line.

> +{
> +	dev_t devt = (dev_t)(uintptr_t)priv;
> +	size_t bytes_left = bytes;
> +	loff_t pos = from;
> +	int ret = 0;
> +
> +	struct file *bdev_file __free(fput) =
> +		bdev_file_open_by_dev(devt, BLK_OPEN_READ, NULL, NULL);

Please try to avoid all that magic autfree stuff that makes the code
impossible to read about.

> +	if (IS_ERR(bdev_file))
> +		return PTR_ERR(bdev_file);
> +
> +	while (bytes_left) {

.. and just use __kernel_read instead of badly reimplementing it.

> +	config.name = dev_name(dev);
> +	config.owner = THIS_MODULE;
> +	config.priv = (void *)(uintptr_t)dev->devt;

Why is this storing the dev_t and not the block device itself?

> +
> +		blk_nvmem_add(disk->part0);

Hmm, and you're always doing it for the whole device.  So the intefaces
really should be based on a gendisk, not a block_device.

Also once you register the whole devices as nvmem provider, we really
should keep it from being used a block device, shouldn't we?

^ permalink raw reply

* Re: [PATCH ath-current v2 0/8] wifi: ath12k: support firmware-allocated MLD peer ID
From: Rameshkumar Sundaram @ 2026-07-24  4:55 UTC (permalink / raw)
  To: Baochen Qiang, Jeff Johnson; +Cc: linux-wireless, ath12k
In-Reply-To: <20260720-ath12k-fw-allocated-ml-peer-id-v2-0-630632758a80@oss.qualcomm.com>

On 7/20/2026 12:13 PM, Baochen Qiang wrote:
> ath12k currently assumes the host allocates the MLD peer ID and passes
> it down to firmware via WMI_PEER_ASSOC_CMDID. This works on QCN9274
> but breaks WCN7850/QCC2072, whose firmware always picks the ID itself
> and reports it back through HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP. As a
> result dp_hw->dp_peers[] is never populated for MLO peers and the data
> path lookup fails. On QCC2072 the firmware additionally crashes on MLO
> disconnect when ATH12K_WMI_FLAG_MLO_PEER_ID_VALID was set in the peer
> assoc command.
> 
> Add a host_alloc_ml_id hw_param to branch behavior, defer the
> dp_peers[] publish to the HTT event for firmware-allocated chips, and
> propagate the firmware-assigned ID through the existing host
> bookkeeping when it arrives.
> 
> Patch summary:
> 
> 1: fix for an out-of-bounds clear_bit() in
>       ath12k_mac_dp_peer_cleanup().
>    2: group peer assoc send-and-wait into a helper
>    3: refactor, keep ATH12K_PEER_ML_ID_VALID set in ahsta->ml_peer_id
>       so later patches do not have to OR or mask it at every call site;
>    4: parse the HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP message;
>    5: introduce hw_param host_alloc_ml_id, set true on QCN9274 family
>       and false on WCN7850/QCC2072;
>    6: on host_alloc_ml_id == false, leave peer_id_valid unset and send
>       ml_peer_id == 0 in WMI_PEER_ASSOC_CMDID;
>    7: on host_alloc_ml_id == false, mark ahsta->ml_peer_id and
>       dp_peer->peer_id as ATH12K_MLO_PEER_ID_PENDING and skip the
>       dp_hw->dp_peers[] publish until the firmware reports the ID;
>    8: in the MLO_RX_PEER_MAP handler, propagate the firmware-assigned
>       ID into dp_peer->peer_id, every dp_link_peer in
>       dp_peer->link_peers[], and ahsta->ml_peer_id, all under
>       dp_hw->peer_lock.
> 
> ---
> Changes in v2:
> - patch 5/8: initialize ret before goto err handling in ath12k_mac_allocate()
> - Link to v1: https://lore.kernel.org/r/20260713-ath12k-fw-allocated-ml-peer-id-v1-0-d0a2a1a519eb@oss.qualcomm.com
> 
> ---
> Baochen Qiang (8):
>        wifi: ath12k: fix out-of-bounds clear_bit in ath12k_mac_dp_peer_cleanup()
>        wifi: ath12k: factor out peer assoc send-and-wait into a helper
>        wifi: ath12k: keep ATH12K_PEER_ML_ID_VALID set in ath12k_sta::ml_peer_id
>        wifi: ath12k: add support for HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP
>        wifi: ath12k: introduce host_alloc_ml_id hardware parameter
>        wifi: ath12k: do not advertise MLD peer ID for firmware-allocate devices
>        wifi: ath12k: defer dp_peer registration when firmware allocates MLD peer ID
>        wifi: ath12k: resolve PENDING ML peer ID from MLO_PEER_MAP HTT event
> 
>   drivers/net/wireless/ath/ath12k/core.c     |   2 +
>   drivers/net/wireless/ath/ath12k/core.h     |   3 +
>   drivers/net/wireless/ath/ath12k/dp_htt.c   |  49 +++++++++
>   drivers/net/wireless/ath/ath12k/dp_htt.h   |  12 +++
>   drivers/net/wireless/ath/ath12k/dp_peer.c  |  75 +++++++++++--
>   drivers/net/wireless/ath/ath12k/dp_peer.h  |   2 +
>   drivers/net/wireless/ath/ath12k/hw.h       |   2 +
>   drivers/net/wireless/ath/ath12k/mac.c      | 165 ++++++++++++++++++++---------
>   drivers/net/wireless/ath/ath12k/peer.c     |  31 +++++-
>   drivers/net/wireless/ath/ath12k/peer.h     |   1 +
>   drivers/net/wireless/ath/ath12k/wifi7/hw.c |  12 +++
>   11 files changed, 293 insertions(+), 61 deletions(-)
> ---
> base-commit: 951dc0a744e4dc8490935316d3b76e23990bde3c
> change-id: 20260527-ath12k-fw-allocated-ml-peer-id-2b456891157f
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox