#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))
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);
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;
}
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:
dsa_default_offload_fwd_mark(skb);
break;
case YT921X_TAG_CODE_UNK_UCAST:
case YT921X_TAG_CODE_UNK_MCAST:
break;
default:
dev_warn_ratelimited(&netdev->dev,
"Unknown code 0x%02x\n", code);
break;
}
}
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);