* [PATCH net-next v7 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
The KS8995 100Mbit switch can do proper DSA per-port tagging
with the proper set-up. This adds the code to handle ingress
and egress KS8995 tags.
The tag is a modified 0x8100 ethertype tag where a bit in the
last nybble is set for each target port.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
MAINTAINERS | 1 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +++
net/dsa/Makefile | 1 +
net/dsa/tag_ks8995.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 147 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 52f1a55eca99..6bd1baec7b44 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17598,6 +17598,7 @@ F: Documentation/devicetree/bindings/net/dsa/microchip,lan937x.yaml
F: drivers/net/dsa/microchip/*
F: include/linux/dsa/ksz_common.h
F: include/linux/platform_data/microchip-ksz.h
+F: net/dsa/tag_ks8995.c
F: net/dsa/tag_ksz.c
MICROCHIP LAN743X ETHERNET DRIVER
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc10..abd159527782 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -59,6 +59,7 @@ struct tc_action;
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
#define DSA_TAG_PROTO_NETC_VALUE 33
+#define DSA_TAG_PROTO_KS8995_VALUE 34
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -95,6 +96,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE,
+ DSA_TAG_PROTO_KS8995 = DSA_TAG_PROTO_KS8995_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index d5e725b90d78..bbdf324addac 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -119,6 +119,12 @@ config NET_DSA_TAG_MXL_GSW1XX
Say Y or M if you want to enable support for tagging frames for
MaxLinear GSW1xx switches.
+config NET_DSA_TAG_KS8995
+ tristate "Tag driver for Micrel KS8995 switch"
+ help
+ Say Y if you want to enable support for tagging frames for the
+ Micrel KS8995 switch.
+
config NET_DSA_TAG_KSZ
tristate "Tag driver for Microchip 8795/937x/9477/9893 families of switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index b8c2667cd14a..a9c2a0569e9e 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_NET_DSA_TAG_BRCM_COMMON) += tag_brcm.o
obj-$(CONFIG_NET_DSA_TAG_DSA_COMMON) += tag_dsa.o
obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o
obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o
+obj-$(CONFIG_NET_DSA_TAG_KS8995) += tag_ks8995.o
obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
diff --git a/net/dsa/tag_ks8995.c b/net/dsa/tag_ks8995.c
new file mode 100644
index 000000000000..5e81ce1ea91a
--- /dev/null
+++ b/net/dsa/tag_ks8995.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Linus Walleij <linusw@kernel.org>
+ */
+#include <linux/etherdevice.h>
+#include <linux/log2.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "tag.h"
+
+/* The Micrel KS8995XA / Microchip KSZ8995XA Special Tag Packet ID (STPID)
+ * pushes its tag in a modified VLAN (802.1Q) tag.
+ * -----------------------------------------------------------
+ * | MAC DA | MAC SA | 2 bytes tag | 2 bytes TCI | EtherType |
+ * -----------------------------------------------------------
+ * The tag is: 0x8100 |= BIT(port), ports 0,1,2,3
+ */
+
+#define KS8995_NAME "ks8995"
+
+#define KS8995M_STPID_STD GENMASK(15, 4)
+#define KS8995M_STPID_PORTMASK GENMASK(3, 0)
+#define KS8995M_STPID(portmask) htons(ETH_P_8021Q | FIELD_PREP(KS8995M_STPID_PORTMASK, portmask))
+
+static struct sk_buff *ks8995_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct vlan_ethhdr *hdr = vlan_eth_hdr(skb);
+ bool have_hwaccel_tag = false;
+ u16 tci = 0, portmask;
+
+ /* Prepare the special KS8995 tags */
+ portmask = dsa_xmit_port_mask(skb, dev);
+
+ if (skb_vlan_tag_present(skb) && skb->vlan_proto == htons(ETH_P_8021Q)) {
+ tci = skb_vlan_tag_get(skb);
+ __vlan_hwaccel_clear_tag(skb);
+ have_hwaccel_tag = true;
+ }
+
+ if (have_hwaccel_tag || hdr->h_vlan_proto != htons(ETH_P_8021Q)) {
+ skb = vlan_insert_tag(skb, KS8995M_STPID(portmask), tci);
+ /* vlan_insert_tag() drops the skb on failure */
+ if (!skb)
+ return NULL;
+ hdr = vlan_eth_hdr(skb);
+ netdev_dbg(dev, "%s: inserted VLAN TAG %04x TCI %04x\n",
+ __func__, hdr->h_vlan_proto, hdr->h_vlan_TCI);
+ } else {
+ /* VLAN tag already exists in skb head, modify it in place */
+ hdr = vlan_eth_hdr(skb);
+ hdr->h_vlan_proto = KS8995M_STPID(portmask);
+ netdev_dbg(dev, "%s: modified VLAN TAG %04x\n",
+ __func__, hdr->h_vlan_proto);
+ }
+
+ return skb;
+}
+
+static struct sk_buff *ks8995_rcv(struct sk_buff *skb, struct net_device *dev)
+{
+ int portmask;
+ u16 etype;
+
+ /* We are expecting all received packets to have a mangled VLAN
+ * TPID, so drop anything else. Because of the non-standard TPID,
+ * don't even bother looking for a tag in the hwaccel area.
+ *
+ * We have to inspect the ethertype directly because skb->protocol
+ * will contain garbage.
+ */
+ etype = ntohs(*(__be16 *)dsa_etype_header_pos_rx(skb));
+ if ((etype & KS8995M_STPID_STD) != ETH_P_8021Q) {
+ netdev_dbg(dev, "%s: dropped ethertype 0x%04x\n",
+ __func__, etype);
+ kfree_skb(skb);
+ return NULL;
+ }
+ netdev_dbg(dev, "%s: received ethertype %04x\n",
+ __func__, etype);
+
+ /* Move the custom DSA+VLAN tag into the hwaccel area and strip
+ * it from the skb head
+ */
+ skb = skb_vlan_untag(skb);
+ if (!skb) {
+ /* skb_vlan_untag drops the skb on failure */
+ netdev_err(dev, "%s: unable to untag skb\n", __func__);
+ return NULL;
+ }
+
+ portmask = FIELD_GET(KS8995M_STPID_PORTMASK, etype);
+ netdev_dbg(dev, "%s: etype %04x portmask %04x (%d)\n",
+ __func__, etype, portmask, ilog2(portmask));
+ skb->dev = dsa_conduit_find_user(dev, 0, ilog2(portmask));
+ if (!skb->dev) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* Preserve the VLAN tag if it contains a non-zero VID which is not
+ * identical to 0x001, or PCP, and restore its TPID to the standard
+ * value.
+ *
+ * If this is just an ordinary inbound package the datasheet claims
+ * it will "replace null VID with ingress port VID", which means
+ * VID set to 1: 0x8101 0001 for port 0 or 0x8102 0001 for port 1.
+ * So in the DSA driver we will set the default port VID to 0 so
+ * we can properly detect non-VLAN frames.
+ */
+ if (!skb->vlan_tci) {
+ netdev_dbg(dev, "%s: clear VLAN tag from frame\n", __func__);
+ __vlan_hwaccel_clear_tag(skb);
+ } else {
+ skb->vlan_proto = htons(ETH_P_8021Q);
+ netdev_dbg(dev, "%s: vlan_tci = 0x%04x VLAN frame\n",
+ __func__, skb->vlan_tci);
+ }
+
+ dsa_default_offload_fwd_mark(skb);
+
+ return skb;
+}
+
+static const struct dsa_device_ops ks8995_netdev_ops = {
+ .name = KS8995_NAME,
+ .proto = DSA_TAG_PROTO_KS8995,
+ .xmit = ks8995_xmit,
+ .rcv = ks8995_rcv,
+ .needed_headroom = VLAN_HLEN,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for Micrel KS8995 family of switches");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KS8995, KS8995_NAME);
+
+module_dsa_tag_driver(ks8995_netdev_ops);
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v7 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij,
Krzysztof Kozlowski
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
The KSZ8995XA is just like the KSZ8795 and KSZ8864 a Micrel
product. It was renamed from KS8995XA to KSZ8995XA at some point,
but it has the same properties as the KS8995XA.
Be careful to use the full product name in this new compatible:
there is also KSZ8995MA and KSZ8995E which are not compatible
with the KS8995XA.
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml b/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
index 8d4a3a9a33fc..4ed13870ed3a 100644
--- a/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
+++ b/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
@@ -23,6 +23,7 @@ properties:
- microchip,ksz8864 # 4-port version of KSZ8895 family switch
- microchip,ksz8873
- microchip,ksz8895 # 5-port version of KSZ8895 family switch
+ - microchip,ksz8995xa
- microchip,ksz9477
- microchip,ksz9897
- microchip,ksz9896
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v7 1/5] net: dsa: microchip: Add fallback Micrel compatibles
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij, Nicolai Buchwitz
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
Because of forking paths when Micrel was acquired by Microchip,
two devices also exist with the micrel,* prefix bindings.
Add these to the KSZ SPI driver so users can use the more capable
driver.
Make the KS8995 driver mutually exclusive with this driver
to avoid probe races.
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/Kconfig | 1 +
drivers/net/dsa/microchip/ksz_spi.c | 15 +++++++++++++++
2 files changed, 16 insertions(+)
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 4ab567c5bbaf..e704ab702c18 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -100,6 +100,7 @@ config NET_DSA_RZN1_A5PSW
config NET_DSA_KS8995
tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
depends on SPI
+ depends on !NET_DSA_MICROCHIP_KSZ_SPI
select NET_DSA_TAG_NONE
help
This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c
index 373e9054947c..77aecac32466 100644
--- a/drivers/net/dsa/microchip/ksz_spi.c
+++ b/drivers/net/dsa/microchip/ksz_spi.c
@@ -224,6 +224,21 @@ static void ksz_spi_shutdown(struct spi_device *spi)
}
static const struct of_device_id ksz_dt_ids[] = {
+ /*
+ * Legacy Micrel bindings. In 2015 Microchip acquired
+ * Micrel which is the originator of the KSZ series, and
+ * devices branded for Micrel already existed, as well as
+ * some device tree bindings. These two products are identical
+ * to the same Microchip products.
+ */
+ {
+ .compatible = "micrel,ksz8864",
+ .data = &ksz_switch_chips[KSZ8864]
+ },
+ {
+ .compatible = "micrel,ksz8795",
+ .data = &ksz_switch_chips[KSZ8795]
+ },
{
.compatible = "microchip,ksz8463",
.data = &ksz_switch_chips[KSZ8463]
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v7 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij, Nicolai Buchwitz,
Krzysztof Kozlowski
This series breaks with the dated attempt to polish the old
KS8995 driver, and instead implement support for the KS8995XA
in the KSZ driver, and after that delete the old KS8995 driver.
The hardware clearly has the same ancestry, the KSZ8995XA is
just a rebrand of the much older Micrel KX8995XA switch.
The old drivers referce to "KS8995" was actually KS8995XA only,
it never supported the sibling devices KS8995E or KS8995MA.
This is reflected in this patch set.
Add new compatibles, add special code paths for the KSZ8995XA
and add a new tagger for the special front tag found in the
KSZ8995XA.
The patches were tested with the Actiontec MI424WR rev D (which
has the KS8995XA) and OpenWrt as userspace.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
Changes in v7:
- Rebased on v7.2-rc1
- Rebased on top of Bastiens cleanups.
- Rebased and reworked on top of my patch changing the SKB ownership
model in the taggers.
- Drop bogus reset routine, keep per-port reset but use the right
defines.
- Properly implement the 2way queue split support (low/hi prio)
- Drop bogus surplus ops assignment.
- Link to v6: https://patch.msgid.link/20260608-ks8995-to-ksz8-v6-0-d91eb43edcc5@kernel.org
Changes in v6:
- Provide a prompt along with the transition symbol NET_DSA_KS8995
so that oldconfig works.
- Clarify in commit message that the platform data probe path is
unused in the kernel and it is fine to delete this mechanism.
- Link to v5: https://patch.msgid.link/20260604-ks8995-to-ksz8-v5-0-98baeb45e665@kernel.org
Changes in v5:
- Rebase on net-next again to be sure we apply.
- Fix the codepath where DCB was set up per-port, damaging KS8995XA
port registers.
- Fix up the transitional symbol NET_DSA_KS8995 to depend on
SPI and select both NET_DSA_MICROCHIP_KSZ_COMMON and
NET_DSA_MICROCHIP_KSZ_SPI.
- Link to v4: https://patch.msgid.link/20260603-ks8995-to-ksz8-v4-0-e15149ef21e7@kernel.org
Changes in v4:
- Create a stub symbol for NET_DSA_KS8995 to phase over users to
the new driver.
- Link to v3: https://patch.msgid.link/20260526-ks8995-to-ksz8-v3-0-c530f651989f@kernel.org
Changes in v3:
- Rebase on net-next again, moving target!
- Fix a netdev_info() print in the tagger to be netdev_debug()
- Add net/dsa/tag_ks8995.c to MAINTAINERS
- Link to v2: https://patch.msgid.link/20260522-ks8995-to-ksz8-v2-0-5712c0dc9e75@kernel.org
Changes in v2:
- Avoid trying to configure TOS priority settings (DCB) on the KSZ8995XA.
It does have some support for this, but let's add that later.
- Pick up Krzysztof's ACK on the new DT bindings.
- Reset the KSZ8995XA by disabling and enabling the switch like the old
driver does.
- Move mutually exclusive Kconfig over to the patch introducing the
old Micrel compatibles to the Microchip ksz_spi driver.
(this complaint from Sashiko.)
- Use __be16 instead of u16 when casting into the SKB in the tagger
(this complaint from Sashiko.)
- Do not modify the hdr->h_vlan_TCI if no hardware accelerated tag
is detected, it would get nullified. Use whatever the Linux network stack
assigned to TCI there.
(this complaint from Sashiko.)
- Avoid dereference of NULL in debug prints in the tagger.
(this complaint from Sashiko.)
- Ignored comment: Sashiko complains about ilog2(0) being potentially
undefined but that is clearly defined as 0 in the Linux kernel.
- Link to v1: https://patch.msgid.link/20260516-ks8995-to-ksz8-v1-0-70d0ef4aa5f4@kernel.org
---
Linus Walleij (5):
net: dsa: microchip: Add fallback Micrel compatibles
dt-bindings: net: dsa: microchip: Add KSZ8995XA
net: dsa: tag_ks8995: Add the KS8995 tag handling
net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
net: dsa: ks8995: Delete surplus driver
.../devicetree/bindings/net/dsa/microchip,ksz.yaml | 1 +
MAINTAINERS | 1 +
drivers/net/dsa/Kconfig | 9 +-
drivers/net/dsa/Makefile | 1 -
drivers/net/dsa/ks8995.c | 857 ---------------------
drivers/net/dsa/microchip/Kconfig | 1 +
drivers/net/dsa/microchip/ksz8.c | 187 ++++-
drivers/net/dsa/microchip/ksz8.h | 2 +
drivers/net/dsa/microchip/ksz8_reg.h | 7 +
drivers/net/dsa/microchip/ksz_common.c | 54 +-
drivers/net/dsa/microchip/ksz_common.h | 11 +-
drivers/net/dsa/microchip/ksz_spi.c | 33 +-
include/linux/platform_data/microchip-ksz.h | 1 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +
net/dsa/Makefile | 1 +
net/dsa/tag_ks8995.c | 137 ++++
17 files changed, 408 insertions(+), 903 deletions(-)
---
base-commit: b73bc9ca3686b78b642fb35dcc1fdf874ecb74a1
change-id: 20260509-ks8995-to-ksz8-15f3f9c8271f
Best regards,
--
Linus Walleij <linusw@kernel.org>
^ permalink raw reply
* Re: [PATCH net-next v4 2/2] selftests: net: add FOU multicast encapsulation resubmit test
From: Willem de Bruijn @ 2026-07-04 19:08 UTC (permalink / raw)
To: Anton Danilov, netdev
Cc: Willem de Bruijn, David S . Miller, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
In-Reply-To: <a196b53a2829da66c982f88e82805afc498ea13b.1782945956.git.littlesmilingcloud@gmail.com>
Anton Danilov wrote:
> Add a selftest to verify that FOU-encapsulated packets addressed to a
> multicast destination are correctly resubmitted to the inner protocol
> handler (GRE) via the UDP multicast delivery path.
>
> The test creates two network namespaces connected by a veth pair with
> a FOU/GRETAP tunnel using a multicast remote address (239.0.0.1).
> Ping is sent through the tunnel and received packets are counted on
> the receiver's tunnel interface.
>
> A static neighbor entry is configured on the sender because ARP
> replies from the receiver cannot traverse the unidirectional multicast
> tunnel back to the sender.
>
> The early demux optimization (net.ipv4.ip_early_demux) is disabled on
> the receiver to force packets through __udp4_lib_mcast_deliver(),
> which is the code path being tested.
>
> Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
> Assisted-by: Claude:claude-opus-4-6
> ---
> tools/testing/selftests/net/Makefile | 1 +
> .../testing/selftests/net/fou_mcast_encap.sh | 112 ++++++++++++++++++
> 2 files changed, 113 insertions(+)
> create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
>
> diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
> index 708d960ae07d..7e9ae937cffa 100644
> --- a/tools/testing/selftests/net/Makefile
> +++ b/tools/testing/selftests/net/Makefile
> @@ -39,6 +39,7 @@ TEST_PROGS := \
> fib_rule_tests.sh \
> fib_tests.sh \
> fin_ack_lat.sh \
> + fou_mcast_encap.sh \
> fq_band_pktlimit.sh \
> gre_gso.sh \
> gre_ipv6_lladdr.sh \
> diff --git a/tools/testing/selftests/net/fou_mcast_encap.sh b/tools/testing/selftests/net/fou_mcast_encap.sh
> new file mode 100755
> index 000000000000..8db9633f4c28
> --- /dev/null
> +++ b/tools/testing/selftests/net/fou_mcast_encap.sh
> @@ -0,0 +1,112 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Test that UDP encapsulation (FOU) correctly handles packet resubmit
> +# when packets are delivered via the multicast UDP delivery path.
> +#
> +# When a FOU-encapsulated packet arrives with a multicast destination IP,
> +# __udp4_lib_mcast_deliver() must resubmit it to the inner protocol
> +# handler (e.g., GRE) rather than consuming it. This test verifies that
> +# by creating a FOU/GRETAP tunnel with a multicast remote address and
> +# sending ping through it.
> +#
> +# The early demux optimization can mask this issue by routing packets via
> +# the unicast path (udp_unicast_rcv_skb), so we disable it to force
> +# packets through __udp4_lib_mcast_deliver().
> +
> +source lib.sh
> +
> +NSENDER=""
> +NRECV=""
> +
> +cleanup() {
> + cleanup_all_ns
> +}
> +
> +trap cleanup EXIT
> +
> +setup() {
> + setup_ns NSENDER NRECV
> +
> + ip link add veth_s type veth peer name veth_r
> + ip link set veth_s netns "$NSENDER"
> + ip link set veth_r netns "$NRECV"
Sashiko points out that names in the root namespace may collide with
other devices. Unexpected here, but it's just as easy to immediately
assign the new veth nodes to the netns. See for instance sctp_vrf.sh.
> +
> + ip -n "$NSENDER" addr add 10.0.0.1/24 dev veth_s
More importantly, have you also tested IPv6? Can you expand the
test with IPv6.
Again, sashiko points to an important issue: ip_protocol_deliver_rcu
and ip6_protocol_deliver_rcu are not equivalent. From a quick scan it
appears that the latter does not resubmit on negative return code.
^ permalink raw reply
* Re: [PATCH net v2] bnge/bng_re: fix ring ID widths
From: Andrew Lunn @ 2026-07-04 18:34 UTC (permalink / raw)
To: Vikas Gupta, gg
Cc: davem, edumazet, kuba, pabeni, andrew+netdev, horms, netdev,
linux-kernel, linux-rdma, leonro, jgg, bhargava.marreddy,
rahul-rg.gupta, vsrama-krishna.nemani, rajashekar.hudumula,
ajit.khaparde, Siva Reddy Kallam, Dharmender Garg,
Yendapally Reddy Dhananjaya Reddy
In-Reply-To: <20260704164747.1995227-1-vikas.gupta@broadcom.com>
> - Backward compatibility with older firmware is not a concern.
Could you expand on that please.
Andrew
^ permalink raw reply
* [PATCH net] ipv4: fib: free fib_alias with kfree_rcu() on insert error path
From: Weiming Shi @ 2026-07-04 17:14 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, edumazet, kuba, pabeni, davem, horms, xmei5,
linux-kernel, Weiming Shi
fib_table_insert() publishes new_fa into the leaf's fa_list with
fib_insert_alias() before calling the fib entry notifiers. When a
notifier fails, the error path removes new_fa with fib_remove_alias()
(hlist_del_rcu) and frees it right away with kmem_cache_free().
fib_table_lookup() walks that list under rcu_read_lock() only, so a
concurrent lookup that already reached new_fa keeps reading it after the
free:
BUG: KASAN: slab-use-after-free in fib_table_lookup (net/ipv4/fib_trie.c:1601)
Read of size 1 at addr ffff88810676d4eb by task exploit/297
Call Trace:
fib_table_lookup (net/ipv4/fib_trie.c:1601)
ip_route_output_key_hash_rcu (net/ipv4/route.c:2814)
ip_route_output_key_hash (net/ipv4/route.c:2705)
__ip4_datagram_connect (net/ipv4/datagram.c:49)
udp_connect (net/ipv4/udp.c:2144)
__sys_connect (net/socket.c:2167)
__x64_sys_connect (net/socket.c:2173)
do_syscall_64
entry_SYSCALL_64_after_hwframe
which belongs to the cache ip_fib_alias of size 56
Triggering the error path needs CAP_NET_ADMIN and a registered fib
notifier that can reject a route; a netdevsim device whose IPv4 FIB
resource is exhausted is enough.
Free new_fa with alias_free_mem_rcu(), as fib_table_delete() already
does for a fib_alias removed from the trie.
Fixes: a6c76c17df02 ("ipv4: Notify route after insertion to the routing table")
Reported-by: Xiang Mei <xmei5@asu.edu>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
net/ipv4/fib_trie.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c
index 1308213791f1..2dc87e2156d3 100644
--- a/net/ipv4/fib_trie.c
+++ b/net/ipv4/fib_trie.c
@@ -1385,7 +1385,7 @@ int fib_table_insert(struct net *net, struct fib_table *tb,
out_remove_new_fa:
fib_remove_alias(t, tp, l, new_fa);
out_free_new_fa:
- kmem_cache_free(fn_alias_kmem, new_fa);
+ alias_free_mem_rcu(new_fa);
out:
fib_release_info(fi);
err:
--
2.43.0
^ permalink raw reply related
* [PATCH net v2] bnge/bng_re: fix ring ID widths
From: Vikas Gupta @ 2026-07-04 16:47 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, andrew+netdev, horms
Cc: netdev, linux-kernel, linux-rdma, leonro, jgg, bhargava.marreddy,
rahul-rg.gupta, vsrama-krishna.nemani, rajashekar.hudumula,
ajit.khaparde, Vikas Gupta, Siva Reddy Kallam, Dharmender Garg,
Yendapally Reddy Dhananjaya Reddy
Firmware requires more than 16 bits to address TX ring IDs for its
internal QP management. Widen the associated HSI ring ID fields to
32 bits. The values firmware assigns remain within 24 bits, bounded
by the hardware doorbell XID field.
RX, completion, and NQ ring IDs are unaffected and remain 16-bit.
Fixes: 42d1c54d6248 ("bnge/bng_re: Add a new HSI")
Signed-off-by: Vikas Gupta <vikas.gupta@broadcom.com>
Reviewed-by: Siva Reddy Kallam <siva.kallam@broadcom.com>
Reviewed-by: Dharmender Garg <dharmender.garg@broadcom.com>
Reviewed-by: Yendapally Reddy Dhananjaya Reddy <yendapally.reddy@broadcom.com>
---
V2:
Sashiko review:
- Updated commit message only, no code change.
- Sashiko's concern about XID overflow is valid in theory but is
handled by firmware, which guarantees TX ring IDs stay within the
24-bit hardware doorbell XID field.
- Backward compatibility with older firmware is not a concern.
drivers/infiniband/hw/bng_re/bng_dev.c | 6 +--
drivers/net/ethernet/broadcom/bnge/bnge.h | 1 +
.../ethernet/broadcom/bnge/bnge_hwrm_lib.c | 8 +--
.../ethernet/broadcom/bnge/bnge_hwrm_lib.h | 2 +-
.../net/ethernet/broadcom/bnge/bnge_netdev.c | 50 +++++++++----------
.../net/ethernet/broadcom/bnge/bnge_netdev.h | 4 +-
.../net/ethernet/broadcom/bnge/bnge_rmem.h | 2 +-
include/linux/bnge/hsi.h | 7 ++-
8 files changed, 39 insertions(+), 41 deletions(-)
diff --git a/drivers/infiniband/hw/bng_re/bng_dev.c b/drivers/infiniband/hw/bng_re/bng_dev.c
index 71a7ca2196ad..311c8bc93160 100644
--- a/drivers/infiniband/hw/bng_re/bng_dev.c
+++ b/drivers/infiniband/hw/bng_re/bng_dev.c
@@ -113,7 +113,7 @@ static void bng_re_fill_fw_msg(struct bnge_fw_msg *fw_msg, void *msg,
}
static int bng_re_net_ring_free(struct bng_re_dev *rdev,
- u16 fw_ring_id, int type)
+ u32 fw_ring_id, int type)
{
struct bnge_auxr_dev *aux_dev = rdev->aux_dev;
struct hwrm_ring_free_input req = {};
@@ -123,7 +123,7 @@ static int bng_re_net_ring_free(struct bng_re_dev *rdev,
bng_re_init_hwrm_hdr((void *)&req, HWRM_RING_FREE);
req.ring_type = type;
- req.ring_id = cpu_to_le16(fw_ring_id);
+ req.ring_id = cpu_to_le32(fw_ring_id);
bng_re_fill_fw_msg(&fw_msg, (void *)&req, sizeof(req), (void *)&resp,
sizeof(resp), BNGE_DFLT_HWRM_CMD_TIMEOUT);
rc = bnge_send_msg(aux_dev, &fw_msg);
@@ -161,7 +161,7 @@ static int bng_re_net_ring_alloc(struct bng_re_dev *rdev,
sizeof(resp), BNGE_DFLT_HWRM_CMD_TIMEOUT);
rc = bnge_send_msg(aux_dev, &fw_msg);
if (!rc)
- *fw_ring_id = le16_to_cpu(resp.ring_id);
+ *fw_ring_id = (u16)le32_to_cpu(resp.ring_id);
return rc;
}
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge.h b/drivers/net/ethernet/broadcom/bnge/bnge.h
index f21cff651fd4..4479ccd071f5 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge.h
+++ b/drivers/net/ethernet/broadcom/bnge/bnge.h
@@ -36,6 +36,7 @@ struct bnge_pf_info {
};
#define INVALID_HW_RING_ID ((u16)-1)
+#define INVALID_HW_RING_ID_32BIT (U32_MAX)
enum {
BNGE_FW_CAP_SHORT_CMD = BIT_ULL(0),
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
index 1c9cfec1b633..651c5e783516 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
@@ -1283,7 +1283,7 @@ int bnge_hwrm_stat_ctx_alloc(struct bnge_net *bn)
int hwrm_ring_free_send_msg(struct bnge_net *bn,
struct bnge_ring_struct *ring,
- u32 ring_type, int cmpl_ring_id)
+ u32 ring_type, u32 cmpl_ring_id)
{
struct hwrm_ring_free_input *req;
struct bnge_dev *bd = bn->bd;
@@ -1295,7 +1295,7 @@ int hwrm_ring_free_send_msg(struct bnge_net *bn,
req->cmpl_ring = cpu_to_le16(cmpl_ring_id);
req->ring_type = ring_type;
- req->ring_id = cpu_to_le16(ring->fw_ring_id);
+ req->ring_id = cpu_to_le32(ring->fw_ring_id);
bnge_hwrm_req_hold(bd, req);
rc = bnge_hwrm_req_send(bd, req);
@@ -1317,7 +1317,7 @@ int hwrm_ring_alloc_send_msg(struct bnge_net *bn,
struct hwrm_ring_alloc_output *resp;
struct hwrm_ring_alloc_input *req;
struct bnge_dev *bd = bn->bd;
- u16 ring_id, flags = 0;
+ u32 ring_id, flags = 0;
int rc;
rc = bnge_hwrm_req_init(bd, req, HWRM_RING_ALLOC);
@@ -1401,7 +1401,7 @@ int hwrm_ring_alloc_send_msg(struct bnge_net *bn,
resp = bnge_hwrm_req_hold(bd, req);
rc = bnge_hwrm_req_send(bd, req);
- ring_id = le16_to_cpu(resp->ring_id);
+ ring_id = le32_to_cpu(resp->ring_id);
bnge_hwrm_req_drop(bd, req);
exit:
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h
index 3501de7a89b9..bf452e390d5b 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.h
@@ -50,7 +50,7 @@ int bnge_hwrm_cfa_l2_set_rx_mask(struct bnge_dev *bd,
void bnge_hwrm_stat_ctx_free(struct bnge_net *bn);
int bnge_hwrm_stat_ctx_alloc(struct bnge_net *bn);
int hwrm_ring_free_send_msg(struct bnge_net *bn, struct bnge_ring_struct *ring,
- u32 ring_type, int cmpl_ring_id);
+ u32 ring_type, u32 cmpl_ring_id);
int hwrm_ring_alloc_send_msg(struct bnge_net *bn,
struct bnge_ring_struct *ring,
u32 ring_type, u32 map_index);
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c
index 70768193004c..6f7ef506d4e1 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c
@@ -1327,12 +1327,12 @@ static int bnge_alloc_core(struct bnge_net *bn)
return rc;
}
-u16 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr)
+u32 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr)
{
return rxr->rx_cpr->ring_struct.fw_ring_id;
}
-u16 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr)
+u32 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr)
{
return txr->tx_cpr->ring_struct.fw_ring_id;
}
@@ -1375,12 +1375,12 @@ static void bnge_init_nq_tree(struct bnge_net *bn)
struct bnge_nq_ring_info *nqr = &bn->bnapi[i]->nq_ring;
struct bnge_ring_struct *ring = &nqr->ring_struct;
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
for (j = 0; j < nqr->cp_ring_count; j++) {
struct bnge_cp_ring_info *cpr = &nqr->cp_ring_arr[j];
ring = &cpr->ring_struct;
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
}
}
}
@@ -1637,7 +1637,7 @@ static void bnge_init_one_rx_ring_rxbd(struct bnge_net *bn,
ring = &rxr->rx_ring_struct;
bnge_init_rxbd_pages(ring, type);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
}
static void bnge_init_one_agg_ring_rxbd(struct bnge_net *bn,
@@ -1647,7 +1647,7 @@ static void bnge_init_one_agg_ring_rxbd(struct bnge_net *bn,
u32 type;
ring = &rxr->rx_agg_ring_struct;
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
if (bnge_is_agg_reqd(bn->bd)) {
type = ((u32)BNGE_RX_PAGE_SIZE << RX_BD_LEN_SHIFT) |
RX_BD_TYPE_RX_AGG_BD | RX_BD_FLAGS_SOP;
@@ -1708,7 +1708,7 @@ static void bnge_init_tx_rings(struct bnge_net *bn)
struct bnge_tx_ring_info *txr = &bn->tx_ring[i];
struct bnge_ring_struct *ring = &txr->tx_ring_struct;
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
netif_queue_set_napi(bn->netdev, i, NETDEV_QUEUE_TYPE_TX,
&txr->bnapi->napi);
@@ -1867,7 +1867,7 @@ static int bnge_hwrm_rx_agg_ring_alloc(struct bnge_net *bn,
ring->fw_ring_id);
bnge_db_write(bn->bd, &rxr->rx_agg_db, rxr->rx_agg_prod);
bnge_db_write(bn->bd, &rxr->rx_db, rxr->rx_prod);
- bn->grp_info[grp_idx].agg_fw_ring_id = ring->fw_ring_id;
+ bn->grp_info[grp_idx].agg_fw_ring_id = (u16)ring->fw_ring_id;
return 0;
}
@@ -1886,7 +1886,7 @@ static int bnge_hwrm_rx_ring_alloc(struct bnge_net *bn,
return rc;
bnge_set_db(bn, &rxr->rx_db, type, map_idx, ring->fw_ring_id);
- bn->grp_info[map_idx].rx_fw_ring_id = ring->fw_ring_id;
+ bn->grp_info[map_idx].rx_fw_ring_id = (u16)ring->fw_ring_id;
return 0;
}
@@ -1916,7 +1916,7 @@ static int bnge_hwrm_ring_alloc(struct bnge_net *bn)
bnge_set_db(bn, &nqr->nq_db, type, map_idx, ring->fw_ring_id);
bnge_db_nq(bn, &nqr->nq_db, nqr->nq_raw_cons);
enable_irq(vector);
- bn->grp_info[i].nq_fw_ring_id = ring->fw_ring_id;
+ bn->grp_info[i].nq_fw_ring_id = (u16)ring->fw_ring_id;
if (!i) {
rc = bnge_hwrm_set_async_event_cr(bd, ring->fw_ring_id);
@@ -1986,15 +1986,13 @@ void bnge_fill_hw_rss_tbl(struct bnge_net *bn, struct bnge_vnic_info *vnic)
tbl_size = bnge_get_rxfh_indir_size(bd);
for (i = 0; i < tbl_size; i++) {
- u16 ring_id, j;
+ u32 j;
j = bd->rss_indir_tbl[i];
rxr = &bn->rx_ring[j];
- ring_id = rxr->rx_ring_struct.fw_ring_id;
- *ring_tbl++ = cpu_to_le16(ring_id);
- ring_id = bnge_cp_ring_for_rx(rxr);
- *ring_tbl++ = cpu_to_le16(ring_id);
+ *ring_tbl++ = cpu_to_le16(rxr->rx_ring_struct.fw_ring_id);
+ *ring_tbl++ = cpu_to_le16(bnge_cp_ring_for_rx(rxr));
}
}
@@ -2285,7 +2283,7 @@ static void bnge_disable_int(struct bnge_net *bn)
nqr = &bnapi->nq_ring;
ring = &nqr->ring_struct;
- if (ring->fw_ring_id != INVALID_HW_RING_ID)
+ if (ring->fw_ring_id != INVALID_HW_RING_ID_32BIT)
bnge_db_nq(bn, &nqr->nq_db, nqr->nq_raw_cons);
}
}
@@ -2401,7 +2399,7 @@ static void bnge_hwrm_rx_ring_free(struct bnge_net *bn,
u32 grp_idx = rxr->bnapi->index;
u32 cmpl_ring_id;
- if (ring->fw_ring_id == INVALID_HW_RING_ID)
+ if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT)
return;
cmpl_ring_id = bnge_cp_ring_for_rx(rxr);
@@ -2409,7 +2407,7 @@ static void bnge_hwrm_rx_ring_free(struct bnge_net *bn,
RING_FREE_REQ_RING_TYPE_RX,
close_path ? cmpl_ring_id :
INVALID_HW_RING_ID);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
bn->grp_info[grp_idx].rx_fw_ring_id = INVALID_HW_RING_ID;
}
@@ -2421,14 +2419,14 @@ static void bnge_hwrm_rx_agg_ring_free(struct bnge_net *bn,
u32 grp_idx = rxr->bnapi->index;
u32 cmpl_ring_id;
- if (ring->fw_ring_id == INVALID_HW_RING_ID)
+ if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT)
return;
cmpl_ring_id = bnge_cp_ring_for_rx(rxr);
hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_RX_AGG,
close_path ? cmpl_ring_id :
INVALID_HW_RING_ID);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
bn->grp_info[grp_idx].agg_fw_ring_id = INVALID_HW_RING_ID;
}
@@ -2439,14 +2437,14 @@ static void bnge_hwrm_tx_ring_free(struct bnge_net *bn,
struct bnge_ring_struct *ring = &txr->tx_ring_struct;
u32 cmpl_ring_id;
- if (ring->fw_ring_id == INVALID_HW_RING_ID)
+ if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT)
return;
cmpl_ring_id = close_path ? bnge_cp_ring_for_tx(txr) :
INVALID_HW_RING_ID;
hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_TX,
cmpl_ring_id);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
}
static void bnge_hwrm_cp_ring_free(struct bnge_net *bn,
@@ -2455,12 +2453,12 @@ static void bnge_hwrm_cp_ring_free(struct bnge_net *bn,
struct bnge_ring_struct *ring;
ring = &cpr->ring_struct;
- if (ring->fw_ring_id == INVALID_HW_RING_ID)
+ if (ring->fw_ring_id == INVALID_HW_RING_ID_32BIT)
return;
hwrm_ring_free_send_msg(bn, ring, RING_FREE_REQ_RING_TYPE_L2_CMPL,
INVALID_HW_RING_ID);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
}
static void bnge_hwrm_ring_free(struct bnge_net *bn, bool close_path)
@@ -2496,11 +2494,11 @@ static void bnge_hwrm_ring_free(struct bnge_net *bn, bool close_path)
bnge_hwrm_cp_ring_free(bn, &nqr->cp_ring_arr[j]);
ring = &nqr->ring_struct;
- if (ring->fw_ring_id != INVALID_HW_RING_ID) {
+ if (ring->fw_ring_id != INVALID_HW_RING_ID_32BIT) {
hwrm_ring_free_send_msg(bn, ring,
RING_FREE_REQ_RING_TYPE_NQ,
INVALID_HW_RING_ID);
- ring->fw_ring_id = INVALID_HW_RING_ID;
+ ring->fw_ring_id = INVALID_HW_RING_ID_32BIT;
bn->grp_info[i].nq_fw_ring_id = INVALID_HW_RING_ID;
}
}
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h
index f4636b5b0cf3..d177919c2e11 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.h
@@ -630,8 +630,8 @@ struct bnge_l2_filter {
refcount_t refcnt;
};
-u16 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr);
-u16 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr);
+u32 bnge_cp_ring_for_rx(struct bnge_rx_ring_info *rxr);
+u32 bnge_cp_ring_for_tx(struct bnge_tx_ring_info *txr);
void bnge_fill_hw_rss_tbl(struct bnge_net *bn, struct bnge_vnic_info *vnic);
int bnge_alloc_rx_data(struct bnge_net *bn, struct bnge_rx_ring_info *rxr,
u16 prod, gfp_t gfp);
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
index 341c7f81ed09..bb0c79a1ee60 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
@@ -184,7 +184,7 @@ struct bnge_ctx_mem_info {
struct bnge_ring_struct {
struct bnge_ring_mem_info ring_mem;
- u16 fw_ring_id;
+ u32 fw_ring_id;
union {
u16 grp_idx;
u16 map_idx; /* Used by NQs */
diff --git a/include/linux/bnge/hsi.h b/include/linux/bnge/hsi.h
index 8ea13d5407ee..1f7bd96415a5 100644
--- a/include/linux/bnge/hsi.h
+++ b/include/linux/bnge/hsi.h
@@ -8317,8 +8317,7 @@ struct hwrm_ring_alloc_output {
__le16 req_type;
__le16 seq_id;
__le16 resp_len;
- __le16 ring_id;
- __le16 logical_ring_id;
+ __le32 ring_id;
u8 push_buffer_index;
#define RING_ALLOC_RESP_PUSH_BUFFER_INDEX_PING_BUFFER 0x0UL
#define RING_ALLOC_RESP_PUSH_BUFFER_INDEX_PONG_BUFFER 0x1UL
@@ -8345,10 +8344,10 @@ struct hwrm_ring_free_input {
u8 flags;
#define RING_FREE_REQ_FLAGS_VIRTIO_RING_VALID 0x1UL
#define RING_FREE_REQ_FLAGS_LAST RING_FREE_REQ_FLAGS_VIRTIO_RING_VALID
- __le16 ring_id;
+ __le16 unused_1;
__le32 prod_idx;
__le32 opaque;
- __le32 unused_1;
+ __le32 ring_id;
};
/* hwrm_ring_free_output (size:128b/16B) */
--
2.47.1
^ permalink raw reply related
* Re: [RFC PATCH bpf-next v1 0/7] xdp: RX checksum metadata hint and checksum assertion over redirect
From: Jesper Dangaard Brouer @ 2026-07-04 16:41 UTC (permalink / raw)
To: Lorenzo Bianconi, Vladimir Vdovin
Cc: sdf, kuba, andrii, ast, daniel, john.fastabend, martin.lau,
sdf.kernel, bpf, netdev, David S. Miller, Alexander Lobakin,
Eric Dumazet, kernel-team
In-Reply-To: <akZ7QPfn83OUx5Vm@lore-desk>
On 02/07/2026 16.52, Lorenzo Bianconi wrote:
[...]
>>
>> What's left that is genuinely separate is the "assertion" half -- a non-dev-bound
>> bpf_xdp_assert_rx_csum() that preserves the HW verdict across a
>> cpumap/redirect: it sets a flag on the xdp_buff that rides into the
>> xdp_frame and becomes skb->ip_summed = CHECKSUM_UNNECESSARY in
>> __xdp_build_skb_from_frame().
>>
>> Should I resend that as a small standalone series (v2, assert-only)?
>> It also looks like a PoC that you and Jakub discussed on v3 [1].
>
> I think we should address the 'CHECKSUM_COMPLETE' use case for it, let's work
> on the problem later, even Jesper is interested in it ;)
>
Yes, I'm very interested in this use-case of transferring HW offload
info when XDP_REDIRECT'ing into CPUMAP or veth. In my patchset[1] I've
added rx_hash, rx_vlan_tag, and rx_timestamp, but not csum as it was not
available. My short term use-case is: RX-hash as setting it earlier
allows the GRO engine to hash flows (see dev_gro_receive[2]), plus it
carries info on packet protocol type TCP/UDP IPv4/IPv6.
- [1]
https://lore.kernel.org/all/175146824674.1421237.18351246421763677468.stgit@firesoul/#r
- [2] https://elixir.bootlin.com/linux/v7.1.2/source/net/core/gro.c#L477
The long term[3] vision behind the patchset is that xdp_frame becomes
the "mini-SKB" that carries enough offload info to avoid allocating SKBs
in the driver. The ICE driver is very close to this vision. See [4]
where it calls xdp_build_skb_from_buff() and later fills out SKB fields
with HW offloads (ice_process_skb_fields).
What I'm asking is that we generalize this idea of transferring or
storing HW offloads, that are relevant to the SKB. My patchset makes
info avail to both xdp_buff and xdp_frame by storing data[5] in the
packet top headroom (extending xdp_frame). And like your patch[6] use
xdp_buff_flags to tell with is active. You patch only needed a single
flag as you only handle CHECKSUM_UNNECESSARY, but Lorenzo's patch also
have CHECKSUM_COMPLETE, for which we need to store a csum.
I cannot remember if it was Kuba or Eric that suggested (at netdevconf)
that instead of using a fixed struct, that I should use the same scheme
as SKB extensions. We could call it SKB core-extensions, to signal the
possibility to move existing SKB member into this area.
--Jesper
- [3]
https://lore.kernel.org/all/fbb026f9-54cf-49ba-b0dc-0df0f54c6961@kernel.org/
- [4]
https://elixir.bootlin.com/linux/v7.1.2/source/drivers/net/ethernet/intel/ice/ice_txrx.c#L1044-L1060
- [5]
https://lore.kernel.org/all/175146829944.1421237.13943404585579626611.stgit@firesoul/
- [6] https://lore.kernel.org/all/20260630191510.81402-1-deliran@verdict.gg/
^ permalink raw reply
* [PATCH] net: davicom: dm9051: add missing MODULE_DEVICE_TABLE()
From: Pengpeng Hou @ 2026-07-04 15:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Pengpeng Hou, netdev, linux-kernel
The driver has a match table for the spi bus wired into its driver
structure, but the table is not exported with MODULE_DEVICE_TABLE().
Add the missing MODULE_DEVICE_TABLE() entry so module alias information
is generated for automatic module loading.
This is a source-level fix. It does not claim dynamic hardware
reproduction; the evidence is the driver-owned match table, its use by
the driver registration structure, and the missing module alias
publication.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/davicom/dm9051.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/davicom/dm9051.c b/drivers/net/ethernet/davicom/dm9051.c
index 59ea48d4c9de..194494ac5582 100644
--- a/drivers/net/ethernet/davicom/dm9051.c
+++ b/drivers/net/ethernet/davicom/dm9051.c
@@ -1241,6 +1241,7 @@ static const struct spi_device_id dm9051_id_table[] = {
{ "dm9051", 0 },
{}
};
+MODULE_DEVICE_TABLE(spi, dm9051_id_table);
static struct spi_driver dm9051_driver = {
.driver = {
--
2.53.0
^ permalink raw reply related
* [PATCH] net: alacritech: add missing MODULE_DEVICE_TABLE()
From: Pengpeng Hou @ 2026-07-04 15:20 UTC (permalink / raw)
To: Lino Sanfilippo, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: Pengpeng Hou, netdev, linux-kernel
The driver has a match table for the pci bus wired into its driver
structure, but the table is not exported with MODULE_DEVICE_TABLE().
Add the missing MODULE_DEVICE_TABLE() entry so module alias information
is generated for automatic module loading.
This is a source-level fix. It does not claim dynamic hardware
reproduction; the evidence is the driver-owned match table, its use by
the driver registration structure, and the missing module alias
publication.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/alacritech/slicoss.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/alacritech/slicoss.c b/drivers/net/ethernet/alacritech/slicoss.c
index c1949ca060ca..14b74c09233e 100644
--- a/drivers/net/ethernet/alacritech/slicoss.c
+++ b/drivers/net/ethernet/alacritech/slicoss.c
@@ -34,6 +34,7 @@ static const struct pci_device_id slic_id_tbl[] = {
PCI_DEVICE_ID_ALACRITECH_OASIS) },
{ 0 }
};
+MODULE_DEVICE_TABLE(pci, slic_id_tbl);
static const char slic_stats_strings[][ETH_GSTRING_LEN] = {
"rx_packets",
--
2.53.0
^ permalink raw reply related
* Re: [PATCH 1/1] net: usb: aqc111: fix set_mac_address return value for bonding
From: Andrew Lunn @ 2026-07-04 14:32 UTC (permalink / raw)
To: Hanson Wang; +Cc: netdev, linux-usb, oneukum
In-Reply-To: <20260704084258.3513069-1-hanson.wang@ugreen.com>
On Sat, Jul 04, 2026 at 04:42:58PM +0800, Hanson Wang wrote:
> Hi Andrew,
>
> Thanks for the review. You are right that this is not bonding-specific -
> any caller of ndo_set_mac_address() (including netif_set_mac_address()
> and "ip link set address") expects 0 on success and treats any non-zero
> return value as failure.
>
> I audited all drivers under drivers/net/usb/ that implement a custom
> ndo_set_mac_address callback:
>
> aqc111.c - BUG: returns usb_control_msg byte count (6) [this patch]
> ax88179_178a.c - OK: returns 0 after ax_write_cmd()
> asix_common.c - OK: returns 0 (async write to hardware)
> ch397.c - OK: returns 0
> dm9601.c - OK: returns 0 (async write)
> lan78xx.c - OK: returns 0
> mcs7830.c - OK: returns 0
> qmi_wwan.c - OK: returns 0
> r8152.c/r8157.c - OK: returns 0 on success (via usb_autopm_get_interface)
> rtl8150.c - OK: returns 0
> sr9700.c - OK: returns 0 (async write)
> sr9800.c - OK: returns 0 (async write)
Thanks for reviewing these drivers.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply
* [PATCH net] net: airoha: fix ETS channel derivation in airoha_tc_setup_qdisc_ets()
From: Lorenzo Bianconi @ 2026-07-04 14:21 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev
Derive the hardware QoS channel from opt->parent instead of opt->handle
in airoha_tc_setup_qdisc_ets(). The ETS qdisc handle is either
user-specified or auto-allocated by qdisc_alloc_handle() and bears no
relation to the HTB leaf classid that identifies the hardware channel.
HTB derives the channel from TC_H_MIN(opt->classid), and ETS is always
attached as a child of an HTB leaf, so its opt->parent matches that
classid. Using opt->handle instead can cause two ETS qdiscs on different
HTB leaves to collide on the same hardware channel, corrupting scheduler
configuration and stats.
Fixes: 20bf7d07c956 ("net: airoha: Add sched ETS offload support")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 59001fd4b6f7..fac2aaefffff 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2504,8 +2504,7 @@ static int airoha_tc_setup_qdisc_ets(struct net_device *dev,
if (opt->parent == TC_H_ROOT)
return -EINVAL;
- channel = TC_H_MAJ(opt->handle) >> 16;
- channel = channel % AIROHA_NUM_QOS_CHANNELS;
+ channel = TC_H_MIN(opt->parent) % AIROHA_NUM_QOS_CHANNELS;
switch (opt->command) {
case TC_ETS_REPLACE:
---
base-commit: d6456743424721a837e1509b912f362caaeecd97
change-id: 20260704-airoha-ets-handle-fix-6c166de3d396
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* Re: [RFC] xdp: add device context to bpf_xdp_link_attach_failed tracepoint
From: Leon Hwang @ 2026-07-04 13:28 UTC (permalink / raw)
To: Masashi Honma
Cc: netdev, bpf, linux-trace-kernel, ast, daniel, kuba, hawk, andrii,
rostedt, mhiramat, edumazet, pabeni, linux-kernel
In-Reply-To: <CAFk-A4n9U0r07tn6XHHqmOZ++-bmM4bSd-SwdpEU-88gjKts7Q@mail.gmail.com>
Pls do not top-post.
See
https://docs.kernel.org/process/submitting-patches.html#use-trimmed-interleaved-replies-in-email-discussions.
On 2026/7/4 13:50, Masashi Honma wrote:
> Thank you Leon. I think this is a much better direction than my RFC, so I'll
> drop my tracepoint proposal and support your approach instead.
>
> Since a user-space dependency on the existing tracepoint would make it hard
> for you to retire it, I'll make sure the Cilium PR doesn't rely on the
> tracepoint either.
Probably, you can get the 'extack->_msg' by tracing dev_xdp_attach using
kprobe+kretprobe or kprobe.session, if the extack is not NULL.
>
> Is there anything I can help with on the new approach?
You are welcome to review the series in the future.
Thanks,
Leon
[...]
^ permalink raw reply
* Re: [PATCH net] tun/tap & vhost-net: make qdisc backpressure opt-in via IFF_BACKPRESSURE
From: Michael S. Tsirkin @ 2026-07-04 12:52 UTC (permalink / raw)
To: Simon Schippers
Cc: Willem de Bruijn, Jason Wang, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, Simon Horman,
Jonathan Corbet, Shuah Khan, Andrew Lunn, Tim Gebauer,
Brett Sheffield, linux-doc, linux-kernel
In-Reply-To: <20260704112058.95421-1-simon.schippers@tu-dortmund.de>
On Sat, Jul 04, 2026 at 01:20:58PM +0200, Simon Schippers wrote:
> Commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop
> when a qdisc is present") did not show a relevant performance regression
> in my testing but on Brett Sheffield's librecast testbed it shows a
> significant performance drop. The regression can be pinpointed when
> multiple iperf3 TCP threads are sending. For 8 threads the performance
> dropped from 13.5 Gbit/s to 9.13 Gbit/s. This is the reason why this
> patch makes the qdisc backpressure behavior opt-in.
>
> One option to accomplish the opt-in would be to set the default qdisc to
> noqueue at init. However this may also break userspace as users might
> have chosen a custom qdisc even though most of the qdiscs did nothing
> for tun/tap in the past due to missing backpressure...
>
> This is the reason why in this patch, the flag IFF_BACKPRESSURE is
> introduced instead which is required to enable the backpressure logic.
> This means the stopping logic in tun_net_xmit() and the waking logic in
> __tun_wake_queue() are skipped if the flag is disabled.
>
> In tun_set_iff(), netif_tx_wake_all_queues() is replaced with looping
> over all tfiles in which the netdev queues are woken and cons_cnt is
> reset while the consumer_lock and producer_lock are held. This is to
> ensure that tun_net_xmit() can not stop the queue concurrently, avoiding
> a possible stall.
>
> The documentation in tuntap.rst is updated accordingly.
>
> Fixes: 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present")
> Reported-by: Brett Sheffield <brett@librecast.net>
> Closes: https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/T/#u
> Signed-off-by: Simon Schippers <simon.schippers@tu-dortmund.de>
the patch itself makes sense
Acked-by: Michael S. Tsirkin <mst@redhat.com>
The issue is it would ideally be in next, but we need it now
to fix the regression introduced by 1d6e569b7d0c.
> ---
> Documentation/networking/tuntap.rst | 17 +++++++++++++++++
> drivers/net/tun.c | 29 +++++++++++++++++++++++------
> include/uapi/linux/if_tun.h | 1 +
> tools/include/uapi/linux/if_tun.h | 1 +
> 4 files changed, 42 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/networking/tuntap.rst b/Documentation/networking/tuntap.rst
> index 4d7087f727be..599264825dd2 100644
> --- a/Documentation/networking/tuntap.rst
> +++ b/Documentation/networking/tuntap.rst
> @@ -206,6 +206,23 @@ enable is true we enable it, otherwise we disable it::
> return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
> }
>
> +3.4 qdisc backpressure
> +----------------------
> +
> +Starting with Linux 7.2, IFF_BACKPRESSURE can be set to enable qdisc
> +backpressure. Without it, TX drops occur when the internal ring buffer is
> +full. With it, the kernel stops the TX queue instead, letting the qdisc
> +hold packets. Drops only occur as a rare race. This can benefit protocols
> +like TCP that react to drops. Backpressure requires a qdisc to be
> +attached and has no effect with noqueue.
> +
> +The TUN/TAP ring buffer size can be reduced alongside this flag to
> +further shift buffering into the qdisc and reduce bufferbloat, but comes
> +at possible performance cost.
> +
> +When running multiple network streams in parallel, the flag may reduce
> +performance due to the extra overhead of the backpressure mechanism.
> +
> Universal TUN/TAP device driver Frequently Asked Question
> =========================================================
>
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index ffbe6f13fb1f..3bf8a73a0816 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -98,7 +98,8 @@ static void tun_default_link_ksettings(struct net_device *dev,
> #define TUN_FASYNC IFF_ATTACH_QUEUE
>
> #define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \
> - IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS)
> + IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS | \
> + IFF_BACKPRESSURE)
>
> #define GOODCOPY_LEN 128
>
> @@ -1077,7 +1078,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
>
> spin_lock(&tfile->tx_ring.producer_lock);
> ret = __ptr_ring_produce(&tfile->tx_ring, skb);
> - if (!qdisc_txq_has_no_queue(queue) &&
> + if ((tun->flags & IFF_BACKPRESSURE) &&
> + !qdisc_txq_has_no_queue(queue) &&
> __ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) {
> netif_tx_stop_queue(queue);
> /* Paired with smp_mb() in __tun_wake_queue() */
> @@ -2151,8 +2153,12 @@ static ssize_t tun_put_user(struct tun_struct *tun,
> static void __tun_wake_queue(struct tun_struct *tun,
> struct tun_file *tfile, int consumed)
> {
> - struct netdev_queue *txq = netdev_get_tx_queue(tun->dev,
> - tfile->queue_index);
> + struct netdev_queue *txq;
> +
> + if (!(tun->flags & IFF_BACKPRESSURE))
> + return;
> +
> + txq = netdev_get_tx_queue(tun->dev, tfile->queue_index);
>
> /* Paired with smp_mb__after_atomic() in tun_net_xmit() */
> smp_mb();
> @@ -2893,8 +2899,19 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
> /* Make sure persistent devices do not get stuck in
> * xoff state.
> */
> - if (netif_running(tun->dev))
> - netif_tx_wake_all_queues(tun->dev);
> + if (netif_running(tun->dev)) {
> + for (int i = 0; i < tun->numqueues; i++) {
> + struct tun_file *i_tfile;
> +
> + i_tfile = rtnl_dereference(tun->tfiles[i]);
> + spin_lock_bh(&i_tfile->tx_ring.consumer_lock);
> + spin_lock(&i_tfile->tx_ring.producer_lock);
> + netif_wake_subqueue(tun->dev, i_tfile->queue_index);
> + i_tfile->cons_cnt = 0;
> + spin_unlock(&i_tfile->tx_ring.producer_lock);
> + spin_unlock_bh(&i_tfile->tx_ring.consumer_lock);
> + }
> + }
>
> strscpy(ifr->ifr_name, tun->dev->name);
> return 0;
> diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h
> index 79d53c7a1ebd..73a77141315c 100644
> --- a/include/uapi/linux/if_tun.h
> +++ b/include/uapi/linux/if_tun.h
> @@ -69,6 +69,7 @@
> #define IFF_NAPI_FRAGS 0x0020
> /* Used in TUNSETIFF to bring up tun/tap without carrier */
> #define IFF_NO_CARRIER 0x0040
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
> diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h
> index 2ec07de1d73b..97b670f5bc0a 100644
> --- a/tools/include/uapi/linux/if_tun.h
> +++ b/tools/include/uapi/linux/if_tun.h
> @@ -67,6 +67,7 @@
> #define IFF_TAP 0x0002
> #define IFF_NAPI 0x0010
> #define IFF_NAPI_FRAGS 0x0020
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
> --
> 2.43.0
^ permalink raw reply
* Re: [PATCH net] tun/tap & vhost-net: make qdisc backpressure opt-in via IFF_BACKPRESSURE
From: Michael S. Tsirkin @ 2026-07-04 12:28 UTC (permalink / raw)
To: Simon Schippers
Cc: Willem de Bruijn, Jason Wang, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, Simon Horman,
Jonathan Corbet, Shuah Khan, Andrew Lunn, Tim Gebauer,
Brett Sheffield, linux-doc, linux-kernel
In-Reply-To: <20260704112058.95421-1-simon.schippers@tu-dortmund.de>
On Sat, Jul 04, 2026 at 01:20:58PM +0200, Simon Schippers wrote:
> Commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop
> when a qdisc is present") did not show a relevant performance regression
> in my testing but on Brett Sheffield's librecast testbed it shows a
> significant performance drop. The regression can be pinpointed when
> multiple iperf3 TCP threads are sending. For 8 threads the performance
> dropped from 13.5 Gbit/s to 9.13 Gbit/s. This is the reason why this
> patch makes the qdisc backpressure behavior opt-in.
>
> One option to accomplish the opt-in would be to set the default qdisc to
> noqueue at init. However this may also break userspace as users might
> have chosen a custom qdisc even though most of the qdiscs did nothing
> for tun/tap in the past due to missing backpressure...
>
> This is the reason why in this patch, the flag IFF_BACKPRESSURE is
> introduced instead which is required to enable the backpressure logic.
> This means the stopping logic in tun_net_xmit() and the waking logic in
> __tun_wake_queue() are skipped if the flag is disabled.
>
> In tun_set_iff(), netif_tx_wake_all_queues() is replaced with looping
> over all tfiles in which the netdev queues are woken and cons_cnt is
> reset while the consumer_lock and producer_lock are held. This is to
> ensure that tun_net_xmit() can not stop the queue concurrently, avoiding
> a possible stall.
>
> The documentation in tuntap.rst is updated accordingly.
>
> Fixes: 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present")
> Reported-by: Brett Sheffield <brett@librecast.net>
> Closes: https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/T/#u
> Signed-off-by: Simon Schippers <simon.schippers@tu-dortmund.de>
I don't object to this approach. At the same time - a new UAPI outside
the merge window? Is this acceptable to net maintainers?
> ---
> Documentation/networking/tuntap.rst | 17 +++++++++++++++++
> drivers/net/tun.c | 29 +++++++++++++++++++++++------
> include/uapi/linux/if_tun.h | 1 +
> tools/include/uapi/linux/if_tun.h | 1 +
> 4 files changed, 42 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/networking/tuntap.rst b/Documentation/networking/tuntap.rst
> index 4d7087f727be..599264825dd2 100644
> --- a/Documentation/networking/tuntap.rst
> +++ b/Documentation/networking/tuntap.rst
> @@ -206,6 +206,23 @@ enable is true we enable it, otherwise we disable it::
> return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
> }
>
> +3.4 qdisc backpressure
> +----------------------
> +
> +Starting with Linux 7.2, IFF_BACKPRESSURE can be set to enable qdisc
> +backpressure. Without it, TX drops occur when the internal ring buffer is
> +full. With it, the kernel stops the TX queue instead, letting the qdisc
> +hold packets. Drops only occur as a rare race. This can benefit protocols
> +like TCP that react to drops. Backpressure requires a qdisc to be
> +attached and has no effect with noqueue.
> +
> +The TUN/TAP ring buffer size can be reduced alongside this flag to
> +further shift buffering into the qdisc and reduce bufferbloat, but comes
> +at possible performance cost.
> +
> +When running multiple network streams in parallel, the flag may reduce
> +performance due to the extra overhead of the backpressure mechanism.
> +
> Universal TUN/TAP device driver Frequently Asked Question
> =========================================================
>
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index ffbe6f13fb1f..3bf8a73a0816 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -98,7 +98,8 @@ static void tun_default_link_ksettings(struct net_device *dev,
> #define TUN_FASYNC IFF_ATTACH_QUEUE
>
> #define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \
> - IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS)
> + IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS | \
> + IFF_BACKPRESSURE)
>
> #define GOODCOPY_LEN 128
>
> @@ -1077,7 +1078,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
>
> spin_lock(&tfile->tx_ring.producer_lock);
> ret = __ptr_ring_produce(&tfile->tx_ring, skb);
> - if (!qdisc_txq_has_no_queue(queue) &&
> + if ((tun->flags & IFF_BACKPRESSURE) &&
> + !qdisc_txq_has_no_queue(queue) &&
> __ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) {
> netif_tx_stop_queue(queue);
> /* Paired with smp_mb() in __tun_wake_queue() */
> @@ -2151,8 +2153,12 @@ static ssize_t tun_put_user(struct tun_struct *tun,
> static void __tun_wake_queue(struct tun_struct *tun,
> struct tun_file *tfile, int consumed)
> {
> - struct netdev_queue *txq = netdev_get_tx_queue(tun->dev,
> - tfile->queue_index);
> + struct netdev_queue *txq;
> +
> + if (!(tun->flags & IFF_BACKPRESSURE))
> + return;
> +
> + txq = netdev_get_tx_queue(tun->dev, tfile->queue_index);
>
> /* Paired with smp_mb__after_atomic() in tun_net_xmit() */
> smp_mb();
> @@ -2893,8 +2899,19 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
> /* Make sure persistent devices do not get stuck in
> * xoff state.
> */
> - if (netif_running(tun->dev))
> - netif_tx_wake_all_queues(tun->dev);
> + if (netif_running(tun->dev)) {
> + for (int i = 0; i < tun->numqueues; i++) {
> + struct tun_file *i_tfile;
> +
> + i_tfile = rtnl_dereference(tun->tfiles[i]);
> + spin_lock_bh(&i_tfile->tx_ring.consumer_lock);
> + spin_lock(&i_tfile->tx_ring.producer_lock);
> + netif_wake_subqueue(tun->dev, i_tfile->queue_index);
> + i_tfile->cons_cnt = 0;
> + spin_unlock(&i_tfile->tx_ring.producer_lock);
> + spin_unlock_bh(&i_tfile->tx_ring.consumer_lock);
> + }
> + }
>
> strscpy(ifr->ifr_name, tun->dev->name);
> return 0;
> diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h
> index 79d53c7a1ebd..73a77141315c 100644
> --- a/include/uapi/linux/if_tun.h
> +++ b/include/uapi/linux/if_tun.h
> @@ -69,6 +69,7 @@
> #define IFF_NAPI_FRAGS 0x0020
> /* Used in TUNSETIFF to bring up tun/tap without carrier */
> #define IFF_NO_CARRIER 0x0040
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
> diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h
> index 2ec07de1d73b..97b670f5bc0a 100644
> --- a/tools/include/uapi/linux/if_tun.h
> +++ b/tools/include/uapi/linux/if_tun.h
> @@ -67,6 +67,7 @@
> #define IFF_TAP 0x0002
> #define IFF_NAPI 0x0010
> #define IFF_NAPI_FRAGS 0x0020
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
> --
> 2.43.0
^ permalink raw reply
* [PATCH] net: xscale: add missing MODULE_DEVICE_TABLE()
From: Pengpeng Hou @ 2026-07-04 12:27 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Richard Cochran
Cc: Pengpeng Hou, netdev, linux-kernel
The driver has an OF match table wired to .of_match_table, but does
not export the table with MODULE_DEVICE_TABLE().
Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias
information is generated for OF based module autoloading.
This is a source-level fix. It does not claim dynamic hardware
reproduction; the evidence is the driver-owned match table, its use by
the platform driver, and the missing module alias publication.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/xscale/ptp_ixp46x.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/xscale/ptp_ixp46x.c b/drivers/net/ethernet/xscale/ptp_ixp46x.c
index 558c4f8d23f7..c805d1df5d4e 100644
--- a/drivers/net/ethernet/xscale/ptp_ixp46x.c
+++ b/drivers/net/ethernet/xscale/ptp_ixp46x.c
@@ -307,6 +307,7 @@ static const struct of_device_id ptp_ixp_match[] = {
},
{ },
};
+MODULE_DEVICE_TABLE(of, ptp_ixp_match);
static struct platform_driver ptp_ixp_driver = {
.driver = {
^ permalink raw reply related
* [PATCH net-next] net: chelsio: cxgb4: Use str_plural() in mem_intr_handler()
From: Thorsten Blum @ 2026-07-04 12:10 UTC (permalink / raw)
To: Potnuri Bharat Teja, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: Thorsten Blum, netdev, linux-kernel
Replace the manual ternary "s" pluralization with str_plural() to
simplify the code.
Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
---
drivers/net/ethernet/chelsio/cxgb4/t4_hw.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c
index 6871127427fa..29d7b786e51e 100644
--- a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c
+++ b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c
@@ -33,6 +33,7 @@
*/
#include <linux/delay.h>
+#include <linux/string_choices.h>
#include "cxgb4.h"
#include "t4_regs.h"
#include "t4_values.h"
@@ -4867,7 +4868,7 @@ static void mem_intr_handler(struct adapter *adapter, int idx)
if (printk_ratelimit())
dev_warn(adapter->pdev_dev,
"%u %s correctable ECC data error%s\n",
- cnt, name[idx], cnt > 1 ? "s" : "");
+ cnt, name[idx], str_plural(cnt));
}
if (v & ECC_UE_INT_CAUSE_F)
dev_alert(adapter->pdev_dev,
^ permalink raw reply related
* Re: [PATCH net] tun/tap & vhost-net: make qdisc backpressure opt-in via IFF_BACKPRESSURE
From: Brett Sheffield @ 2026-07-04 11:58 UTC (permalink / raw)
To: Simon Schippers
Cc: Willem de Bruijn, Jason Wang, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Michael S . Tsirkin, netdev,
Simon Horman, Jonathan Corbet, Shuah Khan, Andrew Lunn,
Tim Gebauer, linux-doc, linux-kernel
In-Reply-To: <20260704112058.95421-1-simon.schippers@tu-dortmund.de>
On 2026-07-04 13:20, Simon Schippers wrote:
> Commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop
> when a qdisc is present") did not show a relevant performance regression
> in my testing but on Brett Sheffield's librecast testbed it shows a
> significant performance drop. The regression can be pinpointed when
> multiple iperf3 TCP threads are sending. For 8 threads the performance
> dropped from 13.5 Gbit/s to 9.13 Gbit/s. This is the reason why this
> patch makes the qdisc backpressure behavior opt-in.
Thanks Simon.
I've tested and confirmed this fixes the original failing IPv6 multicast (UDP)
test which flagged the problem.
Tested-by: Brett A C Sheffield <bacs@librecast.net>
Cheers,
Brett
--
Brett Sheffield (he/him)
Librecast - Decentralising the Internet with Multicast
https://librecast.net/
https://blog.brettsheffield.com/
^ permalink raw reply
* [PATCH net] tun/tap & vhost-net: make qdisc backpressure opt-in via IFF_BACKPRESSURE
From: Simon Schippers @ 2026-07-04 11:20 UTC (permalink / raw)
To: Willem de Bruijn, Jason Wang, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Michael S . Tsirkin, netdev
Cc: Simon Horman, Jonathan Corbet, Shuah Khan, Andrew Lunn,
Tim Gebauer, Brett Sheffield, linux-doc, linux-kernel,
Simon Schippers
Commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop
when a qdisc is present") did not show a relevant performance regression
in my testing but on Brett Sheffield's librecast testbed it shows a
significant performance drop. The regression can be pinpointed when
multiple iperf3 TCP threads are sending. For 8 threads the performance
dropped from 13.5 Gbit/s to 9.13 Gbit/s. This is the reason why this
patch makes the qdisc backpressure behavior opt-in.
One option to accomplish the opt-in would be to set the default qdisc to
noqueue at init. However this may also break userspace as users might
have chosen a custom qdisc even though most of the qdiscs did nothing
for tun/tap in the past due to missing backpressure...
This is the reason why in this patch, the flag IFF_BACKPRESSURE is
introduced instead which is required to enable the backpressure logic.
This means the stopping logic in tun_net_xmit() and the waking logic in
__tun_wake_queue() are skipped if the flag is disabled.
In tun_set_iff(), netif_tx_wake_all_queues() is replaced with looping
over all tfiles in which the netdev queues are woken and cons_cnt is
reset while the consumer_lock and producer_lock are held. This is to
ensure that tun_net_xmit() can not stop the queue concurrently, avoiding
a possible stall.
The documentation in tuntap.rst is updated accordingly.
Fixes: 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present")
Reported-by: Brett Sheffield <brett@librecast.net>
Closes: https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/T/#u
Signed-off-by: Simon Schippers <simon.schippers@tu-dortmund.de>
---
Documentation/networking/tuntap.rst | 17 +++++++++++++++++
drivers/net/tun.c | 29 +++++++++++++++++++++++------
include/uapi/linux/if_tun.h | 1 +
tools/include/uapi/linux/if_tun.h | 1 +
4 files changed, 42 insertions(+), 6 deletions(-)
diff --git a/Documentation/networking/tuntap.rst b/Documentation/networking/tuntap.rst
index 4d7087f727be..599264825dd2 100644
--- a/Documentation/networking/tuntap.rst
+++ b/Documentation/networking/tuntap.rst
@@ -206,6 +206,23 @@ enable is true we enable it, otherwise we disable it::
return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
}
+3.4 qdisc backpressure
+----------------------
+
+Starting with Linux 7.2, IFF_BACKPRESSURE can be set to enable qdisc
+backpressure. Without it, TX drops occur when the internal ring buffer is
+full. With it, the kernel stops the TX queue instead, letting the qdisc
+hold packets. Drops only occur as a rare race. This can benefit protocols
+like TCP that react to drops. Backpressure requires a qdisc to be
+attached and has no effect with noqueue.
+
+The TUN/TAP ring buffer size can be reduced alongside this flag to
+further shift buffering into the qdisc and reduce bufferbloat, but comes
+at possible performance cost.
+
+When running multiple network streams in parallel, the flag may reduce
+performance due to the extra overhead of the backpressure mechanism.
+
Universal TUN/TAP device driver Frequently Asked Question
=========================================================
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index ffbe6f13fb1f..3bf8a73a0816 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -98,7 +98,8 @@ static void tun_default_link_ksettings(struct net_device *dev,
#define TUN_FASYNC IFF_ATTACH_QUEUE
#define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \
- IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS)
+ IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS | \
+ IFF_BACKPRESSURE)
#define GOODCOPY_LEN 128
@@ -1077,7 +1078,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
spin_lock(&tfile->tx_ring.producer_lock);
ret = __ptr_ring_produce(&tfile->tx_ring, skb);
- if (!qdisc_txq_has_no_queue(queue) &&
+ if ((tun->flags & IFF_BACKPRESSURE) &&
+ !qdisc_txq_has_no_queue(queue) &&
__ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) {
netif_tx_stop_queue(queue);
/* Paired with smp_mb() in __tun_wake_queue() */
@@ -2151,8 +2153,12 @@ static ssize_t tun_put_user(struct tun_struct *tun,
static void __tun_wake_queue(struct tun_struct *tun,
struct tun_file *tfile, int consumed)
{
- struct netdev_queue *txq = netdev_get_tx_queue(tun->dev,
- tfile->queue_index);
+ struct netdev_queue *txq;
+
+ if (!(tun->flags & IFF_BACKPRESSURE))
+ return;
+
+ txq = netdev_get_tx_queue(tun->dev, tfile->queue_index);
/* Paired with smp_mb__after_atomic() in tun_net_xmit() */
smp_mb();
@@ -2893,8 +2899,19 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
/* Make sure persistent devices do not get stuck in
* xoff state.
*/
- if (netif_running(tun->dev))
- netif_tx_wake_all_queues(tun->dev);
+ if (netif_running(tun->dev)) {
+ for (int i = 0; i < tun->numqueues; i++) {
+ struct tun_file *i_tfile;
+
+ i_tfile = rtnl_dereference(tun->tfiles[i]);
+ spin_lock_bh(&i_tfile->tx_ring.consumer_lock);
+ spin_lock(&i_tfile->tx_ring.producer_lock);
+ netif_wake_subqueue(tun->dev, i_tfile->queue_index);
+ i_tfile->cons_cnt = 0;
+ spin_unlock(&i_tfile->tx_ring.producer_lock);
+ spin_unlock_bh(&i_tfile->tx_ring.consumer_lock);
+ }
+ }
strscpy(ifr->ifr_name, tun->dev->name);
return 0;
diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h
index 79d53c7a1ebd..73a77141315c 100644
--- a/include/uapi/linux/if_tun.h
+++ b/include/uapi/linux/if_tun.h
@@ -69,6 +69,7 @@
#define IFF_NAPI_FRAGS 0x0020
/* Used in TUNSETIFF to bring up tun/tap without carrier */
#define IFF_NO_CARRIER 0x0040
+#define IFF_BACKPRESSURE 0x0080
#define IFF_NO_PI 0x1000
/* This flag has no real effect */
#define IFF_ONE_QUEUE 0x2000
diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h
index 2ec07de1d73b..97b670f5bc0a 100644
--- a/tools/include/uapi/linux/if_tun.h
+++ b/tools/include/uapi/linux/if_tun.h
@@ -67,6 +67,7 @@
#define IFF_TAP 0x0002
#define IFF_NAPI 0x0010
#define IFF_NAPI_FRAGS 0x0020
+#define IFF_BACKPRESSURE 0x0080
#define IFF_NO_PI 0x1000
/* This flag has no real effect */
#define IFF_ONE_QUEUE 0x2000
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net-next v8 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload
From: Lorenzo Bianconi @ 2026-07-04 11:09 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Simon Horman, Alexander Lobakin, linux-arm-kernel, linux-mediatek,
netdev, Madhur Agrawal
In-Reply-To: <20260703-airoha-ethtool-priv_flags-v8-3-015ba5ac89ee@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 22228 bytes --]
> GDM3 and GDM4 ports require GDM2 loopback to be enabled for hardware
> QoS offload to function. Without it, HTB and ETS offload on these ports
> do not work.
> Previously, GDM3/GDM4 ports were automatically configured as WAN with
> GDM2 loopback enabled during ndo_init(). Add the capability to configure
> GDM3/GDM4 as WAN/LAN on demand when QoS offload is created or destroyed.
> Hook airoha_enable_qos_for_gdm34() into TC_HTB_CREATE so that requesting
> HTB offload on a GDM3/GDM4 LAN port switches it to WAN mode and enables
> GDM2 loopback, with proper rollback on failure. Introduce the
> AIROHA_DEV_F_QOS flag to track whether a device has an active HTB
> qdisc; clear it on TC_HTB_DESTROY. The device keeps its WAN role after
> qdisc teardown so that its configuration is preserved until another
> device explicitly needs the WAN role for QoS offload.
> If another GDM3/GDM4 device already holds the WAN role without an active
> QoS qdisc, demote it to LAN before promoting the requesting device. Skip
> the demotion when the requesting device is itself already the WAN device.
> Since airoha_dev_set_qdma() can now be called on a running device to
> migrate between QDMA blocks, make dev->qdma an RCU pointer so the TX
> path can safely dereference it without holding RTNL.
> Hold flow_offload_mutex in airoha_enable_qos_for_gdm34() and
> airoha_disable_qos_for_gdm34() around the dev->flags update,
> airoha_dev_set_qdma() and GDM2 loopback configuration, serializing
> against concurrent airoha_ppe_hw_init() in the TC_SETUP_CLSFLOWER
> offload path.
> Introduce airoha_qdma_deref() helper that wraps rcu_dereference_protected()
> with a lockdep condition accepting either rtnl_lock or flow_offload_mutex,
> and use it across all control-path dereferences of the RCU-protected
> dev->qdma pointer.
> Add airoha_disable_gdm2_loopback() to disable GDM2 hw loopback.
>
> Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
> Reviewed-by: Alexander Lobakin <aleksander.lobakin@intel.com>
> Reviewed-by: Simon Horman <horms@kernel.org>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
commenting on sashiko's report:
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260703-airoha-ethtool-priv_flags-v8-0-015ba5ac89ee%40kernel.org
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 233 ++++++++++++++++++++++++++----
> drivers/net/ethernet/airoha/airoha_eth.h | 13 +-
> drivers/net/ethernet/airoha/airoha_ppe.c | 9 +-
> drivers/net/ethernet/airoha/airoha_regs.h | 1 +
> 4 files changed, 227 insertions(+), 29 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index aaf2a4717d12..fe2e2af67173 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -934,7 +934,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
> if (!dev)
> continue;
>
> - if (dev->qdma != qdma)
> + if (rcu_access_pointer(dev->qdma) != qdma)
> continue;
>
> netdev = netdev_from_priv(dev);
> @@ -1866,8 +1866,8 @@ static int airoha_dev_open(struct net_device *netdev)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> struct airoha_gdm_port *port = dev->port;
> - struct airoha_qdma *qdma = dev->qdma;
> u32 pse_port = FE_PSE_PORT_PPE1;
> + struct airoha_qdma *qdma;
> int err;
>
> netif_tx_start_all_queues(netdev);
> @@ -1875,6 +1875,7 @@ static int airoha_dev_open(struct net_device *netdev)
> if (err)
> return err;
>
> + qdma = airoha_qdma_deref(dev);
> if (netdev_uses_dsa(netdev))
> airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
> GDM_STAG_EN_MASK);
> @@ -1898,7 +1899,6 @@ static int airoha_dev_stop(struct net_device *netdev)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> struct airoha_gdm_port *port = dev->port;
> - struct airoha_qdma *qdma = dev->qdma;
>
> netif_tx_disable(netdev);
> airoha_set_vip_for_gdm_port(dev, false);
> @@ -1906,7 +1906,7 @@ static int airoha_dev_stop(struct net_device *netdev)
> if (--port->users)
> airoha_ppe_set_xmit_frame_size(dev);
> else
> - airoha_set_gdm_port_fwd_cfg(qdma->eth,
> + airoha_set_gdm_port_fwd_cfg(dev->eth,
> REG_GDM_FWD_CFG(port->id),
> FE_PSE_PORT_DROP);
> return 0;
> @@ -1989,6 +1989,53 @@ static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev)
> return 0;
> }
>
> +static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev)
> +{
> + struct airoha_gdm_port *port = dev->port;
> + struct airoha_eth *eth = dev->eth;
> + int i, src_port;
> + u32 pse_port;
> +
> + src_port = eth->soc->ops.get_sport(dev->port, dev->nbq);
> + if (src_port < 0)
> + return src_port;
> +
> + airoha_fe_clear(eth,
> + REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)),
> + SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK));
> +
> + airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
> + GDM_STRIP_CRC_MASK);
> + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
> + FE_PSE_PORT_DROP);
> + airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX),
> + LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK);
> + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
> + : FE_PSE_PORT_PPE1;
> + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
> + pse_port);
> +
> + airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK,
> + FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX));
> +
> + for (i = 0; i < eth->soc->num_ppe; i++)
> + airoha_fe_clear(eth, REG_PPE_DFT_CPORT(i, AIROHA_GDM2_IDX),
> + DFT_CPORT_MASK(AIROHA_GDM2_IDX));
> +
> + /* Enable VIP and IFC for GDM2 */
> + airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
> + airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
> +
> + if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
> + u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq);
> +
> + airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask,
> + FC_MAP6_DEF_VALUE & mask);
> + }
> +
> + return 0;
> +}
> +
> static struct airoha_gdm_dev *
> airoha_get_wan_gdm_dev(struct airoha_eth *eth)
> {
> @@ -2015,15 +2062,35 @@ airoha_get_wan_gdm_dev(struct airoha_eth *eth)
> static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev)
> {
> struct net_device *netdev = netdev_from_priv(dev);
> + struct airoha_qdma *cur_qdma, *qdma;
> struct airoha_eth *eth = dev->eth;
> - int ppe_id;
> + int i, ppe_id;
>
> /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
> - dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
> - netdev->irq = dev->qdma->irq_banks[0].irq;
> + qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
> + cur_qdma = airoha_qdma_deref(dev);
> +
> + rcu_assign_pointer(dev->qdma, qdma);
> + netdev->irq = qdma->irq_banks[0].irq;
>
> ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
> airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
> +
> + if (!cur_qdma)
> + return;
> +
> + /* Seed qos_stats baselines with the new QDMA block's current
> + * counter values to avoid a spurious spike on the first stats
> + * query after migration.
> + */
> + for (i = 0; i < ARRAY_SIZE(dev->qos_stats); i++) {
> + dev->qos_stats[i].cpu_tx_packets =
> + airoha_qdma_rr(qdma, REG_CNTR_VAL(i << 1));
> + dev->qos_stats[i].fwd_tx_packets =
> + airoha_qdma_rr(qdma, REG_CNTR_VAL((i << 1) + 1));
> + }
> + synchronize_rcu();
> + netif_tx_wake_all_queues(netdev);
> }
- Is the ordering here safe with respect to the TX fast path?
- There is a small window where in-flight packets can see the hw not fully
re-configured (e.g. FE not fully re-configured) but this is not harmful
(e.g. causing persistent state corruption or hardware), just some packets
are drop during user-requested configuration. I think we can ignore this
comment.
>
> static int airoha_dev_init(struct net_device *netdev)
> @@ -2178,9 +2245,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> struct net_device *netdev)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_qdma *qdma = dev->qdma;
> u32 nr_frags, tag, msg0, msg1, len;
> struct airoha_queue_entry *e;
> + struct airoha_qdma *qdma;
> struct netdev_queue *txq;
> struct airoha_queue *q;
> LIST_HEAD(tx_list);
> @@ -2189,6 +2256,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> u16 index;
> u8 fport;
>
> + rcu_read_lock();
> + qdma = rcu_dereference(dev->qdma);
> qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
> tag = airoha_get_dsa_tag(skb, netdev);
>
> @@ -2238,6 +2307,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> netif_tx_stop_queue(txq);
> q->txq_stopped = true;
> spin_unlock_bh(&q->lock);
> + rcu_read_unlock();
> +
> return NETDEV_TX_BUSY;
> }
>
> @@ -2303,6 +2374,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
>
> spin_unlock_bh(&q->lock);
> + rcu_read_unlock();
>
> return NETDEV_TX_OK;
>
> @@ -2315,6 +2387,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> error:
> dev_kfree_skb_any(skb);
> netdev->stats.tx_dropped++;
> + rcu_read_unlock();
>
> return NETDEV_TX_OK;
> }
> @@ -2394,17 +2467,19 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
> const u16 *weights, u8 n_weights)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_qdma *qdma;
> int i;
>
> + qdma = airoha_qdma_deref(dev);
> for (i = 0; i < AIROHA_NUM_QOS_QUEUES; i++)
> - airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel),
> + airoha_qdma_clear(qdma, REG_QUEUE_CLOSE_CFG(channel),
> TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
>
> for (i = 0; i < n_weights; i++) {
> u32 status;
> int err;
>
> - airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG,
> + airoha_qdma_wr(qdma, REG_TXWRR_WEIGHT_CFG,
> TWRR_RW_CMD_MASK |
> FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
> FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
> @@ -2412,12 +2487,12 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
> err = read_poll_timeout(airoha_qdma_rr, status,
> status & TWRR_RW_CMD_DONE,
> USEC_PER_MSEC, 10 * USEC_PER_MSEC,
> - true, dev->qdma, REG_TXWRR_WEIGHT_CFG);
> + true, qdma, REG_TXWRR_WEIGHT_CFG);
> if (err)
> return err;
> }
>
> - airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3),
> + airoha_qdma_rmw(qdma, REG_CHAN_QOS_MODE(channel >> 3),
> CHAN_QOS_MODE_MASK(channel),
> __field_prep(CHAN_QOS_MODE_MASK(channel), mode));
>
> @@ -2481,10 +2556,11 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
> struct tc_ets_qopt_offload *opt)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_qdma *qdma = dev->qdma;
> u32 cpu_tx_packets, fwd_tx_packets;
> + struct airoha_qdma *qdma;
> u64 tx_packets;
>
> + qdma = airoha_qdma_deref(dev);
> cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
> fwd_tx_packets = airoha_qdma_rr(qdma,
> REG_CNTR_VAL((channel << 1) + 1));
> @@ -2751,16 +2827,18 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
> u32 bucket_size)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_qdma *qdma;
> int i, err;
>
> + qdma = airoha_qdma_deref(dev);
> for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
> - err = airoha_qdma_set_trtcm_config(dev->qdma, channel,
> + err = airoha_qdma_set_trtcm_config(qdma, channel,
> REG_EGRESS_TRTCM_CFG, i,
> !!rate, TRTCM_METER_MODE);
> if (err)
> return err;
>
> - err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel,
> + err = airoha_qdma_set_trtcm_token_bucket(qdma, channel,
> REG_EGRESS_TRTCM_CFG,
> i, rate, bucket_size);
> if (err)
> @@ -2796,11 +2874,12 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> int err, num_tx_queues = AIROHA_NUM_TX_RING + channel + 1;
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> - struct airoha_qdma *qdma = dev->qdma;
> + struct airoha_qdma *qdma;
>
> /* Here we need to check the requested QDMA channel is not already
> * in use by another net_device running on the same QDMA block.
> */
> + qdma = airoha_qdma_deref(dev);
> if (test_and_set_bit(channel, qdma->qos_channel_map)) {
> NL_SET_ERR_MSG_MOD(opt->extack,
> "qdma qos channel already in use");
> @@ -2836,7 +2915,7 @@ static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
> u32 rate, u32 bucket_size,
> enum trtcm_unit_type unit_type)
> {
> - struct airoha_qdma *qdma = dev->qdma;
> + struct airoha_qdma *qdma = airoha_qdma_deref(dev);
> int i;
>
> for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
> @@ -3011,10 +3090,11 @@ static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> int num_tx_queues = AIROHA_NUM_TX_RING;
> - struct airoha_qdma *qdma = dev->qdma;
> + struct airoha_qdma *qdma;
>
> airoha_qdma_set_tx_rate_limit(netdev, queue, 0, 0);
>
> + qdma = airoha_qdma_deref(dev);
> clear_bit(queue, qdma->qos_channel_map);
> clear_bit(queue, dev->qos_sq_bmap);
>
> @@ -3040,6 +3120,98 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
> return 0;
> }
>
> +static void airoha_disable_qos_for_gdm34(struct net_device *netdev)
> +{
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> + int err;
> +
> + if (port->id != AIROHA_GDM3_IDX &&
> + port->id != AIROHA_GDM4_IDX)
> + return;
> +
> + err = airoha_disable_gdm2_loopback(dev);
> + if (err)
> + netdev_warn(netdev,
> + "failed disabling GDM2 loopback: %d\n", err);
> +
> + dev->flags &= ~AIROHA_DEV_F_WAN;
> + airoha_dev_set_qdma(dev);
> +
> + airoha_set_macaddr(dev, netdev->dev_addr);
- Should the return value of airoha_set_macaddr() be checked here?
- This issue has been reported and discussed in previous iterations.
airoha_set_macaddr() can fail just if the device is misconfigured.
Moreover, airoha_set_macaddr() already logs warnings via dev_warn()
when the MSB check fails, so the failure won't be silent.
> + airoha_ppe_set_xmit_frame_size(dev);
> +
> + if (netif_running(netdev))
> + airoha_set_gdm_port_fwd_cfg(dev->eth,
> + REG_GDM_FWD_CFG(port->id),
> + FE_PSE_PORT_PPE1);
> +}
> +
> +static int airoha_enable_qos_for_gdm34(struct net_device *netdev,
> + struct netlink_ext_ack *extack)
> +{
> + struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> + struct airoha_eth *eth = dev->eth;
> + int err = -EBUSY;
> +
> + if (port->id != AIROHA_GDM3_IDX &&
> + port->id != AIROHA_GDM4_IDX) {
> + /* HW QoS is always supported by GDM1 and GDM2 */
> + return 0;
> + }
> +
> + if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */
> + return 0;
> +
> + mutex_lock(&flow_offload_mutex);
> +
> + wan_dev = airoha_get_wan_gdm_dev(eth);
> + if (wan_dev) {
> + if ((wan_dev->flags & AIROHA_DEV_F_QOS) ||
> + wan_dev->port->id == AIROHA_GDM2_IDX) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "QoS configured for WAN device");
> + goto error_unlock;
> + }
> + airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev));
> + }
> +
> + dev->flags |= AIROHA_DEV_F_WAN;
> + airoha_dev_set_qdma(dev);
> + err = airoha_enable_gdm2_loopback(dev);
> + if (err)
> + goto error_disable_wan;
> +
> + err = airoha_set_macaddr(dev, netdev->dev_addr);
> + if (err)
> + goto error_disable_loopback;
> +
> + airoha_dev_set_xmit_frame_size(netdev);
> + if (netif_running(netdev)) {
> + u32 pse_port;
> +
> + pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
> + : FE_PSE_PORT_PPE1;
> + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
> + pse_port);
> + }
> +
> + mutex_unlock(&flow_offload_mutex);
> +
> + return 0;
> +
> +error_disable_loopback:
> + airoha_disable_gdm2_loopback(dev);
> +error_disable_wan:
> + dev->flags &= ~AIROHA_DEV_F_WAN;
> + airoha_dev_set_qdma(dev);
> +error_unlock:
> + mutex_unlock(&flow_offload_mutex);
> +
> + return err;
- Does this rollback path fully undo the effect of demoting the previous WAN device?
- This issue has been reported and discussed in previous iterations.
If the promotion of the new device fails, the tc command returns an error to userspace,
the requested configuration was not applied. The system is still processing
packets and the user can log and reapply the configuration.
Re-promoting the old WAN device on error would add complexity for failure cases that
do not really occur in practice since airoha_set_macaddr() and
airoha_enable_gdm2_loopback() fail only if the device is misconfigured.
Regards,
Lorenzo
> +}
> +
> static int airoha_tc_htb_destroy(struct net_device *netdev)
> {
> struct airoha_gdm_dev *dev = netdev_priv(netdev);
> @@ -3048,6 +3220,8 @@ static int airoha_tc_htb_destroy(struct net_device *netdev)
> for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
> airoha_tc_remove_htb_queue(netdev, q);
>
> + dev->flags &= ~AIROHA_DEV_F_QOS;
> +
> return 0;
> }
>
> @@ -3067,24 +3241,33 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
> return 0;
> }
>
> -static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
> +static int airoha_tc_setup_qdisc_htb(struct net_device *netdev,
> struct tc_htb_qopt_offload *opt)
> {
> switch (opt->command) {
> - case TC_HTB_CREATE:
> + case TC_HTB_CREATE: {
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + int err;
> +
> + err = airoha_enable_qos_for_gdm34(netdev, opt->extack);
> + if (err)
> + return err;
> +
> + dev->flags |= AIROHA_DEV_F_QOS;
> break;
> + }
> case TC_HTB_DESTROY:
> - return airoha_tc_htb_destroy(dev);
> + return airoha_tc_htb_destroy(netdev);
> case TC_HTB_NODE_MODIFY:
> - return airoha_tc_htb_modify_queue(dev, opt);
> + return airoha_tc_htb_modify_queue(netdev, opt);
> case TC_HTB_LEAF_ALLOC_QUEUE:
> - return airoha_tc_htb_alloc_leaf_queue(dev, opt);
> + return airoha_tc_htb_alloc_leaf_queue(netdev, opt);
> case TC_HTB_LEAF_DEL:
> case TC_HTB_LEAF_DEL_LAST:
> case TC_HTB_LEAF_DEL_LAST_FORCE:
> - return airoha_tc_htb_delete_leaf_queue(dev, opt);
> + return airoha_tc_htb_delete_leaf_queue(netdev, opt);
> case TC_HTB_LEAF_QUERY_QUEUE:
> - return airoha_tc_get_htb_get_leaf_queue(dev, opt);
> + return airoha_tc_get_htb_get_leaf_queue(netdev, opt);
> default:
> return -EOPNOTSUPP;
> }
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index bf44be9f0954..4d03ebae1a2d 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -545,11 +545,12 @@ struct airoha_qdma {
>
> enum airoha_dev_flags {
> AIROHA_DEV_F_WAN = BIT(0),
> + AIROHA_DEV_F_QOS = BIT(1),
> };
>
> struct airoha_gdm_dev {
> + struct airoha_qdma __rcu *qdma;
> struct airoha_gdm_port *port;
> - struct airoha_qdma *qdma;
> struct airoha_eth *eth;
>
> DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
> @@ -685,6 +686,16 @@ int airoha_get_fe_port(struct airoha_gdm_dev *dev);
> bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
> struct airoha_gdm_dev *dev);
>
> +extern struct mutex flow_offload_mutex;
> +
> +static inline struct airoha_qdma *
> +airoha_qdma_deref(struct airoha_gdm_dev *dev)
> +{
> + return rcu_dereference_protected(dev->qdma,
> + lockdep_rtnl_is_held() ||
> + lockdep_is_held(&flow_offload_mutex));
> +}
> +
> void airoha_ppe_set_xmit_frame_size(struct airoha_gdm_dev *dev);
> void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
> bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index);
> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
> index e7c78293002a..78378a6262f9 100644
> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
> @@ -15,7 +15,10 @@
> #include "airoha_regs.h"
> #include "airoha_eth.h"
>
> -static DEFINE_MUTEX(flow_offload_mutex);
> +/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port
> + * configuration.
> + */
> +DEFINE_MUTEX(flow_offload_mutex);
> static DEFINE_SPINLOCK(ppe_lock);
>
> static const struct rhashtable_params airoha_flow_table_params = {
> @@ -86,8 +89,8 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
>
> void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
> {
> - struct airoha_qdma *qdma = dev->qdma;
> - struct airoha_eth *eth = qdma->eth;
> + struct airoha_qdma *qdma = airoha_qdma_deref(dev);
> + struct airoha_eth *eth = dev->eth;
> u8 qdma_id = qdma - ð->qdma[0];
> u32 fe_cpu_port;
>
> diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h
> index 6fed63d013b4..442b48c9b991 100644
> --- a/drivers/net/ethernet/airoha/airoha_regs.h
> +++ b/drivers/net/ethernet/airoha/airoha_regs.h
> @@ -375,6 +375,7 @@
>
> #define REG_SRC_PORT_FC_MAP6 0x2298
> #define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3))
> +#define FC_MAP6_DEF_VALUE 0x1b1a1918
>
> #define REG_WAN_MTU0 0x2300
> #define WAN_MTU1_MASK GENMASK(29, 16)
>
> --
> 2.55.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* [PATCH net-next v6 4/4] net: dsa: initial support for MT7628 embedded switch
From: Joris Vaisvila @ 2026-07-04 10:56 UTC (permalink / raw)
To: netdev
Cc: horms, pabeni, kuba, edumazet, davem, olteanv, Andrew Lunn,
devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Arınç ÜNAL, Landen Chao, DENG Qingfang, Sean Wang,
Daniel Golle, Joris Vaisvila
In-Reply-To: <20260704105659.140970-1-joey@tinyisr.com>
Add support for the MT7628 embedded switch.
The switch has 5 built-in 100Mbps user ports (ports 0-4) and one 1Gbps
port that is internally attached to the SoCs CPU MAC and serves as the
CPU port.
The switch hardware has a very limited 16 entry VLAN table. Configuring
VLANs is the only way to control switch forwarding. Currently 6 entries
are used by tag_8021q to isolate the ports. Double tag feature is
enabled to force the switch to append the VLAN tag even if the incoming
packet is already tagged, this simulates VLAN-unaware functionality and
simplifies the tagger implementation.
Signed-off-by: Joris Vaisvila <joey@tinyisr.com>
Reviewed-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/Kconfig | 8 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/mt7628.c | 649 +++++++++++++++++++++++++++++++++++++++
3 files changed, 658 insertions(+)
create mode 100644 drivers/net/dsa/mt7628.c
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 4ab567c5bbaf..daa1d3d4ba60 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -63,6 +63,14 @@ config NET_DSA_MT7530_MMIO
are directly mapped into the SoCs register space rather than being
accessible via MDIO.
+config NET_DSA_MT7628
+ tristate "MediaTek MT7628 Embedded Ethernet switch support"
+ select NET_DSA_TAG_MT7628
+ select MEDIATEK_FE_SOC_PHY
+ help
+ This enables support for the built-in Ethernet switch found
+ in the MT7628 SoC.
+
config NET_DSA_MV88E6060
tristate "Marvell 88E6060 ethernet switch chip support"
select NET_DSA_TAG_TRAILER
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index d2975badffc0..6ceb78a755d7 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_NET_DSA_KS8995) += ks8995.o
obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o
+obj-$(CONFIG_NET_DSA_MT7628) += mt7628.o
obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o
obj-$(CONFIG_NET_DSA_RZN1_A5PSW) += rzn1_a5psw.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
diff --git a/drivers/net/dsa/mt7628.c b/drivers/net/dsa/mt7628.c
new file mode 100644
index 000000000000..eccdb05fce9b
--- /dev/null
+++ b/drivers/net/dsa/mt7628.c
@@ -0,0 +1,649 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Mediatek MT7628 Embedded Switch (ESW) DSA driver
+ * Copyright (C) 2026 Joris Vaisvila <joey@tinyisr.com>
+ *
+ * Portions derived from OpenWRT esw_rt3050 driver:
+ * Copyright (C) 2009-2015 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2009-2015 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2013-2015 Michael Lee <igvtee@gmail.com>
+ * Copyright (C) 2016 Vittorio Gambaletta <openwrt@vittgam.net>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <linux/dsa/8021q.h>
+#include <linux/if_bridge.h>
+#include <linux/module.h>
+#include <linux/mdio.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <net/dsa.h>
+
+#define MT7628_ESW_REG_IMR 0x04
+#define MT7628_ESW_REG_FCT0 0x08
+#define MT7628_ESW_REG_PFC1 0x14
+#define MT7628_ESW_REG_PVIDC(port) (0x40 + 4 * ((port) / 2))
+#define MT7628_ESW_REG_VLANI(vlan) (0x50 + 4 * ((vlan) / 2))
+#define MT7628_ESW_REG_VMSC(vlan) (0x70 + 4 * ((vlan) / 4))
+#define MT7628_ESW_REG_VUB(vlan) (0x100 + 4 * ((vlan) / 4))
+#define MT7628_ESW_REG_SOCPC 0x8c
+#define MT7628_ESW_REG_POC0 0x90
+#define MT7628_ESW_REG_POC2 0x98
+#define MT7628_ESW_REG_SGC 0x9c
+#define MT7628_ESW_REG_PCR0 0xc0
+#define MT7628_ESW_REG_PCR1 0xc4
+#define MT7628_ESW_REG_FPA2 0xc8
+#define MT7628_ESW_REG_FCT2 0xcc
+#define MT7628_ESW_REG_SGC2 0xe4
+
+#define MT7628_ESW_FCT0_DROP_SET_TH GENMASK(7, 0)
+#define MT7628_ESW_FCT0_DROP_RLS_TH GENMASK(15, 8)
+#define MT7628_ESW_FCT0_FC_SET_TH GENMASK(23, 16)
+#define MT7628_ESW_FCT0_FC_RLS_TH GENMASK(31, 24)
+
+#define MT7628_ESW_PFC1_EN_VLAN GENMASK(22, 16)
+
+#define MT7628_ESW_PVID_S 12
+#define MT7628_ESW_PVID_M GENMASK(11, 0)
+#define MT7628_ESW_PVID_SHIFT(port) \
+ (MT7628_ESW_PVID_S * ((port) % 2))
+#define MT7628_ESW_PVID_MASK(port) \
+ (MT7628_ESW_PVID_M << MT7628_ESW_PVID_SHIFT(port))
+#define MT7628_ESW_PVID_PREP(port, pvid) \
+ (((pvid) & MT7628_ESW_PVID_M) << MT7628_ESW_PVID_SHIFT(port))
+
+#define MT7628_ESW_VID_S 12
+#define MT7628_ESW_VID_M GENMASK(11, 0)
+#define MT7628_ESW_VID_SHIFT(vlan) \
+ (MT7628_ESW_VID_S * ((vlan) % 2))
+#define MT7628_ESW_VID_MASK(vlan) \
+ (MT7628_ESW_VID_M << MT7628_ESW_VID_SHIFT(vlan))
+#define MT7628_ESW_VID_PREP(vlan, vid) \
+ (((vid) & MT7628_ESW_VID_M) << MT7628_ESW_VID_SHIFT(vlan))
+
+#define MT7628_ESW_VMSC_S 8
+#define MT7628_ESW_VMSC_M GENMASK(7, 0)
+#define MT7628_ESW_VMSC_SHIFT(vlan) \
+ (MT7628_ESW_VMSC_S * ((vlan) % 4))
+#define MT7628_ESW_VMSC_MASK(vlan) \
+ (MT7628_ESW_VMSC_M << MT7628_ESW_VMSC_SHIFT(vlan))
+#define MT7628_ESW_VMSC_PREP(vlan, vmsc) \
+ (((vmsc) & MT7628_ESW_VMSC_M) << MT7628_ESW_VMSC_SHIFT(vlan))
+
+#define MT7628_ESW_VUB_S 7
+#define MT7628_ESW_VUB_M GENMASK(6, 0)
+#define MT7628_ESW_VUB_SHIFT(vlan) \
+ (MT7628_ESW_VUB_S * ((vlan) % 4))
+#define MT7628_ESW_VUB_MASK(vlan) \
+ (MT7628_ESW_VUB_M << MT7628_ESW_VUB_SHIFT(vlan))
+#define MT7628_ESW_VUB_PREP(vlan, vub) \
+ (((vub) & MT7628_ESW_VUB_M) << MT7628_ESW_VUB_SHIFT(vlan))
+
+#define MT7628_ESW_SOCPC_CRC_PADDING BIT(25)
+#define MT7628_ESW_SOCPC_DISBC2CPU GENMASK(22, 16)
+#define MT7628_ESW_SOCPC_DISMC2CPU GENMASK(14, 8)
+#define MT7628_ESW_SOCPC_DISUN2CPU GENMASK(6, 0)
+
+#define MT7628_ESW_POC0_PORT_DISABLE GENMASK(29, 23)
+
+#define MT7628_ESW_POC2_PER_VLAN_UNTAG_EN BIT(15)
+
+#define MT7628_ESW_SGC_AGING_INTERVAL GENMASK(3, 0)
+#define MT7628_ESW_BC_STORM_PROT GENMASK(5, 4)
+#define MT7628_ESW_PKT_MAX_LEN GENMASK(7, 6)
+#define MT7628_ESW_DIS_PKT_ABORT BIT(8)
+#define MT7628_ESW_ADDRESS_HASH_ALG GENMASK(10, 9)
+#define MT7628_ESW_DISABLE_TX_BACKOFF BIT(11)
+#define MT7628_ESW_BP_JAM_CNT GENMASK(15, 12)
+#define MT7628_ESW_DISMIIPORT_WASTX GENMASK(17, 16)
+#define MT7628_ESW_BP_MODE GENMASK(19, 18)
+#define MT7628_ESW_BISH_DIS BIT(20)
+#define MT7628_ESW_BISH_TH GENMASK(22, 21)
+#define MT7628_ESW_LED_FLASH_TIME GENMASK(24, 23)
+#define MT7628_ESW_RMC_RULE GENMASK(26, 25)
+#define MT7628_ESW_IP_MULT_RULE GENMASK(28, 27)
+#define MT7628_ESW_LEN_ERR_CHK BIT(29)
+#define MT7628_ESW_BKOFF_ALG BIT(30)
+
+#define MT7628_ESW_PCR0_WT_NWAY_DATA GENMASK(31, 16)
+#define MT7628_ESW_PCR0_RD_PHY_CMD BIT(14)
+#define MT7628_ESW_PCR0_WT_PHY_CMD BIT(13)
+#define MT7628_ESW_PCR0_CPU_PHY_REG GENMASK(12, 8)
+#define MT7628_ESW_PCR0_CPU_PHY_ADDR GENMASK(4, 0)
+
+#define MT7628_ESW_PCR1_RD_DATA GENMASK(31, 16)
+#define MT7628_ESW_PCR1_RD_DONE BIT(1)
+#define MT7628_ESW_PCR1_WT_DONE BIT(0)
+
+#define MT7628_ESW_FPA2_AP_EN BIT(29)
+#define MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE GENMASK(28, 24)
+#define MT7628_ESW_FPA2_FORCE_RGMII_LINK1 BIT(13)
+#define MT7628_ESW_FPA2_FORCE_RGMII_EN1 BIT(11)
+
+#define MT7628_ESW_FCT2_MUST_DROP_RLS_TH GENMASK(17, 13)
+#define MT7628_ESW_FCT2_MUST_DROP_SET_TH GENMASK(12, 8)
+#define MT7628_ESW_FCT2_MC_PER_PORT_TH GENMASK(5, 0)
+
+#define MT7628_ESW_SGC2_SPECIAL_TAG_EN BIT(23)
+#define MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP GENMASK(22, 16)
+#define MT7628_ESW_SGC2_DOUBLE_TAG_EN GENMASK(6, 0)
+
+#define MT7628_ESW_PORTS_NOCPU GENMASK(5, 0)
+#define MT7628_ESW_PORTS_CPU BIT(6)
+#define MT7628_ESW_PORTS_ALL GENMASK(6, 0)
+
+#define MT7628_ESW_NUM_PORTS 7
+#define MT7628_NUM_VLANS 16
+
+static const struct regmap_config mt7628_esw_regmap_cfg = {
+ .name = "mt7628-esw",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .fast_io = true,
+ .reg_format_endian = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+struct mt7628_vlan {
+ bool active;
+ u8 members;
+ u8 untag;
+ u16 vid;
+};
+
+struct mt7628_esw {
+ struct reset_control *rst_ephy;
+ struct reset_control *rst_esw;
+ struct regmap *regmap;
+ struct dsa_switch *ds;
+ u16 tag_8021q_pvid[MT7628_ESW_NUM_PORTS];
+ struct mt7628_vlan vlans[MT7628_NUM_VLANS];
+ struct device *dev;
+};
+
+static int mt7628_mii_read(struct mii_bus *bus, int port, int regnum)
+{
+ struct mt7628_esw *esw = bus->priv;
+ int ret;
+ u32 val;
+
+ /*
+ * RD_DONE bit is read to clear. Read PCR1 once to acknowledge any
+ * stale completion indicator before starting a new transaction.
+ */
+ ret = regmap_read(esw->regmap, MT7628_ESW_REG_PCR1, &val);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0,
+ FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG,
+ regnum) |
+ FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR,
+ port) | MT7628_ESW_PCR0_RD_PHY_CMD);
+ if (ret)
+ goto out;
+
+ ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val,
+ (val & MT7628_ESW_PCR1_RD_DONE), 10,
+ 5000);
+ if (ret)
+ goto out;
+
+ return FIELD_GET(MT7628_ESW_PCR1_RD_DATA, val);
+
+out:
+ dev_err(&bus->dev, "read failed. MDIO timeout?\n");
+ return ret;
+}
+
+static int mt7628_mii_write(struct mii_bus *bus, int port, int regnum, u16 dat)
+{
+ struct mt7628_esw *esw = bus->priv;
+ u32 val;
+ int ret;
+
+ /*
+ * WT_DONE bit is read to clear. Read PCR1 once to acknowledge any
+ * stale completion indicator before starting a new transaction.
+ */
+ ret = regmap_read(esw->regmap, MT7628_ESW_REG_PCR1, &val);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0,
+ FIELD_PREP(MT7628_ESW_PCR0_WT_NWAY_DATA, dat) |
+ FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG,
+ regnum) |
+ FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR,
+ port) | MT7628_ESW_PCR0_WT_PHY_CMD);
+ if (ret)
+ goto out;
+
+ ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val,
+ (val & MT7628_ESW_PCR1_WT_DONE), 10,
+ 5000);
+ if (ret)
+ goto out;
+
+ return 0;
+
+out:
+ dev_err(&bus->dev, "write failed. MDIO timeout?\n");
+ return ret;
+}
+
+static int mt7628_setup_internal_mdio(struct dsa_switch *ds)
+{
+ struct mt7628_esw *esw = ds->priv;
+ struct device *dev = ds->dev;
+ struct mii_bus *bus;
+
+ bus = devm_mdiobus_alloc(dev);
+ if (!bus)
+ return -ENOMEM;
+
+ bus->name = "MT7628 internal MDIO bus";
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev));
+ bus->priv = esw;
+ bus->read = mt7628_mii_read;
+ bus->write = mt7628_mii_write;
+ bus->parent = dev;
+
+ ds->user_mii_bus = bus;
+ bus->phy_mask = ~ds->phys_mii_mask;
+
+ return devm_mdiobus_register(dev, bus);
+}
+
+static void mt7628_switch_init(struct dsa_switch *ds)
+{
+ struct mt7628_esw *esw = ds->priv;
+
+ regmap_write(esw->regmap, MT7628_ESW_REG_FCT0,
+ FIELD_PREP(MT7628_ESW_FCT0_DROP_SET_TH, 0x50) |
+ FIELD_PREP(MT7628_ESW_FCT0_DROP_RLS_TH, 0x78) |
+ FIELD_PREP(MT7628_ESW_FCT0_FC_SET_TH, 0xa0) |
+ FIELD_PREP(MT7628_ESW_FCT0_FC_RLS_TH, 0xc8));
+
+ regmap_write(esw->regmap, MT7628_ESW_REG_FCT2,
+ FIELD_PREP(MT7628_ESW_FCT2_MC_PER_PORT_TH, 0xc) |
+ FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_SET_TH, 0x10) |
+ FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_RLS_TH, 0x12));
+
+ /*
+ * general switch configuration:
+ * 300s aging interval
+ * broadcast storm prevention disabled
+ * max packet length 1536 bytes
+ * disable collision 16 packet abort and late collision abort
+ * use xor48 for address hashing
+ * disable tx backoff
+ * 10 packet back pressure jam
+ * disable was_transmit
+ * jam until BP condition released
+ * 30ms LED flash
+ * rmc tb fault to all ports
+ * unmatched IGMP as broadcast
+ */
+ regmap_write(esw->regmap, MT7628_ESW_REG_SGC,
+ FIELD_PREP(MT7628_ESW_SGC_AGING_INTERVAL, 1) |
+ FIELD_PREP(MT7628_ESW_BC_STORM_PROT, 0) |
+ FIELD_PREP(MT7628_ESW_PKT_MAX_LEN, 0) |
+ MT7628_ESW_DIS_PKT_ABORT |
+ FIELD_PREP(MT7628_ESW_ADDRESS_HASH_ALG, 1) |
+ MT7628_ESW_DISABLE_TX_BACKOFF |
+ FIELD_PREP(MT7628_ESW_BP_JAM_CNT, 10) |
+ FIELD_PREP(MT7628_ESW_DISMIIPORT_WASTX, 0) |
+ FIELD_PREP(MT7628_ESW_BP_MODE, 0b10) |
+ FIELD_PREP(MT7628_ESW_LED_FLASH_TIME, 0) |
+ FIELD_PREP(MT7628_ESW_RMC_RULE, 0) |
+ FIELD_PREP(MT7628_ESW_IP_MULT_RULE, 0));
+
+ regmap_write(esw->regmap, MT7628_ESW_REG_SOCPC,
+ MT7628_ESW_SOCPC_CRC_PADDING |
+ FIELD_PREP(MT7628_ESW_SOCPC_DISUN2CPU,
+ MT7628_ESW_PORTS_CPU) |
+ FIELD_PREP(MT7628_ESW_SOCPC_DISMC2CPU,
+ MT7628_ESW_PORTS_CPU) |
+ FIELD_PREP(MT7628_ESW_SOCPC_DISBC2CPU,
+ MT7628_ESW_PORTS_CPU));
+
+ regmap_set_bits(esw->regmap, MT7628_ESW_REG_FPA2,
+ MT7628_ESW_FPA2_FORCE_RGMII_EN1 |
+ MT7628_ESW_FPA2_FORCE_RGMII_LINK1 |
+ MT7628_ESW_FPA2_AP_EN);
+
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_FPA2,
+ MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE,
+ FIELD_PREP(MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE, 31));
+
+ /* disable all interrupts */
+ regmap_write(esw->regmap, MT7628_ESW_REG_IMR, 0);
+
+ /* enable MT7628 DSA tag on CPU port */
+ regmap_write(esw->regmap, MT7628_ESW_REG_SGC2,
+ MT7628_ESW_SGC2_SPECIAL_TAG_EN |
+ FIELD_PREP(MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP,
+ MT7628_ESW_PORTS_CPU));
+
+ /*
+ * Double tag feature allows switch to always append the port PVID VLAN tag
+ * regardless of if the incoming packet already has a VLAN tag.
+ * This is enabled to simulate VLAN unawareness.
+ */
+ regmap_set_bits(esw->regmap, MT7628_ESW_REG_SGC2,
+ FIELD_PREP(MT7628_ESW_SGC2_DOUBLE_TAG_EN,
+ MT7628_ESW_PORTS_NOCPU));
+
+ regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC2,
+ MT7628_ESW_POC2_PER_VLAN_UNTAG_EN);
+
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_PFC1,
+ MT7628_ESW_PFC1_EN_VLAN,
+ FIELD_PREP(MT7628_ESW_PFC1_EN_VLAN,
+ MT7628_ESW_PORTS_ALL));
+}
+
+static void mt7628_esw_set_pvid(struct mt7628_esw *esw, unsigned int port,
+ unsigned int pvid)
+{
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_PVIDC(port),
+ MT7628_ESW_PVID_MASK(port),
+ MT7628_ESW_PVID_PREP(port, pvid));
+}
+
+static void mt7628_esw_set_vlan_id(struct mt7628_esw *esw, unsigned int vlan,
+ unsigned int vid)
+{
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_VLANI(vlan),
+ MT7628_ESW_VID_MASK(vlan),
+ MT7628_ESW_VID_PREP(vlan, vid));
+}
+
+static void mt7628_esw_set_vmsc(struct mt7628_esw *esw, unsigned int vlan,
+ unsigned int msc)
+{
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_VMSC(vlan),
+ MT7628_ESW_VMSC_MASK(vlan),
+ MT7628_ESW_VMSC_PREP(vlan, msc));
+}
+
+static void mt7628_esw_set_vub(struct mt7628_esw *esw, unsigned int vlan,
+ unsigned int vub)
+{
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_VUB(vlan),
+ MT7628_ESW_VUB_MASK(vlan),
+ MT7628_ESW_VUB_PREP(vlan, vub));
+}
+
+static void mt7628_vlan_sync(struct dsa_switch *ds)
+{
+ struct mt7628_esw *esw = ds->priv;
+ int i;
+
+ for (i = 0; i < MT7628_NUM_VLANS; i++) {
+ struct mt7628_vlan *vlan = &esw->vlans[i];
+
+ mt7628_esw_set_vmsc(esw, i, vlan->members);
+ mt7628_esw_set_vlan_id(esw, i, vlan->vid);
+ mt7628_esw_set_vub(esw, i, vlan->untag);
+ }
+
+ for (i = 0; i < ds->num_ports; i++)
+ mt7628_esw_set_pvid(esw, i, esw->tag_8021q_pvid[i]);
+}
+
+static int mt7628_setup(struct dsa_switch *ds)
+{
+ struct mt7628_esw *esw = ds->priv;
+ int ret;
+
+ ret = reset_control_reset(esw->rst_esw);
+ if (ret)
+ return ret;
+ usleep_range(1000, 2000);
+
+ ret = reset_control_reset(esw->rst_ephy);
+ if (ret)
+ return ret;
+ usleep_range(1000, 2000);
+ /*
+ * all MMIO reads hang if esw is not out of reset
+ * ephy needs extra time to get out of reset or it ends up misconfigured
+ */
+
+ mt7628_switch_init(ds);
+
+ ret = mt7628_setup_internal_mdio(ds);
+ if (ret)
+ return ret;
+
+ rtnl_lock();
+ ret = dsa_tag_8021q_register(ds, htons(ETH_P_8021Q));
+ rtnl_unlock();
+
+ return ret;
+}
+
+static int mt7628_port_enable(struct dsa_switch *ds, int port,
+ struct phy_device *phy)
+{
+ struct mt7628_esw *esw = ds->priv;
+
+ regmap_clear_bits(esw->regmap, MT7628_ESW_REG_POC0,
+ FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port)));
+ return 0;
+}
+
+static void mt7628_port_disable(struct dsa_switch *ds, int port)
+{
+ struct mt7628_esw *esw = ds->priv;
+
+ regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC0,
+ FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port)));
+}
+
+static enum dsa_tag_protocol
+mt7628_get_tag_proto(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp)
+{
+ return DSA_TAG_PROTO_MT7628;
+}
+
+static void mt7628_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ switch (port) {
+ case 6:
+ config->mac_capabilities |= MAC_1000;
+ fallthrough;
+ case 0 ... 4:
+ config->mac_capabilities |= MAC_100 | MAC_10;
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ break;
+ default:
+ break; /* port 5 does not exist on MT7628 */
+ }
+}
+
+static int mt7628_dsa_8021q_vlan_add(struct dsa_switch *ds, int port,
+ u16 vid, u16 flags)
+{
+ struct mt7628_esw *esw = ds->priv;
+ struct mt7628_vlan *vlan = NULL;
+ int i;
+
+ for (i = 0; i < MT7628_NUM_VLANS; i++) {
+ struct mt7628_vlan *check_vlan = &esw->vlans[i];
+
+ if (!check_vlan->active && !vlan)
+ vlan = check_vlan;
+
+ if (check_vlan->active && check_vlan->vid == vid) {
+ vlan = check_vlan;
+ break;
+ }
+ }
+
+ if (!vlan)
+ return -ENOSPC;
+
+ vlan->vid = vid;
+ vlan->active = true;
+ vlan->members |= BIT(port);
+
+ if (flags & BRIDGE_VLAN_INFO_PVID)
+ esw->tag_8021q_pvid[port] = vid;
+
+ if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
+ vlan->untag |= BIT(port);
+
+ mt7628_vlan_sync(ds);
+ return 0;
+}
+
+static int mt7628_dsa_8021q_vlan_del(struct dsa_switch *ds, int port, u16 vid)
+{
+ struct mt7628_esw *esw = ds->priv;
+ struct mt7628_vlan *vlan = NULL;
+ int i;
+
+ for (i = 0; i < MT7628_NUM_VLANS; i++) {
+ struct mt7628_vlan *check_vlan = &esw->vlans[i];
+
+ if (!check_vlan->active || check_vlan->vid != vid)
+ continue;
+ vlan = check_vlan;
+ break;
+ }
+ if (!vlan)
+ return -ENOENT;
+
+ if (esw->tag_8021q_pvid[port] == vid)
+ esw->tag_8021q_pvid[port] = 0;
+
+ vlan->members &= ~BIT(port);
+ vlan->untag &= ~BIT(port);
+
+ if (!vlan->members) {
+ vlan->active = false;
+ vlan->vid = 0;
+ }
+
+ mt7628_vlan_sync(ds);
+ return 0;
+}
+
+static void mt7628_teardown(struct dsa_switch *ds)
+{
+ rtnl_lock();
+ dsa_tag_8021q_unregister(ds);
+ rtnl_unlock();
+}
+
+static const struct dsa_switch_ops mt7628_switch_ops = {
+ .get_tag_protocol = mt7628_get_tag_proto,
+ .setup = mt7628_setup,
+ .teardown = mt7628_teardown,
+ .port_enable = mt7628_port_enable,
+ .port_disable = mt7628_port_disable,
+ .phylink_get_caps = mt7628_phylink_get_caps,
+ .tag_8021q_vlan_add = mt7628_dsa_8021q_vlan_add,
+ .tag_8021q_vlan_del = mt7628_dsa_8021q_vlan_del,
+};
+
+static int mt7628_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mt7628_esw *esw;
+ struct dsa_switch *ds;
+ void __iomem *base;
+
+ ds = devm_kzalloc(&pdev->dev, sizeof(*ds), GFP_KERNEL);
+ if (!ds)
+ return -ENOMEM;
+
+ esw = devm_kzalloc(&pdev->dev, sizeof(*esw), GFP_KERNEL);
+ if (!esw)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ esw->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &mt7628_esw_regmap_cfg);
+ if (IS_ERR(esw->regmap))
+ return PTR_ERR(esw->regmap);
+
+ esw->rst_ephy = devm_reset_control_get_exclusive(&pdev->dev, "ephy");
+ if (IS_ERR(esw->rst_ephy))
+ return dev_err_probe(dev, PTR_ERR(esw->rst_ephy),
+ "failed to get EPHY reset\n");
+
+ esw->rst_esw = devm_reset_control_get_exclusive(&pdev->dev, "esw");
+ if (IS_ERR(esw->rst_esw))
+ return dev_err_probe(dev, PTR_ERR(esw->rst_esw),
+ "failed to get ESW reset\n");
+
+ ds->dev = dev;
+ ds->num_ports = MT7628_ESW_NUM_PORTS;
+ ds->ops = &mt7628_switch_ops;
+ ds->priv = esw;
+ esw->ds = ds;
+ esw->dev = dev;
+ dev_set_drvdata(dev, esw);
+
+ return dsa_register_switch(ds);
+}
+
+static void mt7628_remove(struct platform_device *pdev)
+{
+ struct mt7628_esw *esw = platform_get_drvdata(pdev);
+
+ if (!esw)
+ return;
+
+ dsa_unregister_switch(esw->ds);
+}
+
+static void mt7628_shutdown(struct platform_device *pdev)
+{
+ struct mt7628_esw *esw = platform_get_drvdata(pdev);
+
+ if (!esw)
+ return;
+
+ dsa_switch_shutdown(esw->ds);
+ dev_set_drvdata(&pdev->dev, NULL);
+}
+
+static const struct of_device_id mt7628_of_match[] = {
+ { .compatible = "mediatek,mt7628-esw" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, mt7628_of_match);
+
+static struct platform_driver mt7628_driver = {
+ .driver = {
+ .name = "mt7628-esw",
+ .of_match_table = mt7628_of_match,
+ },
+ .probe = mt7628_probe,
+ .remove = mt7628_remove,
+ .shutdown = mt7628_shutdown,
+};
+
+module_platform_driver(mt7628_driver);
+
+MODULE_AUTHOR("Joris Vaisvila <joey@tinyisr.com>");
+MODULE_DESCRIPTION("Driver for Mediatek MT7628 embedded switch");
+MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v6 3/4] net: dsa: initial MT7628 tagging driver
From: Joris Vaisvila @ 2026-07-04 10:56 UTC (permalink / raw)
To: netdev
Cc: horms, pabeni, kuba, edumazet, davem, olteanv, Andrew Lunn,
devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Arınç ÜNAL, Landen Chao, DENG Qingfang, Sean Wang,
Daniel Golle, Joris Vaisvila
In-Reply-To: <20260704105659.140970-1-joey@tinyisr.com>
Add support for the MT7628 embedded switch's tag.
The MT7628 tag is merged with the VLAN TPID field when a VLAN is
appended by the switch hardware. It is not installed if the VLAN tag is
already there on ingress. Due to this hardware quirk the tag cannot be
trusted for port 0 if we don't know that the VLAN was added by the
hardware. As a workaround for this the switch is configured to always
append the port PVID tag even if the incoming packet is already tagged.
The tagging driver can then trust that the tag is always accurate and
the whole VLAN tag can be removed on ingress as it's only metadata for
the tagger.
On egress the MT7628 tag allows precise TX, but the correct VLAN tag
from tag_8021q is still appended or the switch will not forward the
packet.
Signed-off-by: Joris Vaisvila <joey@tinyisr.com>
---
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +++
net/dsa/Makefile | 1 +
net/dsa/tag_mt7628.c | 93 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 102 insertions(+)
create mode 100644 net/dsa/tag_mt7628.c
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc10..913d1f71e3db 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -59,6 +59,7 @@ struct tc_action;
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
#define DSA_TAG_PROTO_NETC_VALUE 33
+#define DSA_TAG_PROTO_MT7628_VALUE 34
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -95,6 +96,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE,
+ DSA_TAG_PROTO_MT7628 = DSA_TAG_PROTO_MT7628_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index d5e725b90d78..23b4b74004ed 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -98,6 +98,12 @@ config NET_DSA_TAG_EDSA
Say Y or M if you want to enable support for tagging frames for the
Marvell switches which use EtherType DSA headers.
+config NET_DSA_TAG_MT7628
+ tristate "Tag driver for the MT7628 embedded switch"
+ help
+ Say Y or M if you want to enable support for tagging frames for the
+ switch embedded in the MT7628 SoC.
+
config NET_DSA_TAG_MTK
tristate "Tag driver for Mediatek switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index b8c2667cd14a..d15bcf5c68f0 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o
obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o
obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
+obj-$(CONFIG_NET_DSA_TAG_MT7628) += tag_mt7628.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o
obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o
diff --git a/net/dsa/tag_mt7628.c b/net/dsa/tag_mt7628.c
new file mode 100644
index 000000000000..80b50ff08e53
--- /dev/null
+++ b/net/dsa/tag_mt7628.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026, Joris Vaisvila <joey@tinyisr.com>
+ * MT7628 switch tag support
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/dsa/8021q.h>
+#include <net/dsa.h>
+
+#include "tag.h"
+
+/*
+ * The MT7628 tag is encoded in the VLAN TPID field.
+ * On TX the lower 6 bits encode the destination port bitmask.
+ * On RX the lower 3 bits encode the source port number.
+ *
+ * The switch hardware will not modify the TPID of an incoming packet if it is
+ * already VLAN tagged. To work around this the switch is configured to always
+ * append a tag_8021q standalone VLAN tag for each port. That means we can
+ * safely strip the outer VLAN tag after parsing it.
+ *
+ * A VLAN tag is constructed on egress to target the standalone VLAN and
+ * destination port.
+ */
+
+#define MT7628_TAG_NAME "mt7628"
+
+#define MT7628_TAG_TX_PORT GENMASK(5, 0)
+#define MT7628_TAG_RX_PORT GENMASK(2, 0)
+#define MT7628_TAG_LEN 4
+
+static struct sk_buff *mt7628_tag_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ struct dsa_port *dp;
+ u16 xmit_vlan;
+ __be16 *tag;
+
+ dp = dsa_user_to_port(dev);
+ xmit_vlan = dsa_tag_8021q_standalone_vid(dp);
+
+ skb_push(skb, MT7628_TAG_LEN);
+ dsa_alloc_etype_header(skb, MT7628_TAG_LEN);
+
+ tag = dsa_etype_header_pos_tx(skb);
+
+ tag[0] = htons(ETH_P_8021Q |
+ FIELD_PREP(MT7628_TAG_TX_PORT,
+ dsa_xmit_port_mask(skb, dev)));
+ tag[1] = htons(xmit_vlan);
+
+ return skb;
+}
+
+static struct sk_buff *mt7628_tag_rcv(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ __be16 *phdr;
+
+ if (unlikely(!pskb_may_pull(skb, MT7628_TAG_LEN))) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ phdr = dsa_etype_header_pos_rx(skb);
+ skb->dev =
+ dsa_conduit_find_user(dev, 0,
+ FIELD_GET(MT7628_TAG_RX_PORT, ntohs(*phdr)));
+ if (!skb->dev) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ skb_pull_rcsum(skb, MT7628_TAG_LEN);
+ dsa_strip_etype_header(skb, MT7628_TAG_LEN);
+ dsa_default_offload_fwd_mark(skb);
+ return skb;
+}
+
+static const struct dsa_device_ops mt7628_tag_ops = {
+ .name = MT7628_TAG_NAME,
+ .proto = DSA_TAG_PROTO_MT7628,
+ .xmit = mt7628_tag_xmit,
+ .rcv = mt7628_tag_rcv,
+ .needed_headroom = MT7628_TAG_LEN,
+};
+
+module_dsa_tag_driver(mt7628_tag_ops);
+
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MT7628, MT7628_TAG_NAME);
+MODULE_DESCRIPTION("DSA tag driver for MT7628 switch");
+MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v6 2/4] net: phy: mediatek: add phy driver for MT7628 built-in Fast Ethernet PHYs
From: Joris Vaisvila @ 2026-07-04 10:56 UTC (permalink / raw)
To: netdev
Cc: horms, pabeni, kuba, edumazet, davem, olteanv, Andrew Lunn,
devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Arınç ÜNAL, Landen Chao, DENG Qingfang, Sean Wang,
Daniel Golle, Joris Vaisvila
In-Reply-To: <20260704105659.140970-1-joey@tinyisr.com>
The Fast Ethernet PHYs present in the MT7628 SoCs require an
undocumented bit to be set before they can establish 100mbps links.
This commit adds the Kconfig option MEDIATEK_FE_SOC_PHY and the
corresponding driver mtk-fe-soc.c.
Signed-off-by: Joris Vaisvila <joey@tinyisr.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/phy/mediatek/Kconfig | 10 +++++-
drivers/net/phy/mediatek/Makefile | 1 +
drivers/net/phy/mediatek/mtk-fe-soc.c | 50 +++++++++++++++++++++++++++
3 files changed, 60 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/phy/mediatek/mtk-fe-soc.c
diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig
index bb7dc876271e..b6a51f38c358 100644
--- a/drivers/net/phy/mediatek/Kconfig
+++ b/drivers/net/phy/mediatek/Kconfig
@@ -21,8 +21,16 @@ config MEDIATEK_GE_PHY
common operations with MediaTek SoC built-in Gigabit
Ethernet PHYs.
+config MEDIATEK_FE_SOC_PHY
+ tristate "MediaTek SoC Fast Ethernet PHYs"
+ help
+ Support for MediaTek MT7628 built-in Fast Ethernet PHYs.
+ This driver only sets an initialization bit required for the PHY
+ to establish 100 Mbps links. All other PHY operations are handled
+ by the kernel's generic PHY code.
+
config MEDIATEK_GE_SOC_PHY
- tristate "MediaTek SoC Ethernet PHYs"
+ tristate "MediaTek SoC Gigabit Ethernet PHYs"
depends on ARM64 || COMPILE_TEST
depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \
COMPILE_TEST
diff --git a/drivers/net/phy/mediatek/Makefile b/drivers/net/phy/mediatek/Makefile
index ac57ecc799fc..6f9cacf7f906 100644
--- a/drivers/net/phy/mediatek/Makefile
+++ b/drivers/net/phy/mediatek/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_MEDIATEK_2P5GE_PHY) += mtk-2p5ge.o
+obj-$(CONFIG_MEDIATEK_FE_SOC_PHY) += mtk-fe-soc.o
obj-$(CONFIG_MEDIATEK_GE_PHY) += mtk-ge.o
obj-$(CONFIG_MEDIATEK_GE_SOC_PHY) += mtk-ge-soc.o
obj-$(CONFIG_MTK_NET_PHYLIB) += mtk-phy-lib.o
diff --git a/drivers/net/phy/mediatek/mtk-fe-soc.c b/drivers/net/phy/mediatek/mtk-fe-soc.c
new file mode 100644
index 000000000000..9eb4960bcaad
--- /dev/null
+++ b/drivers/net/phy/mediatek/mtk-fe-soc.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for MT7628 Embedded Switch internal Fast Ethernet PHYs
+ */
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define MTK_FPHY_ID_MT7628 0x03a29410
+#define MTK_EXT_PAGE_ACCESS 0x1f
+
+static int mt7628_phy_read_page(struct phy_device *phydev)
+{
+ return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+}
+
+static int mt7628_phy_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
+}
+
+static int mt7628_phy_config_init(struct phy_device *phydev)
+{
+ /*
+ * This undocumented bit is required for the PHYs to be able to
+ * establish 100mbps links.
+ */
+ return phy_modify_paged(phydev, 0x8000, 30, BIT(13), BIT(13));
+}
+
+static struct phy_driver mtk_soc_fe_phy_driver[] = {
+ {
+ PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628),
+ .name = "MediaTek MT7628 PHY",
+ .config_init = mt7628_phy_config_init,
+ .read_page = mt7628_phy_read_page,
+ .write_page = mt7628_phy_write_page,
+ },
+};
+
+module_phy_driver(mtk_soc_fe_phy_driver);
+static const struct mdio_device_id __maybe_unused mtk_soc_fe_phy_tbl[] = {
+ { PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628) },
+ { }
+};
+
+MODULE_DESCRIPTION("MediaTek SoC Fast Ethernet PHY driver");
+MODULE_AUTHOR("Joris Vaisvila <joey@tinyisr.com>");
+MODULE_LICENSE("GPL");
+
+MODULE_DEVICE_TABLE(mdio, mtk_soc_fe_phy_tbl);
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v6 1/4] dt-bindings: net: dsa: add MT7628 ESW
From: Joris Vaisvila @ 2026-07-04 10:56 UTC (permalink / raw)
To: netdev
Cc: horms, pabeni, kuba, edumazet, davem, olteanv, Andrew Lunn,
devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Arınç ÜNAL, Landen Chao, DENG Qingfang, Sean Wang,
Daniel Golle, Joris Vaisvila, Krzysztof Kozlowski
In-Reply-To: <20260704105659.140970-1-joey@tinyisr.com>
Add device tree bindings for the MediaTek MT7628 embedded Ethernet
Switch.
The Switch provides 5 external user ports and 1 internal CPU port, with
integrated 10/100 PHYs and fixed port to PHY mapping.
The CPU port is internally connected and uses port index 6.
Signed-off-by: Joris Vaisvila <joey@tinyisr.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
.../bindings/net/dsa/mediatek,mt7628-esw.yaml | 96 +++++++++++++++++++
1 file changed, 96 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
diff --git a/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
new file mode 100644
index 000000000000..e0e7ffef6648
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
@@ -0,0 +1,96 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/mediatek,mt7628-esw.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Mediatek MT7628 Embedded Ethernet Switch
+
+maintainers:
+ - Joris Vaisvila <joey@tinyisr.com>
+
+description:
+ The MT7628 SoC's built-in Ethernet Switch has five user ports and one
+ internally connected CPU port. The user ports are all connected to the SoC's
+ integrated Fast Ethernet PHYs. The switch registers are directly mapped in
+ the SoC's memory.
+
+allOf:
+ - $ref: dsa.yaml#/$defs/ethernet-ports
+
+properties:
+ compatible:
+ const: mediatek,mt7628-esw
+
+ reg:
+ maxItems: 1
+
+ resets:
+ items:
+ - description: internal switch block reset
+ - description: internal phy package reset
+
+ reset-names:
+ items:
+ - const: esw
+ - const: ephy
+
+required:
+ - compatible
+ - reg
+ - resets
+ - reset-names
+ - ethernet-ports
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ switch@10110000 {
+ compatible = "mediatek,mt7628-esw";
+ reg = <0x10110000 0x8000>;
+
+ resets = <&sysc 23>, <&sysc 24>;
+ reset-names = "esw", "ephy";
+
+ ethernet-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-port@0 {
+ reg = <0>;
+ phy-mode = "internal";
+ };
+
+ ethernet-port@1 {
+ reg = <1>;
+ phy-mode = "internal";
+ };
+
+ ethernet-port@2 {
+ reg = <2>;
+ phy-mode = "internal";
+ };
+
+ ethernet-port@3 {
+ reg = <3>;
+ phy-mode = "internal";
+ };
+
+ ethernet-port@4 {
+ reg = <4>;
+ phy-mode = "internal";
+ };
+
+ ethernet-port@6 {
+ reg = <6>;
+ phy-mode = "internal";
+ ethernet = <ðernet>;
+
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
+ };
+ };
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox