Merge branch 'introduce-the-dsa_xmit_port_mask-tagging-protocol-helper'

Vladimir Oltean says:

====================
Introduce the dsa_xmit_port_mask() tagging protocol helper

What
----

Some DSA tags have just the port number in the TX header format, others
have a bit field where in theory, multiple bits can be set, even though
DSA only sets one.

The latter kind is now making use of a dsa_xmit_port_mask() helper,
which will decide when to set more than 1 bit in that mask.

Why
---

David Yang has pointed out in a recently posted patch that HSR packet
duplication on transmission can be offloaded even on HSR-unaware
switches. This should be made generally available to all DSA switches.

How to test
-----------

These patches just lay the groundwork, and there should be no functional
change - so for this set, regression testing is all that's necessary.

For testing the HSR packet duplication idea, I've put together a branch:
https://github.com/vladimiroltean/linux/commits/dsa-simple-hsr-offload/
where most drivers are patched to call dsa_port_simple_hsr_join() and
dsa_port_simple_hsr_leave().

Assuming there are volunteers to also test the latter, one can enable
CONFIG_HSR and create a HSR device using:
$ ip link add name hsr0 type hsr slave1 swp0 slave2 swp1 supervision 45 version 1

This needs to be connected using 2 cables to another system where the
same command was run. Then, one should be able to ping the other board
through the hsr0 interface.

Without the Github branch, a ping over HSR should increase the DSA
conduit interface's TX counters by 2 packets. With the Github branch,
the TX counters should increase by only 1 packet.

Why so many patches
-------------------

To avoid the situation where a patch has to be backported, conflicts
with the work done here, pulls this in as a dependency, and that pulls
in 13 other unrelated drivers. These don't have any dependencies between
each other and can be cherry-picked at will (except they all depend on
patch 1/15).
====================

Link: https://patch.msgid.link/20251127120902.292555-1-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2025-11-28 20:03:43 -08:00
15 changed files with 46 additions and 55 deletions

View File

@@ -319,6 +319,24 @@ static inline void *dsa_etype_header_pos_tx(struct sk_buff *skb)
return skb->data + 2 * ETH_ALEN;
}
static inline unsigned long dsa_xmit_port_mask(const struct sk_buff *skb,
const struct net_device *dev)
{
struct dsa_port *dp = dsa_user_to_port(dev);
unsigned long mask = BIT(dp->index);
if (IS_ENABLED(CONFIG_HSR) &&
unlikely(dev->features & NETIF_F_HW_HSR_DUP)) {
struct net_device *hsr_dev = dp->hsr_dev;
struct dsa_port *other_dp;
dsa_hsr_foreach_port(other_dp, dp->ds, hsr_dev)
mask |= BIT(other_dp->index);
}
return mask;
}
/* Create 2 modaliases per tagging protocol, one to auto-load the module
* given the ID reported by get_tag_protocol(), and the other by name.
*/

View File

@@ -92,6 +92,7 @@ static struct sk_buff *brcm_tag_xmit_ll(struct sk_buff *skb,
{
struct dsa_port *dp = dsa_user_to_port(dev);
u16 queue = skb_get_queue_mapping(skb);
u16 port_mask;
u8 *brcm_tag;
/* The Ethernet switch we are interfaced with needs packets to be at
@@ -119,10 +120,9 @@ static struct sk_buff *brcm_tag_xmit_ll(struct sk_buff *skb,
brcm_tag[0] = (1 << BRCM_OPCODE_SHIFT) |
((queue & BRCM_IG_TC_MASK) << BRCM_IG_TC_SHIFT);
brcm_tag[1] = 0;
brcm_tag[2] = 0;
if (dp->index == 8)
brcm_tag[2] = BRCM_IG_DSTMAP2_MASK;
brcm_tag[3] = (1 << dp->index) & BRCM_IG_DSTMAP1_MASK;
port_mask = dsa_xmit_port_mask(skb, dev);
brcm_tag[2] = (port_mask >> 8) & BRCM_IG_DSTMAP2_MASK;
brcm_tag[3] = port_mask & BRCM_IG_DSTMAP1_MASK;
/* Now tell the conduit network device about the desired output queue
* as well

View File

@@ -48,8 +48,7 @@
/* Byte 3 */
#define GSWIP_TX_DPID_EN BIT(0)
#define GSWIP_TX_PORT_MAP_SHIFT 1
#define GSWIP_TX_PORT_MAP_MASK GENMASK(6, 1)
#define GSWIP_TX_PORT_MAP GENMASK(6, 1)
#define GSWIP_RX_HEADER_LEN 8
@@ -61,7 +60,6 @@
static struct sk_buff *gswip_tag_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct dsa_port *dp = dsa_user_to_port(dev);
u8 *gswip_tag;
skb_push(skb, GSWIP_TX_HEADER_LEN);
@@ -70,7 +68,7 @@ static struct sk_buff *gswip_tag_xmit(struct sk_buff *skb,
gswip_tag[0] = GSWIP_TX_SLPID_CPU;
gswip_tag[1] = GSWIP_TX_DPID_ELAN;
gswip_tag[2] = GSWIP_TX_PORT_MAP_EN | GSWIP_TX_PORT_MAP_SEL;
gswip_tag[3] = BIT(dp->index + GSWIP_TX_PORT_MAP_SHIFT) & GSWIP_TX_PORT_MAP_MASK;
gswip_tag[3] = FIELD_PREP(GSWIP_TX_PORT_MAP, dsa_xmit_port_mask(skb, dev));
gswip_tag[3] |= GSWIP_TX_DPID_EN;
return skb;

View File

@@ -20,7 +20,6 @@
static struct sk_buff *hellcreek_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct dsa_port *dp = dsa_user_to_port(dev);
u8 *tag;
/* Calculate checksums (if required) before adding the trailer tag to
@@ -33,7 +32,7 @@ static struct sk_buff *hellcreek_xmit(struct sk_buff *skb,
/* Tag encoding */
tag = skb_put(skb, HELLCREEK_TAG_LEN);
*tag = BIT(dp->index);
*tag = dsa_xmit_port_mask(skb, dev);
return skb;
}

View File

@@ -120,7 +120,6 @@ static struct sk_buff *ksz_common_rcv(struct sk_buff *skb,
static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct dsa_port *dp = dsa_user_to_port(dev);
struct ethhdr *hdr;
u8 *tag;
@@ -131,7 +130,7 @@ static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev)
tag = skb_put(skb, KSZ_INGRESS_TAG_LEN);
hdr = skb_eth_hdr(skb);
*tag = 1 << dp->index;
*tag = dsa_xmit_port_mask(skb, dev);
if (is_link_local_ether_addr(hdr->h_dest))
*tag |= KSZ8795_TAIL_TAG_OVERRIDE;
@@ -294,21 +293,12 @@ static struct sk_buff *ksz9477_xmit(struct sk_buff *skb,
tag = skb_put(skb, KSZ9477_INGRESS_TAG_LEN);
hdr = skb_eth_hdr(skb);
val = BIT(dp->index);
val = dsa_xmit_port_mask(skb, dev);
val |= FIELD_PREP(KSZ9477_TAIL_TAG_PRIO, prio);
if (is_link_local_ether_addr(hdr->h_dest))
val |= KSZ9477_TAIL_TAG_OVERRIDE;
if (dev->features & NETIF_F_HW_HSR_DUP) {
struct net_device *hsr_dev = dp->hsr_dev;
struct dsa_port *other_dp;
dsa_hsr_foreach_port(other_dp, dp->ds, hsr_dev)
val |= BIT(other_dp->index);
}
*tag = cpu_to_be16(val);
return ksz_defer_xmit(dp, skb);
@@ -371,8 +361,7 @@ static struct sk_buff *ksz9893_xmit(struct sk_buff *skb,
tag = skb_put(skb, KSZ_INGRESS_TAG_LEN);
hdr = skb_eth_hdr(skb);
*tag = BIT(dp->index);
*tag = dsa_xmit_port_mask(skb, dev);
*tag |= FIELD_PREP(KSZ9893_TAIL_TAG_PRIO, prio);
if (is_link_local_ether_addr(hdr->h_dest))
@@ -436,8 +425,7 @@ static struct sk_buff *lan937x_xmit(struct sk_buff *skb,
tag = skb_put(skb, LAN937X_EGRESS_TAG_LEN);
val = BIT(dp->index);
val = dsa_xmit_port_mask(skb, dev);
val |= FIELD_PREP(LAN937X_TAIL_TAG_PRIO, prio);
if (is_link_local_ether_addr(hdr->h_dest))

View File

@@ -54,7 +54,8 @@ static struct sk_buff *mtk_tag_xmit(struct sk_buff *skb,
* whether that's a combined special tag with 802.1Q header.
*/
mtk_tag[0] = xmit_tpid;
mtk_tag[1] = (1 << dp->index) & MTK_HDR_XMIT_DP_BIT_MASK;
mtk_tag[1] = FIELD_PREP(MTK_HDR_XMIT_DP_BIT_MASK,
dsa_xmit_port_mask(skb, dev));
/* Tag control information is kept for 802.1Q */
if (xmit_tpid == MTK_HDR_XMIT_UNTAGGED) {

View File

@@ -43,8 +43,8 @@
static struct sk_buff *gsw1xx_tag_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct dsa_port *dp = dsa_user_to_port(dev);
__be16 *gsw1xx_tag;
u16 tag;
/* provide additional space 'GSW1XX_HEADER_LEN' bytes */
skb_push(skb, GSW1XX_HEADER_LEN);
@@ -55,9 +55,10 @@ static struct sk_buff *gsw1xx_tag_xmit(struct sk_buff *skb,
/* special tag ingress */
gsw1xx_tag = dsa_etype_header_pos_tx(skb);
gsw1xx_tag[0] = htons(ETH_P_MXLGSW);
gsw1xx_tag[1] = htons(GSW1XX_TX_PORT_MAP_EN | GSW1XX_TX_LRN_DIS |
FIELD_PREP(GSW1XX_TX_PORT_MAP, BIT(dp->index)));
tag = FIELD_PREP(GSW1XX_TX_PORT_MAP, dsa_xmit_port_mask(skb, dev)) |
GSW1XX_TX_PORT_MAP_EN | GSW1XX_TX_LRN_DIS;
gsw1xx_tag[1] = htons(tag);
gsw1xx_tag[2] = 0;
gsw1xx_tag[3] = 0;

View File

@@ -46,11 +46,10 @@ static void ocelot_xmit_common(struct sk_buff *skb, struct net_device *netdev,
static struct sk_buff *ocelot_xmit(struct sk_buff *skb,
struct net_device *netdev)
{
struct dsa_port *dp = dsa_user_to_port(netdev);
void *injection;
ocelot_xmit_common(skb, netdev, cpu_to_be32(0x8880000a), &injection);
ocelot_ifh_set_dest(injection, BIT_ULL(dp->index));
ocelot_ifh_set_dest(injection, dsa_xmit_port_mask(skb, netdev));
return skb;
}
@@ -58,11 +57,10 @@ static struct sk_buff *ocelot_xmit(struct sk_buff *skb,
static struct sk_buff *seville_xmit(struct sk_buff *skb,
struct net_device *netdev)
{
struct dsa_port *dp = dsa_user_to_port(netdev);
void *injection;
ocelot_xmit_common(skb, netdev, cpu_to_be32(0x88800005), &injection);
seville_ifh_set_dest(injection, BIT_ULL(dp->index));
seville_ifh_set_dest(injection, dsa_xmit_port_mask(skb, netdev));
return skb;
}

View File

@@ -14,7 +14,6 @@
static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct dsa_port *dp = dsa_user_to_port(dev);
__be16 *phdr;
u16 hdr;
@@ -26,7 +25,7 @@ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
/* Set the version field, and set destination port information */
hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
hdr |= QCA_HDR_XMIT_FROM_CPU;
hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(dp->index));
hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, dsa_xmit_port_mask(skb, dev));
*phdr = htons(hdr);

View File

@@ -57,7 +57,7 @@ static struct sk_buff *rtl4a_tag_xmit(struct sk_buff *skb,
out = (RTL4_A_PROTOCOL_RTL8366RB << RTL4_A_PROTOCOL_SHIFT);
/* The lower bits indicate the port number */
out |= BIT(dp->index);
out |= dsa_xmit_port_mask(skb, dev);
p = (__be16 *)(tag + 2);
*p = htons(out);

View File

@@ -103,7 +103,6 @@
static void rtl8_4_write_tag(struct sk_buff *skb, struct net_device *dev,
void *tag)
{
struct dsa_port *dp = dsa_user_to_port(dev);
__be16 tag16[RTL8_4_TAG_LEN / 2];
/* Set Realtek EtherType */
@@ -116,7 +115,7 @@ static void rtl8_4_write_tag(struct sk_buff *skb, struct net_device *dev,
tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1));
/* Zero ALLOW; set RX (CPU->switch) forwarding port mask */
tag16[3] = htons(FIELD_PREP(RTL8_4_RX, BIT(dp->index)));
tag16[3] = htons(FIELD_PREP(RTL8_4_RX, dsa_xmit_port_mask(skb, dev)));
memcpy(tag, tag16, RTL8_4_TAG_LEN);
}

View File

@@ -39,7 +39,6 @@ struct a5psw_tag {
static struct sk_buff *a5psw_tag_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct dsa_port *dp = dsa_user_to_port(dev);
struct a5psw_tag *ptag;
u32 data2_val;
@@ -60,7 +59,7 @@ static struct sk_buff *a5psw_tag_xmit(struct sk_buff *skb, struct net_device *de
ptag = dsa_etype_header_pos_tx(skb);
data2_val = FIELD_PREP(A5PSW_CTRL_DATA_PORT, BIT(dp->index));
data2_val = FIELD_PREP(A5PSW_CTRL_DATA_PORT, dsa_xmit_port_mask(skb, dev));
ptag->ctrl_tag = htons(ETH_P_DSA_A5PSW);
ptag->ctrl_data = htons(A5PSW_CTRL_DATA_FORCE_FORWARD);
ptag->ctrl_data2_lo = htons(data2_val);

View File

@@ -14,12 +14,11 @@
static struct sk_buff *trailer_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct dsa_port *dp = dsa_user_to_port(dev);
u8 *trailer;
trailer = skb_put(skb, 4);
trailer[0] = 0x80;
trailer[1] = 1 << dp->index;
trailer[1] = dsa_xmit_port_mask(skb, dev);
trailer[2] = 0x10;
trailer[3] = 0x00;

View File

@@ -13,16 +13,10 @@
static struct sk_buff *xrs700x_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct dsa_port *partner, *dp = dsa_user_to_port(dev);
u8 *trailer;
trailer = skb_put(skb, 1);
trailer[0] = BIT(dp->index);
if (dp->hsr_dev)
dsa_hsr_foreach_port(partner, dp->ds, dp->hsr_dev)
if (partner != dp)
trailer[0] |= BIT(partner->index);
trailer[0] = dsa_xmit_port_mask(skb, dev);
return skb;
}

View File

@@ -38,14 +38,11 @@
#define YT921X_TAG_RX_CMD_FORWARDED 0x80
#define YT921X_TAG_RX_CMD_UNK_UCAST 0xb2
#define YT921X_TAG_RX_CMD_UNK_MCAST 0xb4
#define YT921X_TAG_TX_PORTS_M GENMASK(10, 0)
#define YT921X_TAG_TX_PORTn(port) BIT(port)
#define YT921X_TAG_TX_PORTS GENMASK(10, 0)
static struct sk_buff *
yt921x_tag_xmit(struct sk_buff *skb, struct net_device *netdev)
{
struct dsa_port *dp = dsa_user_to_port(netdev);
unsigned int port = dp->index;
__be16 *tag;
u16 tx;
@@ -58,7 +55,8 @@ yt921x_tag_xmit(struct sk_buff *skb, struct net_device *netdev)
/* VLAN tag unrelated when TX */
tag[1] = 0;
tag[2] = 0;
tx = YT921X_TAG_PORT_EN | YT921X_TAG_TX_PORTn(port);
tx = FIELD_PREP(YT921X_TAG_TX_PORTS, dsa_xmit_port_mask(skb, netdev)) |
YT921X_TAG_PORT_EN;
tag[3] = htons(tx);
return skb;