Add proprietary special tag format for the MaxLinear MXL862xx family of
switches. While using the same Ethertype as MaxLinear's GSW1xx switches,
the actual tag format differs significantly, hence we need a dedicated
tag driver for that.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v13: no changes
v12: no changes
v11:
* arrange local variables in reverse xmas tree order (again)
* use MXL862_NAME as .name in struct dsa_device_ops
v10: count ports starting from 0
v9: no changes
v8: no changes
v7: no changes
v6:
* remove unnecessary check for skb != NULL
* merge consecutively printed warnings into single dev_warn_ratelimited
v5:
* remove unused macro definitions
RFC v4:
* describe fields and variables with comments
* sub-interface is only 5 bits
* harmonize Kconfig symbol name
* maintain alphabetic order in Kconfig
* fix typo s/beginnig/beginning/
* fix typo s/swtiches/switches/
* arrange local variables in reverse xmas tree order
RFC v3: no changes
RFC v2: make sure all tag fields are initialized
---
MAINTAINERS | 1 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 7 +++
net/dsa/Makefile | 1 +
net/dsa/tag_mxl862xx.c | 110 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 121 insertions(+)
create mode 100644 net/dsa/tag_mxl862xx.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 5b46466cc346..45d9d0ff52f5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15624,6 +15624,7 @@ M: Daniel Golle <daniel@makrotopia.org>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml
+F: net/dsa/tag_mxl862xx.c
MCAN DEVICE DRIVER
M: Markus Schneider-Pargmann <msp@baylibre.com>
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 6b2b5ed64ea4..1e33242b6d94 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -57,6 +57,7 @@ struct tc_action;
#define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE 29
#define DSA_TAG_PROTO_YT921X_VALUE 30
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
+#define DSA_TAG_PROTO_MXL862_VALUE 32
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -91,6 +92,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_VSC73XX_8021Q = DSA_TAG_PROTO_VSC73XX_8021Q_VALUE,
DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE,
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
+ DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index f86b30742122..efc95759a10e 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -104,6 +104,13 @@ config NET_DSA_TAG_MTK
Say Y or M if you want to enable support for tagging frames for
Mediatek switches.
+config NET_DSA_TAG_MXL_862XX
+ tristate "Tag driver for MxL862xx switches"
+ help
+ Say Y or M if you want to enable support for tagging frames for the
+ Maxlinear MxL86252 and MxL86282 switches using their native 8-byte
+ tagging protocol.
+
config NET_DSA_TAG_MXL_GSW1XX
tristate "Tag driver for MaxLinear GSW1xx switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index 42d173f5a701..bf7247759a64 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -28,6 +28,7 @@ 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_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
obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o
obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o
diff --git a/net/dsa/tag_mxl862xx.c b/net/dsa/tag_mxl862xx.c
new file mode 100644
index 000000000000..a0ec878e698e
--- /dev/null
+++ b/net/dsa/tag_mxl862xx.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DSA Special Tag for MaxLinear 862xx switch chips
+ *
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ * Copyright (C) 2024 MaxLinear Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <net/dsa.h>
+#include "tag.h"
+
+#define MXL862_NAME "mxl862xx"
+
+#define MXL862_HEADER_LEN 8
+
+/* Word 0 -> EtherType */
+
+/* Word 2 */
+#define MXL862_SUBIF_ID GENMASK(4, 0)
+
+/* Word 3 */
+#define MXL862_IGP_EGP GENMASK(3, 0)
+
+static struct sk_buff *mxl862_tag_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ struct dsa_port *dp = dsa_user_to_port(dev);
+ struct dsa_port *cpu_dp = dp->cpu_dp;
+ unsigned int cpu_port, sub_interface;
+ __be16 *mxl862_tag;
+
+ cpu_port = cpu_dp->index;
+
+ /* target port sub-interface ID relative to the CPU port */
+ sub_interface = dp->index + 16 - cpu_port;
+
+ /* provide additional space 'MXL862_HEADER_LEN' bytes */
+ skb_push(skb, MXL862_HEADER_LEN);
+
+ /* shift MAC address to the beginning of the enlarged buffer,
+ * releasing the space required for DSA tag (between MAC address and
+ * Ethertype)
+ */
+ dsa_alloc_etype_header(skb, MXL862_HEADER_LEN);
+
+ /* special tag ingress */
+ mxl862_tag = dsa_etype_header_pos_tx(skb);
+ mxl862_tag[0] = htons(ETH_P_MXLGSW);
+ mxl862_tag[1] = 0;
+ mxl862_tag[2] = htons(FIELD_PREP(MXL862_SUBIF_ID, sub_interface));
+ mxl862_tag[3] = htons(FIELD_PREP(MXL862_IGP_EGP, cpu_port));
+
+ return skb;
+}
+
+static struct sk_buff *mxl862_tag_rcv(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ __be16 *mxl862_tag;
+ int port;
+
+ if (unlikely(!pskb_may_pull(skb, MXL862_HEADER_LEN))) {
+ dev_warn_ratelimited(&dev->dev, "Cannot pull SKB, packet dropped\n");
+ return NULL;
+ }
+
+ mxl862_tag = dsa_etype_header_pos_rx(skb);
+
+ if (unlikely(mxl862_tag[0] != htons(ETH_P_MXLGSW))) {
+ dev_warn_ratelimited(&dev->dev,
+ "Invalid special tag marker, packet dropped, tag: %8ph\n",
+ mxl862_tag);
+ return NULL;
+ }
+
+ /* Get source port information */
+ port = FIELD_GET(MXL862_IGP_EGP, ntohs(mxl862_tag[3]));
+ skb->dev = dsa_conduit_find_user(dev, 0, port);
+ if (unlikely(!skb->dev)) {
+ dev_warn_ratelimited(&dev->dev,
+ "Invalid source port, packet dropped, tag: %8ph\n",
+ mxl862_tag);
+ return NULL;
+ }
+
+ /* remove the MxL862xx special tag between the MAC addresses and the
+ * current ethertype field.
+ */
+ skb_pull_rcsum(skb, MXL862_HEADER_LEN);
+ dsa_strip_etype_header(skb, MXL862_HEADER_LEN);
+
+ return skb;
+}
+
+static const struct dsa_device_ops mxl862_netdev_ops = {
+ .name = MXL862_NAME,
+ .proto = DSA_TAG_PROTO_MXL862,
+ .xmit = mxl862_tag_xmit,
+ .rcv = mxl862_tag_rcv,
+ .needed_headroom = MXL862_HEADER_LEN,
+};
+
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MXL862, MXL862_NAME);
+MODULE_DESCRIPTION("DSA tag driver for MaxLinear MxL862xx switches");
+MODULE_LICENSE("GPL");
+
+module_dsa_tag_driver(mxl862_netdev_ops);
--
2.52.0
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
net: dsa: add tag format for MxL862xx switches
This patch adds a DSA tag driver for MaxLinear MxL862xx switches. The
tag format uses an 8-byte header with an ethertype marker that is shared
with GSW1xx switches, though the format itself differs.
> diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
> --- a/net/dsa/Kconfig
> +++ b/net/dsa/Kconfig
> @@ -104,6 +104,13 @@
[ ... ]
> +config NET_DSA_TAG_MXL_862XX
> + tristate "Tag driver for MxL862xx switches"
> + help
> + Say Y or M if you want to enable support for tagging frames for the
> + Maxlinear MxL86252 and MxL86282 switches using their native 8-byte
^^^^^^^^^
This isn't a bug, but there's inconsistent capitalization here. The help
text uses "Maxlinear" while the adjacent NET_DSA_TAG_MXL_GSW1XX entry uses
"MaxLinear" (with capital L). The MODULE_DESCRIPTION in tag_mxl862xx.c also
uses "MaxLinear".
> + tagging protocol.
> diff --git a/net/dsa/tag_mxl862xx.c b/net/dsa/tag_mxl862xx.c
> --- /dev/null
> +++ b/net/dsa/tag_mxl862xx.c
> @@ -0,0 +1,110 @@
[ ... ]
> +static struct sk_buff *mxl862_tag_xmit(struct sk_buff *skb,
> + struct net_device *dev)
> +{
> + struct dsa_port *dp = dsa_user_to_port(dev);
> + struct dsa_port *cpu_dp = dp->cpu_dp;
> + unsigned int cpu_port, sub_interface;
> + __be16 *mxl862_tag;
> +
> + cpu_port = cpu_dp->index;
> +
> + /* target port sub-interface ID relative to the CPU port */
> + sub_interface = dp->index + 16 - cpu_port;
^^^^^^^^^^^^^^^^^^^^^^^^
Can sub_interface overflow the 5-bit MXL862_SUBIF_ID field here? With
dp->index = 16 and cpu_port = 0, the calculation would yield 32 which
exceeds the valid range (0-31) for GENMASK(4, 0). FIELD_PREP would silently
truncate this to 0 via masking. Is there a constraint on port configurations
that prevents this from occurring?
> +
> + /* provide additional space 'MXL862_HEADER_LEN' bytes */
> + skb_push(skb, MXL862_HEADER_LEN);
[ ... ]
> + dsa_alloc_etype_header(skb, MXL862_HEADER_LEN);
> +
> + /* special tag ingress */
^^^^^^^
This comment says "ingress" but mxl862_tag_xmit() is the TX/egress path.
Should this say "egress" instead?
> + mxl862_tag = dsa_etype_header_pos_tx(skb);
--
pw-bot: cr
On Thu, Feb 05, 2026 at 06:21:34PM -0800, Jakub Kicinski wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
>
> For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
> ---
> net: dsa: add tag format for MxL862xx switches
>
> This patch adds a DSA tag driver for MaxLinear MxL862xx switches. The
> tag format uses an 8-byte header with an ethertype marker that is shared
> with GSW1xx switches, though the format itself differs.
>
> > diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
> > --- a/net/dsa/Kconfig
> > +++ b/net/dsa/Kconfig
> > @@ -104,6 +104,13 @@
>
> [ ... ]
>
> > +config NET_DSA_TAG_MXL_862XX
> > + tristate "Tag driver for MxL862xx switches"
> > + help
> > + Say Y or M if you want to enable support for tagging frames for the
> > + Maxlinear MxL86252 and MxL86282 switches using their native 8-byte
> ^^^^^^^^^
>
> This isn't a bug, but there's inconsistent capitalization here. The help
> text uses "Maxlinear" while the adjacent NET_DSA_TAG_MXL_GSW1XX entry uses
> "MaxLinear" (with capital L). The MODULE_DESCRIPTION in tag_mxl862xx.c also
> uses "MaxLinear".
+1 Will fix that.
>
> > + tagging protocol.
>
> > diff --git a/net/dsa/tag_mxl862xx.c b/net/dsa/tag_mxl862xx.c
> > --- /dev/null
> > +++ b/net/dsa/tag_mxl862xx.c
> > @@ -0,0 +1,110 @@
>
> [ ... ]
>
> > +static struct sk_buff *mxl862_tag_xmit(struct sk_buff *skb,
> > + struct net_device *dev)
> > +{
> > + struct dsa_port *dp = dsa_user_to_port(dev);
> > + struct dsa_port *cpu_dp = dp->cpu_dp;
> > + unsigned int cpu_port, sub_interface;
> > + __be16 *mxl862_tag;
> > +
> > + cpu_port = cpu_dp->index;
> > +
> > + /* target port sub-interface ID relative to the CPU port */
> > + sub_interface = dp->index + 16 - cpu_port;
> ^^^^^^^^^^^^^^^^^^^^^^^^
>
> Can sub_interface overflow the 5-bit MXL862_SUBIF_ID field here? With
> dp->index = 16 and cpu_port = 0, the calculation would yield 32 which
> exceeds the valid range (0-31) for GENMASK(4, 0). FIELD_PREP would silently
> truncate this to 0 via masking. Is there a constraint on port configurations
> that prevents this from occurring?
Yes. Only the two SerDes interfaces (port 9 and port 13) can be used as
CPU ports. Also, when using a USXGMII-M 4-port PHY, it is recommended to
connect the PHY to SerDes 1 (port 9 to 12) while using port 13 as CPU
port.
Addressing port 16 is problematic when using the switch with DSA also
because the 4-bit EGP/IGP field used in the receive function can only
present port 0 to 15 obviously. There is a way to do it by also using
the sub-interface ID on switch-to-host tagged frames, but it is worth
avoiding that due to the performance hit which comes with the additional
lookup and multiple pointer dereference steps required when doing so.
As there are only disadvantages when using SerDes 1 for a USXGMII-M
quad-PHY while using SerDes 0 as CPU port it is reasonable to expect
that board designers will opt for the convenient and also
vendor-recommended way and use SerDes 0 for the quad-PHY while using
SerDes 1 as CPU port.
Hence this theoretical limitation will play no role in practise.
>
> > +
> > + /* provide additional space 'MXL862_HEADER_LEN' bytes */
> > + skb_push(skb, MXL862_HEADER_LEN);
>
> [ ... ]
>
> > + dsa_alloc_etype_header(skb, MXL862_HEADER_LEN);
> > +
> > + /* special tag ingress */
> ^^^^^^^
>
> This comment says "ingress" but mxl862_tag_xmit() is the TX/egress path.
> Should this say "egress" instead?
"ingress" is the perspective of the switch, and how the tag to be used
to send frames from the host to the switch is called in the datasheet[1]
[1]: https://www.mouser.com/datasheet/2/146/621181_MxL86282I_DS_Rev1_1-3675315.pdf
page 95
On Fri, 6 Feb 2026 03:30:30 +0000 Daniel Golle wrote: > > > + > > > + /* provide additional space 'MXL862_HEADER_LEN' bytes */ > > > + skb_push(skb, MXL862_HEADER_LEN); > > > > [ ... ] > > > > > + dsa_alloc_etype_header(skb, MXL862_HEADER_LEN); > > > + > > > + /* special tag ingress */ > > ^^^^^^^ > > > > This comment says "ingress" but mxl862_tag_xmit() is the TX/egress path. > > Should this say "egress" instead? > > "ingress" is the perspective of the switch, and how the tag to be used > to send frames from the host to the switch is called in the datasheet[1] Maybe expand on the comment a bit? Or remove it? Cause reading the code I was also consued why this is called ingress
© 2016 - 2026 Red Hat, Inc.