// SPDX-License-Identifier: GPL-2.0 /* * Handler for Realtek 8 byte switch tags * * Copyright (C) 2021 Alvin Šipraga * * NOTE: Currently only supports protocol "4" found in the RTL8365MB, hence * named tag_rtl8_4. * * This tag has the following format: * * 0 7|8 15 * |-----------------------------------+-----------------------------------|--- * | (16-bit) | ^ * | Realtek EtherType [0x8899] | | * |-----------------------------------+-----------------------------------| 8 * | (8-bit) | (8-bit) | * | Protocol [0x04] | REASON | b * |-----------------------------------+-----------------------------------| y * | (1) | (1) | (2) | (1) | (3) | (1) | (1) | (1) | (5) | t * | FID_EN | X | FID | PRI_EN | PRI | KEEP | X | LEARN_DIS | X | e * |-----------------------------------+-----------------------------------| s * | (1) | (15-bit) | | * | ALLOW | TX/RX | v * |-----------------------------------+-----------------------------------|--- * * With the following field descriptions: * * field | description * ------------+------------- * Realtek | 0x8899: indicates that this is a proprietary Realtek tag; * EtherType | note that Realtek uses the same EtherType for * | other incompatible tag formats (e.g. tag_rtl4_a.c) * Protocol | 0x04: indicates that this tag conforms to this format * X | reserved * ------------+------------- * REASON | reason for forwarding packet to CPU * | 0: packet was forwarded or flooded to CPU * | 80: packet was trapped to CPU * FID_EN | 1: packet has an FID * | 0: no FID * FID | FID of packet (if FID_EN=1) * PRI_EN | 1: force priority of packet * | 0: don't force priority * PRI | priority of packet (if PRI_EN=1) * KEEP | preserve packet VLAN tag format * LEARN_DIS | don't learn the source MAC address of the packet * ALLOW | 1: treat TX/RX field as an allowance port mask, meaning the * | packet may only be forwarded to ports specified in the * | mask * | 0: no allowance port mask, TX/RX field is the forwarding * | port mask * TX/RX | TX (switch->CPU): port number the packet was received on * | RX (CPU->switch): forwarding port mask (if ALLOW=0) * | allowance port mask (if ALLOW=1) * * The tag can be positioned before Ethertype, using tag "rtl8_4": * * +--------+--------+------------+------+----- * | MAC DA | MAC SA | 8 byte tag | Type | ... * +--------+--------+------------+------+----- * * The tag can also appear between the end of the payload and before the CRC, * using tag "rtl8_4t": * * +--------+--------+------+-----+---------+------------+-----+ * | MAC DA | MAC SA | TYPE | ... | payload | 8-byte tag | CRC | * +--------+--------+------+-----+---------+------------+-----+ * * The added bytes after the payload will break most checksums, either in * software or hardware. To avoid this issue, if the checksum is still pending, * this tagger checksums the packet in software before adding the tag. * */ #include #include #include #include "tag.h" /* Protocols supported: * * 0x04 = RTL8365MB DSA protocol */ #define RTL8_4_NAME "rtl8_4" #define RTL8_4T_NAME "rtl8_4t" #define RTL8_4_TAG_LEN 8 #define RTL8_4_PROTOCOL GENMASK(15, 8) #define RTL8_4_PROTOCOL_RTL8365MB 0x04 #define RTL8_4_REASON GENMASK(7, 0) #define RTL8_4_REASON_FORWARD 0 #define RTL8_4_REASON_TRAP 80 #define RTL8_4_LEARN_DIS BIT(5) #define RTL8_4_TX GENMASK(3, 0) #define RTL8_4_RX GENMASK(10, 0) 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 */ tag16[0] = htons(ETH_P_REALTEK); /* Set Protocol; zero REASON */ tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB)); /* Zero FID_EN, FID, PRI_EN, PRI, KEEP; set LEARN_DIS */ 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))); memcpy(tag, tag16, RTL8_4_TAG_LEN); } static struct sk_buff *rtl8_4_tag_xmit(struct sk_buff *skb, struct net_device *dev) { skb_push(skb, RTL8_4_TAG_LEN); dsa_alloc_etype_header(skb, RTL8_4_TAG_LEN); rtl8_4_write_tag(skb, dev, dsa_etype_header_pos_tx(skb)); return skb; } static struct sk_buff *rtl8_4t_tag_xmit(struct sk_buff *skb, struct net_device *dev) { /* Calculate the checksum here if not done yet as trailing tags will * break either software or hardware based checksum */ if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) return NULL; rtl8_4_write_tag(skb, dev, skb_put(skb, RTL8_4_TAG_LEN)); return skb; } static int rtl8_4_read_tag(struct sk_buff *skb, struct net_device *dev, void *tag) { __be16 tag16[RTL8_4_TAG_LEN / 2]; u16 etype; u8 reason; u8 proto; u8 port; memcpy(tag16, tag, RTL8_4_TAG_LEN); /* Parse Realtek EtherType */ etype = ntohs(tag16[0]); if (unlikely(etype != ETH_P_REALTEK)) { dev_warn_ratelimited(&dev->dev, "non-realtek ethertype 0x%04x\n", etype); return -EPROTO; } /* Parse Protocol */ proto = FIELD_GET(RTL8_4_PROTOCOL, ntohs(tag16[1])); if (unlikely(proto != RTL8_4_PROTOCOL_RTL8365MB)) { dev_warn_ratelimited(&dev->dev, "unknown realtek protocol 0x%02x\n", proto); return -EPROTO; } /* Parse REASON */ reason = FIELD_GET(RTL8_4_REASON, ntohs(tag16[1])); /* Parse TX (switch->CPU) */ port = FIELD_GET(RTL8_4_TX, ntohs(tag16[3])); skb->dev = dsa_conduit_find_user(dev, 0, port); if (!skb->dev) { dev_warn_ratelimited(&dev->dev, "could not find user for port %d\n", port); return -ENOENT; } if (reason != RTL8_4_REASON_TRAP) dsa_default_offload_fwd_mark(skb); return 0; } static struct sk_buff *rtl8_4_tag_rcv(struct sk_buff *skb, struct net_device *dev) { if (unlikely(!pskb_may_pull(skb, RTL8_4_TAG_LEN))) return NULL; if (unlikely(rtl8_4_read_tag(skb, dev, dsa_etype_header_pos_rx(skb)))) return NULL; /* Remove tag and recalculate checksum */ skb_pull_rcsum(skb, RTL8_4_TAG_LEN); dsa_strip_etype_header(skb, RTL8_4_TAG_LEN); return skb; } static struct sk_buff *rtl8_4t_tag_rcv(struct sk_buff *skb, struct net_device *dev) { if (skb_linearize(skb)) return NULL; if (unlikely(rtl8_4_read_tag(skb, dev, skb_tail_pointer(skb) - RTL8_4_TAG_LEN))) return NULL; if (pskb_trim_rcsum(skb, skb->len - RTL8_4_TAG_LEN)) return NULL; return skb; } /* Ethertype version */ static const struct dsa_device_ops rtl8_4_netdev_ops = { .name = "rtl8_4", .proto = DSA_TAG_PROTO_RTL8_4, .xmit = rtl8_4_tag_xmit, .rcv = rtl8_4_tag_rcv, .needed_headroom = RTL8_4_TAG_LEN, }; DSA_TAG_DRIVER(rtl8_4_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_RTL8_4, RTL8_4_NAME); /* Tail version */ static const struct dsa_device_ops rtl8_4t_netdev_ops = { .name = "rtl8_4t", .proto = DSA_TAG_PROTO_RTL8_4T, .xmit = rtl8_4t_tag_xmit, .rcv = rtl8_4t_tag_rcv, .needed_tailroom = RTL8_4_TAG_LEN, }; DSA_TAG_DRIVER(rtl8_4t_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_RTL8_4T, RTL8_4T_NAME); static struct dsa_tag_driver *dsa_tag_drivers[] = { &DSA_TAG_DRIVER_NAME(rtl8_4_netdev_ops), &DSA_TAG_DRIVER_NAME(rtl8_4t_netdev_ops), }; module_dsa_tag_drivers(dsa_tag_drivers); MODULE_DESCRIPTION("DSA tag driver for Realtek 8 byte protocol 4 tags"); MODULE_LICENSE("GPL");