Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: dsa: Add plumbing for port mirroring

Add necessary plumbing at the slave network device level to have switch
drivers implement ndo_setup_tc() and most particularly the cls_matchall
classifier. We add support for two switch operations:

port_add_mirror and port_del_mirror() which configure, on a per-port
basis the mirror parameters requested from the cls_matchall classifier.

Code is largely borrowed from the Mellanox Spectrum switch driver.

Reviewed-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Florian Fainelli and committed by
David S. Miller
f50f2127 4be99934

+172 -1
+33
include/net/dsa.h
··· 20 20 #include <linux/phy_fixed.h> 21 21 #include <linux/ethtool.h> 22 22 23 + struct tc_action; 24 + 23 25 enum dsa_tag_protocol { 24 26 DSA_TAG_PROTO_NONE = 0, 25 27 DSA_TAG_PROTO_DSA, ··· 140 138 */ 141 139 const struct dsa_device_ops *tag_ops; 142 140 }; 141 + 142 + /* TC matchall action types, only mirroring for now */ 143 + enum dsa_port_mall_action_type { 144 + DSA_PORT_MALL_MIRROR, 145 + }; 146 + 147 + /* TC mirroring entry */ 148 + struct dsa_mall_mirror_tc_entry { 149 + u8 to_local_port; 150 + bool ingress; 151 + }; 152 + 153 + /* TC matchall entry */ 154 + struct dsa_mall_tc_entry { 155 + struct list_head list; 156 + unsigned long cookie; 157 + enum dsa_port_mall_action_type type; 158 + union { 159 + struct dsa_mall_mirror_tc_entry mirror; 160 + }; 161 + }; 162 + 143 163 144 164 struct dsa_port { 145 165 struct dsa_switch *ds; ··· 409 385 struct ethtool_rxnfc *nfc, u32 *rule_locs); 410 386 int (*set_rxnfc)(struct dsa_switch *ds, int port, 411 387 struct ethtool_rxnfc *nfc); 388 + 389 + /* 390 + * TC integration 391 + */ 392 + int (*port_mirror_add)(struct dsa_switch *ds, int port, 393 + struct dsa_mall_mirror_tc_entry *mirror, 394 + bool ingress); 395 + void (*port_mirror_del)(struct dsa_switch *ds, int port, 396 + struct dsa_mall_mirror_tc_entry *mirror); 412 397 }; 413 398 414 399 struct dsa_switch_driver {
+3
net/dsa/dsa_priv.h
··· 41 41 #ifdef CONFIG_NET_POLL_CONTROLLER 42 42 struct netpoll *netpoll; 43 43 #endif 44 + 45 + /* TC context */ 46 + struct list_head mall_tc_list; 44 47 }; 45 48 46 49 /* dsa.c */
+136 -1
net/dsa/slave.c
··· 16 16 #include <linux/of_net.h> 17 17 #include <linux/of_mdio.h> 18 18 #include <linux/mdio.h> 19 + #include <linux/list.h> 19 20 #include <net/rtnetlink.h> 20 21 #include <net/switchdev.h> 22 + #include <net/pkt_cls.h> 23 + #include <net/tc_act/tc_mirred.h> 21 24 #include <linux/if_bridge.h> 22 25 #include <linux/netpoll.h> 23 26 #include "dsa_priv.h" 27 + 28 + static bool dsa_slave_dev_check(struct net_device *dev); 24 29 25 30 /* slave mii_bus handling ***************************************************/ 26 31 static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg) ··· 1000 995 return 0; 1001 996 } 1002 997 998 + static struct dsa_mall_tc_entry * 999 + dsa_slave_mall_tc_entry_find(struct dsa_slave_priv *p, 1000 + unsigned long cookie) 1001 + { 1002 + struct dsa_mall_tc_entry *mall_tc_entry; 1003 + 1004 + list_for_each_entry(mall_tc_entry, &p->mall_tc_list, list) 1005 + if (mall_tc_entry->cookie == cookie) 1006 + return mall_tc_entry; 1007 + 1008 + return NULL; 1009 + } 1010 + 1011 + static int dsa_slave_add_cls_matchall(struct net_device *dev, 1012 + __be16 protocol, 1013 + struct tc_cls_matchall_offload *cls, 1014 + bool ingress) 1015 + { 1016 + struct dsa_slave_priv *p = netdev_priv(dev); 1017 + struct dsa_mall_tc_entry *mall_tc_entry; 1018 + struct dsa_switch *ds = p->dp->ds; 1019 + struct net *net = dev_net(dev); 1020 + struct dsa_slave_priv *to_p; 1021 + struct net_device *to_dev; 1022 + const struct tc_action *a; 1023 + int err = -EOPNOTSUPP; 1024 + LIST_HEAD(actions); 1025 + int ifindex; 1026 + 1027 + if (!ds->ops->port_mirror_add) 1028 + return err; 1029 + 1030 + if (!tc_single_action(cls->exts)) 1031 + return err; 1032 + 1033 + tcf_exts_to_list(cls->exts, &actions); 1034 + a = list_first_entry(&actions, struct tc_action, list); 1035 + 1036 + if (is_tcf_mirred_egress_mirror(a) && protocol == htons(ETH_P_ALL)) { 1037 + struct dsa_mall_mirror_tc_entry *mirror; 1038 + 1039 + ifindex = tcf_mirred_ifindex(a); 1040 + to_dev = __dev_get_by_index(net, ifindex); 1041 + if (!to_dev) 1042 + return -EINVAL; 1043 + 1044 + if (!dsa_slave_dev_check(to_dev)) 1045 + return -EOPNOTSUPP; 1046 + 1047 + mall_tc_entry = kzalloc(sizeof(*mall_tc_entry), GFP_KERNEL); 1048 + if (!mall_tc_entry) 1049 + return -ENOMEM; 1050 + 1051 + mall_tc_entry->cookie = cls->cookie; 1052 + mall_tc_entry->type = DSA_PORT_MALL_MIRROR; 1053 + mirror = &mall_tc_entry->mirror; 1054 + 1055 + to_p = netdev_priv(to_dev); 1056 + 1057 + mirror->to_local_port = to_p->dp->index; 1058 + mirror->ingress = ingress; 1059 + 1060 + err = ds->ops->port_mirror_add(ds, p->dp->index, mirror, 1061 + ingress); 1062 + if (err) { 1063 + kfree(mall_tc_entry); 1064 + return err; 1065 + } 1066 + 1067 + list_add_tail(&mall_tc_entry->list, &p->mall_tc_list); 1068 + } 1069 + 1070 + return 0; 1071 + } 1072 + 1073 + static void dsa_slave_del_cls_matchall(struct net_device *dev, 1074 + struct tc_cls_matchall_offload *cls) 1075 + { 1076 + struct dsa_slave_priv *p = netdev_priv(dev); 1077 + struct dsa_mall_tc_entry *mall_tc_entry; 1078 + struct dsa_switch *ds = p->dp->ds; 1079 + 1080 + if (!ds->ops->port_mirror_del) 1081 + return; 1082 + 1083 + mall_tc_entry = dsa_slave_mall_tc_entry_find(p, cls->cookie); 1084 + if (!mall_tc_entry) 1085 + return; 1086 + 1087 + list_del(&mall_tc_entry->list); 1088 + 1089 + switch (mall_tc_entry->type) { 1090 + case DSA_PORT_MALL_MIRROR: 1091 + ds->ops->port_mirror_del(ds, p->dp->index, 1092 + &mall_tc_entry->mirror); 1093 + break; 1094 + default: 1095 + WARN_ON(1); 1096 + } 1097 + 1098 + kfree(mall_tc_entry); 1099 + } 1100 + 1101 + static int dsa_slave_setup_tc(struct net_device *dev, u32 handle, 1102 + __be16 protocol, struct tc_to_netdev *tc) 1103 + { 1104 + bool ingress = TC_H_MAJ(handle) == TC_H_MAJ(TC_H_INGRESS); 1105 + int ret = -EOPNOTSUPP; 1106 + 1107 + switch (tc->type) { 1108 + case TC_SETUP_MATCHALL: 1109 + switch (tc->cls_mall->command) { 1110 + case TC_CLSMATCHALL_REPLACE: 1111 + return dsa_slave_add_cls_matchall(dev, protocol, 1112 + tc->cls_mall, 1113 + ingress); 1114 + case TC_CLSMATCHALL_DESTROY: 1115 + dsa_slave_del_cls_matchall(dev, tc->cls_mall); 1116 + return 0; 1117 + } 1118 + default: 1119 + break; 1120 + } 1121 + 1122 + return ret; 1123 + } 1124 + 1003 1125 void dsa_cpu_port_ethtool_init(struct ethtool_ops *ops) 1004 1126 { 1005 1127 ops->get_sset_count = dsa_cpu_port_get_sset_count; ··· 1201 1069 .ndo_bridge_setlink = switchdev_port_bridge_setlink, 1202 1070 .ndo_bridge_dellink = switchdev_port_bridge_dellink, 1203 1071 .ndo_get_phys_port_name = dsa_slave_get_phys_port_name, 1072 + .ndo_setup_tc = dsa_slave_setup_tc, 1204 1073 }; 1205 1074 1206 1075 static const struct switchdev_ops dsa_slave_switchdev_ops = { ··· 1418 1285 if (slave_dev == NULL) 1419 1286 return -ENOMEM; 1420 1287 1421 - slave_dev->features = master->vlan_features; 1288 + slave_dev->features = master->vlan_features | NETIF_F_HW_TC; 1289 + slave_dev->hw_features |= NETIF_F_HW_TC; 1422 1290 slave_dev->ethtool_ops = &dsa_slave_ethtool_ops; 1423 1291 eth_hw_addr_inherit(slave_dev, master); 1424 1292 slave_dev->priv_flags |= IFF_NO_QUEUE; ··· 1438 1304 1439 1305 p = netdev_priv(slave_dev); 1440 1306 p->dp = &ds->ports[port]; 1307 + INIT_LIST_HEAD(&p->mall_tc_list); 1441 1308 p->xmit = dst->tag_ops->xmit; 1442 1309 1443 1310 p->old_pause = -1;