Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | // SPDX-License-Identifier: GPL-2.0+ /* * net/dsa/tag_dsa.c - (Non-ethertype) DSA tagging * Copyright (c) 2008-2009 Marvell Semiconductor */ #include <linux/etherdevice.h> #include <linux/list.h> #include <linux/slab.h> #include "dsa_priv.h" #define DSA_HLEN 4 static struct sk_buff *dsa_xmit(struct sk_buff *skb, struct net_device *dev) { struct dsa_port *dp = dsa_slave_to_port(dev); u8 *dsa_header; /* * Convert the outermost 802.1q tag to a DSA tag for tagged * packets, or insert a DSA tag between the addresses and * the ethertype field for untagged packets. */ if (skb->protocol == htons(ETH_P_8021Q)) { /* * Construct tagged FROM_CPU DSA tag from 802.1q tag. */ dsa_header = skb->data + 2 * ETH_ALEN; dsa_header[0] = 0x60 | dp->ds->index; dsa_header[1] = dp->index << 3; /* * Move CFI field from byte 2 to byte 1. */ if (dsa_header[2] & 0x10) { dsa_header[1] |= 0x01; dsa_header[2] &= ~0x10; } } else { skb_push(skb, DSA_HLEN); memmove(skb->data, skb->data + DSA_HLEN, 2 * ETH_ALEN); /* * Construct untagged FROM_CPU DSA tag. */ dsa_header = skb->data + 2 * ETH_ALEN; dsa_header[0] = 0x40 | dp->ds->index; dsa_header[1] = dp->index << 3; dsa_header[2] = 0x00; dsa_header[3] = 0x00; } return skb; } static struct sk_buff *dsa_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt) { u8 *dsa_header; int source_device; int source_port; if (unlikely(!pskb_may_pull(skb, DSA_HLEN))) return NULL; /* * The ethertype field is part of the DSA header. */ dsa_header = skb->data - 2; /* * Check that frame type is either TO_CPU or FORWARD. */ if ((dsa_header[0] & 0xc0) != 0x00 && (dsa_header[0] & 0xc0) != 0xc0) return NULL; /* * Determine source device and port. */ source_device = dsa_header[0] & 0x1f; source_port = (dsa_header[1] >> 3) & 0x1f; skb->dev = dsa_master_find_slave(dev, source_device, source_port); if (!skb->dev) return NULL; /* * Convert the DSA header to an 802.1q header if the 'tagged' * bit in the DSA header is set. If the 'tagged' bit is clear, * delete the DSA header entirely. */ if (dsa_header[0] & 0x20) { u8 new_header[4]; /* * Insert 802.1q ethertype and copy the VLAN-related * fields, but clear the bit that will hold CFI (since * DSA uses that bit location for another purpose). */ new_header[0] = (ETH_P_8021Q >> 8) & 0xff; new_header[1] = ETH_P_8021Q & 0xff; new_header[2] = dsa_header[2] & ~0x10; new_header[3] = dsa_header[3]; /* * Move CFI bit from its place in the DSA header to * its 802.1q-designated place. */ if (dsa_header[1] & 0x01) new_header[2] |= 0x10; /* * Update packet checksum if skb is CHECKSUM_COMPLETE. */ if (skb->ip_summed == CHECKSUM_COMPLETE) { __wsum c = skb->csum; c = csum_add(c, csum_partial(new_header + 2, 2, 0)); c = csum_sub(c, csum_partial(dsa_header + 2, 2, 0)); skb->csum = c; } memcpy(dsa_header, new_header, DSA_HLEN); } else { /* * Remove DSA tag and update checksum. */ skb_pull_rcsum(skb, DSA_HLEN); memmove(skb->data - ETH_HLEN, skb->data - ETH_HLEN - DSA_HLEN, 2 * ETH_ALEN); } skb->offload_fwd_mark = 1; return skb; } static const struct dsa_device_ops dsa_netdev_ops = { .name = "dsa", .proto = DSA_TAG_PROTO_DSA, .xmit = dsa_xmit, .rcv = dsa_rcv, .overhead = DSA_HLEN, }; MODULE_LICENSE("GPL"); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_DSA); module_dsa_tag_driver(dsa_netdev_ops); |