15037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/*
25037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * Broadcom tag support
35037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli *
45037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * Copyright (C) 2014 Broadcom Corporation
55037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli *
65037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * This program is free software; you can redistribute it and/or modify
75037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * it under the terms of the GNU General Public License as published by
85037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * the Free Software Foundation; either version 2 of the License, or
95037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * (at your option) any later version.
105037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli */
115037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
125037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#include <linux/etherdevice.h>
135037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#include <linux/list.h>
145037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#include <linux/slab.h>
155037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#include "dsa_priv.h"
165037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
175037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* This tag length is 4 bytes, older ones were 6 bytes, we do not
185037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * handle them
195037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli */
205037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_TAG_LEN	4
215037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
225037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* Tag is constructed and desconstructed using byte by byte access
235037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * because the tag is placed after the MAC Source Address, which does
245037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * not make it 4-bytes aligned, so this might cause unaligned accesses
255037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli * on most systems where this is used.
265037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli */
275037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
285037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* Ingress and egress opcodes */
295037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_OPCODE_SHIFT	5
305037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_OPCODE_MASK	0x7
315037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
325037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* Ingress fields */
335037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* 1st byte in the tag */
345037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_IG_TC_SHIFT	2
355037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_IG_TC_MASK		0x7
365037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* 2nd byte in the tag */
375037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_IG_TE_MASK		0x3
385037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_IG_TS_SHIFT	7
395037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* 3rd byte in the tag */
405037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_IG_DSTMAP2_MASK	1
415037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_IG_DSTMAP1_MASK	0xff
425037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
435037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* Egress fields */
445037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
455037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* 2nd byte in the tag */
465037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_EG_CID_MASK	0xff
475037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
485037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli/* 3rd byte in the tag */
495037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_EG_RC_MASK		0xff
505037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define  BRCM_EG_RC_RSVD	(3 << 6)
515037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define  BRCM_EG_RC_EXCEPTION	(1 << 5)
525037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define  BRCM_EG_RC_PROT_SNOOP	(1 << 4)
535037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define  BRCM_EG_RC_PROT_TERM	(1 << 3)
545037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define  BRCM_EG_RC_SWITCH	(1 << 2)
555037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define  BRCM_EG_RC_MAC_LEARN	(1 << 1)
565037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define  BRCM_EG_RC_MIRROR	(1 << 0)
575037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_EG_TC_SHIFT	5
585037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_EG_TC_MASK		0x7
595037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli#define BRCM_EG_PID_MASK	0x1f
605037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
615037d532b83d7325a2743dffe82882a64697a8e8Florian Fainellistatic netdev_tx_t brcm_tag_xmit(struct sk_buff *skb, struct net_device *dev)
625037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli{
635037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	struct dsa_slave_priv *p = netdev_priv(dev);
645037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	u8 *brcm_tag;
655037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
665037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	dev->stats.tx_packets++;
675037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	dev->stats.tx_bytes += skb->len;
685037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
695037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	if (skb_cow_head(skb, BRCM_TAG_LEN) < 0)
705037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli		goto out_free;
715037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
725037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb_push(skb, BRCM_TAG_LEN);
735037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
745037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	memmove(skb->data, skb->data + BRCM_TAG_LEN, 2 * ETH_ALEN);
755037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
765037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* Build the tag after the MAC Source Address */
775037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	brcm_tag = skb->data + 2 * ETH_ALEN;
785037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
795037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* Set the ingress opcode, traffic class, tag enforcment is
805037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	 * deprecated
815037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	 */
825037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	brcm_tag[0] = (1 << BRCM_OPCODE_SHIFT) |
835037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli			((skb->priority << BRCM_IG_TC_SHIFT) & BRCM_IG_TC_MASK);
845037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	brcm_tag[1] = 0;
855037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	brcm_tag[2] = 0;
865037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	if (p->port == 8)
875037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli		brcm_tag[2] = BRCM_IG_DSTMAP2_MASK;
885037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	brcm_tag[3] = (1 << p->port) & BRCM_IG_DSTMAP1_MASK;
895037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
905037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* Queue the SKB for transmission on the parent interface, but
915037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	 * do not modify its EtherType
925037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	 */
935037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb->dev = p->parent->dst->master_netdev;
945037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	dev_queue_xmit(skb);
955037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
965037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	return NETDEV_TX_OK;
975037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
985037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelliout_free:
995037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	kfree_skb(skb);
1005037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	return NETDEV_TX_OK;
1015037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli}
1025037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1035037d532b83d7325a2743dffe82882a64697a8e8Florian Fainellistatic int brcm_tag_rcv(struct sk_buff *skb, struct net_device *dev,
1045037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli			struct packet_type *pt, struct net_device *orig_dev)
1055037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli{
1065037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	struct dsa_switch_tree *dst = dev->dsa_ptr;
1075037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	struct dsa_switch *ds;
1085037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	int source_port;
1095037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	u8 *brcm_tag;
1105037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1115037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	if (unlikely(dst == NULL))
1125037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli		goto out_drop;
1135037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1145037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	ds = dst->ds[0];
1155037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1165037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb = skb_unshare(skb, GFP_ATOMIC);
1175037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	if (skb == NULL)
1185037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli		goto out;
1195037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1205037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	if (unlikely(!pskb_may_pull(skb, BRCM_TAG_LEN)))
1215037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli		goto out_drop;
1225037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1235037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* skb->data points to the EtherType, the tag is right before it */
1245037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	brcm_tag = skb->data - 2;
1255037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1265037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* The opcode should never be different than 0b000 */
1275037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	if (unlikely((brcm_tag[0] >> BRCM_OPCODE_SHIFT) & BRCM_OPCODE_MASK))
1285037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli		goto out_drop;
1295037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1305037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* We should never see a reserved reason code without knowing how to
1315037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	 * handle it
1325037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	 */
1335037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	WARN_ON(brcm_tag[2] & BRCM_EG_RC_RSVD);
1345037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1355037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* Locate which port this is coming from */
1365037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	source_port = brcm_tag[3] & BRCM_EG_PID_MASK;
1375037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1385037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* Validate port against switch setup, either the port is totally */
1395037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	if (source_port >= DSA_MAX_PORTS || ds->ports[source_port] == NULL)
1405037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli		goto out_drop;
1415037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1425037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* Remove Broadcom tag and update checksum */
1435037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb_pull_rcsum(skb, BRCM_TAG_LEN);
1445037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1455037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	/* Move the Ethernet DA and SA */
1465037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	memmove(skb->data - ETH_HLEN,
1475037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli		skb->data - ETH_HLEN - BRCM_TAG_LEN,
1485037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli		2 * ETH_ALEN);
1495037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1505037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb_push(skb, ETH_HLEN);
1515037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb->pkt_type = PACKET_HOST;
1525037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb->dev = ds->ports[source_port];
1535037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb->protocol = eth_type_trans(skb, skb->dev);
1545037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1555037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb->dev->stats.rx_packets++;
1565037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	skb->dev->stats.rx_bytes += skb->len;
1575037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1585037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	netif_receive_skb(skb);
1595037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1605037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	return 0;
1615037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1625037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelliout_drop:
1635037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	kfree_skb(skb);
1645037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelliout:
1655037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	return 0;
1665037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli}
1675037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli
1685037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelliconst struct dsa_device_ops brcm_netdev_ops = {
1695037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	.xmit	= brcm_tag_xmit,
1705037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli	.rcv	= brcm_tag_rcv,
1715037d532b83d7325a2743dffe82882a64697a8e8Florian Fainelli};
172