Linux-mediatek Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH mt76 v2 7/7] wifi: mt76: mt7996: fix iface combination for different chipsets
From: Ilya K @ 2026-05-23 13:09 UTC (permalink / raw)
  To: shayne.chen
  Cc: evelyn.tsai, linux-mediatek, linux-wireless, lorenzo, nbd,
	ryder.lee
In-Reply-To: <20251215063728.3013365-7-shayne.chen@mediatek.com>

Hey folks, this looks like it broke mt7996 init entirely with stable kernel 7.0.10.

Specifically it trips this check in net/wireless/core.c:681:

	if (WARN_ON(types & BIT(NL80211_IFTYPE_ADHOC) &&
		    c->beacon_int_min_gcd)) {
		return -EINVAL;
	}

I'm not sure what the correct fix here is, but unsetting beacon_int_min_gcd at least makes AP mode work again...


^ permalink raw reply

* Re: [PATCH v8 1/3] PCI: mediatek: Use actual physical address instead of virt_to_phys()
From: Manivannan Sadhasivam @ 2026-05-23 14:43 UTC (permalink / raw)
  To: Bjorn Helgaas
  Cc: Caleb James DeLisle, linux-pci, linux-mips, naseefkm, ryder.lee,
	lpieralisi, kwilczynski, robh, krzk+dt, conor+dt, matthias.bgg,
	angelogioacchino.delregno, ansuelsmth, linux-mediatek, devicetree,
	linux-kernel, Manivannan Sadhasivam
In-Reply-To: <20260522224325.GA195169@bhelgaas>

On Fri, May 22, 2026 at 05:43:25PM -0500, Bjorn Helgaas wrote:
> On Thu, May 21, 2026 at 10:44:51AM +0530, Manivannan Sadhasivam wrote:
> > On Wed, May 20, 2026 at 02:59:00PM -0500, Bjorn Helgaas wrote:
> > > On Wed, May 20, 2026 at 09:17:35PM +0200, Caleb James DeLisle wrote:
> > > > 
> > > > On 20/05/2026 20:55, Bjorn Helgaas wrote:
> > > > > On Wed, May 20, 2026 at 06:38:25PM +0000, Caleb James DeLisle wrote:
> > > > > > From: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
> > > > > > 
> > > > > > The driver previously used virt_to_phys() on the ioremapped register base
> > > > > > (port->base) to compute the MSI message address. Using virt_to_phys() on an
> > > > > > IO mapped address is incorrect because it expects a kernel virtual address.
> > > > > > 
> > > > > > To fix it, store the physical start of the I/O register region in
> > > > > > mtk_pcie_port->phys_base and use it to build the MSI address. This replaces
> > > > > > the incorrect virt_to_phys() usage and ensures MSI addresses are generated
> > > > > > correctly.
> > > > > > 
> > > > > > Fixes: 43e6409db64d ("PCI: mediatek: Add MSI support for MT2712 and MT7622")
> > > > > > Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
> > > > > > Tested-by: Caleb James DeLisle <cjd@cjdns.fr>
> > > > > > ---
> > > > > >   drivers/pci/controller/pcie-mediatek.c | 16 +++++++++++++---
> > > > > >   1 file changed, 13 insertions(+), 3 deletions(-)
> > > > > > 
> > > > > > diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c
> > > > > > index 75722524fe74..c503fbd774d0 100644
> > > > > > --- a/drivers/pci/controller/pcie-mediatek.c
> > > > > > +++ b/drivers/pci/controller/pcie-mediatek.c
> > > > > > @@ -175,6 +175,7 @@ struct mtk_pcie_soc {
> > > > > >   /**
> > > > > >    * struct mtk_pcie_port - PCIe port information
> > > > > >    * @base: IO mapped register base
> > > > > > + * @phys_base: Physical address of the I/O register base region
> > > > > >    * @list: port list
> > > > > >    * @pcie: pointer to PCIe host info
> > > > > >    * @reset: pointer to port reset control
> > > > > > @@ -196,6 +197,7 @@ struct mtk_pcie_soc {
> > > > > >    */
> > > > > >   struct mtk_pcie_port {
> > > > > >   	void __iomem *base;
> > > > > > +	phys_addr_t phys_base;
> > > > > >   	struct list_head list;
> > > > > >   	struct mtk_pcie *pcie;
> > > > > >   	struct reset_control *reset;
> > > > > > @@ -405,7 +407,7 @@ static void mtk_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
> > > > > >   	phys_addr_t addr;
> > > > > >   	/* MT2712/MT7622 only support 32-bit MSI addresses */
> > > > > > -	addr = virt_to_phys(port->base + PCIE_MSI_VECTOR);
> > > > > > +	addr = port->phys_base + PCIE_MSI_VECTOR;
> > > > >
> > > > > This doesn't look right because the MSI address is a PCI bus address,
> > > > > and port->phys_base is a CPU physical address.  Often a PCI bus
> > > > > address is the same as the CPU physical address, but not always.
> > > > > I think the DT 'ranges' property tells you the translation.
> > > 
> > > Oops, sorry, I muddied the waters here.
> > > 
> > > 'ranges' tells you the translation applied by a bridge, e.g., when
> > > a CPU does a load/store, the PCI host bridge turns it into a PCI
> > > read/write transaction.  The bridge might add an offset to the CPU
> > > load/store physical address to get the PCI read/write bus address.
> > > 
> > > But that's not the issue here.  The MSI is basically a DMA write
> > > performed by the PCI device, not a store done by a CPU, so I don't
> > > think 'ranges' is the right thing to look at.
> > 
> > Yeah, it is so easy to confuse both. To summarise, 'ranges'
> > describes the outbound translation and 'dma-ranges' describes the
> > inbound translation from host perspective.
> > 
> > > Based on this:
> > > https://elinux.org/Device_Tree_Usage#PCI_DMA_Address_Translation I
> > > think 'dma-ranges' is the relevant property.  I don't think your
> > > DT includes a 'dma-ranges' property, and in that case the default
> > > is that the system bus (CPU) address is the same as the PCI
> > > address.
> > > 
> > > So I think this patch works because it assumes DMA addresses like
> > > the MSI address are mapped to identical system bus addresses.
> > > 
> > > It still seems to me that drivers should be prepared for the
> > > presence of dma-ranges and use it when computing the MSI target
> > > address.  But I don't think any drivers really do that, so for now
> > > I think you should pretend that I never responded about this
> > > patch.
> > 
> > Your observations are correct. This driver assumes that the
> > identical mapping exists between CPU and PCI bus addresses. Usually,
> > the drivers make use of phys_to_dma() to handle the translations.
> 
> What does this look like in the native host bridge drivers?  I don't
> see any direct calls of phys_to_dma(), but there are some higher-level
> interfaces that use it.
> 
> I don't really see a consistent style of constructing MSI addresses,
> e.g., in *_compose_msi_msg() implementations.
> 

That's because, each driver may use different methods for constructing the MSI
address. Two commonly used methods are:

1. Using a pre-defined MSI address in the MMIO range (like the pcie-mediatek
driver). The PCIe controller hardware decodes MWr TLPs targeting this address
and raises an interrupt to the CPU via the platform interrupt controller (e.g.
GIC).

2. Allocating coherent memory using dma_alloc_coherent() and programming it
into platform MMIO registers (like pcie-designware-host driver). The Root
Complex inbound ATU matches writes to this address and raises an interrupt to
the CPU.

> > This API internally makes use of the 'dma_range_map' which gets
> > populated by the OF core based on the 'dma-ranges' property (if
> > present in DT).
> > 
> > But it makes sense to use it irrespective of whether the platform
> > supports non-identical DMA/inbound translation or not. Since this
> > API behaves like a no-op and returns the CPU physical address if
> > there is an identical mapping, there is literally zero overhead in
> > using it.
> 
> Thanks for rescuing me.
> 
> I wonder if there should be something in
> Documentation/core-api/dma-api* about this.  I guess that is mostly
> oriented toward things like PCI device drivers, not so much PCI host
> bridge drivers.  But it would be nice to have a little intro to
> dma-ranges and maybe even the restricted DMA usage.
> 

I think this information should belong to the PCI host controller documentation.
I started writing it, but didn't get time to finish and post it. Maybe I'll
atleast post an initial version and add more info on top.

- Mani

-- 
மணிவண்ணன் சதாசிவம்


^ permalink raw reply

* Re: [PATCH 0/2] pwm: mediatek: fix mt7628 register offset and clock source
From: Uwe Kleine-König @ 2026-05-23 16:57 UTC (permalink / raw)
  To: Shiji Yang, Matthias Brugger, AngeloGioacchino Del Regno
  Cc: linux-pwm, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <ac6OYRkiDt_OJVcp@monoceros>

[-- Attachment #1: Type: text/plain, Size: 592 bytes --]

hello,

On Thu, Apr 02, 2026 at 05:44:32PM +0200, Uwe Kleine-König wrote:
> Hello,
> 
> On Tue, Feb 24, 2026 at 04:51:00PM +0800, Shiji Yang wrote:
> > This patch series fixes support for mt7628.
> 
> The series looks reasonable to me. It would be great to get some
> feedback from the Mediatek maintainers, though?!

I applied the two patches now without further feedback to

https://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux.git pwm/fixes

. Not sure yet if I send this branch to Linus before 7.1, but it will be
part of 7.2-rc1 for sure.

Best regards
Uwe

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply

* Re: [PATCH RFC 00/12] arm64: mediatek: Add M.2 E-key slot on Chromebooks
From: Chen-Yu Tsai @ 2026-05-24  8:06 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
	linux-pm, linux-usb, devicetree, linux-mediatek, linux-arm-kernel,
	linux-kernel, Manivannan Sadhasivam
In-Reply-To: <CAMRc=MdnjRRMVzxPkkrPhQ4dz7rsK8-HKUp9cQ0z11apL3escQ@mail.gmail.com>

On Wed, May 20, 2026 at 7:01 PM Bartosz Golaszewski <brgl@kernel.org> wrote:
>
> On Fri, May 15, 2026 at 11:02 AM Chen-Yu Tsai <wenst@chromium.org> wrote:
> >
> > Hi everyone,
> >
> > This series is my attempt at enabling power sequencing for USB to support
> > the USB connection on M.2 E-key slots. M.2 E-key was enabled in v7.1-rc1
> > with just PCIe and UART supported [1].
> >
> > Most of the series is based on next-20260508, while the DT changes also
> > depend on some other DT cleanup patches I sent [2][3].
> >
> >
> > Patch 1 reworks the power sequencing framework to allow matching against
> > different USB ports. The consumer API gains an "index" parameter (which
> > is the USB port number on the hub), while the provider API is reworked
> > to pass the index to the matching function of the providing driver.
> >
>
> Sigh... I would really prefer to avoid going in this direction. IMO
> it's not very clear what this index actually refers to in generic
> terms, given that pwrseq is flexible on purpose and there's no
> specific, well-defined DT property which could have an "index".
>
> > Patch 2 implements the index matching in the pcie-m2 driver. Matching
> > only happens when a valid (>= 0) index is given.
> >
> > Patch 3 reworks the power sequencing targets for the E-key connector in
> > the pcie-m2 driver to add targets for USB and SDIO. The former is used
> > later on in this series.
> >
> > Patch 4 reworks the USB hub driver to return the actual error code from
> > hub_configure() in hub_probe(). This is needed in the next patch to
> > correctly return -EPROBE_DEFER.
> >
> > Patch 5 lets the USB hub driver look for power sequencers for each port.
> > Currently this only works for M.2 E-key connections, but it could be
> > extended to cover other cases. It should also make port reset via turning
> > off the port VBUS work, even when VBUS is not directly controlled by the
> > hub.
> >
> > I expect some discussion on this patch, because a) it adds some
> > OF-specific code into an otherwise generic (core) driver, and
> > b) it doesn't yet handle USB 2.0 / 3.x shared ports; it ends up powering
> > on the port twice, which negates the port reset part.
> >
>
> I understand that you do this because the port device has no OF node
> assigned. If we wanted to call pwrseq_get() for the port device, is
> there really no other way to associate it with the correct pwrseq
> provider?

I suppose we could tie the "port@X" node to the usb port device, but
AFAIK no other subsystem does this so we would be introducing a new
pattern.

In the M.2 pwrseq driver, we would have to match by port node instead
of its parent device node. We may end up with different behavior for
the USB target vs the other targets.

Also, the "port@X" nodes only exist for the OF graph connections to
connectors and/or muxes (this series doesn't deal with the latter).
For directly connected devices, there is a "device@X" child node
directly under the USB hub node. That node is what gets tied to the
the USB device.

> Does the child index in hub_configure() relate to the port index as
> defined by the unit address of the port DT node? I'm talking about the
> X in port@X?

Yes. The downstream port numbers start at 1. I believe 0 corresponds
to the upstream port.

> > Patch 6 reverts an incorrectly modeled OF graph connection for the
> > MediaTek XHCI controller.
> >
> > Patch 7 then adds a proper representation.
> >
> > Patches 8 through 12 enable the M.2 E-key slots (used for WiFi/BT) and
> > USB type-A connectors found on MediaTek-based Chromebooks. These are
> > provided in this series for reference. The USB type-A connector changes,
> > while not directly related, have overlapping context, and was easier to
> > include. They were also used to test some extra local changes I tried
> > to convert the USB A connector from an onboard USB device to a power
> > sequencing provider.
> >
> >
> > As this series changes existing power sequencing API, and also uses the
> > changed API in subsequent patches, I think the best way to merge this
> > is for Bartosz to take the power sequencing patches and provide an
> > immutable tag for Greg to merge and then merge the USB patches.
> >
> > The DT patches can go through the soc tree once all the driver and DT
> > binding changes are merged.
> >
> >
> > Thanks
> > ChenYu
> >
> > P.S. I'll be at Embedded Recipes if anyone wants to discuss details.
> >
>
> I'll be there too! Or should i say "here"? I live here after all. :) Let's talk!

Sure!


Thanks
ChenYu


^ permalink raw reply

* Re: [RESEND,v2 1/2] dt-bindings: memory-controllers: mtk-smi: Add support for mt8189
From: Krzysztof Kozlowski @ 2026-05-24 19:08 UTC (permalink / raw)
  To: mtk20898, Yong Wu, Rob Herring, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-kernel, devicetree, linux-arm-kernel,
	Project_Global_Chrome_Upstream_Group
In-Reply-To: <20260427070444.20247-2-zhengnan.chen@mediatek.com>

On 27/04/2026 09:04, mtk20898 wrote:
> From: Zhengnan Chen <zhengnan.chen@mediatek.com>
> 
> Add binding description for mt8189.
> 
> The clocks number of mt8189 smi-sub common has a bit difference.
> Its clock count is 2, while mt8195 has 3. Therefore, the minimum
> number of clocks is changed to 2, with the third one being optional.

Then why does the binding say that mt8195 has two clocks? You already
received exactly this question.

> 
> About what smi-sub-common is, please check the below diagram,
> we add it in mediatek,smi-common.yaml file.
> 
> Signed-off-by: Zhengnan Chen <zhengnan.chen@mediatek.com>
> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>


No need to resend this. You received comments at v2 and you should have
implemented them.

Best regards,
Krzysztof


^ permalink raw reply

* [PATCH net-next] net: airoha: bind WLAN-bound flows on PPE driver L2 cache miss
From: Jihong Min @ 2026-05-24 22:43 UTC (permalink / raw)
  To: netdev
  Cc: Lorenzo Bianconi, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-arm-kernel, linux-mediatek,
	linux-kernel, Jihong Min

The Linux bridge FDB can resolve a destination station to WDMA even when
the Airoha PPE driver's L2 offload cache has no entry for that MAC pair.
The normal bind path only checks the PPE driver's L2 offload cache, so an
unbound PPE hit for WLAN egress can stay unbound even though the bridge
already knows the right output path, unless a later offload event fills
that PPE driver cache.

This matters for bridge-visible WLAN egress, such as wired-to-WLAN
forwarding or WLAN peer forwarding across another BSS, radio or MLO link.
Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
datapath and is not covered.

Before touching the PPE table, resolve the destination MAC through the
bridge device above the ingress netdev. If the PPE driver's L2 offload
cache lookup misses, bind the hardware flow to the resolved CDM4/WDMA
path.

Assisted-by: Codex:gpt-5.5
Signed-off-by: Jihong Min <hurryman2212@gmail.com>
---
 drivers/net/ethernet/airoha/airoha_ppe.c | 138 +++++++++++++++++++----
 1 file changed, 119 insertions(+), 19 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 26da519236bf..ea932e6d87f6 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -803,65 +803,163 @@ static void airoha_ppe_foe_flow_remove_entry(struct airoha_ppe *ppe,
 }
 
 static int
-airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
-				    struct airoha_flow_table_entry *e,
-				    u32 hash, bool rx_wlan)
+airoha_ppe_foe_commit_subflow(struct airoha_ppe *ppe,
+			      const struct airoha_foe_entry *bridge,
+			      u32 hash, bool rx_wlan)
 {
 	u32 mask = AIROHA_FOE_IB1_BIND_PACKET_TYPE | AIROHA_FOE_IB1_BIND_UDP;
 	struct airoha_foe_entry *hwe_p, hwe;
-	struct airoha_flow_table_entry *f;
 	int type;
 
 	hwe_p = airoha_ppe_foe_get_entry_locked(ppe, hash);
 	if (!hwe_p)
 		return -EINVAL;
 
-	f = kzalloc_obj(*f, GFP_ATOMIC);
-	if (!f)
-		return -ENOMEM;
-
-	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
-	f->type = FLOW_TYPE_L2_SUBFLOW;
-	f->hash = hash;
-
 	memcpy(&hwe, hwe_p, sizeof(*hwe_p));
-	hwe.ib1 = (hwe.ib1 & mask) | (e->data.ib1 & ~mask);
+	hwe.ib1 = (hwe.ib1 & mask) | (bridge->ib1 & ~mask);
 
 	type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe.ib1);
 	if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
-		memcpy(&hwe.ipv6.l2, &e->data.bridge.l2, sizeof(hwe.ipv6.l2));
-		hwe.ipv6.ib2 = e->data.bridge.ib2;
+		memcpy(&hwe.ipv6.l2, &bridge->bridge.l2,
+		       sizeof(hwe.ipv6.l2));
+		hwe.ipv6.ib2 = bridge->bridge.ib2;
 		/* setting smac_id to 0xf instruct the hw to keep original
 		 * source mac address
 		 */
 		hwe.ipv6.l2.src_mac_hi = FIELD_PREP(AIROHA_FOE_MAC_SMAC_ID,
 						    0xf);
 	} else {
-		memcpy(&hwe.bridge.l2, &e->data.bridge.l2,
+		memcpy(&hwe.bridge.l2, &bridge->bridge.l2,
 		       sizeof(hwe.bridge.l2));
-		hwe.bridge.ib2 = e->data.bridge.ib2;
+		hwe.bridge.ib2 = bridge->bridge.ib2;
 		if (type == PPE_PKT_TYPE_IPV4_HNAPT)
 			memcpy(&hwe.ipv4.new_tuple, &hwe.ipv4.orig_tuple,
 			       sizeof(hwe.ipv4.new_tuple));
 	}
 
-	hwe.bridge.data = e->data.bridge.data;
-	airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
+	hwe.bridge.data = bridge->bridge.data;
+
+	return airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
+}
+
+static int
+airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
+				    struct airoha_flow_table_entry *e,
+				    u32 hash, bool rx_wlan)
+{
+	struct airoha_flow_table_entry *f;
+	int err;
+
+	f = kzalloc_obj(*f, GFP_ATOMIC);
+	if (!f)
+		return -ENOMEM;
+
+	err = airoha_ppe_foe_commit_subflow(ppe, &e->data, hash, rx_wlan);
+	if (err) {
+		kfree(f);
+		return err;
+	}
+
+	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
+	f->type = FLOW_TYPE_L2_SUBFLOW;
+	f->hash = hash;
 
 	return 0;
 }
 
+static bool
+airoha_ppe_foe_prepare_wdma_subflow_dev(struct airoha_ppe *ppe,
+					struct net_device *dev,
+					struct airoha_flow_data *data,
+					struct airoha_foe_entry *hwe)
+{
+	u32 pse_port;
+	int err;
+
+	err = airoha_ppe_foe_entry_prepare(ppe->eth, hwe, dev,
+					   PPE_PKT_TYPE_BRIDGE, data, 0);
+	if (err)
+		return false;
+
+	pse_port = FIELD_GET(AIROHA_FOE_IB2_PSE_PORT, hwe->bridge.ib2);
+	if (pse_port != FE_PSE_PORT_CDM4)
+		return false;
+
+	return true;
+}
+
+static struct net_device *
+airoha_ppe_foe_get_bridge_master(struct net_device *dev)
+{
+	struct net_device *master = NULL;
+
+	rcu_read_lock();
+	master = netdev_master_upper_dev_get_rcu(dev);
+	if (master && netif_is_bridge_master(master))
+		dev_hold(master);
+	else
+		master = NULL;
+	rcu_read_unlock();
+
+	return master;
+}
+
+static bool
+airoha_ppe_foe_prepare_wdma_subflow(struct airoha_ppe *ppe,
+				    struct sk_buff *skb,
+				    struct airoha_foe_entry *hwe)
+{
+	struct ethhdr *eh = eth_hdr(skb);
+	struct airoha_flow_data data = {};
+	struct net_device *master;
+
+	if (!is_valid_ether_addr(eh->h_source) ||
+	    !is_valid_ether_addr(eh->h_dest))
+		return false;
+
+	ether_addr_copy(data.eth.h_dest, eh->h_dest);
+	ether_addr_copy(data.eth.h_source, eh->h_source);
+
+	if (!skb->dev)
+		return false;
+
+	/* WLAN egress unbound hits can arrive before flowtable creates the
+	 * L2 master flow normally used for subflow binding. Resolve only
+	 * through the bridge master so dev_fill_forward_path() must use the
+	 * bridge FDB for the destination MAC. Calling the ingress AP netdev
+	 * directly can describe the source station's WDMA path and would
+	 * corrupt Wi-Fi-to-wired flows whose real egress is not WDMA.
+	 */
+	master = airoha_ppe_foe_get_bridge_master(skb->dev);
+	if (!master)
+		return false;
+
+	if (airoha_ppe_foe_prepare_wdma_subflow_dev(ppe, master, &data,
+						    hwe)) {
+		dev_put(master);
+		return true;
+	}
+
+	dev_put(master);
+	return false;
+}
+
 static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
 					struct sk_buff *skb,
 					u32 hash, bool rx_wlan)
 {
+	struct airoha_foe_entry wdma_hwe = {};
 	struct airoha_flow_table_entry *e;
 	struct airoha_foe_bridge br = {};
 	struct airoha_foe_entry *hwe;
 	bool commit_done = false;
+	bool wdma_ready = false;
 	struct hlist_node *n;
 	u32 index, state;
 
+	wdma_ready = airoha_ppe_foe_prepare_wdma_subflow(ppe, skb,
+							 &wdma_hwe);
+
 	spin_lock_bh(&ppe_lock);
 
 	hwe = airoha_ppe_foe_get_entry_locked(ppe, hash);
@@ -899,6 +997,8 @@ static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
 				   airoha_l2_flow_table_params);
 	if (e)
 		airoha_ppe_foe_commit_subflow_entry(ppe, e, hash, rx_wlan);
+	else if (wdma_ready)
+		airoha_ppe_foe_commit_subflow(ppe, &wdma_hwe, hash, rx_wlan);
 unlock:
 	spin_unlock_bh(&ppe_lock);
 }
-- 
2.53.0



^ permalink raw reply related

* Re: [PATCH RESEND] arm64: dts: mediatek: add LED and key support on Xiaomi AX3000T
From: AngeloGioacchino Del Regno @ 2026-05-25  8:43 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, matthias.bgg, devicetree, linux-kernel,
	linux-arm-kernel, linux-mediatek, Aleksander Jan Bajkowski
In-Reply-To: <20260523101904.293215-1-olek2@wp.pl>

On Sat, 23 May 2026 12:18:55 +0200, Aleksander Jan Bajkowski wrote:
> This patch adds support for keys and LEDs on the Xiaomi AX3000T.

Applied to v7.1-next/dts64, thanks!

[1/1] arm64: dts: mediatek: add LED and key support on Xiaomi AX3000T
      commit: 9897c586b09f79ebcf2e67a888743c046b20d254

Cheers,
Angelo




^ permalink raw reply

* Re: [PATCH] soc: mediatek: pwrap: Remove obsolete NEED CONFIRM comments
From: AngeloGioacchino Del Regno @ 2026-05-25  8:46 UTC (permalink / raw)
  To: Akari Tsuyukusa, matthias.bgg
  Cc: james.lo, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260522134522.368073-1-akkun11.open@gmail.com>

On 5/22/26 15:45, Akari Tsuyukusa wrote:
> Remove the obsolete "/* NEED CONFIRM */" comments from the MT8196

s/mt8196/mt8195/g

> configuration. These values were previously confirmed during review,
> but the placeholder comments were accidentally left behind.
> 
> Link: https://lore.kernel.org/all/2a117e5fe9fe0ece39e9165a463082ef42be973f.camel@mediatek.com/
> Signed-off-by: Akari Tsuyukusa <akkun11.open@gmail.com>


after which...
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>

> ---
>   drivers/soc/mediatek/mtk-pmic-wrap.c | 4 ++--
>   1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/soc/mediatek/mtk-pmic-wrap.c b/drivers/soc/mediatek/mtk-pmic-wrap.c
> index 0bcd85826375..87bcbfa2d5f1 100644
> --- a/drivers/soc/mediatek/mtk-pmic-wrap.c
> +++ b/drivers/soc/mediatek/mtk-pmic-wrap.c
> @@ -2396,8 +2396,8 @@ static const struct pmic_wrapper_type pwrap_mt8183 = {
>   static const struct pmic_wrapper_type pwrap_mt8195 = {
>   	.regs = mt8195_regs,
>   	.type = PWRAP_MT8195,
> -	.arb_en_all = 0x777f, /* NEED CONFIRM */
> -	.int_en_all = 0x180000, /* NEED CONFIRM */
> +	.arb_en_all = 0x777f,
> +	.int_en_all = 0x180000,
>   	.int1_en_all = 0,
>   	.spi_w = PWRAP_MAN_CMD_SPI_WRITE,
>   	.wdt_src = PWRAP_WDT_SRC_MASK_ALL,



^ permalink raw reply

* Re: [PATCH] clk: mediatek: mt8196: Select REGMAP_MMIO for vlpckgen
From: AngeloGioacchino Del Regno @ 2026-05-25  8:46 UTC (permalink / raw)
  To: Akari Tsuyukusa, mturquette, sboyd, bmasney, matthias.bgg, wenst,
	laura.nao
  Cc: linux-clk, linux-kernel, linux-arm-kernel, linux-mediatek, stable
In-Reply-To: <20260522133023.355404-1-akkun11.open@gmail.com>

On 5/22/26 15:30, Akari Tsuyukusa wrote:
> The MediaTek MT8196 vlpckgen clock driver uses
> __devm_regmap_init_mmio_clk() by devm_regmap_init_mmio(),
> which is defined in drivers/base/regmap/regmap-mmio.c.
> However, the driver's Kconfig entry does not select REGMAP_MMIO.
> This causes a linker error when REGMAP_MMIO is not enabled.
> 
> Fix this by selecting REGMAP_MMIO in the Kconfig entry.
> 
> Fixes: 2f8b3ae6f0cb ("clk: mediatek: Add MT8196 vlpckgen clock support")
> Cc: stable@vger.kernel.org
> Signed-off-by: Akari Tsuyukusa <akkun11.open@gmail.com>

Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>

> ---
>   drivers/clk/mediatek/Kconfig | 1 +
>   1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/clk/mediatek/Kconfig b/drivers/clk/mediatek/Kconfig
> index 2c09fd729bab..fd8440122ec2 100644
> --- a/drivers/clk/mediatek/Kconfig
> +++ b/drivers/clk/mediatek/Kconfig
> @@ -1006,6 +1006,7 @@ config COMMON_CLK_MT8196
>   	tristate "Clock driver for MediaTek MT8196"
>   	depends on ARM64 || COMPILE_TEST
>   	select COMMON_CLK_MEDIATEK
> +	select REGMAP_MMIO
>   	default ARCH_MEDIATEK
>   	help
>   	  This driver supports MediaTek MT8196 basic clocks.



^ permalink raw reply

* Re: [PATCH net-next] net: airoha: bind WLAN-bound flows on PPE driver L2 cache miss
From: Lorenzo Bianconi @ 2026-05-25  8:09 UTC (permalink / raw)
  To: Jihong Min
  Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-arm-kernel, linux-mediatek,
	linux-kernel
In-Reply-To: <20260524224330.3995807-1-hurryman2212@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 8142 bytes --]

> The Linux bridge FDB can resolve a destination station to WDMA even when
> the Airoha PPE driver's L2 offload cache has no entry for that MAC pair.
> The normal bind path only checks the PPE driver's L2 offload cache, so an
> unbound PPE hit for WLAN egress can stay unbound even though the bridge
> already knows the right output path, unless a later offload event fills
> that PPE driver cache.
> 
> This matters for bridge-visible WLAN egress, such as wired-to-WLAN
> forwarding or WLAN peer forwarding across another BSS, radio or MLO link.
> Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
> datapath and is not covered.

Hi Jihong,

In order to offload L2 flows, I assume you are using the OpenWrt bridger
package, right?
IIUC the issue you want to resolve is we are not adding PPE L2 entries for
the specified cases (same-link or same-radio intra-BSS forwarding), correct?
Using this approach, we are breaking the assumption PPE flow-table and hw
flow-table are in sync. If the issue is the one described above, why not
fixing the problem directly in the bridger package?
Moreover, I see you developed the patch using Codex:gpt-5.5. Have you tested it
on a real hw?

Some comments inline.

Regards,
Lorenzo

> 
> Before touching the PPE table, resolve the destination MAC through the
> bridge device above the ingress netdev. If the PPE driver's L2 offload
> cache lookup misses, bind the hardware flow to the resolved CDM4/WDMA
> path.
> 
> Assisted-by: Codex:gpt-5.5
> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
> ---
>  drivers/net/ethernet/airoha/airoha_ppe.c | 138 +++++++++++++++++++----
>  1 file changed, 119 insertions(+), 19 deletions(-)
> 
> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
> index 26da519236bf..ea932e6d87f6 100644
> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
> @@ -803,65 +803,163 @@ static void airoha_ppe_foe_flow_remove_entry(struct airoha_ppe *ppe,
>  }
>  
>  static int
> -airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
> -				    struct airoha_flow_table_entry *e,
> -				    u32 hash, bool rx_wlan)
> +airoha_ppe_foe_commit_subflow(struct airoha_ppe *ppe,
> +			      const struct airoha_foe_entry *bridge,

maybe l2_hwe instead of bridge?

> +			      u32 hash, bool rx_wlan)
>  {
>  	u32 mask = AIROHA_FOE_IB1_BIND_PACKET_TYPE | AIROHA_FOE_IB1_BIND_UDP;
>  	struct airoha_foe_entry *hwe_p, hwe;
> -	struct airoha_flow_table_entry *f;
>  	int type;
>  
>  	hwe_p = airoha_ppe_foe_get_entry_locked(ppe, hash);
>  	if (!hwe_p)
>  		return -EINVAL;
>  
> -	f = kzalloc_obj(*f, GFP_ATOMIC);
> -	if (!f)
> -		return -ENOMEM;
> -
> -	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
> -	f->type = FLOW_TYPE_L2_SUBFLOW;
> -	f->hash = hash;
> -
>  	memcpy(&hwe, hwe_p, sizeof(*hwe_p));
> -	hwe.ib1 = (hwe.ib1 & mask) | (e->data.ib1 & ~mask);
> +	hwe.ib1 = (hwe.ib1 & mask) | (bridge->ib1 & ~mask);
>  
>  	type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe.ib1);
>  	if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
> -		memcpy(&hwe.ipv6.l2, &e->data.bridge.l2, sizeof(hwe.ipv6.l2));
> -		hwe.ipv6.ib2 = e->data.bridge.ib2;
> +		memcpy(&hwe.ipv6.l2, &bridge->bridge.l2,
> +		       sizeof(hwe.ipv6.l2));
> +		hwe.ipv6.ib2 = bridge->bridge.ib2;
>  		/* setting smac_id to 0xf instruct the hw to keep original
>  		 * source mac address
>  		 */
>  		hwe.ipv6.l2.src_mac_hi = FIELD_PREP(AIROHA_FOE_MAC_SMAC_ID,
>  						    0xf);
>  	} else {
> -		memcpy(&hwe.bridge.l2, &e->data.bridge.l2,
> +		memcpy(&hwe.bridge.l2, &bridge->bridge.l2,
>  		       sizeof(hwe.bridge.l2));
> -		hwe.bridge.ib2 = e->data.bridge.ib2;
> +		hwe.bridge.ib2 = bridge->bridge.ib2;
>  		if (type == PPE_PKT_TYPE_IPV4_HNAPT)
>  			memcpy(&hwe.ipv4.new_tuple, &hwe.ipv4.orig_tuple,
>  			       sizeof(hwe.ipv4.new_tuple));
>  	}
>  
> -	hwe.bridge.data = e->data.bridge.data;
> -	airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
> +	hwe.bridge.data = bridge->bridge.data;
> +
> +	return airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
> +}
> +
> +static int
> +airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
> +				    struct airoha_flow_table_entry *e,
> +				    u32 hash, bool rx_wlan)
> +{
> +	struct airoha_flow_table_entry *f;
> +	int err;
> +
> +	f = kzalloc_obj(*f, GFP_ATOMIC);
> +	if (!f)
> +		return -ENOMEM;
> +
> +	err = airoha_ppe_foe_commit_subflow(ppe, &e->data, hash, rx_wlan);
> +	if (err) {
> +		kfree(f);
> +		return err;
> +	}
> +
> +	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
> +	f->type = FLOW_TYPE_L2_SUBFLOW;
> +	f->hash = hash;
>  
>  	return 0;
>  }
>  
> +static bool
> +airoha_ppe_foe_prepare_wdma_subflow_dev(struct airoha_ppe *ppe,
> +					struct net_device *dev,
> +					struct airoha_flow_data *data,
> +					struct airoha_foe_entry *hwe)
> +{
> +	u32 pse_port;
> +	int err;
> +
> +	err = airoha_ppe_foe_entry_prepare(ppe->eth, hwe, dev,
> +					   PPE_PKT_TYPE_BRIDGE, data, 0);
> +	if (err)
> +		return false;
> +
> +	pse_port = FIELD_GET(AIROHA_FOE_IB2_PSE_PORT, hwe->bridge.ib2);
> +	if (pse_port != FE_PSE_PORT_CDM4)
> +		return false;
> +
> +	return true;

	return pse_port == FE_PSE_PORT_CDM4;

> +}
> +
> +static struct net_device *
> +airoha_ppe_foe_get_bridge_master(struct net_device *dev)
> +{
> +	struct net_device *master = NULL;
> +
> +	rcu_read_lock();
> +	master = netdev_master_upper_dev_get_rcu(dev);
> +	if (master && netif_is_bridge_master(master))
> +		dev_hold(master);
> +	else
> +		master = NULL;
> +	rcu_read_unlock();
> +
> +	return master;
> +}
> +
> +static bool
> +airoha_ppe_foe_prepare_wdma_subflow(struct airoha_ppe *ppe,
> +				    struct sk_buff *skb,
> +				    struct airoha_foe_entry *hwe)
> +{
> +	struct ethhdr *eh = eth_hdr(skb);
> +	struct airoha_flow_data data = {};
> +	struct net_device *master;
> +
> +	if (!is_valid_ether_addr(eh->h_source) ||
> +	    !is_valid_ether_addr(eh->h_dest))
> +		return false;
> +
> +	ether_addr_copy(data.eth.h_dest, eh->h_dest);
> +	ether_addr_copy(data.eth.h_source, eh->h_source);
> +
> +	if (!skb->dev)
> +		return false;
> +
> +	/* WLAN egress unbound hits can arrive before flowtable creates the
> +	 * L2 master flow normally used for subflow binding. Resolve only
> +	 * through the bridge master so dev_fill_forward_path() must use the
> +	 * bridge FDB for the destination MAC. Calling the ingress AP netdev
> +	 * directly can describe the source station's WDMA path and would
> +	 * corrupt Wi-Fi-to-wired flows whose real egress is not WDMA.
> +	 */
> +	master = airoha_ppe_foe_get_bridge_master(skb->dev);
> +	if (!master)
> +		return false;
> +
> +	if (airoha_ppe_foe_prepare_wdma_subflow_dev(ppe, master, &data,
> +						    hwe)) {
> +		dev_put(master);
> +		return true;
> +	}
> +
> +	dev_put(master);
> +	return false;

maybe something like:

	ret = airoha_ppe_foe_prepare_wdma_subflow_dev();
	dev_put(master);

	return ret;

> +}
> +
>  static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
>  					struct sk_buff *skb,
>  					u32 hash, bool rx_wlan)
>  {
> +	struct airoha_foe_entry wdma_hwe = {};
>  	struct airoha_flow_table_entry *e;
>  	struct airoha_foe_bridge br = {};
>  	struct airoha_foe_entry *hwe;
>  	bool commit_done = false;
> +	bool wdma_ready = false;
>  	struct hlist_node *n;
>  	u32 index, state;
>  
> +	wdma_ready = airoha_ppe_foe_prepare_wdma_subflow(ppe, skb,
> +							 &wdma_hwe);
> +
>  	spin_lock_bh(&ppe_lock);
>  
>  	hwe = airoha_ppe_foe_get_entry_locked(ppe, hash);
> @@ -899,6 +997,8 @@ static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
>  				   airoha_l2_flow_table_params);
>  	if (e)
>  		airoha_ppe_foe_commit_subflow_entry(ppe, e, hash, rx_wlan);
> +	else if (wdma_ready)
> +		airoha_ppe_foe_commit_subflow(ppe, &wdma_hwe, hash, rx_wlan);
>  unlock:
>  	spin_unlock_bh(&ppe_lock);
>  }
> -- 
> 2.53.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* [PATCH v2] soc: mediatek: pwrap: Remove obsolete NEED CONFIRM comments
From: Akari Tsuyukusa @ 2026-05-25  9:24 UTC (permalink / raw)
  To: matthias.bgg, angelogioacchino.delregno
  Cc: james.lo, linux-kernel, linux-arm-kernel, linux-mediatek,
	Akari Tsuyukusa
In-Reply-To: <dd1136db-1a78-4d32-a3ed-13691621c8ba@collabora.com>

Remove the obsolete "/* NEED CONFIRM */" comments from the MT8195
configuration. These values were previously confirmed during review,
but the placeholder comments were accidentally left behind.

Link: https://lore.kernel.org/all/2a117e5fe9fe0ece39e9165a463082ef42be973f.camel@mediatek.com/
Signed-off-by: Akari Tsuyukusa <akkun11.open@gmail.com>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
changes in v2
- Fix commit message (MT8196 -> MT8195)
- Add Reviewed-by tag

 drivers/soc/mediatek/mtk-pmic-wrap.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/soc/mediatek/mtk-pmic-wrap.c b/drivers/soc/mediatek/mtk-pmic-wrap.c
index 0bcd85826375..87bcbfa2d5f1 100644
--- a/drivers/soc/mediatek/mtk-pmic-wrap.c
+++ b/drivers/soc/mediatek/mtk-pmic-wrap.c
@@ -2396,8 +2396,8 @@ static const struct pmic_wrapper_type pwrap_mt8183 = {
 static const struct pmic_wrapper_type pwrap_mt8195 = {
 	.regs = mt8195_regs,
 	.type = PWRAP_MT8195,
-	.arb_en_all = 0x777f, /* NEED CONFIRM */
-	.int_en_all = 0x180000, /* NEED CONFIRM */
+	.arb_en_all = 0x777f,
+	.int_en_all = 0x180000,
 	.int1_en_all = 0,
 	.spi_w = PWRAP_MAN_CMD_SPI_WRITE,
 	.wdt_src = PWRAP_WDT_SRC_MASK_ALL,
-- 
2.54.0



^ permalink raw reply related

* Re: [RFC net PATCH v1] net: pcs: pcs-mtk-lynxi: fix bpi-r3 serdes configuration
From: Frank Wunderlich (linux) @ 2026-05-25 10:07 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: Andrew Lunn, AngeloGioacchino Del Regno, netdev, linux-mediatek,
	Russell King, Daniel Golle, Eric Dumazet, Alexander Couzens,
	linux-arm-kernel, Matthias Brugger, Jakub Kicinski, Paolo Abeni,
	David S. Miller, linux-kernel, Heiner Kallweit
In-Reply-To: <20260409215509.si2dy63seo4iaspa@skbuf>

Am 2026-04-09 23:55, schrieb Vladimir Oltean:
> On Thu, Apr 09, 2026 at 03:33:42PM +0200, Frank Wunderlich wrote:
>> From: Frank Wunderlich <frank-w@public-files.de>
>> 
>> Commit 8871389da151 introduces common pcs dts properties which writes
>> rx=normal,tx=normal polarity to register SGMSYS_QPHY_WRAP_CTRL of 
>> switch.
>> This is initialized with tx-bit set and so change inverts polarity
>> compared to before.
>> 
>> It looks like mt7531 has tx polarity inverted in hardware and set 
>> tx-bit
>> by default to restore the normal polarity.
>> 
>> Till this patch the register write was only called when 
>> mediatek,pnswap
>> property was set which cannot be done for switch because the fw-node 
>> param
>> was always NULL from switch driver in the mtk_pcs_lynxi_create call.
>> 
>> Do not configure switch side like it's done before.
>> 
>> Fixes: 8871389da151 ("net: pcs: pcs-mtk-lynxi: deprecate 
>> "mediatek,pnswap"")
>> Signed-off-by: Frank Wunderlich <frank-w@public-files.de>
>> ---
> 
> Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>

Hi,

just a gentle ping as i still not see this in torvalds/master.

regards Frank


^ permalink raw reply

* Re: [PATCH net-next] net: airoha: bind WLAN-bound flows on PPE driver L2 cache miss
From: Jihong Min @ 2026-05-25 14:13 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-arm-kernel, linux-mediatek,
	linux-kernel
In-Reply-To: <ahQDon-nio0STA4E@lore-desk>



On 5/25/26 17:09, Lorenzo Bianconi wrote:
>> The Linux bridge FDB can resolve a destination station to WDMA even when
>> the Airoha PPE driver's L2 offload cache has no entry for that MAC pair.
>> The normal bind path only checks the PPE driver's L2 offload cache, so an
>> unbound PPE hit for WLAN egress can stay unbound even though the bridge
>> already knows the right output path, unless a later offload event fills
>> that PPE driver cache.
>>
>> This matters for bridge-visible WLAN egress, such as wired-to-WLAN
>> forwarding or WLAN peer forwarding across another BSS, radio or MLO link.
>> Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
>> datapath and is not covered.
> 
> Hi Jihong,

Hi, Lorenzo.

> 
> In order to offload L2 flows, I assume you are using the OpenWrt bridger
> package, right?

Actually, no.

I am using Fanboy's OpenWrt `test` build for the Lumen W1700K2,
together with several of my other patches. It does not include the
bridger package specifically. It now uses native nft-based offloading
with `kmod-br-netfilter`.

> IIUC the issue you want to resolve is we are not adding PPE L2 entries for
> the specified cases (same-link or same-radio intra-BSS forwarding), correct?

No. As written in the patch message, this specifically addresses
bridge-visible WLAN egress, such as:

1. wired-to-WLAN forwarding
2. WLAN peer forwarding across another BSS, radio, or MLO link

Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
datapath and is not covered by this patch, although it did show poor
performance, whether that is due to shared airtime or not. That case
appears to belong to the Wi-Fi stack/driver datapath, such as the
mac80211/mt76/mt7996 path, rather than to this Airoha PPE fallback path.

> Using this approach, we are breaking the assumption PPE flow-table and hw
> flow-table are in sync. If the issue is the one described above, why not
> fixing the problem directly in the bridger package?

Again, this problem exists in an environment without bridger.

> Moreover, I see you developed the patch using Codex:gpt-5.5. Have you tested it
> on a real hw?

Yes. This has been tested on my Lumen W1700K2 with the environment
described above. MLO Wi-Fi P2P communication and some wired-to-WLAN
cases were indeed left unbound by PPE. CPU usage was high, and the
unbound throughput was close to 50% of what this patch achieves now.

> 
> Some comments inline.
> 
> Regards,
> Lorenzo
> >>
>> Before touching the PPE table, resolve the destination MAC through the
>> bridge device above the ingress netdev. If the PPE driver's L2 offload
>> cache lookup misses, bind the hardware flow to the resolved CDM4/WDMA
>> path.
>>
>> Assisted-by: Codex:gpt-5.5
>> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
>> ---
>>  drivers/net/ethernet/airoha/airoha_ppe.c | 138 +++++++++++++++++++----
>>  1 file changed, 119 insertions(+), 19 deletions(-)
>>
>> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
>> index 26da519236bf..ea932e6d87f6 100644
>> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
>> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
>> @@ -803,65 +803,163 @@ static void airoha_ppe_foe_flow_remove_entry(struct airoha_ppe *ppe,
>>  }
>>  
>>  static int
>> -airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
>> -				    struct airoha_flow_table_entry *e,
>> -				    u32 hash, bool rx_wlan)
>> +airoha_ppe_foe_commit_subflow(struct airoha_ppe *ppe,
>> +			      const struct airoha_foe_entry *bridge,
> 
> maybe l2_hwe instead of bridge?
> 
>> +			      u32 hash, bool rx_wlan)
>>  {
>>  	u32 mask = AIROHA_FOE_IB1_BIND_PACKET_TYPE | AIROHA_FOE_IB1_BIND_UDP;
>>  	struct airoha_foe_entry *hwe_p, hwe;
>> -	struct airoha_flow_table_entry *f;
>>  	int type;
>>  
>>  	hwe_p = airoha_ppe_foe_get_entry_locked(ppe, hash);
>>  	if (!hwe_p)
>>  		return -EINVAL;
>>  
>> -	f = kzalloc_obj(*f, GFP_ATOMIC);
>> -	if (!f)
>> -		return -ENOMEM;
>> -
>> -	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
>> -	f->type = FLOW_TYPE_L2_SUBFLOW;
>> -	f->hash = hash;
>> -
>>  	memcpy(&hwe, hwe_p, sizeof(*hwe_p));
>> -	hwe.ib1 = (hwe.ib1 & mask) | (e->data.ib1 & ~mask);
>> +	hwe.ib1 = (hwe.ib1 & mask) | (bridge->ib1 & ~mask);
>>  
>>  	type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe.ib1);
>>  	if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
>> -		memcpy(&hwe.ipv6.l2, &e->data.bridge.l2, sizeof(hwe.ipv6.l2));
>> -		hwe.ipv6.ib2 = e->data.bridge.ib2;
>> +		memcpy(&hwe.ipv6.l2, &bridge->bridge.l2,
>> +		       sizeof(hwe.ipv6.l2));
>> +		hwe.ipv6.ib2 = bridge->bridge.ib2;
>>  		/* setting smac_id to 0xf instruct the hw to keep original
>>  		 * source mac address
>>  		 */
>>  		hwe.ipv6.l2.src_mac_hi = FIELD_PREP(AIROHA_FOE_MAC_SMAC_ID,
>>  						    0xf);
>>  	} else {
>> -		memcpy(&hwe.bridge.l2, &e->data.bridge.l2,
>> +		memcpy(&hwe.bridge.l2, &bridge->bridge.l2,
>>  		       sizeof(hwe.bridge.l2));
>> -		hwe.bridge.ib2 = e->data.bridge.ib2;
>> +		hwe.bridge.ib2 = bridge->bridge.ib2;
>>  		if (type == PPE_PKT_TYPE_IPV4_HNAPT)
>>  			memcpy(&hwe.ipv4.new_tuple, &hwe.ipv4.orig_tuple,
>>  			       sizeof(hwe.ipv4.new_tuple));
>>  	}
>>  
>> -	hwe.bridge.data = e->data.bridge.data;
>> -	airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
>> +	hwe.bridge.data = bridge->bridge.data;
>> +
>> +	return airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
>> +}
>> +
>> +static int
>> +airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
>> +				    struct airoha_flow_table_entry *e,
>> +				    u32 hash, bool rx_wlan)
>> +{
>> +	struct airoha_flow_table_entry *f;
>> +	int err;
>> +
>> +	f = kzalloc_obj(*f, GFP_ATOMIC);
>> +	if (!f)
>> +		return -ENOMEM;
>> +
>> +	err = airoha_ppe_foe_commit_subflow(ppe, &e->data, hash, rx_wlan);
>> +	if (err) {
>> +		kfree(f);
>> +		return err;
>> +	}
>> +
>> +	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
>> +	f->type = FLOW_TYPE_L2_SUBFLOW;
>> +	f->hash = hash;
>>  
>>  	return 0;
>>  }
>>  
>> +static bool
>> +airoha_ppe_foe_prepare_wdma_subflow_dev(struct airoha_ppe *ppe,
>> +					struct net_device *dev,
>> +					struct airoha_flow_data *data,
>> +					struct airoha_foe_entry *hwe)
>> +{
>> +	u32 pse_port;
>> +	int err;
>> +
>> +	err = airoha_ppe_foe_entry_prepare(ppe->eth, hwe, dev,
>> +					   PPE_PKT_TYPE_BRIDGE, data, 0);
>> +	if (err)
>> +		return false;
>> +
>> +	pse_port = FIELD_GET(AIROHA_FOE_IB2_PSE_PORT, hwe->bridge.ib2);
>> +	if (pse_port != FE_PSE_PORT_CDM4)
>> +		return false;
>> +
>> +	return true;
> 
> 	return pse_port == FE_PSE_PORT_CDM4;
> 
>> +}
>> +
>> +static struct net_device *
>> +airoha_ppe_foe_get_bridge_master(struct net_device *dev)
>> +{
>> +	struct net_device *master = NULL;
>> +
>> +	rcu_read_lock();
>> +	master = netdev_master_upper_dev_get_rcu(dev);
>> +	if (master && netif_is_bridge_master(master))
>> +		dev_hold(master);
>> +	else
>> +		master = NULL;
>> +	rcu_read_unlock();
>> +
>> +	return master;
>> +}
>> +
>> +static bool
>> +airoha_ppe_foe_prepare_wdma_subflow(struct airoha_ppe *ppe,
>> +				    struct sk_buff *skb,
>> +				    struct airoha_foe_entry *hwe)
>> +{
>> +	struct ethhdr *eh = eth_hdr(skb);
>> +	struct airoha_flow_data data = {};
>> +	struct net_device *master;
>> +
>> +	if (!is_valid_ether_addr(eh->h_source) ||
>> +	    !is_valid_ether_addr(eh->h_dest))
>> +		return false;
>> +
>> +	ether_addr_copy(data.eth.h_dest, eh->h_dest);
>> +	ether_addr_copy(data.eth.h_source, eh->h_source);
>> +
>> +	if (!skb->dev)
>> +		return false;
>> +
>> +	/* WLAN egress unbound hits can arrive before flowtable creates the
>> +	 * L2 master flow normally used for subflow binding. Resolve only
>> +	 * through the bridge master so dev_fill_forward_path() must use the
>> +	 * bridge FDB for the destination MAC. Calling the ingress AP netdev
>> +	 * directly can describe the source station's WDMA path and would
>> +	 * corrupt Wi-Fi-to-wired flows whose real egress is not WDMA.
>> +	 */
>> +	master = airoha_ppe_foe_get_bridge_master(skb->dev);
>> +	if (!master)
>> +		return false;
>> +
>> +	if (airoha_ppe_foe_prepare_wdma_subflow_dev(ppe, master, &data,
>> +						    hwe)) {
>> +		dev_put(master);
>> +		return true;
>> +	}
>> +
>> +	dev_put(master);
>> +	return false;
> 
> maybe something like:
> 
> 	ret = airoha_ppe_foe_prepare_wdma_subflow_dev();
> 	dev_put(master);
> 
> 	return ret;
> 
>> +}
>> +
>>  static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
>>  					struct sk_buff *skb,
>>  					u32 hash, bool rx_wlan)
>>  {
>> +	struct airoha_foe_entry wdma_hwe = {};
>>  	struct airoha_flow_table_entry *e;
>>  	struct airoha_foe_bridge br = {};
>>  	struct airoha_foe_entry *hwe;
>>  	bool commit_done = false;
>> +	bool wdma_ready = false;
>>  	struct hlist_node *n;
>>  	u32 index, state;
>>  
>> +	wdma_ready = airoha_ppe_foe_prepare_wdma_subflow(ppe, skb,
>> +							 &wdma_hwe);
>> +
>>  	spin_lock_bh(&ppe_lock);
>>  
>>  	hwe = airoha_ppe_foe_get_entry_locked(ppe, hash);
>> @@ -899,6 +997,8 @@ static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
>>  				   airoha_l2_flow_table_params);
>>  	if (e)
>>  		airoha_ppe_foe_commit_subflow_entry(ppe, e, hash, rx_wlan);
>> +	else if (wdma_ready)
>> +		airoha_ppe_foe_commit_subflow(ppe, &wdma_hwe, hash, rx_wlan);
>>  unlock:
>>  	spin_unlock_bh(&ppe_lock);
>>  }
>> -- 
>> 2.53.0
>>

All inline code-style review comments will be addressed in the next
submission of the patch set, together with the responses to Sashiko's
review, if any.


^ permalink raw reply

* Re: [BUG] mt7921e: Intermittent connection failure
From: silverducks @ 2026-05-25 14:15 UTC (permalink / raw)
  To: Sean Wang
  Cc: linux-wireless, nbd, lorenzo, ryder.lee, shayne.chen, sean.wang,
	matthias.bgg, angelogioacchino.delregno,
	moderated list:ARM/Mediatek SoC support
In-Reply-To: <4d4f7e6a-b887-4541-9928-0f255bc61ee7@altlinux.org>

Greetings!

Update:
As of kernel version 7.1-rc4 the error is no longer reproduced.


^ permalink raw reply

* Re: [PATCH net-next] net: airoha: bind WLAN-bound flows on PPE driver L2 cache miss
From: Lorenzo Bianconi @ 2026-05-25 15:19 UTC (permalink / raw)
  To: Jihong Min
  Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-arm-kernel, linux-mediatek,
	linux-kernel
In-Reply-To: <a08289e7-641c-489e-8d89-6eae9f092f90@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 10873 bytes --]

> 
> 
> On 5/25/26 17:09, Lorenzo Bianconi wrote:
> >> The Linux bridge FDB can resolve a destination station to WDMA even when
> >> the Airoha PPE driver's L2 offload cache has no entry for that MAC pair.
> >> The normal bind path only checks the PPE driver's L2 offload cache, so an
> >> unbound PPE hit for WLAN egress can stay unbound even though the bridge
> >> already knows the right output path, unless a later offload event fills
> >> that PPE driver cache.
> >>
> >> This matters for bridge-visible WLAN egress, such as wired-to-WLAN
> >> forwarding or WLAN peer forwarding across another BSS, radio or MLO link.
> >> Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
> >> datapath and is not covered.
> > 
> > Hi Jihong,
> 
> Hi, Lorenzo.
> 
> > 
> > In order to offload L2 flows, I assume you are using the OpenWrt bridger
> > package, right?
> 
> Actually, no.
> 
> I am using Fanboy's OpenWrt `test` build for the Lumen W1700K2,
> together with several of my other patches. It does not include the
> bridger package specifically. It now uses native nft-based offloading
> with `kmod-br-netfilter`.

according to my understanding this is not merged yet, right? I guess the
patches should be based on official/accepted code.

> 
> > IIUC the issue you want to resolve is we are not adding PPE L2 entries for
> > the specified cases (same-link or same-radio intra-BSS forwarding), correct?
> 
> No. As written in the patch message, this specifically addresses
> bridge-visible WLAN egress, such as:
> 
> 1. wired-to-WLAN forwarding
> 2. WLAN peer forwarding across another BSS, radio, or MLO link
> 
> Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
> datapath and is not covered by this patch, although it did show poor
> performance, whether that is due to shared airtime or not. That case
> appears to belong to the Wi-Fi stack/driver datapath, such as the
> mac80211/mt76/mt7996 path, rather than to this Airoha PPE fallback path.

according to my understanding the l2 nft-based offloading solution should
add the missing info to PPE flow-table. As I pointed out, it should be
in-sync with hw flow-table. It seems a bug in the nft code to me.

> 
> > Using this approach, we are breaking the assumption PPE flow-table and hw
> > flow-table are in sync. If the issue is the one described above, why not
> > fixing the problem directly in the bridger package?
> 
> Again, this problem exists in an environment without bridger.

In order to offload L2 traffic bridger is mandatory. Do you mean the issue
occurs even on L3 scenario?

> 
> > Moreover, I see you developed the patch using Codex:gpt-5.5. Have you tested it
> > on a real hw?
> 
> Yes. This has been tested on my Lumen W1700K2 with the environment
> described above. MLO Wi-Fi P2P communication and some wired-to-WLAN
> cases were indeed left unbound by PPE. CPU usage was high, and the
> unbound throughput was close to 50% of what this patch achieves now.

ack

Regards,
Lorenzo

> 
> > 
> > Some comments inline.
> > 
> > Regards,
> > Lorenzo
> > >>
> >> Before touching the PPE table, resolve the destination MAC through the
> >> bridge device above the ingress netdev. If the PPE driver's L2 offload
> >> cache lookup misses, bind the hardware flow to the resolved CDM4/WDMA
> >> path.
> >>
> >> Assisted-by: Codex:gpt-5.5
> >> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
> >> ---
> >>  drivers/net/ethernet/airoha/airoha_ppe.c | 138 +++++++++++++++++++----
> >>  1 file changed, 119 insertions(+), 19 deletions(-)
> >>
> >> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
> >> index 26da519236bf..ea932e6d87f6 100644
> >> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
> >> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
> >> @@ -803,65 +803,163 @@ static void airoha_ppe_foe_flow_remove_entry(struct airoha_ppe *ppe,
> >>  }
> >>  
> >>  static int
> >> -airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
> >> -				    struct airoha_flow_table_entry *e,
> >> -				    u32 hash, bool rx_wlan)
> >> +airoha_ppe_foe_commit_subflow(struct airoha_ppe *ppe,
> >> +			      const struct airoha_foe_entry *bridge,
> > 
> > maybe l2_hwe instead of bridge?
> > 
> >> +			      u32 hash, bool rx_wlan)
> >>  {
> >>  	u32 mask = AIROHA_FOE_IB1_BIND_PACKET_TYPE | AIROHA_FOE_IB1_BIND_UDP;
> >>  	struct airoha_foe_entry *hwe_p, hwe;
> >> -	struct airoha_flow_table_entry *f;
> >>  	int type;
> >>  
> >>  	hwe_p = airoha_ppe_foe_get_entry_locked(ppe, hash);
> >>  	if (!hwe_p)
> >>  		return -EINVAL;
> >>  
> >> -	f = kzalloc_obj(*f, GFP_ATOMIC);
> >> -	if (!f)
> >> -		return -ENOMEM;
> >> -
> >> -	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
> >> -	f->type = FLOW_TYPE_L2_SUBFLOW;
> >> -	f->hash = hash;
> >> -
> >>  	memcpy(&hwe, hwe_p, sizeof(*hwe_p));
> >> -	hwe.ib1 = (hwe.ib1 & mask) | (e->data.ib1 & ~mask);
> >> +	hwe.ib1 = (hwe.ib1 & mask) | (bridge->ib1 & ~mask);
> >>  
> >>  	type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe.ib1);
> >>  	if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
> >> -		memcpy(&hwe.ipv6.l2, &e->data.bridge.l2, sizeof(hwe.ipv6.l2));
> >> -		hwe.ipv6.ib2 = e->data.bridge.ib2;
> >> +		memcpy(&hwe.ipv6.l2, &bridge->bridge.l2,
> >> +		       sizeof(hwe.ipv6.l2));
> >> +		hwe.ipv6.ib2 = bridge->bridge.ib2;
> >>  		/* setting smac_id to 0xf instruct the hw to keep original
> >>  		 * source mac address
> >>  		 */
> >>  		hwe.ipv6.l2.src_mac_hi = FIELD_PREP(AIROHA_FOE_MAC_SMAC_ID,
> >>  						    0xf);
> >>  	} else {
> >> -		memcpy(&hwe.bridge.l2, &e->data.bridge.l2,
> >> +		memcpy(&hwe.bridge.l2, &bridge->bridge.l2,
> >>  		       sizeof(hwe.bridge.l2));
> >> -		hwe.bridge.ib2 = e->data.bridge.ib2;
> >> +		hwe.bridge.ib2 = bridge->bridge.ib2;
> >>  		if (type == PPE_PKT_TYPE_IPV4_HNAPT)
> >>  			memcpy(&hwe.ipv4.new_tuple, &hwe.ipv4.orig_tuple,
> >>  			       sizeof(hwe.ipv4.new_tuple));
> >>  	}
> >>  
> >> -	hwe.bridge.data = e->data.bridge.data;
> >> -	airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
> >> +	hwe.bridge.data = bridge->bridge.data;
> >> +
> >> +	return airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
> >> +}
> >> +
> >> +static int
> >> +airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
> >> +				    struct airoha_flow_table_entry *e,
> >> +				    u32 hash, bool rx_wlan)
> >> +{
> >> +	struct airoha_flow_table_entry *f;
> >> +	int err;
> >> +
> >> +	f = kzalloc_obj(*f, GFP_ATOMIC);
> >> +	if (!f)
> >> +		return -ENOMEM;
> >> +
> >> +	err = airoha_ppe_foe_commit_subflow(ppe, &e->data, hash, rx_wlan);
> >> +	if (err) {
> >> +		kfree(f);
> >> +		return err;
> >> +	}
> >> +
> >> +	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
> >> +	f->type = FLOW_TYPE_L2_SUBFLOW;
> >> +	f->hash = hash;
> >>  
> >>  	return 0;
> >>  }
> >>  
> >> +static bool
> >> +airoha_ppe_foe_prepare_wdma_subflow_dev(struct airoha_ppe *ppe,
> >> +					struct net_device *dev,
> >> +					struct airoha_flow_data *data,
> >> +					struct airoha_foe_entry *hwe)
> >> +{
> >> +	u32 pse_port;
> >> +	int err;
> >> +
> >> +	err = airoha_ppe_foe_entry_prepare(ppe->eth, hwe, dev,
> >> +					   PPE_PKT_TYPE_BRIDGE, data, 0);
> >> +	if (err)
> >> +		return false;
> >> +
> >> +	pse_port = FIELD_GET(AIROHA_FOE_IB2_PSE_PORT, hwe->bridge.ib2);
> >> +	if (pse_port != FE_PSE_PORT_CDM4)
> >> +		return false;
> >> +
> >> +	return true;
> > 
> > 	return pse_port == FE_PSE_PORT_CDM4;
> > 
> >> +}
> >> +
> >> +static struct net_device *
> >> +airoha_ppe_foe_get_bridge_master(struct net_device *dev)
> >> +{
> >> +	struct net_device *master = NULL;
> >> +
> >> +	rcu_read_lock();
> >> +	master = netdev_master_upper_dev_get_rcu(dev);
> >> +	if (master && netif_is_bridge_master(master))
> >> +		dev_hold(master);
> >> +	else
> >> +		master = NULL;
> >> +	rcu_read_unlock();
> >> +
> >> +	return master;
> >> +}
> >> +
> >> +static bool
> >> +airoha_ppe_foe_prepare_wdma_subflow(struct airoha_ppe *ppe,
> >> +				    struct sk_buff *skb,
> >> +				    struct airoha_foe_entry *hwe)
> >> +{
> >> +	struct ethhdr *eh = eth_hdr(skb);
> >> +	struct airoha_flow_data data = {};
> >> +	struct net_device *master;
> >> +
> >> +	if (!is_valid_ether_addr(eh->h_source) ||
> >> +	    !is_valid_ether_addr(eh->h_dest))
> >> +		return false;
> >> +
> >> +	ether_addr_copy(data.eth.h_dest, eh->h_dest);
> >> +	ether_addr_copy(data.eth.h_source, eh->h_source);
> >> +
> >> +	if (!skb->dev)
> >> +		return false;
> >> +
> >> +	/* WLAN egress unbound hits can arrive before flowtable creates the
> >> +	 * L2 master flow normally used for subflow binding. Resolve only
> >> +	 * through the bridge master so dev_fill_forward_path() must use the
> >> +	 * bridge FDB for the destination MAC. Calling the ingress AP netdev
> >> +	 * directly can describe the source station's WDMA path and would
> >> +	 * corrupt Wi-Fi-to-wired flows whose real egress is not WDMA.
> >> +	 */
> >> +	master = airoha_ppe_foe_get_bridge_master(skb->dev);
> >> +	if (!master)
> >> +		return false;
> >> +
> >> +	if (airoha_ppe_foe_prepare_wdma_subflow_dev(ppe, master, &data,
> >> +						    hwe)) {
> >> +		dev_put(master);
> >> +		return true;
> >> +	}
> >> +
> >> +	dev_put(master);
> >> +	return false;
> > 
> > maybe something like:
> > 
> > 	ret = airoha_ppe_foe_prepare_wdma_subflow_dev();
> > 	dev_put(master);
> > 
> > 	return ret;
> > 
> >> +}
> >> +
> >>  static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
> >>  					struct sk_buff *skb,
> >>  					u32 hash, bool rx_wlan)
> >>  {
> >> +	struct airoha_foe_entry wdma_hwe = {};
> >>  	struct airoha_flow_table_entry *e;
> >>  	struct airoha_foe_bridge br = {};
> >>  	struct airoha_foe_entry *hwe;
> >>  	bool commit_done = false;
> >> +	bool wdma_ready = false;
> >>  	struct hlist_node *n;
> >>  	u32 index, state;
> >>  
> >> +	wdma_ready = airoha_ppe_foe_prepare_wdma_subflow(ppe, skb,
> >> +							 &wdma_hwe);
> >> +
> >>  	spin_lock_bh(&ppe_lock);
> >>  
> >>  	hwe = airoha_ppe_foe_get_entry_locked(ppe, hash);
> >> @@ -899,6 +997,8 @@ static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
> >>  				   airoha_l2_flow_table_params);
> >>  	if (e)
> >>  		airoha_ppe_foe_commit_subflow_entry(ppe, e, hash, rx_wlan);
> >> +	else if (wdma_ready)
> >> +		airoha_ppe_foe_commit_subflow(ppe, &wdma_hwe, hash, rx_wlan);
> >>  unlock:
> >>  	spin_unlock_bh(&ppe_lock);
> >>  }
> >> -- 
> >> 2.53.0
> >>
> 
> All inline code-style review comments will be addressed in the next
> submission of the patch set, together with the responses to Sashiko's
> review, if any.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [RFC net PATCH v1] net: pcs: pcs-mtk-lynxi: fix bpi-r3 serdes configuration
From: Jakub Kicinski @ 2026-05-25 15:28 UTC (permalink / raw)
  To: Frank Wunderlich (linux)
  Cc: Andrew Lunn, AngeloGioacchino Del Regno, Vladimir Oltean,
	Russell King, Daniel Golle, Eric Dumazet, Alexander Couzens,
	linux-arm-kernel, netdev, Matthias Brugger, linux-mediatek,
	Paolo Abeni, David S. Miller, linux-kernel, Heiner Kallweit
In-Reply-To: <64b40fbee19dc108752574419a38bcfc@fw-web.de>

On Mon, 25 May 2026 12:07:33 +0200 Frank Wunderlich (linux) wrote:
> Am 2026-04-09 23:55, schrieb Vladimir Oltean:
> > On Thu, Apr 09, 2026 at 03:33:42PM +0200, Frank Wunderlich wrote:  
> >> From: Frank Wunderlich <frank-w@public-files.de>
> >> 
> >> Commit 8871389da151 introduces common pcs dts properties which writes
> >> rx=normal,tx=normal polarity to register SGMSYS_QPHY_WRAP_CTRL of 
> >> switch.
> >> This is initialized with tx-bit set and so change inverts polarity
> >> compared to before.
> >> 
> >> It looks like mt7531 has tx polarity inverted in hardware and set 
> >> tx-bit
> >> by default to restore the normal polarity.
> >> 
> >> Till this patch the register write was only called when 
> >> mediatek,pnswap
> >> property was set which cannot be done for switch because the fw-node 
> >> param
> >> was always NULL from switch driver in the mtk_pcs_lynxi_create call.
> >> 
> >> Do not configure switch side like it's done before.
> >> 
> >> Fixes: 8871389da151 ("net: pcs: pcs-mtk-lynxi: deprecate 
> >> "mediatek,pnswap"")
> >> Signed-off-by: Frank Wunderlich <frank-w@public-files.de>
> >> ---  
> > 
> > Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>  
> 
> just a gentle ping as i still not see this in torvalds/master.
 
You posted it as RFC, patchwork treats RFC as "no action required".
Since the posting is now >1mo old, please resend without the RFC tag?


^ permalink raw reply

* Re: [PATCH v2 0/2] ASoC: codecs: max98090: switch to standard set_jack callback
From: Mark Brown @ 2026-05-25 12:39 UTC (permalink / raw)
  To: Srinivas Kandagatla
  Cc: lgirdwood, perex, tiwai, matthias.bgg, angelogioacchino.delregno,
	sharq0406, kuninori.morimoto.gx, ckeepax, linux-sound,
	linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260520155002.145306-1-srinivas.kandagatla@oss.qualcomm.com>

On Wed, 20 May 2026 15:50:00 +0000, Srinivas Kandagatla wrote:
> ASoC: codecs: max98090: switch to standard set_jack callback
> 
> The MAX98090 codec driver currently exposes a custom
> max98090_mic_detect() helper for machine drivers to register a headset
> jack.
> 
> This series converts the driver to use the standard component
> .set_jack callback and updates the mt8173-max98090 machine driver to use
> snd_soc_component_set_jack() instead of the codec-specific helper.
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-7.2

Thanks!

[1/2] ASoC: mt8173-max98090: use standard callback to set jack
      https://git.kernel.org/broonie/sound/c/bad83abb5c29
[2/2] ASoC: codecs: max98090: use component set_jack callback
      https://git.kernel.org/broonie/sound/c/e2c428c3832e

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark



^ permalink raw reply

* Re: [PATCH net-next] net: airoha: bind WLAN-bound flows on PPE driver L2 cache miss
From: Jihong Min @ 2026-05-25 16:05 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-arm-kernel, linux-mediatek,
	linux-kernel
In-Reply-To: <ahRonGRDCqdFTBQm@lore-desk>



On 5/26/26 00:19, Lorenzo Bianconi wrote:
>>
>>
>> On 5/25/26 17:09, Lorenzo Bianconi wrote:
>>>> The Linux bridge FDB can resolve a destination station to WDMA even when
>>>> the Airoha PPE driver's L2 offload cache has no entry for that MAC pair.
>>>> The normal bind path only checks the PPE driver's L2 offload cache, so an
>>>> unbound PPE hit for WLAN egress can stay unbound even though the bridge
>>>> already knows the right output path, unless a later offload event fills
>>>> that PPE driver cache.
>>>>
>>>> This matters for bridge-visible WLAN egress, such as wired-to-WLAN
>>>> forwarding or WLAN peer forwarding across another BSS, radio or MLO link.
>>>> Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
>>>> datapath and is not covered.
>>>
>>> Hi Jihong,
>>
>> Hi, Lorenzo.
>>
>>>
>>> In order to offload L2 flows, I assume you are using the OpenWrt bridger
>>> package, right?
>>
>> Actually, no.
>>
>> I am using Fanboy's OpenWrt `test` build for the Lumen W1700K2,
>> together with several of my other patches. It does not include the
>> bridger package specifically. It now uses native nft-based offloading
>> with `kmod-br-netfilter`.
> 
> according to my understanding this is not merged yet, right? I guess the
> patches should be based on official/accepted code.
> 

Yes, you are right. I just checked current net-next and realized that the
bridge/L2 nft_flow_offload pieces used in my test environment are not
merged upstream yet. Sorry, I should have checked this before submitting
the patch.

Since I cannot properly test the current upstream code plus bridger at the
moment, I will put this patch on hold.

I will also check whether this should instead be fixed on the
nft_flow_offload side.


Sincerely,
Jihong Min

>>
>>> IIUC the issue you want to resolve is we are not adding PPE L2 entries for
>>> the specified cases (same-link or same-radio intra-BSS forwarding), correct?
>>
>> No. As written in the patch message, this specifically addresses
>> bridge-visible WLAN egress, such as:
>>
>> 1. wired-to-WLAN forwarding
>> 2. WLAN peer forwarding across another BSS, radio, or MLO link
>>
>> Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
>> datapath and is not covered by this patch, although it did show poor
>> performance, whether that is due to shared airtime or not. That case
>> appears to belong to the Wi-Fi stack/driver datapath, such as the
>> mac80211/mt76/mt7996 path, rather than to this Airoha PPE fallback path.
> 
> according to my understanding the l2 nft-based offloading solution should
> add the missing info to PPE flow-table. As I pointed out, it should be
> in-sync with hw flow-table. It seems a bug in the nft code to me.
> 
>>
>>> Using this approach, we are breaking the assumption PPE flow-table and hw
>>> flow-table are in sync. If the issue is the one described above, why not
>>> fixing the problem directly in the bridger package?
>>
>> Again, this problem exists in an environment without bridger.
> 
> In order to offload L2 traffic bridger is mandatory. Do you mean the issue
> occurs even on L3 scenario?
> 
>>
>>> Moreover, I see you developed the patch using Codex:gpt-5.5. Have you tested it
>>> on a real hw?
>>
>> Yes. This has been tested on my Lumen W1700K2 with the environment
>> described above. MLO Wi-Fi P2P communication and some wired-to-WLAN
>> cases were indeed left unbound by PPE. CPU usage was high, and the
>> unbound throughput was close to 50% of what this patch achieves now.
> 
> ack
> 
> Regards,
> Lorenzo
> 
>>
>>>
>>> Some comments inline.
>>>
>>> Regards,
>>> Lorenzo
>>>>>
>>>> Before touching the PPE table, resolve the destination MAC through the
>>>> bridge device above the ingress netdev. If the PPE driver's L2 offload
>>>> cache lookup misses, bind the hardware flow to the resolved CDM4/WDMA
>>>> path.
>>>>
>>>> Assisted-by: Codex:gpt-5.5
>>>> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
>>>> ---
>>>>  drivers/net/ethernet/airoha/airoha_ppe.c | 138 +++++++++++++++++++----
>>>>  1 file changed, 119 insertions(+), 19 deletions(-)
>>>>
>>>> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
>>>> index 26da519236bf..ea932e6d87f6 100644
>>>> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
>>>> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
>>>> @@ -803,65 +803,163 @@ static void airoha_ppe_foe_flow_remove_entry(struct airoha_ppe *ppe,
>>>>  }
>>>>  
>>>>  static int
>>>> -airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
>>>> -				    struct airoha_flow_table_entry *e,
>>>> -				    u32 hash, bool rx_wlan)
>>>> +airoha_ppe_foe_commit_subflow(struct airoha_ppe *ppe,
>>>> +			      const struct airoha_foe_entry *bridge,
>>>
>>> maybe l2_hwe instead of bridge?
>>>
>>>> +			      u32 hash, bool rx_wlan)
>>>>  {
>>>>  	u32 mask = AIROHA_FOE_IB1_BIND_PACKET_TYPE | AIROHA_FOE_IB1_BIND_UDP;
>>>>  	struct airoha_foe_entry *hwe_p, hwe;
>>>> -	struct airoha_flow_table_entry *f;
>>>>  	int type;
>>>>  
>>>>  	hwe_p = airoha_ppe_foe_get_entry_locked(ppe, hash);
>>>>  	if (!hwe_p)
>>>>  		return -EINVAL;
>>>>  
>>>> -	f = kzalloc_obj(*f, GFP_ATOMIC);
>>>> -	if (!f)
>>>> -		return -ENOMEM;
>>>> -
>>>> -	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
>>>> -	f->type = FLOW_TYPE_L2_SUBFLOW;
>>>> -	f->hash = hash;
>>>> -
>>>>  	memcpy(&hwe, hwe_p, sizeof(*hwe_p));
>>>> -	hwe.ib1 = (hwe.ib1 & mask) | (e->data.ib1 & ~mask);
>>>> +	hwe.ib1 = (hwe.ib1 & mask) | (bridge->ib1 & ~mask);
>>>>  
>>>>  	type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe.ib1);
>>>>  	if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
>>>> -		memcpy(&hwe.ipv6.l2, &e->data.bridge.l2, sizeof(hwe.ipv6.l2));
>>>> -		hwe.ipv6.ib2 = e->data.bridge.ib2;
>>>> +		memcpy(&hwe.ipv6.l2, &bridge->bridge.l2,
>>>> +		       sizeof(hwe.ipv6.l2));
>>>> +		hwe.ipv6.ib2 = bridge->bridge.ib2;
>>>>  		/* setting smac_id to 0xf instruct the hw to keep original
>>>>  		 * source mac address
>>>>  		 */
>>>>  		hwe.ipv6.l2.src_mac_hi = FIELD_PREP(AIROHA_FOE_MAC_SMAC_ID,
>>>>  						    0xf);
>>>>  	} else {
>>>> -		memcpy(&hwe.bridge.l2, &e->data.bridge.l2,
>>>> +		memcpy(&hwe.bridge.l2, &bridge->bridge.l2,
>>>>  		       sizeof(hwe.bridge.l2));
>>>> -		hwe.bridge.ib2 = e->data.bridge.ib2;
>>>> +		hwe.bridge.ib2 = bridge->bridge.ib2;
>>>>  		if (type == PPE_PKT_TYPE_IPV4_HNAPT)
>>>>  			memcpy(&hwe.ipv4.new_tuple, &hwe.ipv4.orig_tuple,
>>>>  			       sizeof(hwe.ipv4.new_tuple));
>>>>  	}
>>>>  
>>>> -	hwe.bridge.data = e->data.bridge.data;
>>>> -	airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
>>>> +	hwe.bridge.data = bridge->bridge.data;
>>>> +
>>>> +	return airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
>>>> +}
>>>> +
>>>> +static int
>>>> +airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
>>>> +				    struct airoha_flow_table_entry *e,
>>>> +				    u32 hash, bool rx_wlan)
>>>> +{
>>>> +	struct airoha_flow_table_entry *f;
>>>> +	int err;
>>>> +
>>>> +	f = kzalloc_obj(*f, GFP_ATOMIC);
>>>> +	if (!f)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	err = airoha_ppe_foe_commit_subflow(ppe, &e->data, hash, rx_wlan);
>>>> +	if (err) {
>>>> +		kfree(f);
>>>> +		return err;
>>>> +	}
>>>> +
>>>> +	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
>>>> +	f->type = FLOW_TYPE_L2_SUBFLOW;
>>>> +	f->hash = hash;
>>>>  
>>>>  	return 0;
>>>>  }
>>>>  
>>>> +static bool
>>>> +airoha_ppe_foe_prepare_wdma_subflow_dev(struct airoha_ppe *ppe,
>>>> +					struct net_device *dev,
>>>> +					struct airoha_flow_data *data,
>>>> +					struct airoha_foe_entry *hwe)
>>>> +{
>>>> +	u32 pse_port;
>>>> +	int err;
>>>> +
>>>> +	err = airoha_ppe_foe_entry_prepare(ppe->eth, hwe, dev,
>>>> +					   PPE_PKT_TYPE_BRIDGE, data, 0);
>>>> +	if (err)
>>>> +		return false;
>>>> +
>>>> +	pse_port = FIELD_GET(AIROHA_FOE_IB2_PSE_PORT, hwe->bridge.ib2);
>>>> +	if (pse_port != FE_PSE_PORT_CDM4)
>>>> +		return false;
>>>> +
>>>> +	return true;
>>>
>>> 	return pse_port == FE_PSE_PORT_CDM4;
>>>
>>>> +}
>>>> +
>>>> +static struct net_device *
>>>> +airoha_ppe_foe_get_bridge_master(struct net_device *dev)
>>>> +{
>>>> +	struct net_device *master = NULL;
>>>> +
>>>> +	rcu_read_lock();
>>>> +	master = netdev_master_upper_dev_get_rcu(dev);
>>>> +	if (master && netif_is_bridge_master(master))
>>>> +		dev_hold(master);
>>>> +	else
>>>> +		master = NULL;
>>>> +	rcu_read_unlock();
>>>> +
>>>> +	return master;
>>>> +}
>>>> +
>>>> +static bool
>>>> +airoha_ppe_foe_prepare_wdma_subflow(struct airoha_ppe *ppe,
>>>> +				    struct sk_buff *skb,
>>>> +				    struct airoha_foe_entry *hwe)
>>>> +{
>>>> +	struct ethhdr *eh = eth_hdr(skb);
>>>> +	struct airoha_flow_data data = {};
>>>> +	struct net_device *master;
>>>> +
>>>> +	if (!is_valid_ether_addr(eh->h_source) ||
>>>> +	    !is_valid_ether_addr(eh->h_dest))
>>>> +		return false;
>>>> +
>>>> +	ether_addr_copy(data.eth.h_dest, eh->h_dest);
>>>> +	ether_addr_copy(data.eth.h_source, eh->h_source);
>>>> +
>>>> +	if (!skb->dev)
>>>> +		return false;
>>>> +
>>>> +	/* WLAN egress unbound hits can arrive before flowtable creates the
>>>> +	 * L2 master flow normally used for subflow binding. Resolve only
>>>> +	 * through the bridge master so dev_fill_forward_path() must use the
>>>> +	 * bridge FDB for the destination MAC. Calling the ingress AP netdev
>>>> +	 * directly can describe the source station's WDMA path and would
>>>> +	 * corrupt Wi-Fi-to-wired flows whose real egress is not WDMA.
>>>> +	 */
>>>> +	master = airoha_ppe_foe_get_bridge_master(skb->dev);
>>>> +	if (!master)
>>>> +		return false;
>>>> +
>>>> +	if (airoha_ppe_foe_prepare_wdma_subflow_dev(ppe, master, &data,
>>>> +						    hwe)) {
>>>> +		dev_put(master);
>>>> +		return true;
>>>> +	}
>>>> +
>>>> +	dev_put(master);
>>>> +	return false;
>>>
>>> maybe something like:
>>>
>>> 	ret = airoha_ppe_foe_prepare_wdma_subflow_dev();
>>> 	dev_put(master);
>>>
>>> 	return ret;
>>>
>>>> +}
>>>> +
>>>>  static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
>>>>  					struct sk_buff *skb,
>>>>  					u32 hash, bool rx_wlan)
>>>>  {
>>>> +	struct airoha_foe_entry wdma_hwe = {};
>>>>  	struct airoha_flow_table_entry *e;
>>>>  	struct airoha_foe_bridge br = {};
>>>>  	struct airoha_foe_entry *hwe;
>>>>  	bool commit_done = false;
>>>> +	bool wdma_ready = false;
>>>>  	struct hlist_node *n;
>>>>  	u32 index, state;
>>>>  
>>>> +	wdma_ready = airoha_ppe_foe_prepare_wdma_subflow(ppe, skb,
>>>> +							 &wdma_hwe);
>>>> +
>>>>  	spin_lock_bh(&ppe_lock);
>>>>  
>>>>  	hwe = airoha_ppe_foe_get_entry_locked(ppe, hash);
>>>> @@ -899,6 +997,8 @@ static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
>>>>  				   airoha_l2_flow_table_params);
>>>>  	if (e)
>>>>  		airoha_ppe_foe_commit_subflow_entry(ppe, e, hash, rx_wlan);
>>>> +	else if (wdma_ready)
>>>> +		airoha_ppe_foe_commit_subflow(ppe, &wdma_hwe, hash, rx_wlan);
>>>>  unlock:
>>>>  	spin_unlock_bh(&ppe_lock);
>>>>  }
>>>> -- 
>>>> 2.53.0
>>>>
>>
>> All inline code-style review comments will be addressed in the next
>> submission of the patch set, together with the responses to Sashiko's
>> review, if any.



^ permalink raw reply

* Re: [PATCH net-next] net: airoha: bind WLAN-bound flows on PPE driver L2 cache miss
From: Lorenzo Bianconi @ 2026-05-25 16:32 UTC (permalink / raw)
  To: Jihong Min
  Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, linux-arm-kernel, linux-mediatek,
	linux-kernel
In-Reply-To: <82d0e4a5-76b1-4c86-a153-2500c60e8063@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 12161 bytes --]

> 
> 
> On 5/26/26 00:19, Lorenzo Bianconi wrote:
> >>
> >>
> >> On 5/25/26 17:09, Lorenzo Bianconi wrote:
> >>>> The Linux bridge FDB can resolve a destination station to WDMA even when
> >>>> the Airoha PPE driver's L2 offload cache has no entry for that MAC pair.
> >>>> The normal bind path only checks the PPE driver's L2 offload cache, so an
> >>>> unbound PPE hit for WLAN egress can stay unbound even though the bridge
> >>>> already knows the right output path, unless a later offload event fills
> >>>> that PPE driver cache.
> >>>>
> >>>> This matters for bridge-visible WLAN egress, such as wired-to-WLAN
> >>>> forwarding or WLAN peer forwarding across another BSS, radio or MLO link.
> >>>> Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
> >>>> datapath and is not covered.
> >>>
> >>> Hi Jihong,
> >>
> >> Hi, Lorenzo.
> >>
> >>>
> >>> In order to offload L2 flows, I assume you are using the OpenWrt bridger
> >>> package, right?
> >>
> >> Actually, no.
> >>
> >> I am using Fanboy's OpenWrt `test` build for the Lumen W1700K2,
> >> together with several of my other patches. It does not include the
> >> bridger package specifically. It now uses native nft-based offloading
> >> with `kmod-br-netfilter`.
> > 
> > according to my understanding this is not merged yet, right? I guess the
> > patches should be based on official/accepted code.
> > 
> 
> Yes, you are right. I just checked current net-next and realized that the
> bridge/L2 nft_flow_offload pieces used in my test environment are not
> merged upstream yet. Sorry, I should have checked this before submitting
> the patch.

ack, no worries ;)

> 
> Since I cannot properly test the current upstream code plus bridger at the
> moment, I will put this patch on hold.
> 
> I will also check whether this should instead be fixed on the
> nft_flow_offload side.

ack.

Regards,
Lorenzo

> 
> 
> Sincerely,
> Jihong Min
> 
> >>
> >>> IIUC the issue you want to resolve is we are not adding PPE L2 entries for
> >>> the specified cases (same-link or same-radio intra-BSS forwarding), correct?
> >>
> >> No. As written in the patch message, this specifically addresses
> >> bridge-visible WLAN egress, such as:
> >>
> >> 1. wired-to-WLAN forwarding
> >> 2. WLAN peer forwarding across another BSS, radio, or MLO link
> >>
> >> Same-link or same-radio intra-BSS forwarding can stay inside the WLAN
> >> datapath and is not covered by this patch, although it did show poor
> >> performance, whether that is due to shared airtime or not. That case
> >> appears to belong to the Wi-Fi stack/driver datapath, such as the
> >> mac80211/mt76/mt7996 path, rather than to this Airoha PPE fallback path.
> > 
> > according to my understanding the l2 nft-based offloading solution should
> > add the missing info to PPE flow-table. As I pointed out, it should be
> > in-sync with hw flow-table. It seems a bug in the nft code to me.
> > 
> >>
> >>> Using this approach, we are breaking the assumption PPE flow-table and hw
> >>> flow-table are in sync. If the issue is the one described above, why not
> >>> fixing the problem directly in the bridger package?
> >>
> >> Again, this problem exists in an environment without bridger.
> > 
> > In order to offload L2 traffic bridger is mandatory. Do you mean the issue
> > occurs even on L3 scenario?
> > 
> >>
> >>> Moreover, I see you developed the patch using Codex:gpt-5.5. Have you tested it
> >>> on a real hw?
> >>
> >> Yes. This has been tested on my Lumen W1700K2 with the environment
> >> described above. MLO Wi-Fi P2P communication and some wired-to-WLAN
> >> cases were indeed left unbound by PPE. CPU usage was high, and the
> >> unbound throughput was close to 50% of what this patch achieves now.
> > 
> > ack
> > 
> > Regards,
> > Lorenzo
> > 
> >>
> >>>
> >>> Some comments inline.
> >>>
> >>> Regards,
> >>> Lorenzo
> >>>>>
> >>>> Before touching the PPE table, resolve the destination MAC through the
> >>>> bridge device above the ingress netdev. If the PPE driver's L2 offload
> >>>> cache lookup misses, bind the hardware flow to the resolved CDM4/WDMA
> >>>> path.
> >>>>
> >>>> Assisted-by: Codex:gpt-5.5
> >>>> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
> >>>> ---
> >>>>  drivers/net/ethernet/airoha/airoha_ppe.c | 138 +++++++++++++++++++----
> >>>>  1 file changed, 119 insertions(+), 19 deletions(-)
> >>>>
> >>>> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
> >>>> index 26da519236bf..ea932e6d87f6 100644
> >>>> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
> >>>> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
> >>>> @@ -803,65 +803,163 @@ static void airoha_ppe_foe_flow_remove_entry(struct airoha_ppe *ppe,
> >>>>  }
> >>>>  
> >>>>  static int
> >>>> -airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
> >>>> -				    struct airoha_flow_table_entry *e,
> >>>> -				    u32 hash, bool rx_wlan)
> >>>> +airoha_ppe_foe_commit_subflow(struct airoha_ppe *ppe,
> >>>> +			      const struct airoha_foe_entry *bridge,
> >>>
> >>> maybe l2_hwe instead of bridge?
> >>>
> >>>> +			      u32 hash, bool rx_wlan)
> >>>>  {
> >>>>  	u32 mask = AIROHA_FOE_IB1_BIND_PACKET_TYPE | AIROHA_FOE_IB1_BIND_UDP;
> >>>>  	struct airoha_foe_entry *hwe_p, hwe;
> >>>> -	struct airoha_flow_table_entry *f;
> >>>>  	int type;
> >>>>  
> >>>>  	hwe_p = airoha_ppe_foe_get_entry_locked(ppe, hash);
> >>>>  	if (!hwe_p)
> >>>>  		return -EINVAL;
> >>>>  
> >>>> -	f = kzalloc_obj(*f, GFP_ATOMIC);
> >>>> -	if (!f)
> >>>> -		return -ENOMEM;
> >>>> -
> >>>> -	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
> >>>> -	f->type = FLOW_TYPE_L2_SUBFLOW;
> >>>> -	f->hash = hash;
> >>>> -
> >>>>  	memcpy(&hwe, hwe_p, sizeof(*hwe_p));
> >>>> -	hwe.ib1 = (hwe.ib1 & mask) | (e->data.ib1 & ~mask);
> >>>> +	hwe.ib1 = (hwe.ib1 & mask) | (bridge->ib1 & ~mask);
> >>>>  
> >>>>  	type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe.ib1);
> >>>>  	if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
> >>>> -		memcpy(&hwe.ipv6.l2, &e->data.bridge.l2, sizeof(hwe.ipv6.l2));
> >>>> -		hwe.ipv6.ib2 = e->data.bridge.ib2;
> >>>> +		memcpy(&hwe.ipv6.l2, &bridge->bridge.l2,
> >>>> +		       sizeof(hwe.ipv6.l2));
> >>>> +		hwe.ipv6.ib2 = bridge->bridge.ib2;
> >>>>  		/* setting smac_id to 0xf instruct the hw to keep original
> >>>>  		 * source mac address
> >>>>  		 */
> >>>>  		hwe.ipv6.l2.src_mac_hi = FIELD_PREP(AIROHA_FOE_MAC_SMAC_ID,
> >>>>  						    0xf);
> >>>>  	} else {
> >>>> -		memcpy(&hwe.bridge.l2, &e->data.bridge.l2,
> >>>> +		memcpy(&hwe.bridge.l2, &bridge->bridge.l2,
> >>>>  		       sizeof(hwe.bridge.l2));
> >>>> -		hwe.bridge.ib2 = e->data.bridge.ib2;
> >>>> +		hwe.bridge.ib2 = bridge->bridge.ib2;
> >>>>  		if (type == PPE_PKT_TYPE_IPV4_HNAPT)
> >>>>  			memcpy(&hwe.ipv4.new_tuple, &hwe.ipv4.orig_tuple,
> >>>>  			       sizeof(hwe.ipv4.new_tuple));
> >>>>  	}
> >>>>  
> >>>> -	hwe.bridge.data = e->data.bridge.data;
> >>>> -	airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
> >>>> +	hwe.bridge.data = bridge->bridge.data;
> >>>> +
> >>>> +	return airoha_ppe_foe_commit_entry(ppe, &hwe, hash, rx_wlan);
> >>>> +}
> >>>> +
> >>>> +static int
> >>>> +airoha_ppe_foe_commit_subflow_entry(struct airoha_ppe *ppe,
> >>>> +				    struct airoha_flow_table_entry *e,
> >>>> +				    u32 hash, bool rx_wlan)
> >>>> +{
> >>>> +	struct airoha_flow_table_entry *f;
> >>>> +	int err;
> >>>> +
> >>>> +	f = kzalloc_obj(*f, GFP_ATOMIC);
> >>>> +	if (!f)
> >>>> +		return -ENOMEM;
> >>>> +
> >>>> +	err = airoha_ppe_foe_commit_subflow(ppe, &e->data, hash, rx_wlan);
> >>>> +	if (err) {
> >>>> +		kfree(f);
> >>>> +		return err;
> >>>> +	}
> >>>> +
> >>>> +	hlist_add_head(&f->l2_subflow_node, &e->l2_flows);
> >>>> +	f->type = FLOW_TYPE_L2_SUBFLOW;
> >>>> +	f->hash = hash;
> >>>>  
> >>>>  	return 0;
> >>>>  }
> >>>>  
> >>>> +static bool
> >>>> +airoha_ppe_foe_prepare_wdma_subflow_dev(struct airoha_ppe *ppe,
> >>>> +					struct net_device *dev,
> >>>> +					struct airoha_flow_data *data,
> >>>> +					struct airoha_foe_entry *hwe)
> >>>> +{
> >>>> +	u32 pse_port;
> >>>> +	int err;
> >>>> +
> >>>> +	err = airoha_ppe_foe_entry_prepare(ppe->eth, hwe, dev,
> >>>> +					   PPE_PKT_TYPE_BRIDGE, data, 0);
> >>>> +	if (err)
> >>>> +		return false;
> >>>> +
> >>>> +	pse_port = FIELD_GET(AIROHA_FOE_IB2_PSE_PORT, hwe->bridge.ib2);
> >>>> +	if (pse_port != FE_PSE_PORT_CDM4)
> >>>> +		return false;
> >>>> +
> >>>> +	return true;
> >>>
> >>> 	return pse_port == FE_PSE_PORT_CDM4;
> >>>
> >>>> +}
> >>>> +
> >>>> +static struct net_device *
> >>>> +airoha_ppe_foe_get_bridge_master(struct net_device *dev)
> >>>> +{
> >>>> +	struct net_device *master = NULL;
> >>>> +
> >>>> +	rcu_read_lock();
> >>>> +	master = netdev_master_upper_dev_get_rcu(dev);
> >>>> +	if (master && netif_is_bridge_master(master))
> >>>> +		dev_hold(master);
> >>>> +	else
> >>>> +		master = NULL;
> >>>> +	rcu_read_unlock();
> >>>> +
> >>>> +	return master;
> >>>> +}
> >>>> +
> >>>> +static bool
> >>>> +airoha_ppe_foe_prepare_wdma_subflow(struct airoha_ppe *ppe,
> >>>> +				    struct sk_buff *skb,
> >>>> +				    struct airoha_foe_entry *hwe)
> >>>> +{
> >>>> +	struct ethhdr *eh = eth_hdr(skb);
> >>>> +	struct airoha_flow_data data = {};
> >>>> +	struct net_device *master;
> >>>> +
> >>>> +	if (!is_valid_ether_addr(eh->h_source) ||
> >>>> +	    !is_valid_ether_addr(eh->h_dest))
> >>>> +		return false;
> >>>> +
> >>>> +	ether_addr_copy(data.eth.h_dest, eh->h_dest);
> >>>> +	ether_addr_copy(data.eth.h_source, eh->h_source);
> >>>> +
> >>>> +	if (!skb->dev)
> >>>> +		return false;
> >>>> +
> >>>> +	/* WLAN egress unbound hits can arrive before flowtable creates the
> >>>> +	 * L2 master flow normally used for subflow binding. Resolve only
> >>>> +	 * through the bridge master so dev_fill_forward_path() must use the
> >>>> +	 * bridge FDB for the destination MAC. Calling the ingress AP netdev
> >>>> +	 * directly can describe the source station's WDMA path and would
> >>>> +	 * corrupt Wi-Fi-to-wired flows whose real egress is not WDMA.
> >>>> +	 */
> >>>> +	master = airoha_ppe_foe_get_bridge_master(skb->dev);
> >>>> +	if (!master)
> >>>> +		return false;
> >>>> +
> >>>> +	if (airoha_ppe_foe_prepare_wdma_subflow_dev(ppe, master, &data,
> >>>> +						    hwe)) {
> >>>> +		dev_put(master);
> >>>> +		return true;
> >>>> +	}
> >>>> +
> >>>> +	dev_put(master);
> >>>> +	return false;
> >>>
> >>> maybe something like:
> >>>
> >>> 	ret = airoha_ppe_foe_prepare_wdma_subflow_dev();
> >>> 	dev_put(master);
> >>>
> >>> 	return ret;
> >>>
> >>>> +}
> >>>> +
> >>>>  static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
> >>>>  					struct sk_buff *skb,
> >>>>  					u32 hash, bool rx_wlan)
> >>>>  {
> >>>> +	struct airoha_foe_entry wdma_hwe = {};
> >>>>  	struct airoha_flow_table_entry *e;
> >>>>  	struct airoha_foe_bridge br = {};
> >>>>  	struct airoha_foe_entry *hwe;
> >>>>  	bool commit_done = false;
> >>>> +	bool wdma_ready = false;
> >>>>  	struct hlist_node *n;
> >>>>  	u32 index, state;
> >>>>  
> >>>> +	wdma_ready = airoha_ppe_foe_prepare_wdma_subflow(ppe, skb,
> >>>> +							 &wdma_hwe);
> >>>> +
> >>>>  	spin_lock_bh(&ppe_lock);
> >>>>  
> >>>>  	hwe = airoha_ppe_foe_get_entry_locked(ppe, hash);
> >>>> @@ -899,6 +997,8 @@ static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe,
> >>>>  				   airoha_l2_flow_table_params);
> >>>>  	if (e)
> >>>>  		airoha_ppe_foe_commit_subflow_entry(ppe, e, hash, rx_wlan);
> >>>> +	else if (wdma_ready)
> >>>> +		airoha_ppe_foe_commit_subflow(ppe, &wdma_hwe, hash, rx_wlan);
> >>>>  unlock:
> >>>>  	spin_unlock_bh(&ppe_lock);
> >>>>  }
> >>>> -- 
> >>>> 2.53.0
> >>>>
> >>
> >> All inline code-style review comments will be addressed in the next
> >> submission of the patch set, together with the responses to Sashiko's
> >> review, if any.
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* [PATCH v2] wifi: mt76: track rx napi ownership for cleanup
From: Ruslan Isaev @ 2026-05-25 23:39 UTC (permalink / raw)
  To: linux-wireless
  Cc: sean.wang, nbd, lorenzo, ryder.lee, linux-mediatek, linux-kernel


mt76_dma_cleanup() deletes rx napi instances for every allocated rx
queue. This is not symmetric with mt76_dma_init(), which skips WED_RRO
queues when adding rx napi.

Adding an unconditional napi_disable() in the cleanup path fixes the
mt7915 remove warning, but it is not safe for all other mt76 paths. mt7921 and
mt7925 can already disable rx napi in their unregister
paths before calling mt76_dma_cleanup(). On mt7996/mt7992 WED_RRO
queues may have ndesc set even though mt76 never added or enabled napi for them.

Track rx napi ownership and enabled state in struct mt76_queue so
cleanup only disables and deletes napi instances that were actually
added by mt76.

This keeps the existing reset/suspend/remove ordering intact while
avoiding double napi_disable() and avoiding napi cleanup on WED_RRO
queues without napi.

Signed-off-by: Ruslan Isaev <legale.legale@gmail.com>
---
v2:
- track mt76 rx napi state added/enabled
- avoid double napi_disable() on mt7921/mt7925 cleanup paths
- avoid napi cleanup on WED_RRO queues without napi

 drivers/net/wireless/mediatek/mt76/dma.c      |  9 +++--
 drivers/net/wireless/mediatek/mt76/mt76.h     | 40 +++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7603/mac.c   |  8 ++--
 .../net/wireless/mediatek/mt76/mt7615/pci.c   |  6 +--
 .../wireless/mediatek/mt76/mt7615/pci_mac.c   |  4 +-
 .../net/wireless/mediatek/mt76/mt76x0/pci.c   |  4 +-
 .../net/wireless/mediatek/mt76/mt76x02_mmio.c |  4 +-
 .../net/wireless/mediatek/mt76/mt76x2/pci.c   |  6 +--
 .../net/wireless/mediatek/mt76/mt7915/mac.c   |  8 ++--
 .../net/wireless/mediatek/mt76/mt7921/pci.c   |  8 ++--
 .../wireless/mediatek/mt76/mt7921/pci_mac.c   |  4 +-
 .../net/wireless/mediatek/mt76/mt7925/pci.c   |  8 ++--
 .../wireless/mediatek/mt76/mt7925/pci_mac.c   |  8 ++--
 .../net/wireless/mediatek/mt76/mt7996/mac.c   |  8 ++--
 drivers/net/wireless/mediatek/mt76/npu.c      |  3 +-
 15 files changed, 86 insertions(+), 42 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c
index f8c2fe5f2f58..5c531131a25f 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.c
+++ b/drivers/net/wireless/mediatek/mt76/dma.c
@@ -1099,9 +1099,12 @@ static void
 mt76_dma_rx_queue_init(struct mt76_dev *dev, enum mt76_rxq_id qid,
 		       int (*poll)(struct napi_struct *napi, int budget))
 {
+	struct mt76_queue *q = &dev->q_rx[qid];
+
 	netif_napi_add(dev->napi_dev, &dev->napi[qid], poll);
-	mt76_dma_rx_fill_buf(dev, &dev->q_rx[qid], false);
-	napi_enable(&dev->napi[qid]);
+	q->napi_state = MT76_NAPI_ADDED;
+	mt76_dma_rx_fill_buf(dev, q, false);
+	mt76_rx_napi_enable(dev, qid);
 }
 
 static int
@@ -1189,7 +1192,7 @@ void mt76_dma_cleanup(struct mt76_dev *dev)
 	mt76_for_each_q_rx(dev, i) {
 		struct mt76_queue *q = &dev->q_rx[i];
 
-		netif_napi_del(&dev->napi[i]);
+		mt76_rx_napi_del(dev, i);
 		mt76_dma_rx_cleanup(dev, q);
 
 		page_pool_destroy(q->page_pool);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 527bef97e122..3ce936bdcf88 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -251,6 +251,7 @@ struct mt76_queue {
 	int buf_size;
 	bool stopped;
 	bool blocked;
+	u8 napi_state;
 
 	u8 buf_offset;
 	u16 flags;
@@ -1283,6 +1284,45 @@ static inline int mt76_wed_dma_setup(struct mt76_dev *dev, struct mt76_queue *q,
 	for (i = 0; i < ARRAY_SIZE((dev)->q_rx); i++)	\
 		if ((dev)->q_rx[i].ndesc)
 
+#define MT76_NAPI_ADDED		BIT(0)
+#define MT76_NAPI_ENABLED	BIT(1)
+
+static inline void mt76_rx_napi_enable(struct mt76_dev *dev, int qid)
+{
+	struct mt76_queue *q = &dev->q_rx[qid];
+
+	if (!(q->napi_state & MT76_NAPI_ADDED) ||
+	    (q->napi_state & MT76_NAPI_ENABLED))
+		return;
+
+	napi_enable(&dev->napi[qid]);
+	q->napi_state |= MT76_NAPI_ENABLED;
+}
+
+static inline void mt76_rx_napi_disable(struct mt76_dev *dev, int qid)
+{
+	struct mt76_queue *q = &dev->q_rx[qid];
+
+	if (!(q->napi_state & MT76_NAPI_ADDED) ||
+	    !(q->napi_state & MT76_NAPI_ENABLED))
+		return;
+
+	napi_disable(&dev->napi[qid]);
+	q->napi_state &= ~MT76_NAPI_ENABLED;
+}
+
+static inline void mt76_rx_napi_del(struct mt76_dev *dev, int qid)
+{
+	struct mt76_queue *q = &dev->q_rx[qid];
+
+	if (!(q->napi_state & MT76_NAPI_ADDED))
+		return;
+
+	mt76_rx_napi_disable(dev, qid);
+	netif_napi_del(&dev->napi[qid]);
+	q->napi_state = 0;
+}
+
 
 #define mt76_dereference(p, dev) \
 	rcu_dereference_protected(p, lockdep_is_held(&(dev)->mutex))
diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
index d3110eeb45d7..be35ec0d31d4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
@@ -1423,8 +1423,8 @@ static void mt7603_mac_watchdog_reset(struct mt7603_dev *dev)
 
 	mt76_worker_disable(&dev->mt76.tx_worker);
 	tasklet_disable(&dev->mt76.pre_tbtt_tasklet);
-	napi_disable(&dev->mt76.napi[0]);
-	napi_disable(&dev->mt76.napi[1]);
+	mt76_rx_napi_disable(&dev->mt76, 0);
+	mt76_rx_napi_disable(&dev->mt76, 1);
 	napi_disable(&dev->mt76.tx_napi);
 
 	mutex_lock(&dev->mt76.mutex);
@@ -1474,8 +1474,8 @@ static void mt7603_mac_watchdog_reset(struct mt7603_dev *dev)
 	mt7603_beacon_set_timer(dev, -1, beacon_int);
 
 	napi_enable(&dev->mt76.tx_napi);
-	napi_enable(&dev->mt76.napi[0]);
-	napi_enable(&dev->mt76.napi[1]);
+	mt76_rx_napi_enable(&dev->mt76, 0);
+	mt76_rx_napi_enable(&dev->mt76, 1);
 
 	local_bh_disable();
 	napi_schedule(&dev->mt76.tx_napi);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/pci.c b/drivers/net/wireless/mediatek/mt76/mt7615/pci.c
index f5018bfa317a..600b4f2bf1a1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/pci.c
@@ -92,7 +92,7 @@ static int mt7615_pci_suspend(struct pci_dev *pdev, pm_message_t state)
 	mt76_worker_disable(&mdev->tx_worker);
 
 	mt76_for_each_q_rx(mdev, i) {
-		napi_disable(&mdev->napi[i]);
+		mt76_rx_napi_disable(mdev, i);
 	}
 	tasklet_kill(&mdev->irq_tasklet);
 
@@ -127,7 +127,7 @@ static int mt7615_pci_suspend(struct pci_dev *pdev, pm_message_t state)
 
 restore:
 	mt76_for_each_q_rx(mdev, i) {
-		napi_enable(&mdev->napi[i]);
+		mt76_rx_napi_enable(mdev, i);
 	}
 	napi_enable(&mdev->tx_napi);
 	if (hif_suspend)
@@ -166,7 +166,7 @@ static int mt7615_pci_resume(struct pci_dev *pdev)
 	mt76_worker_enable(&mdev->tx_worker);
 
 	mt76_for_each_q_rx(mdev, i) {
-		napi_enable(&mdev->napi[i]);
+		mt76_rx_napi_enable(mdev, i);
 	}
 	napi_enable(&mdev->tx_napi);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c
index 53cb1eed1e4f..a83589c628f9 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/pci_mac.c
@@ -236,7 +236,7 @@ void mt7615_mac_reset_work(struct work_struct *work)
 
 	mt76_worker_disable(&dev->mt76.tx_worker);
 	mt76_for_each_q_rx(&dev->mt76, i)
-		napi_disable(&dev->mt76.napi[i]);
+		mt76_rx_napi_disable(&dev->mt76, i);
 	napi_disable(&dev->mt76.tx_napi);
 
 	mt7615_mutex_acquire(dev);
@@ -264,7 +264,7 @@ void mt7615_mac_reset_work(struct work_struct *work)
 
 	napi_enable(&dev->mt76.tx_napi);
 	mt76_for_each_q_rx(&dev->mt76, i) {
-		napi_enable(&dev->mt76.napi[i]);
+		mt76_rx_napi_enable(&dev->mt76, i);
 	}
 
 	local_bh_disable();
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x0/pci.c b/drivers/net/wireless/mediatek/mt76/mt76x0/pci.c
index f8d206a07f99..96380d016351 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x0/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x0/pci.c
@@ -257,7 +257,7 @@ static int mt76x0e_suspend(struct pci_dev *pdev, pm_message_t state)
 	napi_disable(&mdev->tx_napi);
 
 	mt76_for_each_q_rx(mdev, i)
-		napi_disable(&mdev->napi[i]);
+		mt76_rx_napi_disable(mdev, i);
 
 	mt76x02_dma_disable(dev);
 	mt76x02_mcu_cleanup(dev);
@@ -285,7 +285,7 @@ static int mt76x0e_resume(struct pci_dev *pdev)
 
 	mt76_for_each_q_rx(mdev, i) {
 		mt76_queue_rx_reset(dev, i);
-		napi_enable(&mdev->napi[i]);
+		mt76_rx_napi_enable(mdev, i);
 	}
 	napi_enable(&mdev->tx_napi);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c
index dc7c03d23123..0da4bbad5ba3 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c
@@ -444,7 +444,7 @@ static void mt76x02_watchdog_reset(struct mt76x02_dev *dev)
 	napi_disable(&dev->mt76.tx_napi);
 
 	mt76_for_each_q_rx(&dev->mt76, i) {
-		napi_disable(&dev->mt76.napi[i]);
+		mt76_rx_napi_disable(&dev->mt76, i);
 	}
 
 	mutex_lock(&dev->mt76.mutex);
@@ -505,7 +505,7 @@ static void mt76x02_watchdog_reset(struct mt76x02_dev *dev)
 
 	napi_enable(&dev->mt76.tx_napi);
 	mt76_for_each_q_rx(&dev->mt76, i) {
-		napi_enable(&dev->mt76.napi[i]);
+		mt76_rx_napi_enable(&dev->mt76, i);
 	}
 
 	local_bh_disable();
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2/pci.c b/drivers/net/wireless/mediatek/mt76/mt76x2/pci.c
index 491a32921a06..a23c2560e90f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x2/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x2/pci.c
@@ -119,7 +119,7 @@ mt76x2e_suspend(struct pci_dev *pdev, pm_message_t state)
 	mt76_worker_disable(&mdev->tx_worker);
 
 	mt76_for_each_q_rx(mdev, i)
-		napi_disable(&mdev->napi[i]);
+		mt76_rx_napi_disable(mdev, i);
 
 	pci_enable_wake(pdev, pci_choose_state(pdev, state), true);
 	pci_save_state(pdev);
@@ -131,7 +131,7 @@ mt76x2e_suspend(struct pci_dev *pdev, pm_message_t state)
 
 restore:
 	mt76_for_each_q_rx(mdev, i)
-		napi_enable(&mdev->napi[i]);
+		mt76_rx_napi_enable(mdev, i);
 	napi_enable(&mdev->tx_napi);
 
 	return err;
@@ -153,7 +153,7 @@ mt76x2e_resume(struct pci_dev *pdev)
 	mt76_worker_enable(&mdev->tx_worker);
 
 	mt76_for_each_q_rx(mdev, i) {
-		napi_enable(&mdev->napi[i]);
+		mt76_rx_napi_enable(mdev, i);
 	}
 	napi_enable(&mdev->tx_napi);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index cec2c4208255..354ed0b69013 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -1327,7 +1327,7 @@ mt7915_mac_restart(struct mt7915_dev *dev)
 	mt76_worker_disable(&dev->mt76.tx_worker);
 	mt76_for_each_q_rx(mdev, i) {
 		if (mdev->q_rx[i].ndesc)
-			napi_disable(&dev->mt76.napi[i]);
+			mt76_rx_napi_disable(&dev->mt76, i);
 	}
 	napi_disable(&dev->mt76.tx_napi);
 
@@ -1339,7 +1339,7 @@ mt7915_mac_restart(struct mt7915_dev *dev)
 
 	mt76_for_each_q_rx(mdev, i) {
 		if (mdev->q_rx[i].ndesc) {
-			napi_enable(&dev->mt76.napi[i]);
+			mt76_rx_napi_enable(&dev->mt76, i);
 		}
 	}
 
@@ -1527,7 +1527,7 @@ void mt7915_mac_reset_work(struct work_struct *work)
 
 	mt76_worker_disable(&dev->mt76.tx_worker);
 	mt76_for_each_q_rx(&dev->mt76, i)
-		napi_disable(&dev->mt76.napi[i]);
+		mt76_rx_napi_disable(&dev->mt76, i);
 	napi_disable(&dev->mt76.tx_napi);
 
 
@@ -1558,7 +1558,7 @@ void mt7915_mac_reset_work(struct work_struct *work)
 		clear_bit(MT76_RESET, &phy2->mt76->state);
 
 	mt76_for_each_q_rx(&dev->mt76, i) {
-		napi_enable(&dev->mt76.napi[i]);
+		mt76_rx_napi_enable(&dev->mt76, i);
 	}
 
 	local_bh_disable();
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
index 7a790ddf43bb..c3188b344e50 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
@@ -52,7 +52,7 @@ static void mt7921e_unregister_device(struct mt792x_dev *dev)
 	cancel_work_sync(&dev->init_work);
 	mt76_unregister_device(&dev->mt76);
 	mt76_for_each_q_rx(&dev->mt76, i)
-		napi_disable(&dev->mt76.napi[i]);
+		mt76_rx_napi_disable(&dev->mt76, i);
 	cancel_delayed_work_sync(&pm->ps_work);
 	cancel_work_sync(&pm->wake_work);
 	cancel_work_sync(&dev->reset_work);
@@ -492,7 +492,7 @@ static int mt7921_pci_suspend(struct device *device)
 	mt76_worker_disable(&mdev->tx_worker);
 
 	mt76_for_each_q_rx(mdev, i) {
-		napi_disable(&mdev->napi[i]);
+		mt76_rx_napi_disable(mdev, i);
 	}
 
 	/* wait until dma is idle  */
@@ -518,7 +518,7 @@ static int mt7921_pci_suspend(struct device *device)
 
 restore_napi:
 	mt76_for_each_q_rx(mdev, i) {
-		napi_enable(&mdev->napi[i]);
+		mt76_rx_napi_enable(mdev, i);
 	}
 	napi_enable(&mdev->tx_napi);
 
@@ -564,7 +564,7 @@ static int mt7921_pci_resume(struct device *device)
 	mt76_worker_enable(&mdev->tx_worker);
 
 	mt76_for_each_q_rx(mdev, i) {
-		napi_enable(&mdev->napi[i]);
+		mt76_rx_napi_enable(mdev, i);
 	}
 	napi_enable(&mdev->tx_napi);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c
index 0db7acb3a637..5d7871b8f77c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c
@@ -72,7 +72,7 @@ int mt7921e_mac_reset(struct mt792x_dev *dev)
 
 	mt76_worker_disable(&dev->mt76.tx_worker);
 	mt76_for_each_q_rx(&dev->mt76, i) {
-		napi_disable(&dev->mt76.napi[i]);
+		mt76_rx_napi_disable(&dev->mt76, i);
 	}
 	napi_disable(&dev->mt76.tx_napi);
 
@@ -82,7 +82,7 @@ int mt7921e_mac_reset(struct mt792x_dev *dev)
 	mt792x_wpdma_reset(dev, true);
 
 	mt76_for_each_q_rx(&dev->mt76, i) {
-		napi_enable(&dev->mt76.napi[i]);
+		mt76_rx_napi_enable(&dev->mt76, i);
 	}
 
 	local_bh_disable();
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
index c4161754c01d..1da650ac7f4d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
@@ -40,7 +40,7 @@ static void mt7925e_unregister_device(struct mt792x_dev *dev)
 	cancel_work_sync(&dev->init_work);
 	mt76_unregister_device(&dev->mt76);
 	mt76_for_each_q_rx(&dev->mt76, i)
-		napi_disable(&dev->mt76.napi[i]);
+		mt76_rx_napi_disable(&dev->mt76, i);
 	cancel_delayed_work_sync(&pm->ps_work);
 	cancel_work_sync(&pm->wake_work);
 	cancel_work_sync(&dev->reset_work);
@@ -481,7 +481,7 @@ static int mt7925_pci_suspend(struct device *device)
 	mt76_worker_disable(&mdev->tx_worker);
 
 	mt76_for_each_q_rx(mdev, i) {
-		napi_disable(&mdev->napi[i]);
+		mt76_rx_napi_disable(mdev, i);
 	}
 
 	/* wait until dma is idle  */
@@ -509,7 +509,7 @@ static int mt7925_pci_suspend(struct device *device)
 
 restore_napi:
 	mt76_for_each_q_rx(mdev, i) {
-		napi_enable(&mdev->napi[i]);
+		mt76_rx_napi_enable(mdev, i);
 	}
 	napi_enable(&mdev->tx_napi);
 
@@ -559,7 +559,7 @@ static int _mt7925_pci_resume(struct device *device, bool restore)
 	mt76_worker_enable(&mdev->tx_worker);
 
 	mt76_for_each_q_rx(mdev, i) {
-		napi_enable(&mdev->napi[i]);
+		mt76_rx_napi_enable(mdev, i);
 	}
 	napi_enable(&mdev->tx_napi);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
index 3072850c2752..6bb47db8d16a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
@@ -88,11 +88,11 @@ int mt7925e_mac_reset(struct mt792x_dev *dev)
 
 	mt76_worker_disable(&dev->mt76.tx_worker);
 	if (irq_map->rx.data_complete_mask)
-		napi_disable(&dev->mt76.napi[MT_RXQ_MAIN]);
+		mt76_rx_napi_disable(&dev->mt76, MT_RXQ_MAIN);
 	if (irq_map->rx.wm_complete_mask)
-		napi_disable(&dev->mt76.napi[MT_RXQ_MCU]);
+		mt76_rx_napi_disable(&dev->mt76, MT_RXQ_MCU);
 	if (irq_map->rx.wm2_complete_mask)
-		napi_disable(&dev->mt76.napi[MT_RXQ_MCU_WA]);
+		mt76_rx_napi_disable(&dev->mt76, MT_RXQ_MCU_WA);
 	if (irq_map->tx.all_complete_mask)
 		napi_disable(&dev->mt76.tx_napi);
 
@@ -102,7 +102,7 @@ int mt7925e_mac_reset(struct mt792x_dev *dev)
 	mt792x_wpdma_reset(dev, true);
 
 	mt76_for_each_q_rx(&dev->mt76, i) {
-		napi_enable(&dev->mt76.napi[i]);
+		mt76_rx_napi_enable(&dev->mt76, i);
 	}
 	napi_enable(&dev->mt76.tx_napi);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index e2a83da3a09c..a75238ce8807 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -2262,7 +2262,7 @@ mt7996_mac_restart(struct mt7996_dev *dev)
 			continue;
 
 		if (mdev->q_rx[i].ndesc)
-			napi_disable(&dev->mt76.napi[i]);
+			mt76_rx_napi_disable(&dev->mt76, i);
 	}
 	napi_disable(&dev->mt76.tx_napi);
 
@@ -2278,7 +2278,7 @@ mt7996_mac_restart(struct mt7996_dev *dev)
 			continue;
 
 		if (mdev->q_rx[i].ndesc) {
-			napi_enable(&dev->mt76.napi[i]);
+			mt76_rx_napi_enable(&dev->mt76, i);
 			local_bh_disable();
 			napi_schedule(&dev->mt76.napi[i]);
 			local_bh_enable();
@@ -2534,7 +2534,7 @@ void mt7996_mac_reset_work(struct work_struct *work)
 		if (mt76_queue_is_npu_txfree(&dev->mt76.q_rx[i]))
 			continue;
 
-		napi_disable(&dev->mt76.napi[i]);
+		mt76_rx_napi_disable(&dev->mt76, i);
 	}
 	napi_disable(&dev->mt76.tx_napi);
 
@@ -2596,7 +2596,7 @@ void mt7996_mac_reset_work(struct work_struct *work)
 		if (mt76_queue_is_npu_txfree(&dev->mt76.q_rx[i]))
 			continue;
 
-		napi_enable(&dev->mt76.napi[i]);
+		mt76_rx_napi_enable(&dev->mt76, i);
 		local_bh_disable();
 		napi_schedule(&dev->mt76.napi[i]);
 		local_bh_enable();
diff --git a/drivers/net/wireless/mediatek/mt76/npu.c b/drivers/net/wireless/mediatek/mt76/npu.c
index c4c7c0af6321..fb4ba5bdbf48 100644
--- a/drivers/net/wireless/mediatek/mt76/npu.c
+++ b/drivers/net/wireless/mediatek/mt76/npu.c
@@ -291,8 +291,9 @@ int mt76_npu_rx_queue_init(struct mt76_dev *dev, struct mt76_queue *q)
 		goto out;
 
 	netif_napi_add(dev->napi_dev, &dev->napi[qid], mt76_npu_rx_poll);
+	q->napi_state = MT76_NAPI_ADDED;
 	mt76_npu_fill_rx_queue(dev, q);
-	napi_enable(&dev->napi[qid]);
+	mt76_rx_napi_enable(dev, qid);
 out:
 	mutex_unlock(&dev->mutex);
 
-- 
2.39.5



^ permalink raw reply related

* [RFC PATCH net] netfilter: flowtable: fix offloaded ct timeout never being extended
From: Adrian Bente @ 2026-05-26  6:01 UTC (permalink / raw)
  To: pablo, kadlec, fw, netfilter-devel
  Cc: phil, davem, edumazet, kuba, pabeni, horms, nbd, sean.wang,
	lorenzo, andrew+netdev, matthias.bgg, angelogioacchino.delregno,
	daniel, coreteam, netdev, linux-kernel, linux-arm-kernel,
	linux-mediatek, Adrian Bente

OpenWrt has recently migrated many platforms to kernel 6.18. On the
MediaTek platform, which supports hardware network offloading, WiFi
connections accelerated via the WED path were observed to drop after
roughly 300 seconds.

After several debugging sessions, assisted by the Claude LLM, the
problem was narrowed down as follows:

nf_flow_table_extend_ct_timeout() extends ct->timeout for offloaded
flows using:

	cmpxchg(&ct->timeout, expires, new_timeout);

'expires' comes from nf_ct_expires(ct) and is a relative value, while
ct->timeout holds an absolute timestamp. The two are never equal, so
the cmpxchg always fails and the timeout is never extended.

This goes unnoticed for most flows, but a long-lived hardware (WED)
offloaded flow on MediaTek MT7986 eventually has ct->timeout decay to
zero, the conntrack entry is reaped and the connection breaks.

Compare against the current ct->timeout value instead.

This patch is sent as RFC: the diagnosis is verified on hardware and
the fix resolves the drop, but review of the chosen approach is
welcome.

Fixes: 03428ca5cee9 ("netfilter: conntrack: rework offload nf_conn timeout extension logic")
Signed-off-by: Adrian Bente <adibente@gmail.com>
---
 net/netfilter/nf_flow_table_core.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -541,8 +541,10 @@
 		 * after this -- is fine, datapath is authoritative.
 		 */
 		if (new_timeout) {
+			u32 old = READ_ONCE(ct->timeout);
+
 			new_timeout += nfct_time_stamp;
-			cmpxchg(&ct->timeout, expires, new_timeout);
+			cmpxchg(&ct->timeout, old, new_timeout);
 		}
 	}
 

-- 
2.46.0


^ permalink raw reply

* [PATCH] wifi: mt76: mt7925: add wcid publish check in mt76_sta_add
From: Jiajia Liu @ 2026-05-26  6:08 UTC (permalink / raw)
  To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
	Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
	Ming Yen Hsieh, Michael Lo, Leon Yen
  Cc: linux-wireless, linux-kernel, linux-arm-kernel, linux-mediatek,
	Jiajia Liu

Since mt7925_mac_sta_add publishes wcid, add publish check in mt76_sta_add
to avoid reinitializing the wcid->poll_list for mt7925.

Found dev->sta_poll_list corruption when using mt7925 and 7.0-rc4.
According to the corruption information, prev->next was changed to itself.

wlan0: disconnect from AP 90:fb:5d:94:8b:e3 for new auth to 90:fb:5d:94:8b:e2
wlan0: authenticate with 90:fb:5d:94:8b:e2 (local address=84:9e:56:9c:7e:6b)
wlan0: send auth to 90:fb:5d:94:8b:e2 (try 1/3)
 slab kmalloc-8k start ffff8c80958a6000 pointer offset 4160 size 8192
list_add corruption. prev->next should be next (ffff8c808a7488f8), but was ffff8c80958a7040. (prev=ffff8c80958a7040).

 mt76_wcid_add_poll+0x95/0xd0 [mt76]
 mt7925_mac_add_txs.part.0+0xa5/0xe0 [mt7925_common]
 mt7925_rx_check+0xa7/0xc0 [mt7925_common]
 mt76_dma_rx_poll+0x50d/0x790 [mt76]
 mt792x_poll_rx+0x52/0xe0 [mt792x_lib]

Signed-off-by: Jiajia Liu <liujiajia@kylinos.cn>
---

Reproduced and tested using the script below over ssh. Roam between two
bssids with the same SSID on a router.

#!/bin/bash

set -ex

while :; do
	num=$(sudo iw wlan0 scan | grep Polaris | wc -l)
	if [ $num -eq 2 ]; then
		break
	fi
done

for i in $(seq 1 500); do

echo "index $i"
wpa_cli -i wlan0 roam 90:fb:5d:94:8b:e3
sleep 5
wpa_cli -i wlan0 roam 90:fb:5d:94:8b:e2
sleep 5

done

---
 drivers/net/wireless/mediatek/mt76/mac80211.c    | 11 ++++++++---
 drivers/net/wireless/mediatek/mt76/mt76.h        |  1 +
 drivers/net/wireless/mediatek/mt76/mt7925/main.c |  3 +++
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 4ae5e4715a9c..83f4f941b890 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1595,11 +1595,16 @@ mt76_sta_add(struct mt76_phy *phy, struct ieee80211_vif *vif,
 		mtxq->wcid = wcid->idx;
 	}
 
-	ewma_signal_init(&wcid->rssi);
-	rcu_assign_pointer(dev->wcid[wcid->idx], wcid);
+	if (!test_bit(MT_WCID_FLAG_DRV_PUBLISH, &wcid->flags)) {
+		ewma_signal_init(&wcid->rssi);
+		rcu_assign_pointer(dev->wcid[wcid->idx], wcid);
+		mt76_wcid_init(wcid, phy->band_idx);
+	} else {
+		wcid->phy_idx = phy->band_idx;
+	}
+
 	phy->num_sta++;
 
-	mt76_wcid_init(wcid, phy->band_idx);
 out:
 	mutex_unlock(&dev->mutex);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 527bef97e122..8bfce686bff7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -361,6 +361,7 @@ enum mt76_wcid_flags {
 	MT_WCID_FLAG_PS,
 	MT_WCID_FLAG_4ADDR,
 	MT_WCID_FLAG_HDR_TRANS,
+	MT_WCID_FLAG_DRV_PUBLISH,
 };
 
 #define MT76_N_WCIDS 1088
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 73d3722739d0..35b5c718475c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -1102,6 +1102,9 @@ int mt7925_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
 					      &msta->deflink);
 	}
 
+	if (!err)
+		set_bit(MT_WCID_FLAG_DRV_PUBLISH, &msta->deflink.wcid.flags);
+
 	return err;
 }
 EXPORT_SYMBOL_GPL(mt7925_mac_sta_add);
-- 
2.53.0



^ permalink raw reply related

* [PATCH RFC net-next v2] net: airoha: Add TCP LRO support
From: Lorenzo Bianconi @ 2026-05-26  6:58 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Lorenzo Bianconi
  Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal

Add hardware TCP Large Receive Offload (LRO) support to the airoha_eth
driver, leveraging the EN7581/AN7583 SoC's 8 dedicated LRO hardware queues
mapped to RX queues 24–31. LRO hw offloading does not support
Scatter-Gather (SG) so it is required to increase the page_pool allocation
order to 2 for RX queues 24–31 (LRO queues).

Performance comparison between GRO and hw LRO has been carried out using
a 10Gbps NIC:

GRO: ~2.7 Gbps
LRO: ~8.1 Gbps

Please note with respect to the previous implementation, page_pool
allocation order has been reduced from 5 to 2.

Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
Changes in RFC v2:
- Improve performances fixing buf_size computation.
- Fix possible overflow in REG_CDM_LRO_LIMIT() register configuration.
- Require the device to be not running before configuring LRO.
- Fix configuration order in airoha_fe_lro_is_enabled().
- Check skb header length in airoha_qdma_lro_rx_process().
- Do not check net_device feature in airoha_qdma_rx_process() before
  executing airoha_qdma_lro_rx_process() but rely on
  airoha_qdma_lro_rx_process() logic.
- Fix possible double recycle in airoha_qdma_rx_process() for LRO
  packets.
- Always use AIROHA_RXQ_LRO_MAX_AGG_COUNT macro for max LRO aggregated
  fragments in airoha_fe_lro_init_rx_queue().
- Link to v1: https://lore.kernel.org/r/20260520-airoha-eth-lro-v1-1-129cc33766e9@kernel.org
---
 drivers/net/ethernet/airoha/airoha_eth.c  | 217 +++++++++++++++++++++++++++---
 drivers/net/ethernet/airoha/airoha_eth.h  |  24 ++++
 drivers/net/ethernet/airoha/airoha_regs.h |  22 ++-
 3 files changed, 247 insertions(+), 16 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 6418fe0c9f80..c8e5abe8942a 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -12,6 +12,7 @@
 #include <net/dst_metadata.h>
 #include <net/page_pool/helpers.h>
 #include <net/pkt_cls.h>
+#include <net/tcp.h>
 #include <uapi/linux/ppp_defs.h>
 
 #include "airoha_regs.h"
@@ -431,6 +432,48 @@ static void airoha_fe_crsn_qsel_init(struct airoha_eth *eth)
 				 CDM_CRSN_QSEL_Q1));
 }
 
+static void airoha_fe_lro_init_rx_queue(struct airoha_eth *eth, int qdma_id,
+					int lro_queue_index, int qid,
+					int buf_size)
+{
+	int id = qdma_id + 1;
+
+	airoha_fe_rmw(eth, REG_CDM_LRO_LIMIT(id),
+		      CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK,
+		      FIELD_PREP(CDM_LRO_AGG_SIZE_MASK, buf_size) |
+		      FIELD_PREP(CDM_LRO_AGG_NUM_MASK,
+				 AIROHA_RXQ_LRO_MAX_AGG_COUNT));
+	airoha_fe_rmw(eth, REG_CDM_LRO_AGE_TIME(id),
+		      CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK,
+		      FIELD_PREP(CDM_LRO_AGE_TIME_MASK,
+				 AIROHA_RXQ_LRO_MAX_AGE_TIME) |
+		      FIELD_PREP(CDM_LRO_AGG_TIME_MASK,
+				 AIROHA_RXQ_LRO_MAX_AGG_TIME));
+	airoha_fe_rmw(eth, REG_CDM_LRO_RXQ(id, lro_queue_index),
+		      LRO_RXQ_MASK(lro_queue_index),
+		      __field_prep(LRO_RXQ_MASK(lro_queue_index), qid));
+	airoha_fe_set(eth, REG_CDM_LRO_EN(id), BIT(lro_queue_index));
+}
+
+static void airoha_fe_lro_disable(struct airoha_eth *eth, int qdma_id)
+{
+	int i, id = qdma_id + 1;
+
+	airoha_fe_clear(eth, REG_CDM_LRO_EN(id), LRO_RXQ_EN_MASK);
+	airoha_fe_clear(eth, REG_CDM_LRO_LIMIT(id),
+			CDM_LRO_AGG_NUM_MASK | CDM_LRO_AGG_SIZE_MASK);
+	airoha_fe_clear(eth, REG_CDM_LRO_AGE_TIME(id),
+			CDM_LRO_AGE_TIME_MASK | CDM_LRO_AGG_TIME_MASK);
+	for (i = 0; i < AIROHA_MAX_NUM_LRO_QUEUES; i++)
+		airoha_fe_clear(eth, REG_CDM_LRO_RXQ(id, i), LRO_RXQ_MASK(i));
+}
+
+static bool airoha_fe_lro_is_enabled(struct airoha_eth *eth, int qdma_id)
+{
+	return airoha_fe_get(eth, REG_CDM_LRO_EN(qdma_id + 1),
+			     LRO_RXQ_EN_MASK);
+}
+
 static int airoha_fe_init(struct airoha_eth *eth)
 {
 	airoha_fe_maccr_init(eth);
@@ -587,6 +630,85 @@ static int airoha_qdma_get_gdm_port(struct airoha_eth *eth,
 	return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
 }
 
+static int airoha_qdma_lro_rx_process(struct airoha_queue *q,
+				      struct airoha_qdma_desc *desc)
+{
+	u32 desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
+	u32 msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
+	u32 msg2 = le32_to_cpu(READ_ONCE(desc->msg2));
+	u32 msg3 = le32_to_cpu(READ_ONCE(desc->msg3));
+	struct sk_buff *skb = q->skb;
+	u32 len, th_off, tcp_ack_seq;
+	u16 tcp_win, l2_len;
+	struct tcphdr *th;
+	bool ipv4, ipv6;
+
+	if (FIELD_GET(QDMA_ETH_RXMSG_AGG_COUNT_MASK, msg2) <= 1)
+		return 0;
+
+	ipv4 = FIELD_GET(QDMA_ETH_RXMSG_IP4_MASK, msg1);
+	ipv6 = FIELD_GET(QDMA_ETH_RXMSG_IP6_MASK, msg1);
+	if (!ipv4 && !ipv6)
+		return -EOPNOTSUPP;
+
+	l2_len = FIELD_GET(QDMA_ETH_RXMSG_L2_LEN_MASK, msg2);
+	len = FIELD_GET(QDMA_DESC_LEN_MASK, desc_ctrl);
+	if (ipv4) {
+		struct iphdr *iph;
+
+		if (!pskb_may_pull(skb, l2_len + sizeof(*iph)))
+			return -EINVAL;
+
+		iph = (struct iphdr *)(skb->data + l2_len);
+		if (iph->protocol != IPPROTO_TCP)
+			return -EOPNOTSUPP;
+
+		iph->tot_len = cpu_to_be16(len - l2_len);
+		iph->check = 0;
+		iph->check = ip_fast_csum((void *)iph, iph->ihl);
+		th_off = l2_len + (iph->ihl << 2);
+	} else {
+		struct ipv6hdr *ip6h;
+
+		if (!pskb_may_pull(skb, l2_len + sizeof(*ip6h)))
+			return -EINVAL;
+
+		ip6h = (struct ipv6hdr *)(skb->data + l2_len);
+		if (ip6h->nexthdr != NEXTHDR_TCP)
+			return -EOPNOTSUPP;
+
+		ip6h->payload_len = cpu_to_be16(len - l2_len - sizeof(*ip6h));
+		th_off = l2_len + sizeof(*ip6h);
+	}
+
+	tcp_win = FIELD_GET(QDMA_ETH_RXMSG_TCP_WIN_MASK, msg3);
+	tcp_ack_seq = le32_to_cpu(READ_ONCE(desc->data));
+
+	if (!pskb_may_pull(skb, th_off + sizeof(*th)))
+		return -EINVAL;
+
+	th = (struct tcphdr *)(skb->data + th_off);
+	th->ack_seq = cpu_to_be32(tcp_ack_seq);
+	th->window = cpu_to_be16(tcp_win);
+
+	/* Check tcp timestamp option */
+	if (th->doff == (sizeof(*th) + TCPOLEN_TSTAMP_ALIGNED) / 4) {
+		__be32 *topt = (__be32 *)(th + 1);
+
+		if (*topt == cpu_to_be32((TCPOPT_NOP << 24) |
+					 (TCPOPT_NOP << 16) |
+					 (TCPOPT_TIMESTAMP << 8) |
+					 TCPOLEN_TIMESTAMP)) {
+			__le32 tcp_ts_reply = READ_ONCE(desc->tcp_ts_reply);
+
+			put_unaligned_be32(le32_to_cpu(tcp_ts_reply),
+					   topt + 2);
+		}
+	}
+
+	return 0;
+}
+
 static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
 {
 	enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
@@ -634,11 +756,15 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
 
 			skb_reserve(q->skb, AIROHA_RX_HEADROOM);
 			__skb_put(q->skb, len);
-			skb_mark_for_recycle(q->skb);
 			q->skb->dev = port->dev;
-			q->skb->protocol = eth_type_trans(q->skb, port->dev);
 			q->skb->ip_summed = CHECKSUM_UNNECESSARY;
 			skb_record_rx_queue(q->skb, qid);
+
+			if (airoha_qdma_lro_rx_process(q, desc) < 0)
+				goto free_frag;
+
+			q->skb->protocol = eth_type_trans(q->skb, port->dev);
+			skb_mark_for_recycle(q->skb);
 		} else { /* scattered frame */
 			struct skb_shared_info *shinfo = skb_shinfo(q->skb);
 			int nr_frags = shinfo->nr_frags;
@@ -727,23 +853,18 @@ static int airoha_qdma_rx_napi_poll(struct napi_struct *napi, int budget)
 static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
 				     struct airoha_qdma *qdma, int ndesc)
 {
-	const struct page_pool_params pp_params = {
-		.order = 0,
-		.pool_size = 256,
+	struct page_pool_params pp_params = {
 		.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
 		.dma_dir = DMA_FROM_DEVICE,
-		.max_len = PAGE_SIZE,
 		.nid = NUMA_NO_NODE,
 		.dev = qdma->eth->dev,
 		.napi = &q->napi,
 	};
+	int pp_order, qid = q - &qdma->q_rx[0], thr;
 	struct airoha_eth *eth = qdma->eth;
-	int qid = q - &qdma->q_rx[0], thr;
 	dma_addr_t dma_addr;
 
-	q->buf_size = PAGE_SIZE / 2;
 	q->qdma = qdma;
-
 	q->entry = devm_kzalloc(eth->dev, ndesc * sizeof(*q->entry),
 				GFP_KERNEL);
 	if (!q->entry)
@@ -754,6 +875,11 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
 	if (!q->desc)
 		return -ENOMEM;
 
+	pp_order = airoha_qdma_is_lro_queue(q) ? AIROHA_LRO_PAGE_ORDER : 0;
+	pp_params.order = pp_order;
+	pp_params.pool_size = 256;
+	pp_params.max_len = PAGE_SIZE << pp_order;
+
 	q->page_pool = page_pool_create(&pp_params);
 	if (IS_ERR(q->page_pool)) {
 		int err = PTR_ERR(q->page_pool);
@@ -762,6 +888,8 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
 		return err;
 	}
 
+	q->buf_size = airoha_qdma_is_lro_queue(q) ? pp_params.max_len
+						  : pp_params.max_len / 2;
 	q->ndesc = ndesc;
 	netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
 
@@ -1993,6 +2121,67 @@ int airoha_get_fe_port(struct airoha_gdm_port *port)
 	}
 }
 
+static int airoha_dev_set_features(struct net_device *dev,
+				   netdev_features_t features)
+{
+	netdev_features_t diff = dev->features ^ features;
+	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_eth *eth = qdma->eth;
+	int qdma_id = qdma - &eth->qdma[0];
+	int i;
+
+	if (!(diff & NETIF_F_LRO))
+		return 0;
+
+	if (netif_running(dev))
+		return -EBUSY;
+
+	/* reset LRO configuration */
+	if (features & NETIF_F_LRO) {
+		int lro_queue_index = 0;
+
+		if (airoha_fe_lro_is_enabled(eth, qdma_id))
+			return 0;
+
+		for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+			struct airoha_queue *q = &qdma->q_rx[i];
+			u32 size;
+
+			if (!q->ndesc)
+				continue;
+
+			if (!airoha_qdma_is_lro_queue(q))
+				continue;
+
+			size = SKB_WITH_OVERHEAD(AIROHA_RX_LEN(q->buf_size));
+			size = min_t(u32, size, CDM_LRO_AGG_SIZE_MASK);
+			airoha_fe_lro_init_rx_queue(eth, qdma_id,
+						    lro_queue_index, i, size);
+			lro_queue_index++;
+		}
+	} else {
+		for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+			struct airoha_gdm_port *p = eth->ports[i];
+
+			if (!p)
+				continue;
+
+			if (p->qdma != qdma)
+				continue;
+
+			if (p->dev == dev)
+				continue;
+
+			if (p->dev->features & NETIF_F_LRO)
+				return 0;
+		}
+		airoha_fe_lro_disable(eth, qdma_id);
+	}
+
+	return 0;
+}
+
 static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 				   struct net_device *dev)
 {
@@ -2892,6 +3081,7 @@ static const struct net_device_ops airoha_netdev_ops = {
 	.ndo_stop		= airoha_dev_stop,
 	.ndo_change_mtu		= airoha_dev_change_mtu,
 	.ndo_select_queue	= airoha_dev_select_queue,
+	.ndo_set_features	= airoha_dev_set_features,
 	.ndo_start_xmit		= airoha_dev_xmit,
 	.ndo_get_stats64        = airoha_dev_get_stats64,
 	.ndo_set_mac_address	= airoha_dev_set_macaddr,
@@ -2989,12 +3179,9 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
 	dev->ethtool_ops = &airoha_ethtool_ops;
 	dev->max_mtu = AIROHA_MAX_MTU;
 	dev->watchdog_timeo = 5 * HZ;
-	dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
-			   NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
-			   NETIF_F_SG | NETIF_F_TSO |
-			   NETIF_F_HW_TC;
-	dev->features |= dev->hw_features;
-	dev->vlan_features = dev->hw_features;
+	dev->hw_features = AIROHA_HW_FEATURES | NETIF_F_LRO;
+	dev->features |= AIROHA_HW_FEATURES;
+	dev->vlan_features = AIROHA_HW_FEATURES;
 	dev->dev.of_node = np;
 	SET_NETDEV_DEV(dev, eth->dev);
 
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index d3781103abb5..aed5077d3db5 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -43,6 +43,18 @@
 	 (_n) == 15 ? 128 :		\
 	 (_n) ==  0 ? 1024 : 16)
 
+#define AIROHA_LRO_PAGE_ORDER		2
+#define AIROHA_MAX_NUM_LRO_QUEUES	8
+#define AIROHA_RXQ_LRO_EN_MASK		0xff000000
+#define AIROHA_RXQ_LRO_MAX_AGG_COUNT	64
+#define AIROHA_RXQ_LRO_MAX_AGG_TIME	100
+#define AIROHA_RXQ_LRO_MAX_AGE_TIME	2000 /* 1ms */
+
+#define AIROHA_HW_FEATURES			\
+	(NETIF_F_IP_CSUM | NETIF_F_RXCSUM |	\
+	 NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |	\
+	 NETIF_F_SG | NETIF_F_TSO | NETIF_F_HW_TC)
+
 #define PSE_RSV_PAGES			128
 #define PSE_QUEUE_RSV_PAGES		64
 
@@ -661,6 +673,18 @@ static inline bool airoha_is_7583(struct airoha_eth *eth)
 	return eth->soc->version == 0x7583;
 }
 
+static inline bool airoha_qdma_is_lro_queue(struct airoha_queue *q)
+{
+	struct airoha_qdma *qdma = q->qdma;
+	int qid = q - &qdma->q_rx[0];
+
+	/* EN7581 SoC supports at most 8 LRO rx queues */
+	BUILD_BUG_ON(hweight32(AIROHA_RXQ_LRO_EN_MASK) >
+		     AIROHA_MAX_NUM_LRO_QUEUES);
+
+	return !!(AIROHA_RXQ_LRO_EN_MASK & BIT(qid));
+}
+
 int airoha_get_fe_port(struct airoha_gdm_port *port);
 bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
 			      struct airoha_gdm_port *port);
diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h
index 436f3c8779c1..dfc786583774 100644
--- a/drivers/net/ethernet/airoha/airoha_regs.h
+++ b/drivers/net/ethernet/airoha/airoha_regs.h
@@ -122,6 +122,20 @@
 #define CDM_CRSN_QSEL_REASON_MASK(_n)	\
 	GENMASK(4 + (((_n) % 4) << 3),	(((_n) % 4) << 3))
 
+#define REG_CDM_LRO_RXQ(_n, _m)		(CDM_BASE(_n) + 0x78 + ((_m) & 0x4))
+#define LRO_RXQ_MASK(_n)		GENMASK(4 + (((_n) & 0x3) << 3), ((_n) & 0x3) << 3)
+
+#define REG_CDM_LRO_EN(_n)		(CDM_BASE(_n) + 0x80)
+#define LRO_RXQ_EN_MASK			GENMASK(7, 0)
+
+#define REG_CDM_LRO_LIMIT(_n)		(CDM_BASE(_n) + 0x84)
+#define CDM_LRO_AGG_NUM_MASK		GENMASK(23, 16)
+#define CDM_LRO_AGG_SIZE_MASK		GENMASK(15, 0)
+
+#define REG_CDM_LRO_AGE_TIME(_n)	(CDM_BASE(_n) + 0x88)
+#define CDM_LRO_AGE_TIME_MASK		GENMASK(31, 16)
+#define CDM_LRO_AGG_TIME_MASK		GENMASK(15, 0)
+
 #define REG_GDM_FWD_CFG(_n)		GDM_BASE(_n)
 #define GDM_PAD_EN_MASK			BIT(28)
 #define GDM_DROP_CRC_ERR_MASK		BIT(23)
@@ -883,9 +897,15 @@
 #define QDMA_ETH_RXMSG_SPORT_MASK	GENMASK(25, 21)
 #define QDMA_ETH_RXMSG_CRSN_MASK	GENMASK(20, 16)
 #define QDMA_ETH_RXMSG_PPE_ENTRY_MASK	GENMASK(15, 0)
+/* RX MSG2 */
+#define QDMA_ETH_RXMSG_AGG_COUNT_MASK	GENMASK(31, 24)
+#define QDMA_ETH_RXMSG_L2_LEN_MASK	GENMASK(6, 0)
+/* RX MSG3 */
+#define QDMA_ETH_RXMSG_AGG_LEN_MASK	GENMASK(31, 16)
+#define QDMA_ETH_RXMSG_TCP_WIN_MASK	GENMASK(15, 0)
 
 struct airoha_qdma_desc {
-	__le32 rsv;
+	__le32 tcp_ts_reply;
 	__le32 ctrl;
 	__le32 addr;
 	__le32 data;

---
base-commit: 3baa7ba4ab98af452925926ffc2ee58c683e3f28
change-id: 20260520-airoha-eth-lro-a5d1c3631811

Best regards,
-- 
Lorenzo Bianconi <lorenzo@kernel.org>



^ permalink raw reply related

* Re: [PATCH v1 1/6] sdio: Add syntactic sugar to store a pointer in sdio_driver_id
From: Uwe Kleine-König (The Capable Hub) @ 2026-05-26  9:38 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: Ulf Hansson, Christian A. Ehrhardt, linux-mmc, Greg Kroah-Hartman,
	Wolfram Sang, linux-kernel, Marcel Holtmann, linux-bluetooth,
	Matthias Brugger, AngeloGioacchino Del Regno, linux-mediatek,
	Ping-Ke Shih, linux-wireless, Felix Fietkau, Lorenzo Bianconi,
	Ryder Lee, Shayne Chen, Sean Wang, Brian Norris,
	Francesco Dolcini, Andy Shevchenko
In-Reply-To: <CABBYNZJzbEmYzTk2m+Y8SoHVouTMA6Gje_55iJsQ6cYtDLftbQ@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 1709 bytes --]

On Mon, Apr 20, 2026 at 04:46:56PM -0400, Luiz Augusto von Dentz wrote:
> Hi Uwe,
> 
> On Mon, Apr 20, 2026 at 4:31 PM Uwe Kleine-König (The Capable Hub)
> <u.kleine-koenig@baylibre.com> wrote:
> >
> > Hello,
> >
> > On Fri, Apr 17, 2026 at 03:10:47PM +0200, Uwe Kleine-König (The Capable Hub) wrote:
> > > On all current Linux architectures sizeof(long) == sizeof(void *) and
> > > this is used a lot through the kernel. For example it enables the usual
> > > practice to store pointers in sdio_driver_id's .driver_data member.
> > >
> > > This works fine, but involves casting and thus isn't type-safe.
> > > Additionally with the CHERI architecture extension there are machines
> > > with sizeof(void *) > sizeof(long) for with the traditional approach of
> > > storing a pointer in .driver_data doesn't work.
> > >
> > > By replacing the plain unsigned long .driver_data by an anonymous union,
> > > most of the casting can be dropped and it yields a working solution for
> > > CHERI.
> > >
> > > All users of struct sdio_driver_id are initialized in a way that is
> > > compatible with the new definition, so no adaptions are needed there.
> >
> > sashiko.dev found s/sdio_driver_id/sdio_device_id/ twice in the commit
> > log and once in the short log. If you consider applying this patch
> > please adapt the commit message accordingly.
> 
> No problem I can fix them up once applying.

Now that Ulf gave his blessing, would you please merge the first patch
and the bluetooth ones (#2 and #3)? I will follow up then once your tree
hits Linus's tree with the remaining patches for wifi.

Please don't forget about s/sdio_driver_id/sdio_device_id/.

Thanks
Uwe

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply

* Re: [PATCH RFC 00/12] arm64: mediatek: Add M.2 E-key slot on Chromebooks
From: Bartosz Golaszewski @ 2026-05-26  9:48 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
	linux-pm, linux-usb, devicetree, linux-mediatek, linux-arm-kernel,
	linux-kernel, Manivannan Sadhasivam
In-Reply-To: <CAGXv+5HC3dqgcE3KnKzakHHWFHB6m_X42orOkNUvZvp=SL_O8g@mail.gmail.com>

On Sun, May 24, 2026 at 10:06 AM Chen-Yu Tsai <wenst@chromium.org> wrote:
>
> > >
> > > I expect some discussion on this patch, because a) it adds some
> > > OF-specific code into an otherwise generic (core) driver, and
> > > b) it doesn't yet handle USB 2.0 / 3.x shared ports; it ends up powering
> > > on the port twice, which negates the port reset part.
> > >
> >
> > I understand that you do this because the port device has no OF node
> > assigned. If we wanted to call pwrseq_get() for the port device, is
> > there really no other way to associate it with the correct pwrseq
> > provider?
>
> I suppose we could tie the "port@X" node to the usb port device, but
> AFAIK no other subsystem does this so we would be introducing a new
> pattern.
>
> In the M.2 pwrseq driver, we would have to match by port node instead
> of its parent device node. We may end up with different behavior for
> the USB target vs the other targets.
>

I imagine, we can check the bus type of the parent device to know if
this is USB?

> Also, the "port@X" nodes only exist for the OF graph connections to
> connectors and/or muxes (this series doesn't deal with the latter).
> For directly connected devices, there is a "device@X" child node
> directly under the USB hub node. That node is what gets tied to the
> the USB device.
>

Is this a problem? I don't think I understand what you're saying here.

Bart


^ 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