From: Minghuan Lian <minghuan.lian@nxp.com>
To: netdev@vger.kernel.org
Cc: devicetree@vger.kernel.org, andrew+netdev@lunn.ch,
olteanv@gmail.com, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org,
Minghuan Lian <minghuan.lian@nxp.com>
Subject: [PATCH net-next 2/4] net: dsa: tag_hms: Add HMS tag protocol
Date: Sat, 9 May 2026 18:06:30 +0900 [thread overview]
Message-ID: <20260509090632.2959553-3-minghuan.lian@nxp.com> (raw)
In-Reply-To: <20260509090632.2959553-1-minghuan.lian@nxp.com>
Add a DSA tagger for NXP Heterogeneous Multi-SoC (HMS) switches.
HMS is used by systems where the Ethernet switch is managed by firmware
running on a companion SoC or MCU, while Linux runs on the host SoC and
exposes the switch ports through DSA. The host data path uses a regular
Ethernet controller as the DSA conduit port.
The HMS tag protocol has two frame formats. Regular data traffic is
identified using DSA tag_8021q VLAN IDs, which provide the source and
destination port information needed by DSA without requiring a custom
Ethernet header on every packet.
Link-local and control traffic uses an HMS meta frame format with
EtherType ETH_P_HMS_META. This is needed for frames where explicit
metadata must be exchanged with the switch firmware, such as slow
protocols, PTP-over-Ethernet traffic and in-band control notifications.
The tagger parses received meta frames, resolves the source switch and
port, and provides a callback hook for the switch driver to handle meta
commands.
The tagger therefore supports both:
- tag_8021q based transmit and receive handling for regular data
traffic
- HMS meta frame transmit and receive handling for link-local and
control traffic
Signed-off-by: Minghuan Lian <minghuan.lian@nxp.com>
---
include/linux/dsa/tag_hms.h | 28 +++
include/net/dsa.h | 2 +
net/dsa/Kconfig | 9 +
net/dsa/Makefile | 1 +
net/dsa/tag_hms.c | 366 ++++++++++++++++++++++++++++++++++++
5 files changed, 406 insertions(+)
create mode 100644 include/linux/dsa/tag_hms.h
create mode 100644 net/dsa/tag_hms.c
diff --git a/include/linux/dsa/tag_hms.h b/include/linux/dsa/tag_hms.h
new file mode 100644
index 0000000000000..180e969dc06f4
--- /dev/null
+++ b/include/linux/dsa/tag_hms.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef _NET_DSA_TAG_HMS_H
+#define _NET_DSA_TAG_HMS_H
+
+#include <linux/dsa/8021q.h>
+#include <net/dsa.h>
+
+#define ETH_P_HMS_8021Q ETH_P_8021Q /* 0x8100 */
+
+#define HMS_META_ETYPE 0xDADC /* HMS internal meta frame EtherType */
+
+/* IEEE 802.3 Annex 57A: Slow Protocols PDUs (01:80:C2:xx:xx:xx) */
+#define HMS_LINKLOCAL_FILTER_A 0x0180C2000000ull
+#define HMS_LINKLOCAL_FILTER_A_MASK 0xFFFFFF000000ull
+/* IEEE 1588 Annex F: Transport of PTP over Ethernet (01:1B:19:xx:xx:xx) */
+#define HMS_LINKLOCAL_FILTER_B 0x011B19000000ull
+#define HMS_LINKLOCAL_FILTER_B_MASK 0xFFFFFF000000ull
+
+struct hms_tagger_data {
+ void (*meta_cmd_handler)(struct dsa_switch *ds, int port,
+ void *buf, size_t len);
+};
+
+#endif /* _NET_DSA_TAG_HMS_H */
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 4cc67469cf2ec..625dee53488dc 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -58,6 +58,7 @@ struct tc_action;
#define DSA_TAG_PROTO_YT921X_VALUE 30
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
+#define DSA_TAG_PROTO_HMS_VALUE 33
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -93,6 +94,7 @@ enum dsa_tag_protocol {
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,
+ DSA_TAG_PROTO_HMS = DSA_TAG_PROTO_HMS_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 5ed8c704636dd..3f0295a364f18 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -211,4 +211,13 @@ config NET_DSA_TAG_YT921X
Say Y or M if you want to enable support for tagging frames for
Motorcomm YT921x switches.
+config NET_DSA_TAG_HMS
+ tristate "Tag driver for NXP Heterogeneous Multi-SoC switches"
+ select NET_DSA_TAG_8021Q
+ help
+ Say Y or M if you want to enable support for tagging frames for
+ NXP Heterogeneous Multi-SoC switch family. This driver uses a
+ custom 802.1Q VLAN header for injection and extraction of frames
+ between the host and the NXP i.MX RT1180 NETC switch.
+
endif
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index bf7247759a64a..5f9dbbe782381 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
obj-$(CONFIG_NET_DSA_TAG_VSC73XX_8021Q) += tag_vsc73xx_8021q.o
obj-$(CONFIG_NET_DSA_TAG_XRS700X) += tag_xrs700x.o
obj-$(CONFIG_NET_DSA_TAG_YT921X) += tag_yt921x.o
+obj-$(CONFIG_NET_DSA_TAG_HMS) += tag_hms.o
# for tracing framework to find trace.h
CFLAGS_trace.o := -I$(src)
diff --git a/net/dsa/tag_hms.c b/net/dsa/tag_hms.c
new file mode 100644
index 0000000000000..ea485b7088ce7
--- /dev/null
+++ b/net/dsa/tag_hms.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/if_vlan.h>
+#include <linux/dsa/tag_hms.h>
+#include <linux/packing.h>
+#include <linux/slab.h>
+#include "tag.h"
+#include "tag_8021q.h"
+
+#define HMS_TAG_NAME "hms"
+
+/*
+ * HMS meta header inserted after the source MAC address.
+ *
+ * | 2B | 2B | 0 / 4B / 8B / 12B / 16B / 20B |
+ * +------------ +-------------+---------------------------------+
+ * | 0xDADC | HEADER | DATA |
+ * +------------ +------------ +---------------------------------+
+ */
+
+#define HMS_HEADER_LEN 4
+
+#define HMS_HEADER_HOST_TO_SWITCH BIT(15)
+
+/* Binary structure of the HMS Header ETH_P_HMS_META:
+ *
+ * | 15 | 14 | 13 | 12 | 11 | 10 - 9 | 7 - 4 | 3 - 0 |
+ * +-----------+------+-----------+-------+-------+--------+-----------+---------+
+ * | TO HOST 0 | META | HOST Only | | | | Switch ID | Port ID |
+ * +-----------+------+-----------+-------+-------+--------+-----------+---------+
+ */
+#define HMS_RX_HEADER_IS_METADATA BIT(14)
+#define HMS_RX_HEADER_HOST_ONLY BIT(13)
+
+#define HMS_HEADER_PORT_MASK 0x0F
+#define HMS_HEADER_SWITCH_MASK 0xF0
+#define HMS_HEADER_SWITCH_OFFSET 4
+#define HMS_RX_HEADER_PORT_ID(x) ((x) & HMS_HEADER_PORT_MASK)
+#define HMS_RX_HEADER_SWITCH_ID(x) (((x) & HMS_HEADER_SWITCH_MASK) >> HMS_HEADER_SWITCH_OFFSET)
+
+/* TX header */
+
+/*
+ * Binary structure of the HMS Header ETH_P_HMS_META:
+ *
+ * | 15 | 14 | 13 | 12 | 11 | 10 - 9 | 7 - 4 | 3 - 0 |
+ * +-----------+------+--------+-------+---------+--------+-----------+---------+
+ * | To SW 1 | META | | | | | SWITCH ID | PORT ID |
+ * +-----------+------+--------+-------+------ -+--------+-----------+---------+
+ */
+
+#define HMS_TX_HEADER_SWITCHID(x) (((x) << HMS_HEADER_SWITCH_OFFSET) & HMS_HEADER_SWITCH_MASK)
+#define HMS_TX_HEADER_DESTPORTID(x) ((x) & HMS_HEADER_PORT_MASK)
+
+/* Similar to is_link_local_ether_addr(hdr->h_dest) but also covers PTP */
+static inline bool hms_is_link_local(const struct sk_buff *skb)
+{
+ const struct ethhdr *hdr = eth_hdr(skb);
+ u64 dmac = ether_addr_to_u64(hdr->h_dest);
+
+ if (ntohs(hdr->h_proto) == HMS_META_ETYPE)
+ return true;
+
+ if ((dmac & HMS_LINKLOCAL_FILTER_A_MASK) ==
+ HMS_LINKLOCAL_FILTER_A)
+ return true;
+
+ if ((dmac & HMS_LINKLOCAL_FILTER_B_MASK) ==
+ HMS_LINKLOCAL_FILTER_B)
+ return true;
+
+ return false;
+}
+
+/* Send VLAN tags with a TPID that blends in with whatever VLAN protocol a
+ * bridge spanning ports of this switch might have.
+ */
+static u16 hms_xmit_tpid(struct dsa_port *dp)
+{
+ struct dsa_switch *ds = dp->ds;
+ struct dsa_port *other_dp;
+ u16 proto;
+
+ if (!dsa_port_is_vlan_filtering(dp))
+ return ETH_P_HMS_8021Q;
+
+ /* Port is VLAN-aware, so there is a bridge somewhere (a single one,
+ * we're sure about that). It may not be on this port though, so we
+ * need to find it.
+ */
+ dsa_switch_for_each_port(other_dp, ds) {
+ struct net_device *br = dsa_port_bridge_dev_get(other_dp);
+
+ if (!br)
+ continue;
+
+ /* Error is returned only if CONFIG_BRIDGE_VLAN_FILTERING,
+ * which seems pointless to handle, as our port cannot become
+ * VLAN-aware in that case.
+ */
+ br_vlan_get_proto(br, &proto);
+
+ return proto;
+ }
+
+ WARN_ONCE(1, "Port is VLAN-aware but cannot find associated bridge!\n");
+
+ return ETH_P_HMS_8021Q;
+}
+
+static struct sk_buff *hms_imprecise_xmit(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ struct dsa_port *dp = dsa_user_to_port(netdev);
+ unsigned int bridge_num = dsa_port_bridge_num_get(dp);
+ struct net_device *br = dsa_port_bridge_dev_get(dp);
+ u16 tx_vid;
+
+ /* If the port is under a VLAN-aware bridge, just slide the
+ * VLAN-tagged packet into the FDB and hope for the best.
+ * This works because we support a single VLAN-aware bridge
+ * across the entire dst, and its VLANs cannot be shared with
+ * any standalone port.
+ */
+ if (br_vlan_enabled(br))
+ return skb;
+
+ /* If the port is under a VLAN-unaware bridge, use an imprecise
+ * TX VLAN that targets the bridge's entire broadcast domain,
+ * instead of just the specific port.
+ */
+ tx_vid = dsa_tag_8021q_bridge_vid(bridge_num);
+
+ if (unlikely(skb_vlan_tag_present(skb))) {
+ skb = __vlan_hwaccel_push_inside(skb);
+ if (!skb) {
+ WARN_ONCE(1, "Failed to push VLAN tag to payload!\n");
+ return NULL;
+ }
+ }
+
+ return dsa_8021q_xmit(skb, netdev, hms_xmit_tpid(dp), tx_vid);
+}
+
+static struct sk_buff *hms_meta_xmit(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ struct dsa_port *dp = dsa_user_to_port(netdev);
+ int len = HMS_HEADER_LEN;
+ __be16 *tx_header;
+
+ skb_push(skb, len);
+
+ dsa_alloc_etype_header(skb, len);
+
+ tx_header = dsa_etype_header_pos_tx(skb);
+
+ tx_header[0] = htons(HMS_META_ETYPE);
+ tx_header[1] = htons(HMS_HEADER_HOST_TO_SWITCH |
+ HMS_TX_HEADER_SWITCHID(dp->ds->index) |
+ HMS_TX_HEADER_DESTPORTID(dp->index));
+
+ return skb;
+}
+
+static struct sk_buff *hms_8021q_xmit(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ struct dsa_port *dp = dsa_user_to_port(netdev);
+ u16 queue_mapping = skb_get_queue_mapping(skb);
+ u8 pcp = netdev_txq_to_tc(netdev, queue_mapping);
+ u16 tx_vid = dsa_tag_8021q_standalone_vid(dp);
+
+ return dsa_8021q_xmit(skb, netdev, hms_xmit_tpid(dp),
+ ((pcp << VLAN_PRIO_SHIFT) | tx_vid));
+}
+
+static struct sk_buff *hms_xmit(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ if (skb->offload_fwd_mark)
+ return hms_imprecise_xmit(skb, netdev);
+
+ if (unlikely(hms_is_link_local(skb)))
+ return hms_meta_xmit(skb, netdev);
+
+ return hms_8021q_xmit(skb, netdev);
+}
+
+static bool hms_skb_has_tag_8021q(const struct sk_buff *skb)
+{
+ u16 tpid = ntohs(eth_hdr(skb)->h_proto);
+
+ return tpid == ETH_P_8021AD || tpid == ETH_P_8021Q ||
+ skb_vlan_tag_present(skb);
+}
+
+static bool hms_skb_has_inband_control_extension(const struct sk_buff *skb)
+{
+ return ntohs(eth_hdr(skb)->h_proto) == HMS_META_ETYPE;
+}
+
+static struct sk_buff *hms_rcv_meta_cmd(struct sk_buff *skb, u16 rx_header)
+{
+ u8 *buf = dsa_etype_header_pos_rx(skb) + HMS_HEADER_LEN;
+ int switch_id = HMS_RX_HEADER_SWITCH_ID(rx_header);
+ int source_port = HMS_RX_HEADER_PORT_ID(rx_header);
+ struct hms_tagger_data *tagger_data;
+ struct net_device *master = skb->dev;
+ struct dsa_port *cpu_dp;
+ struct dsa_switch *ds;
+
+ cpu_dp = master->dsa_ptr;
+ ds = dsa_switch_find(cpu_dp->dst->index, switch_id);
+ if (!ds) {
+ net_err_ratelimited("%s: cannot find switch id %d\n",
+ master->name, switch_id);
+ return NULL;
+ }
+
+ tagger_data = ds->tagger_data;
+ if (!tagger_data->meta_cmd_handler)
+ return NULL;
+
+ if (skb_is_nonlinear(skb))
+ if (skb_linearize(skb))
+ return NULL;
+
+ tagger_data->meta_cmd_handler(ds, source_port, buf,
+ skb->len - HMS_HEADER_LEN - 2 * ETH_ALEN);
+
+ /* Discard the meta frame */
+ return NULL;
+}
+
+static struct sk_buff *hms_rcv_inband_control_extension(struct sk_buff *skb,
+ int *source_port,
+ int *switch_id,
+ bool *host_only)
+{
+ u16 rx_header;
+ int len = 0;
+
+ if (unlikely(!pskb_may_pull(skb, HMS_HEADER_LEN)))
+ return NULL;
+
+ rx_header = ntohs(*(__be16 *)skb->data);
+ if (rx_header & HMS_RX_HEADER_HOST_ONLY)
+ *host_only = true;
+
+ if (rx_header & HMS_RX_HEADER_IS_METADATA)
+ return hms_rcv_meta_cmd(skb, rx_header);
+
+ *source_port = HMS_RX_HEADER_PORT_ID(rx_header);
+ *switch_id = HMS_RX_HEADER_SWITCH_ID(rx_header);
+
+ len += HMS_HEADER_LEN;
+
+ /* Advance skb->data past the DSA header */
+ skb_pull_rcsum(skb, len);
+
+ dsa_strip_etype_header(skb, len);
+
+ /* With skb->data in its final place, update the MAC header
+ * so that eth_hdr() continues to works properly.
+ */
+ skb_set_mac_header(skb, -ETH_HLEN);
+
+ return skb;
+}
+
+/* If the VLAN in the packet is a tag_8021q one, set @source_port and
+ * @switch_id and strip the header. Otherwise set @vid and keep it in the
+ * packet.
+ */
+static void hms_vlan_rcv(struct sk_buff *skb, int *source_port,
+ int *switch_id, int *vbid, int *vid)
+{
+ dsa_8021q_rcv(skb, source_port, switch_id, vbid, vid);
+}
+
+static struct sk_buff *hms_rcv(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ int src_port = -1, switch_id = -1, vbid = -1, vid = -1;
+ bool host_only = false;
+
+ if (hms_skb_has_inband_control_extension(skb)) {
+ skb = hms_rcv_inband_control_extension(skb, &src_port,
+ &switch_id,
+ &host_only);
+ if (!skb)
+ return NULL;
+ }
+
+ /* Packets with in-band control extensions might still have RX VLANs */
+ if (likely(hms_skb_has_tag_8021q(skb)))
+ hms_vlan_rcv(skb, &src_port, &switch_id, &vbid, &vid);
+
+ if (src_port == -1) /* Need to check it - bridge mode */
+ return NULL;
+
+ skb->dev = dsa_tag_8021q_find_user(netdev, src_port, switch_id,
+ vid, vbid);
+ if (!skb->dev) {
+ /* netdev_warn(netdev, "Couldn't decode source port\n"); */
+ return NULL;
+ }
+
+ if (!host_only)
+ dsa_default_offload_fwd_mark(skb);
+
+ return skb;
+}
+
+static void hms_disconnect(struct dsa_switch *ds)
+{
+ struct hms_tagger_data *tagger_data = ds->tagger_data;
+
+ kfree(tagger_data);
+ ds->tagger_data = NULL;
+}
+
+static int hms_connect(struct dsa_switch *ds)
+{
+ struct hms_tagger_data *data;
+
+ data = kzalloc_obj(*data, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ds->tagger_data = data;
+
+ return 0;
+}
+
+static void hms_flow_dissect(const struct sk_buff *skb, __be16 *proto,
+ int *offset)
+{
+ /* No tag added for management frames, all ok */
+ if (unlikely(hms_is_link_local(skb)))
+ return;
+
+ dsa_tag_generic_flow_dissect(skb, proto, offset);
+}
+
+static const struct dsa_device_ops hms_netdev_ops = {
+ .name = HMS_TAG_NAME,
+ .proto = DSA_TAG_PROTO_HMS,
+ .xmit = hms_xmit,
+ .rcv = hms_rcv,
+ .connect = hms_connect,
+ .disconnect = hms_disconnect,
+ .needed_headroom = VLAN_HLEN,
+ .flow_dissect = hms_flow_dissect,
+ .promisc_on_conduit = true,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for Heterogeneous Multi-SoC switches");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_HMS, HMS_TAG_NAME);
+
+module_dsa_tag_driver(hms_netdev_ops);
--
2.43.0
next prev parent reply other threads:[~2026-05-09 9:06 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-09 9:06 [PATCH net-next 0/4] net: dsa: Add NXP i.MX RT1180 NETC switch support Minghuan Lian
2026-05-09 9:06 ` [PATCH net-next 1/4] dt-bindings: net: dsa: add NXP i.MX RT1180 NETC switch Minghuan Lian
2026-05-09 9:06 ` Minghuan Lian [this message]
2026-05-10 9:10 ` [PATCH net-next 2/4] net: dsa: tag_hms: Add HMS tag protocol sashiko-bot
2026-05-09 9:06 ` [PATCH net-next 3/4] net: dsa: hms: Add NXP i.MX RT1180 NETC switch driver Minghuan Lian
2026-05-10 9:10 ` sashiko-bot
2026-05-09 9:06 ` [PATCH net-next 4/4] net: dsa: hms: Add ethtool statistics support Minghuan Lian
2026-05-10 9:10 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260509090632.2959553-3-minghuan.lian@nxp.com \
--to=minghuan.lian@nxp.com \
--cc=andrew+netdev@lunn.ch \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=olteanv@gmail.com \
--cc=pabeni@redhat.com \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox