From cf4ce59b3b70238352c7f1729f0f7223214828ad Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Fri, 20 Sep 2024 01:46:19 +0000 Subject: [PATCH] rtl88x2CE_WiFi_linux add concurrent mode --- kernel/drivers/net/dsa/bcm_sf2_cfp.c | 841 +++++++++++++++++++++++++++++-------------------------- 1 files changed, 442 insertions(+), 399 deletions(-) diff --git a/kernel/drivers/net/dsa/bcm_sf2_cfp.c b/kernel/drivers/net/dsa/bcm_sf2_cfp.c index 12156ab..cbf44fc 100644 --- a/kernel/drivers/net/dsa/bcm_sf2_cfp.c +++ b/kernel/drivers/net/dsa/bcm_sf2_cfp.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Broadcom Starfighter 2 DSA switch CFP support * * Copyright (C) 2016, Broadcom - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include <linux/list.h> @@ -16,9 +12,18 @@ #include <linux/netdevice.h> #include <net/dsa.h> #include <linux/bitmap.h> +#include <net/flow_offload.h> +#include <net/switchdev.h> +#include <uapi/linux/if_bridge.h> #include "bcm_sf2.h" #include "bcm_sf2_regs.h" + +struct cfp_rule { + int port; + struct ethtool_rx_flow_spec fs; + struct list_head next; +}; struct cfp_udf_slice_layout { u8 slices[UDFS_PER_SLICE]; @@ -123,12 +128,12 @@ return count; } -static inline u32 udf_upper_bits(unsigned int num_udf) +static inline u32 udf_upper_bits(int num_udf) { return GENMASK(num_udf - 1, 0) >> (UDFS_PER_SLICE - 1); } -static inline u32 udf_lower_bits(unsigned int num_udf) +static inline u32 udf_lower_bits(int num_udf) { return (u8)GENMASK(num_udf - 1, 0); } @@ -206,6 +211,7 @@ static int bcm_sf2_cfp_act_pol_set(struct bcm_sf2_priv *priv, unsigned int rule_index, + int src_port, unsigned int port_num, unsigned int queue_num, bool fwd_map_change) @@ -222,6 +228,10 @@ CHANGE_TC | queue_num << NEW_TC_SHIFT; else reg = 0; + + /* Enable looping back to the original port */ + if (src_port == port_num) + reg |= LOOP_BK_EN; core_writel(priv, reg, CORE_ACT_POL_DATA0); @@ -251,17 +261,29 @@ } static void bcm_sf2_cfp_slice_ipv4(struct bcm_sf2_priv *priv, - struct ethtool_tcpip4_spec *v4_spec, - unsigned int slice_num, + struct flow_dissector_key_ipv4_addrs *addrs, + struct flow_dissector_key_ports *ports, + const __be16 vlan_tci, + unsigned int slice_num, u8 num_udf, bool mask) { u32 reg, offset; + + /* UDF_Valid[7:0] [31:24] + * S-Tag [23:8] + * C-Tag [7:0] + */ + reg = udf_lower_bits(num_udf) << 24 | be16_to_cpu(vlan_tci) >> 8; + if (mask) + core_writel(priv, reg, CORE_CFP_MASK_PORT(5)); + else + core_writel(priv, reg, CORE_CFP_DATA_PORT(5)); /* C-Tag [31:24] * UDF_n_A8 [23:8] * UDF_n_A7 [7:0] */ - reg = 0; + reg = (u32)(be16_to_cpu(vlan_tci) & 0xff) << 24; if (mask) offset = CORE_CFP_MASK_PORT(4); else @@ -272,7 +294,7 @@ * UDF_n_A6 [23:8] * UDF_n_A5 [7:0] */ - reg = be16_to_cpu(v4_spec->pdst) >> 8; + reg = be16_to_cpu(ports->dst) >> 8; if (mask) offset = CORE_CFP_MASK_PORT(3); else @@ -283,9 +305,9 @@ * UDF_n_A4 [23:8] * UDF_n_A3 [7:0] */ - reg = (be16_to_cpu(v4_spec->pdst) & 0xff) << 24 | - (u32)be16_to_cpu(v4_spec->psrc) << 8 | - (be32_to_cpu(v4_spec->ip4dst) & 0x0000ff00) >> 8; + reg = (be16_to_cpu(ports->dst) & 0xff) << 24 | + (u32)be16_to_cpu(ports->src) << 8 | + (be32_to_cpu(addrs->dst) & 0x0000ff00) >> 8; if (mask) offset = CORE_CFP_MASK_PORT(2); else @@ -296,9 +318,9 @@ * UDF_n_A2 [23:8] * UDF_n_A1 [7:0] */ - reg = (u32)(be32_to_cpu(v4_spec->ip4dst) & 0xff) << 24 | - (u32)(be32_to_cpu(v4_spec->ip4dst) >> 16) << 8 | - (be32_to_cpu(v4_spec->ip4src) & 0x0000ff00) >> 8; + reg = (u32)(be32_to_cpu(addrs->dst) & 0xff) << 24 | + (u32)(be32_to_cpu(addrs->dst) >> 16) << 8 | + (be32_to_cpu(addrs->src) & 0x0000ff00) >> 8; if (mask) offset = CORE_CFP_MASK_PORT(1); else @@ -311,8 +333,8 @@ * Slice ID [3:2] * Slice valid [1:0] */ - reg = (u32)(be32_to_cpu(v4_spec->ip4src) & 0xff) << 24 | - (u32)(be32_to_cpu(v4_spec->ip4src) >> 16) << 8 | + reg = (u32)(be32_to_cpu(addrs->src) & 0xff) << 24 | + (u32)(be32_to_cpu(addrs->src) >> 16) << 8 | SLICE_NUM(slice_num) | SLICE_VALID; if (mask) offset = CORE_CFP_MASK_PORT(0); @@ -326,9 +348,14 @@ unsigned int queue_num, struct ethtool_rx_flow_spec *fs) { - struct ethtool_tcpip4_spec *v4_spec, *v4_m_spec; + __be16 vlan_tci = 0, vlan_m_tci = htons(0xffff); + struct ethtool_rx_flow_spec_input input = {}; const struct cfp_udf_layout *layout; unsigned int slice_num, rule_index; + struct ethtool_rx_flow_rule *flow; + struct flow_match_ipv4_addrs ipv4; + struct flow_match_ports ports; + struct flow_match_ip ip; u8 ip_proto, ip_frag; u8 num_udf; u32 reg; @@ -337,19 +364,21 @@ switch (fs->flow_type & ~FLOW_EXT) { case TCP_V4_FLOW: ip_proto = IPPROTO_TCP; - v4_spec = &fs->h_u.tcp_ip4_spec; - v4_m_spec = &fs->m_u.tcp_ip4_spec; break; case UDP_V4_FLOW: ip_proto = IPPROTO_UDP; - v4_spec = &fs->h_u.udp_ip4_spec; - v4_m_spec = &fs->m_u.udp_ip4_spec; break; default: return -EINVAL; } - ip_frag = be32_to_cpu(fs->m_ext.data[0]); + ip_frag = !!(be32_to_cpu(fs->h_ext.data[0]) & 1); + + /* Extract VLAN TCI */ + if (fs->flow_type & FLOW_EXT) { + vlan_tci = fs->h_ext.vlan_tci; + vlan_m_tci = fs->m_ext.vlan_tci; + } /* Locate the first rule available */ if (fs->location == RX_CLS_LOC_ANY) @@ -361,11 +390,22 @@ if (rule_index > bcm_sf2_cfp_rule_size(priv)) return -ENOSPC; + input.fs = fs; + flow = ethtool_rx_flow_rule_create(&input); + if (IS_ERR(flow)) + return PTR_ERR(flow); + + flow_rule_match_ipv4_addrs(flow->rule, &ipv4); + flow_rule_match_ports(flow->rule, &ports); + flow_rule_match_ip(flow->rule, &ip); + layout = &udf_tcpip4_layout; /* We only use one UDF slice for now */ slice_num = bcm_sf2_get_slice_number(layout, 0); - if (slice_num == UDF_NUM_SLICES) - return -EINVAL; + if (slice_num == UDF_NUM_SLICES) { + ret = -EINVAL; + goto out_err_flow_rule; + } num_udf = bcm_sf2_get_num_udf_slices(layout->udfs[slice_num].slices); @@ -392,7 +432,7 @@ * Reserved [1] * UDF_Valid[8] [0] */ - core_writel(priv, v4_spec->tos << IPTOS_SHIFT | + core_writel(priv, ip.key->tos << IPTOS_SHIFT | ip_proto << IPPROTO_SHIFT | ip_frag << IP_FRAG_SHIFT | udf_upper_bits(num_udf), CORE_CFP_DATA_PORT(6)); @@ -401,18 +441,11 @@ core_writel(priv, layout->udfs[slice_num].mask_value | udf_upper_bits(num_udf), CORE_CFP_MASK_PORT(6)); - /* UDF_Valid[7:0] [31:24] - * S-Tag [23:8] - * C-Tag [7:0] - */ - core_writel(priv, udf_lower_bits(num_udf) << 24, CORE_CFP_DATA_PORT(5)); - - /* Mask all but valid UDFs */ - core_writel(priv, udf_lower_bits(num_udf) << 24, CORE_CFP_MASK_PORT(5)); - /* Program the match and the mask */ - bcm_sf2_cfp_slice_ipv4(priv, v4_spec, slice_num, false); - bcm_sf2_cfp_slice_ipv4(priv, v4_m_spec, SLICE_NUM_MASK, true); + bcm_sf2_cfp_slice_ipv4(priv, ipv4.key, ports.key, vlan_tci, + slice_num, num_udf, false); + bcm_sf2_cfp_slice_ipv4(priv, ipv4.mask, ports.mask, vlan_m_tci, + SLICE_NUM_MASK, num_udf, true); /* Insert into TCAM now */ bcm_sf2_cfp_rule_addr_set(priv, rule_index); @@ -420,14 +453,14 @@ ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | TCAM_SEL); if (ret) { pr_err("TCAM entry at addr %d failed\n", rule_index); - return ret; + goto out_err_flow_rule; } /* Insert into Action and policer RAMs now */ - ret = bcm_sf2_cfp_act_pol_set(priv, rule_index, port_num, + ret = bcm_sf2_cfp_act_pol_set(priv, rule_index, port, port_num, queue_num, true); if (ret) - return ret; + goto out_err_flow_rule; /* Turn on CFP for this rule now */ reg = core_readl(priv, CORE_CFP_CTL_REG); @@ -440,14 +473,29 @@ fs->location = rule_index; return 0; + +out_err_flow_rule: + ethtool_rx_flow_rule_destroy(flow); + return ret; } static void bcm_sf2_cfp_slice_ipv6(struct bcm_sf2_priv *priv, const __be32 *ip6_addr, const __be16 port, - unsigned int slice_num, + const __be16 vlan_tci, + unsigned int slice_num, u32 udf_bits, bool mask) { u32 reg, tmp, val, offset; + + /* UDF_Valid[7:0] [31:24] + * S-Tag [23:8] + * C-Tag [7:0] + */ + reg = udf_bits << 24 | be16_to_cpu(vlan_tci) >> 8; + if (mask) + core_writel(priv, reg, CORE_CFP_MASK_PORT(5)); + else + core_writel(priv, reg, CORE_CFP_DATA_PORT(5)); /* C-Tag [31:24] * UDF_n_B8 [23:8] (port) @@ -455,6 +503,7 @@ */ reg = be32_to_cpu(ip6_addr[3]); val = (u32)be16_to_cpu(port) << 8 | ((reg >> 8) & 0xff); + val |= (u32)(be16_to_cpu(vlan_tci) & 0xff) << 24; if (mask) offset = CORE_CFP_MASK_PORT(4); else @@ -515,14 +564,78 @@ core_writel(priv, reg, offset); } +static struct cfp_rule *bcm_sf2_cfp_rule_find(struct bcm_sf2_priv *priv, + int port, u32 location) +{ + struct cfp_rule *rule; + + list_for_each_entry(rule, &priv->cfp.rules_list, next) { + if (rule->port == port && rule->fs.location == location) + return rule; + } + + return NULL; +} + +static int bcm_sf2_cfp_rule_cmp(struct bcm_sf2_priv *priv, int port, + struct ethtool_rx_flow_spec *fs) +{ + struct cfp_rule *rule = NULL; + size_t fs_size = 0; + int ret = 1; + + if (list_empty(&priv->cfp.rules_list)) + return ret; + + list_for_each_entry(rule, &priv->cfp.rules_list, next) { + ret = 1; + if (rule->port != port) + continue; + + if (rule->fs.flow_type != fs->flow_type || + rule->fs.ring_cookie != fs->ring_cookie || + rule->fs.h_ext.data[0] != fs->h_ext.data[0]) + continue; + + switch (fs->flow_type & ~FLOW_EXT) { + case TCP_V6_FLOW: + case UDP_V6_FLOW: + fs_size = sizeof(struct ethtool_tcpip6_spec); + break; + case TCP_V4_FLOW: + case UDP_V4_FLOW: + fs_size = sizeof(struct ethtool_tcpip4_spec); + break; + default: + continue; + } + + ret = memcmp(&rule->fs.h_u, &fs->h_u, fs_size); + ret |= memcmp(&rule->fs.m_u, &fs->m_u, fs_size); + /* Compare VLAN TCI values as well */ + if (rule->fs.flow_type & FLOW_EXT) { + ret |= rule->fs.h_ext.vlan_tci != fs->h_ext.vlan_tci; + ret |= rule->fs.m_ext.vlan_tci != fs->m_ext.vlan_tci; + } + if (ret == 0) + break; + } + + return ret; +} + static int bcm_sf2_cfp_ipv6_rule_set(struct bcm_sf2_priv *priv, int port, unsigned int port_num, unsigned int queue_num, struct ethtool_rx_flow_spec *fs) { - struct ethtool_tcpip6_spec *v6_spec, *v6_m_spec; + __be16 vlan_tci = 0, vlan_m_tci = htons(0xffff); + struct ethtool_rx_flow_spec_input input = {}; unsigned int slice_num, rule_index[2]; const struct cfp_udf_layout *layout; + struct ethtool_rx_flow_rule *flow; + struct flow_match_ipv6_addrs ipv6; + struct flow_match_ports ports; u8 ip_proto, ip_frag; int ret = 0; u8 num_udf; @@ -531,19 +644,21 @@ switch (fs->flow_type & ~FLOW_EXT) { case TCP_V6_FLOW: ip_proto = IPPROTO_TCP; - v6_spec = &fs->h_u.tcp_ip6_spec; - v6_m_spec = &fs->m_u.tcp_ip6_spec; break; case UDP_V6_FLOW: ip_proto = IPPROTO_UDP; - v6_spec = &fs->h_u.udp_ip6_spec; - v6_m_spec = &fs->m_u.udp_ip6_spec; break; default: return -EINVAL; } - ip_frag = be32_to_cpu(fs->m_ext.data[0]); + ip_frag = !!(be32_to_cpu(fs->h_ext.data[0]) & 1); + + /* Extract VLAN TCI */ + if (fs->flow_type & FLOW_EXT) { + vlan_tci = fs->h_ext.vlan_tci; + vlan_m_tci = fs->m_ext.vlan_tci; + } layout = &udf_tcpip6_layout; slice_num = bcm_sf2_get_slice_number(layout, 0); @@ -584,6 +699,15 @@ goto out_err; } + input.fs = fs; + flow = ethtool_rx_flow_rule_create(&input); + if (IS_ERR(flow)) { + ret = PTR_ERR(flow); + goto out_err; + } + flow_rule_match_ipv6_addrs(flow->rule, &ipv6); + flow_rule_match_ports(flow->rule, &ports); + /* Apply the UDF layout for this filter */ bcm_sf2_cfp_udf_set(priv, layout, slice_num); @@ -617,20 +741,13 @@ reg = layout->udfs[slice_num].mask_value | udf_upper_bits(num_udf); core_writel(priv, reg, CORE_CFP_MASK_PORT(6)); - /* UDF_Valid[7:0] [31:24] - * S-Tag [23:8] - * C-Tag [7:0] - */ - core_writel(priv, udf_lower_bits(num_udf) << 24, CORE_CFP_DATA_PORT(5)); - - /* Mask all but valid UDFs */ - core_writel(priv, udf_lower_bits(num_udf) << 24, CORE_CFP_MASK_PORT(5)); - /* Slice the IPv6 source address and port */ - bcm_sf2_cfp_slice_ipv6(priv, v6_spec->ip6src, v6_spec->psrc, - slice_num, false); - bcm_sf2_cfp_slice_ipv6(priv, v6_m_spec->ip6src, v6_m_spec->psrc, - SLICE_NUM_MASK, true); + bcm_sf2_cfp_slice_ipv6(priv, ipv6.key->src.in6_u.u6_addr32, + ports.key->src, vlan_tci, slice_num, + udf_lower_bits(num_udf), false); + bcm_sf2_cfp_slice_ipv6(priv, ipv6.mask->src.in6_u.u6_addr32, + ports.mask->src, vlan_m_tci, SLICE_NUM_MASK, + udf_lower_bits(num_udf), true); /* Insert into TCAM now because we need to insert a second rule */ bcm_sf2_cfp_rule_addr_set(priv, rule_index[0]); @@ -638,20 +755,20 @@ ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | TCAM_SEL); if (ret) { pr_err("TCAM entry at addr %d failed\n", rule_index[0]); - goto out_err; + goto out_err_flow_rule; } /* Insert into Action and policer RAMs now */ - ret = bcm_sf2_cfp_act_pol_set(priv, rule_index[0], port_num, + ret = bcm_sf2_cfp_act_pol_set(priv, rule_index[0], port, port_num, queue_num, false); if (ret) - goto out_err; + goto out_err_flow_rule; /* Now deal with the second slice to chain this rule */ slice_num = bcm_sf2_get_slice_number(layout, slice_num + 1); if (slice_num == UDF_NUM_SLICES) { ret = -EINVAL; - goto out_err; + goto out_err_flow_rule; } num_udf = bcm_sf2_get_num_udf_slices(layout->udfs[slice_num].slices); @@ -681,16 +798,12 @@ udf_lower_bits(num_udf) << 8; core_writel(priv, reg, CORE_CFP_MASK_PORT(6)); - /* Don't care */ - core_writel(priv, 0, CORE_CFP_DATA_PORT(5)); - - /* Mask all */ - core_writel(priv, 0, CORE_CFP_MASK_PORT(5)); - - bcm_sf2_cfp_slice_ipv6(priv, v6_spec->ip6dst, v6_spec->pdst, slice_num, - false); - bcm_sf2_cfp_slice_ipv6(priv, v6_m_spec->ip6dst, v6_m_spec->pdst, - SLICE_NUM_MASK, true); + bcm_sf2_cfp_slice_ipv6(priv, ipv6.key->dst.in6_u.u6_addr32, + ports.key->dst, 0, slice_num, + 0, false); + bcm_sf2_cfp_slice_ipv6(priv, ipv6.mask->dst.in6_u.u6_addr32, + ports.key->dst, 0, SLICE_NUM_MASK, + 0, true); /* Insert into TCAM now */ bcm_sf2_cfp_rule_addr_set(priv, rule_index[1]); @@ -698,16 +811,16 @@ ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | TCAM_SEL); if (ret) { pr_err("TCAM entry at addr %d failed\n", rule_index[1]); - goto out_err; + goto out_err_flow_rule; } /* Insert into Action and policer RAMs now, set chain ID to * the one we are chained to */ - ret = bcm_sf2_cfp_act_pol_set(priv, rule_index[1], port_num, + ret = bcm_sf2_cfp_act_pol_set(priv, rule_index[1], port, port_num, queue_num, true); if (ret) - goto out_err; + goto out_err_flow_rule; /* Turn on CFP for this rule now */ reg = core_readl(priv, CORE_CFP_CTL_REG); @@ -723,32 +836,23 @@ return ret; +out_err_flow_rule: + ethtool_rx_flow_rule_destroy(flow); out_err: clear_bit(rule_index[1], priv->cfp.used); return ret; } -static int bcm_sf2_cfp_rule_set(struct dsa_switch *ds, int port, - struct ethtool_rx_flow_spec *fs) +static int bcm_sf2_cfp_rule_insert(struct dsa_switch *ds, int port, + struct ethtool_rx_flow_spec *fs) { struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); - s8 cpu_port = ds->ports[port].cpu_dp->index; + s8 cpu_port = dsa_to_port(ds, port)->cpu_dp->index; __u64 ring_cookie = fs->ring_cookie; + struct switchdev_obj_port_vlan vlan; unsigned int queue_num, port_num; - int ret = -EINVAL; - - /* Check for unsupported extensions */ - if ((fs->flow_type & FLOW_EXT) && (fs->m_ext.vlan_etype || - fs->m_ext.data[1])) - return -EINVAL; - - if (fs->location != RX_CLS_LOC_ANY && - fs->location > bcm_sf2_cfp_rule_size(priv)) - return -EINVAL; - - if (fs->location != RX_CLS_LOC_ANY && - test_bit(fs->location, priv->cfp.used)) - return -EBUSY; + u16 vid; + int ret; /* This rule is a Wake-on-LAN filter and we must specifically * target the CPU port in order for it to be working. @@ -767,6 +871,34 @@ dsa_is_cpu_port(ds, port_num)) || port_num >= priv->hw_params.num_ports) return -EINVAL; + + /* If the rule is matching a particular VLAN, make sure that we honor + * the matching and have it tagged or untagged on the destination port, + * we do this on egress with a VLAN entry. The egress tagging attribute + * is expected to be provided in h_ext.data[1] bit 0. A 1 means untagged, + * a 0 means tagged. + */ + if (fs->flow_type & FLOW_EXT) { + /* We cannot support matching multiple VLAN IDs yet */ + if ((be16_to_cpu(fs->m_ext.vlan_tci) & VLAN_VID_MASK) != + VLAN_VID_MASK) + return -EINVAL; + + vid = be16_to_cpu(fs->h_ext.vlan_tci) & VLAN_VID_MASK; + vlan.vid_begin = vid; + vlan.vid_end = vid; + if (cpu_to_be32(fs->h_ext.data[1]) & 1) + vlan.flags = BRIDGE_VLAN_INFO_UNTAGGED; + else + vlan.flags = 0; + + ret = ds->ops->port_vlan_prepare(ds, port_num, &vlan); + if (ret) + return ret; + + ds->ops->port_vlan_add(ds, port_num, &vlan); + } + /* * We have a small oddity where Port 6 just does not have a * valid bit here (so we substract by one). @@ -787,8 +919,54 @@ queue_num, fs); break; default: + ret = -EINVAL; break; } + + return ret; +} + +static int bcm_sf2_cfp_rule_set(struct dsa_switch *ds, int port, + struct ethtool_rx_flow_spec *fs) +{ + struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); + struct cfp_rule *rule = NULL; + int ret = -EINVAL; + + /* Check for unsupported extensions */ + if (fs->flow_type & FLOW_MAC_EXT) + return -EINVAL; + + if (fs->location != RX_CLS_LOC_ANY && + fs->location > bcm_sf2_cfp_rule_size(priv)) + return -EINVAL; + + if ((fs->flow_type & FLOW_EXT) && + !(ds->ops->port_vlan_prepare || ds->ops->port_vlan_add || + ds->ops->port_vlan_del)) + return -EOPNOTSUPP; + + if (fs->location != RX_CLS_LOC_ANY && + test_bit(fs->location, priv->cfp.used)) + return -EBUSY; + + ret = bcm_sf2_cfp_rule_cmp(priv, port, fs); + if (ret == 0) + return -EEXIST; + + rule = kzalloc(sizeof(*rule), GFP_KERNEL); + if (!rule) + return -ENOMEM; + + ret = bcm_sf2_cfp_rule_insert(ds, port, fs); + if (ret) { + kfree(rule); + return ret; + } + + rule->port = port; + memcpy(&rule->fs, fs, sizeof(*fs)); + list_add_tail(&rule->next, &priv->cfp.rules_list); return ret; } @@ -830,10 +1008,26 @@ return 0; } -static int bcm_sf2_cfp_rule_del(struct bcm_sf2_priv *priv, int port, - u32 loc) +static int bcm_sf2_cfp_rule_remove(struct bcm_sf2_priv *priv, int port, + u32 loc) { u32 next_loc = 0; + int ret; + + ret = bcm_sf2_cfp_rule_del_one(priv, port, loc, &next_loc); + if (ret) + return ret; + + /* If this was an IPv6 rule, delete is companion rule too */ + if (next_loc) + ret = bcm_sf2_cfp_rule_del_one(priv, port, next_loc, NULL); + + return ret; +} + +static int bcm_sf2_cfp_rule_del(struct bcm_sf2_priv *priv, int port, u32 loc) +{ + struct cfp_rule *rule; int ret; if (loc > bcm_sf2_cfp_rule_size(priv)) @@ -846,13 +1040,14 @@ if (!test_bit(loc, priv->cfp.unique) || loc == 0) return -EINVAL; - ret = bcm_sf2_cfp_rule_del_one(priv, port, loc, &next_loc); - if (ret) - return ret; + rule = bcm_sf2_cfp_rule_find(priv, port, loc); + if (!rule) + return -EINVAL; - /* If this was an IPv6 rule, delete is companion rule too */ - if (next_loc) - ret = bcm_sf2_cfp_rule_del_one(priv, port, next_loc, NULL); + ret = bcm_sf2_cfp_rule_remove(priv, port, loc); + + list_del(&rule->next); + kfree(rule); return ret; } @@ -870,304 +1065,16 @@ flow->m_ext.data[1] ^= cpu_to_be32(~0); } -static int bcm_sf2_cfp_unslice_ipv4(struct bcm_sf2_priv *priv, - struct ethtool_tcpip4_spec *v4_spec, - bool mask) -{ - u32 reg, offset, ipv4; - u16 src_dst_port; - - if (mask) - offset = CORE_CFP_MASK_PORT(3); - else - offset = CORE_CFP_DATA_PORT(3); - - reg = core_readl(priv, offset); - /* src port [15:8] */ - src_dst_port = reg << 8; - - if (mask) - offset = CORE_CFP_MASK_PORT(2); - else - offset = CORE_CFP_DATA_PORT(2); - - reg = core_readl(priv, offset); - /* src port [7:0] */ - src_dst_port |= (reg >> 24); - - v4_spec->pdst = cpu_to_be16(src_dst_port); - v4_spec->psrc = cpu_to_be16((u16)(reg >> 8)); - - /* IPv4 dst [15:8] */ - ipv4 = (reg & 0xff) << 8; - - if (mask) - offset = CORE_CFP_MASK_PORT(1); - else - offset = CORE_CFP_DATA_PORT(1); - - reg = core_readl(priv, offset); - /* IPv4 dst [31:16] */ - ipv4 |= ((reg >> 8) & 0xffff) << 16; - /* IPv4 dst [7:0] */ - ipv4 |= (reg >> 24) & 0xff; - v4_spec->ip4dst = cpu_to_be32(ipv4); - - /* IPv4 src [15:8] */ - ipv4 = (reg & 0xff) << 8; - - if (mask) - offset = CORE_CFP_MASK_PORT(0); - else - offset = CORE_CFP_DATA_PORT(0); - reg = core_readl(priv, offset); - - /* Once the TCAM is programmed, the mask reflects the slice number - * being matched, don't bother checking it when reading back the - * mask spec - */ - if (!mask && !(reg & SLICE_VALID)) - return -EINVAL; - - /* IPv4 src [7:0] */ - ipv4 |= (reg >> 24) & 0xff; - /* IPv4 src [31:16] */ - ipv4 |= ((reg >> 8) & 0xffff) << 16; - v4_spec->ip4src = cpu_to_be32(ipv4); - - return 0; -} - -static int bcm_sf2_cfp_ipv4_rule_get(struct bcm_sf2_priv *priv, int port, - struct ethtool_rx_flow_spec *fs) -{ - struct ethtool_tcpip4_spec *v4_spec = NULL, *v4_m_spec = NULL; - u32 reg; - int ret; - - reg = core_readl(priv, CORE_CFP_DATA_PORT(6)); - - switch ((reg & IPPROTO_MASK) >> IPPROTO_SHIFT) { - case IPPROTO_TCP: - fs->flow_type = TCP_V4_FLOW; - v4_spec = &fs->h_u.tcp_ip4_spec; - v4_m_spec = &fs->m_u.tcp_ip4_spec; - break; - case IPPROTO_UDP: - fs->flow_type = UDP_V4_FLOW; - v4_spec = &fs->h_u.udp_ip4_spec; - v4_m_spec = &fs->m_u.udp_ip4_spec; - break; - default: - return -EINVAL; - } - - fs->m_ext.data[0] = cpu_to_be32((reg >> IP_FRAG_SHIFT) & 1); - v4_spec->tos = (reg >> IPTOS_SHIFT) & IPTOS_MASK; - - ret = bcm_sf2_cfp_unslice_ipv4(priv, v4_spec, false); - if (ret) - return ret; - - return bcm_sf2_cfp_unslice_ipv4(priv, v4_m_spec, true); -} - -static int bcm_sf2_cfp_unslice_ipv6(struct bcm_sf2_priv *priv, - __be32 *ip6_addr, __be16 *port, - bool mask) -{ - u32 reg, tmp, offset; - - /* C-Tag [31:24] - * UDF_n_B8 [23:8] (port) - * UDF_n_B7 (upper) [7:0] (addr[15:8]) - */ - if (mask) - offset = CORE_CFP_MASK_PORT(4); - else - offset = CORE_CFP_DATA_PORT(4); - reg = core_readl(priv, offset); - *port = cpu_to_be32(reg) >> 8; - tmp = (u32)(reg & 0xff) << 8; - - /* UDF_n_B7 (lower) [31:24] (addr[7:0]) - * UDF_n_B6 [23:8] (addr[31:16]) - * UDF_n_B5 (upper) [7:0] (addr[47:40]) - */ - if (mask) - offset = CORE_CFP_MASK_PORT(3); - else - offset = CORE_CFP_DATA_PORT(3); - reg = core_readl(priv, offset); - tmp |= (reg >> 24) & 0xff; - tmp |= (u32)((reg >> 8) << 16); - ip6_addr[3] = cpu_to_be32(tmp); - tmp = (u32)(reg & 0xff) << 8; - - /* UDF_n_B5 (lower) [31:24] (addr[39:32]) - * UDF_n_B4 [23:8] (addr[63:48]) - * UDF_n_B3 (upper) [7:0] (addr[79:72]) - */ - if (mask) - offset = CORE_CFP_MASK_PORT(2); - else - offset = CORE_CFP_DATA_PORT(2); - reg = core_readl(priv, offset); - tmp |= (reg >> 24) & 0xff; - tmp |= (u32)((reg >> 8) << 16); - ip6_addr[2] = cpu_to_be32(tmp); - tmp = (u32)(reg & 0xff) << 8; - - /* UDF_n_B3 (lower) [31:24] (addr[71:64]) - * UDF_n_B2 [23:8] (addr[95:80]) - * UDF_n_B1 (upper) [7:0] (addr[111:104]) - */ - if (mask) - offset = CORE_CFP_MASK_PORT(1); - else - offset = CORE_CFP_DATA_PORT(1); - reg = core_readl(priv, offset); - tmp |= (reg >> 24) & 0xff; - tmp |= (u32)((reg >> 8) << 16); - ip6_addr[1] = cpu_to_be32(tmp); - tmp = (u32)(reg & 0xff) << 8; - - /* UDF_n_B1 (lower) [31:24] (addr[103:96]) - * UDF_n_B0 [23:8] (addr[127:112]) - * Reserved [7:4] - * Slice ID [3:2] - * Slice valid [1:0] - */ - if (mask) - offset = CORE_CFP_MASK_PORT(0); - else - offset = CORE_CFP_DATA_PORT(0); - reg = core_readl(priv, offset); - tmp |= (reg >> 24) & 0xff; - tmp |= (u32)((reg >> 8) << 16); - ip6_addr[0] = cpu_to_be32(tmp); - - if (!mask && !(reg & SLICE_VALID)) - return -EINVAL; - - return 0; -} - -static int bcm_sf2_cfp_ipv6_rule_get(struct bcm_sf2_priv *priv, int port, - struct ethtool_rx_flow_spec *fs, - u32 next_loc) -{ - struct ethtool_tcpip6_spec *v6_spec = NULL, *v6_m_spec = NULL; - u32 reg; - int ret; - - /* UDPv6 and TCPv6 both use ethtool_tcpip6_spec so we are fine - * assuming tcp_ip6_spec here being an union. - */ - v6_spec = &fs->h_u.tcp_ip6_spec; - v6_m_spec = &fs->m_u.tcp_ip6_spec; - - /* Read the second half first */ - ret = bcm_sf2_cfp_unslice_ipv6(priv, v6_spec->ip6dst, &v6_spec->pdst, - false); - if (ret) - return ret; - - ret = bcm_sf2_cfp_unslice_ipv6(priv, v6_m_spec->ip6dst, - &v6_m_spec->pdst, true); - if (ret) - return ret; - - /* Read last to avoid next entry clobbering the results during search - * operations. We would not have the port enabled for this rule, so - * don't bother checking it. - */ - (void)core_readl(priv, CORE_CFP_DATA_PORT(7)); - - /* The slice number is valid, so read the rule we are chained from now - * which is our first half. - */ - bcm_sf2_cfp_rule_addr_set(priv, next_loc); - ret = bcm_sf2_cfp_op(priv, OP_SEL_READ | TCAM_SEL); - if (ret) - return ret; - - reg = core_readl(priv, CORE_CFP_DATA_PORT(6)); - - switch ((reg & IPPROTO_MASK) >> IPPROTO_SHIFT) { - case IPPROTO_TCP: - fs->flow_type = TCP_V6_FLOW; - break; - case IPPROTO_UDP: - fs->flow_type = UDP_V6_FLOW; - break; - default: - return -EINVAL; - } - - ret = bcm_sf2_cfp_unslice_ipv6(priv, v6_spec->ip6src, &v6_spec->psrc, - false); - if (ret) - return ret; - - return bcm_sf2_cfp_unslice_ipv6(priv, v6_m_spec->ip6src, - &v6_m_spec->psrc, true); -} - static int bcm_sf2_cfp_rule_get(struct bcm_sf2_priv *priv, int port, struct ethtool_rxnfc *nfc) { - u32 reg, ipv4_or_chain_id; - unsigned int queue_num; - int ret; + struct cfp_rule *rule; - bcm_sf2_cfp_rule_addr_set(priv, nfc->fs.location); - - ret = bcm_sf2_cfp_op(priv, OP_SEL_READ | ACT_POL_RAM); - if (ret) - return ret; - - reg = core_readl(priv, CORE_ACT_POL_DATA0); - - ret = bcm_sf2_cfp_op(priv, OP_SEL_READ | TCAM_SEL); - if (ret) - return ret; - - /* Extract the destination port */ - nfc->fs.ring_cookie = fls((reg >> DST_MAP_IB_SHIFT) & - DST_MAP_IB_MASK) - 1; - - /* There is no Port 6, so we compensate for that here */ - if (nfc->fs.ring_cookie >= 6) - nfc->fs.ring_cookie++; - nfc->fs.ring_cookie *= SF2_NUM_EGRESS_QUEUES; - - /* Extract the destination queue */ - queue_num = (reg >> NEW_TC_SHIFT) & NEW_TC_MASK; - nfc->fs.ring_cookie += queue_num; - - /* Extract the L3_FRAMING or CHAIN_ID */ - reg = core_readl(priv, CORE_CFP_DATA_PORT(6)); - - /* With IPv6 rules this would contain a non-zero chain ID since - * we reserve entry 0 and it cannot be used. So if we read 0 here - * this means an IPv4 rule. - */ - ipv4_or_chain_id = (reg >> L3_FRAMING_SHIFT) & 0xff; - if (ipv4_or_chain_id == 0) - ret = bcm_sf2_cfp_ipv4_rule_get(priv, port, &nfc->fs); - else - ret = bcm_sf2_cfp_ipv6_rule_get(priv, port, &nfc->fs, - ipv4_or_chain_id); - if (ret) - return ret; - - /* Read last to avoid next entry clobbering the results during search - * operations - */ - reg = core_readl(priv, CORE_CFP_DATA_PORT(7)); - if (!(reg & 1 << port)) + rule = bcm_sf2_cfp_rule_find(priv, port, nfc->fs.location); + if (!rule) return -EINVAL; + + memcpy(&nfc->fs, &rule->fs, sizeof(rule->fs)); bcm_sf2_invert_masks(&nfc->fs); @@ -1199,7 +1106,7 @@ int bcm_sf2_get_rxnfc(struct dsa_switch *ds, int port, struct ethtool_rxnfc *nfc, u32 *rule_locs) { - struct net_device *p = ds->ports[port].cpu_dp->master; + struct net_device *p = dsa_to_port(ds, port)->cpu_dp->master; struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); int ret = 0; @@ -1242,7 +1149,7 @@ int bcm_sf2_set_rxnfc(struct dsa_switch *ds, int port, struct ethtool_rxnfc *nfc) { - struct net_device *p = ds->ports[port].cpu_dp->master; + struct net_device *p = dsa_to_port(ds, port)->cpu_dp->master; struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); int ret = 0; @@ -1305,3 +1212,139 @@ return 0; } + +void bcm_sf2_cfp_exit(struct dsa_switch *ds) +{ + struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); + struct cfp_rule *rule, *n; + + if (list_empty(&priv->cfp.rules_list)) + return; + + list_for_each_entry_safe_reverse(rule, n, &priv->cfp.rules_list, next) + bcm_sf2_cfp_rule_del(priv, rule->port, rule->fs.location); +} + +int bcm_sf2_cfp_resume(struct dsa_switch *ds) +{ + struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); + struct cfp_rule *rule; + int ret = 0; + u32 reg; + + if (list_empty(&priv->cfp.rules_list)) + return ret; + + reg = core_readl(priv, CORE_CFP_CTL_REG); + reg &= ~CFP_EN_MAP_MASK; + core_writel(priv, reg, CORE_CFP_CTL_REG); + + ret = bcm_sf2_cfp_rst(priv); + if (ret) + return ret; + + list_for_each_entry(rule, &priv->cfp.rules_list, next) { + ret = bcm_sf2_cfp_rule_remove(priv, rule->port, + rule->fs.location); + if (ret) { + dev_err(ds->dev, "failed to remove rule\n"); + return ret; + } + + ret = bcm_sf2_cfp_rule_insert(ds, rule->port, &rule->fs); + if (ret) { + dev_err(ds->dev, "failed to restore rule\n"); + return ret; + } + } + + return ret; +} + +static const struct bcm_sf2_cfp_stat { + unsigned int offset; + unsigned int ram_loc; + const char *name; +} bcm_sf2_cfp_stats[] = { + { + .offset = CORE_STAT_GREEN_CNTR, + .ram_loc = GREEN_STAT_RAM, + .name = "Green" + }, + { + .offset = CORE_STAT_YELLOW_CNTR, + .ram_loc = YELLOW_STAT_RAM, + .name = "Yellow" + }, + { + .offset = CORE_STAT_RED_CNTR, + .ram_loc = RED_STAT_RAM, + .name = "Red" + }, +}; + +void bcm_sf2_cfp_get_strings(struct dsa_switch *ds, int port, + u32 stringset, uint8_t *data) +{ + struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); + unsigned int s = ARRAY_SIZE(bcm_sf2_cfp_stats); + char buf[ETH_GSTRING_LEN]; + unsigned int i, j, iter; + + if (stringset != ETH_SS_STATS) + return; + + for (i = 1; i < priv->num_cfp_rules; i++) { + for (j = 0; j < s; j++) { + snprintf(buf, sizeof(buf), + "CFP%03d_%sCntr", + i, bcm_sf2_cfp_stats[j].name); + iter = (i - 1) * s + j; + strlcpy(data + iter * ETH_GSTRING_LEN, + buf, ETH_GSTRING_LEN); + } + } +} + +void bcm_sf2_cfp_get_ethtool_stats(struct dsa_switch *ds, int port, + uint64_t *data) +{ + struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); + unsigned int s = ARRAY_SIZE(bcm_sf2_cfp_stats); + const struct bcm_sf2_cfp_stat *stat; + unsigned int i, j, iter; + struct cfp_rule *rule; + int ret; + + mutex_lock(&priv->cfp.lock); + for (i = 1; i < priv->num_cfp_rules; i++) { + rule = bcm_sf2_cfp_rule_find(priv, port, i); + if (!rule) + continue; + + for (j = 0; j < s; j++) { + stat = &bcm_sf2_cfp_stats[j]; + + bcm_sf2_cfp_rule_addr_set(priv, i); + ret = bcm_sf2_cfp_op(priv, stat->ram_loc | OP_SEL_READ); + if (ret) + continue; + + iter = (i - 1) * s + j; + data[iter] = core_readl(priv, stat->offset); + } + + } + mutex_unlock(&priv->cfp.lock); +} + +int bcm_sf2_cfp_get_sset_count(struct dsa_switch *ds, int port, int sset) +{ + struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); + + if (sset != ETH_SS_STATS) + return 0; + + /* 3 counters per CFP rules */ + return (priv->num_cfp_rules - 1) * ARRAY_SIZE(bcm_sf2_cfp_stats); +} -- Gitblit v1.6.2