blob: aefef8c770e39ed7ea1a0ef5467ed1d39eea9c6c [file] [edit]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Motorcomm YT921x Switch Extended CPU Port Tagging
*
* Copyright (c) 2025 David Yang <mmyangfl@gmail.com>
*
* +----+----+-------+-----+----+---------
* | DA | SA | TagET | Tag | ET | Payload ...
* +----+----+-------+-----+----+---------
* 6 6 2 6 2 N
*
* Tag Ethertype: CPU_TAG_TPID_TPID (default: ETH_P_YT921X = 0x9988)
* * Hardcoded for the moment, but still configurable. Discuss it if there
* are conflicts somewhere and/or you want to change it for some reason.
* Tag:
* 2: VLAN Tag
* 2:
* 15b: Rx Port Valid
* 14b-11b: Rx Port
* 10b-8b: Tx/Rx Priority
* 7b: Tx/Rx Code Valid
* 6b-1b: Tx/Rx Code
* 0b: ? (unset)
* 2:
* 15b: Tx Port(s) Valid
* 10b-0b: Tx Port(s) Mask
*/
#include <linux/etherdevice.h>
#include "tag.h"
#define YT921X_TAG_NAME "yt921x"
#define YT921X_TAG_LEN 8
#define YT921X_TAG_PORT_EN BIT(15)
#define YT921X_TAG_RX_PORT_M GENMASK(14, 11)
#define YT921X_TAG_PRIO_M GENMASK(10, 8)
#define YT921X_TAG_PRIO(x) FIELD_PREP(YT921X_TAG_PRIO_M, (x))
#define YT921X_TAG_CODE_EN BIT(7)
#define YT921X_TAG_CODE_M GENMASK(6, 1)
#define YT921X_TAG_CODE(x) FIELD_PREP(YT921X_TAG_CODE_M, (x))
#define YT921X_TAG_TX_PORTS_M GENMASK(10, 0)
#define YT921X_TAG_TX_PORTS(x) FIELD_PREP(YT921X_TAG_TX_PORTS_M, (x))
/* Incomplete. Some are configurable via RMA_CTRL_CPU_CODE, the meaning of
* others remains unknown.
*/
enum yt921x_tag_code {
YT921X_TAG_CODE_FORWARD = 0,
YT921X_TAG_CODE_UNK_UCAST = 0x19,
YT921X_TAG_CODE_UNK_MCAST = 0x1a,
YT921X_TAG_CODE_PORT_COPY = 0x1b,
YT921X_TAG_CODE_FDB_COPY = 0x1c,
};
static struct sk_buff *
yt921x_tag_xmit(struct sk_buff *skb, struct net_device *netdev)
{
__be16 *tag;
u16 ctrl;
skb_push(skb, YT921X_TAG_LEN);
dsa_alloc_etype_header(skb, YT921X_TAG_LEN);
tag = dsa_etype_header_pos_tx(skb);
tag[0] = htons(ETH_P_YT921X);
/* VLAN tag unrelated when TX */
tag[1] = 0;
ctrl = YT921X_TAG_CODE(YT921X_TAG_CODE_FORWARD) | YT921X_TAG_CODE_EN |
YT921X_TAG_PRIO(skb->priority);
tag[2] = htons(ctrl);
ctrl = YT921X_TAG_TX_PORTS(dsa_xmit_port_mask(skb, netdev)) |
YT921X_TAG_PORT_EN;
tag[3] = htons(ctrl);
return skb;
}
static struct sk_buff *
yt921x_tag_rcv(struct sk_buff *skb, struct net_device *netdev)
{
unsigned int port;
__be16 *tag;
u16 rx;
if (unlikely(!pskb_may_pull(skb, YT921X_TAG_LEN)))
return NULL;
tag = dsa_etype_header_pos_rx(skb);
if (unlikely(tag[0] != htons(ETH_P_YT921X))) {
dev_warn_ratelimited(&netdev->dev,
"Unexpected EtherType 0x%04x\n",
ntohs(tag[0]));
return NULL;
}
/* Locate which port this is coming from */
rx = ntohs(tag[2]);
if (unlikely((rx & YT921X_TAG_PORT_EN) == 0)) {
dev_warn_ratelimited(&netdev->dev,
"Unexpected rx tag 0x%04x\n", rx);
return NULL;
}
port = FIELD_GET(YT921X_TAG_RX_PORT_M, rx);
skb->dev = dsa_conduit_find_user(netdev, 0, port);
if (unlikely(!skb->dev)) {
dev_warn_ratelimited(&netdev->dev,
"Couldn't decode source port %u\n", port);
return NULL;
}
skb->priority = FIELD_GET(YT921X_TAG_PRIO_M, rx);
if (!(rx & YT921X_TAG_CODE_EN)) {
dev_warn_ratelimited(&netdev->dev,
"Tag code not enabled in rx packet\n");
} else {
u16 code = FIELD_GET(YT921X_TAG_CODE_M, rx);
switch (code) {
case YT921X_TAG_CODE_FORWARD:
case YT921X_TAG_CODE_PORT_COPY:
case YT921X_TAG_CODE_FDB_COPY:
/* Already forwarded by hardware */
dsa_default_offload_fwd_mark(skb);
break;
case YT921X_TAG_CODE_UNK_UCAST:
case YT921X_TAG_CODE_UNK_MCAST:
/* NOTE: hardware doesn't distinguish between TRAP (copy
* to CPU only) and COPY (forward and copy to CPU). In
* order to perform a soft switch, NEVER use COPY action
* in the switch driver.
*/
break;
default:
dev_warn_ratelimited(&netdev->dev,
"Unknown code 0x%02x\n", code);
break;
}
}
/* Remove YT921x tag and update checksum */
skb_pull_rcsum(skb, YT921X_TAG_LEN);
dsa_strip_etype_header(skb, YT921X_TAG_LEN);
return skb;
}
static const struct dsa_device_ops yt921x_netdev_ops = {
.name = YT921X_TAG_NAME,
.proto = DSA_TAG_PROTO_YT921X,
.xmit = yt921x_tag_xmit,
.rcv = yt921x_tag_rcv,
.needed_headroom = YT921X_TAG_LEN,
};
MODULE_DESCRIPTION("DSA tag driver for Motorcomm YT921x switches");
MODULE_LICENSE("GPL");
MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_YT921X, YT921X_TAG_NAME);
module_dsa_tag_driver(yt921x_netdev_ops);