| /* |
| * 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> |
| #include <linux/ethtool.h> |
| #include <linux/if_ether.h> |
| #include <linux/in.h> |
| #include <linux/netdevice.h> |
| #include <net/dsa.h> |
| #include <linux/bitmap.h> |
| |
| #include "bcm_sf2.h" |
| #include "bcm_sf2_regs.h" |
| |
| struct cfp_udf_layout { |
| u8 slices[UDF_NUM_SLICES]; |
| u32 mask_value; |
| |
| }; |
| |
| /* UDF slices layout for a TCPv4/UDPv4 specification */ |
| static const struct cfp_udf_layout udf_tcpip4_layout = { |
| .slices = { |
| /* End of L2, byte offset 12, src IP[0:15] */ |
| CFG_UDF_EOL2 | 6, |
| /* End of L2, byte offset 14, src IP[16:31] */ |
| CFG_UDF_EOL2 | 7, |
| /* End of L2, byte offset 16, dst IP[0:15] */ |
| CFG_UDF_EOL2 | 8, |
| /* End of L2, byte offset 18, dst IP[16:31] */ |
| CFG_UDF_EOL2 | 9, |
| /* End of L3, byte offset 0, src port */ |
| CFG_UDF_EOL3 | 0, |
| /* End of L3, byte offset 2, dst port */ |
| CFG_UDF_EOL3 | 1, |
| 0, 0, 0 |
| }, |
| .mask_value = L3_FRAMING_MASK | IPPROTO_MASK | IP_FRAG, |
| }; |
| |
| static inline unsigned int bcm_sf2_get_num_udf_slices(const u8 *layout) |
| { |
| unsigned int i, count = 0; |
| |
| for (i = 0; i < UDF_NUM_SLICES; i++) { |
| if (layout[i] != 0) |
| count++; |
| } |
| |
| return count; |
| } |
| |
| static void bcm_sf2_cfp_udf_set(struct bcm_sf2_priv *priv, |
| unsigned int slice_num, |
| const u8 *layout) |
| { |
| u32 offset = CORE_UDF_0_A_0_8_PORT_0 + slice_num * UDF_SLICE_OFFSET; |
| unsigned int i; |
| |
| for (i = 0; i < UDF_NUM_SLICES; i++) |
| core_writel(priv, layout[i], offset + i * 4); |
| } |
| |
| static int bcm_sf2_cfp_op(struct bcm_sf2_priv *priv, unsigned int op) |
| { |
| unsigned int timeout = 1000; |
| u32 reg; |
| |
| reg = core_readl(priv, CORE_CFP_ACC); |
| reg &= ~(OP_SEL_MASK | RAM_SEL_MASK); |
| reg |= OP_STR_DONE | op; |
| core_writel(priv, reg, CORE_CFP_ACC); |
| |
| do { |
| reg = core_readl(priv, CORE_CFP_ACC); |
| if (!(reg & OP_STR_DONE)) |
| break; |
| |
| cpu_relax(); |
| } while (timeout--); |
| |
| if (!timeout) |
| return -ETIMEDOUT; |
| |
| return 0; |
| } |
| |
| static inline void bcm_sf2_cfp_rule_addr_set(struct bcm_sf2_priv *priv, |
| unsigned int addr) |
| { |
| u32 reg; |
| |
| WARN_ON(addr >= CFP_NUM_RULES); |
| |
| reg = core_readl(priv, CORE_CFP_ACC); |
| reg &= ~(XCESS_ADDR_MASK << XCESS_ADDR_SHIFT); |
| reg |= addr << XCESS_ADDR_SHIFT; |
| core_writel(priv, reg, CORE_CFP_ACC); |
| } |
| |
| static inline unsigned int bcm_sf2_cfp_rule_size(struct bcm_sf2_priv *priv) |
| { |
| /* Entry #0 is reserved */ |
| return CFP_NUM_RULES - 1; |
| } |
| |
| 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 ethtool_tcpip4_spec *v4_spec; |
| const struct cfp_udf_layout *layout; |
| unsigned int slice_num, rule_index; |
| unsigned int queue_num, port_num; |
| u8 ip_proto, ip_frag; |
| u8 num_udf; |
| u32 reg; |
| int ret; |
| |
| /* 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 && |
| test_bit(fs->location, priv->cfp.used)) |
| return -EBUSY; |
| |
| if (fs->location != RX_CLS_LOC_ANY && |
| fs->location > bcm_sf2_cfp_rule_size(priv)) |
| return -EINVAL; |
| |
| ip_frag = be32_to_cpu(fs->m_ext.data[0]); |
| |
| /* We do not support discarding packets, check that the |
| * destination port is enabled and that we are within the |
| * number of ports supported by the switch |
| */ |
| port_num = fs->ring_cookie / 8; |
| |
| if (fs->ring_cookie == RX_CLS_FLOW_DISC || |
| !(BIT(port_num) & ds->enabled_port_mask) || |
| port_num >= priv->hw_params.num_ports) |
| return -EINVAL; |
| |
| switch (fs->flow_type & ~FLOW_EXT) { |
| case TCP_V4_FLOW: |
| ip_proto = IPPROTO_TCP; |
| v4_spec = &fs->h_u.tcp_ip4_spec; |
| break; |
| case UDP_V4_FLOW: |
| ip_proto = IPPROTO_UDP; |
| v4_spec = &fs->h_u.udp_ip4_spec; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* We only use one UDF slice for now */ |
| slice_num = 1; |
| layout = &udf_tcpip4_layout; |
| num_udf = bcm_sf2_get_num_udf_slices(layout->slices); |
| |
| /* Apply the UDF layout for this filter */ |
| bcm_sf2_cfp_udf_set(priv, slice_num, layout->slices); |
| |
| /* Apply to all packets received through this port */ |
| core_writel(priv, BIT(port), CORE_CFP_DATA_PORT(7)); |
| |
| /* S-Tag status [31:30] |
| * C-Tag status [29:28] |
| * L2 framing [27:26] |
| * L3 framing [25:24] |
| * IP ToS [23:16] |
| * IP proto [15:08] |
| * IP Fragm [7] |
| * Non 1st frag [6] |
| * IP Authen [5] |
| * TTL range [4:3] |
| * PPPoE session [2] |
| * Reserved [1] |
| * UDF_Valid[8] [0] |
| */ |
| core_writel(priv, v4_spec->tos << 16 | ip_proto << 8 | ip_frag << 7, |
| CORE_CFP_DATA_PORT(6)); |
| |
| /* UDF_Valid[7:0] [31:24] |
| * S-Tag [23:8] |
| * C-Tag [7:0] |
| */ |
| core_writel(priv, GENMASK(num_udf - 1, 0) << 24, CORE_CFP_DATA_PORT(5)); |
| |
| /* C-Tag [31:24] |
| * UDF_n_A8 [23:8] |
| * UDF_n_A7 [7:0] |
| */ |
| core_writel(priv, 0, CORE_CFP_DATA_PORT(4)); |
| |
| /* UDF_n_A7 [31:24] |
| * UDF_n_A6 [23:8] |
| * UDF_n_A5 [7:0] |
| */ |
| core_writel(priv, be16_to_cpu(v4_spec->pdst) >> 8, |
| CORE_CFP_DATA_PORT(3)); |
| |
| /* UDF_n_A5 [31:24] |
| * 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; |
| core_writel(priv, reg, CORE_CFP_DATA_PORT(2)); |
| |
| /* UDF_n_A3 [31:24] |
| * 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; |
| core_writel(priv, reg, CORE_CFP_DATA_PORT(1)); |
| |
| /* UDF_n_A1 [31:24] |
| * UDF_n_A0 [23:8] |
| * Reserved [7:4] |
| * 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 | |
| SLICE_NUM(slice_num) | SLICE_VALID; |
| core_writel(priv, reg, CORE_CFP_DATA_PORT(0)); |
| |
| /* Source port map match */ |
| core_writel(priv, 0xff, CORE_CFP_MASK_PORT(7)); |
| |
| /* Mask with the specific layout for IPv4 packets */ |
| core_writel(priv, layout->mask_value, CORE_CFP_MASK_PORT(6)); |
| |
| /* Mask all but valid UDFs */ |
| core_writel(priv, GENMASK(num_udf - 1, 0) << 24, CORE_CFP_MASK_PORT(5)); |
| |
| /* Mask all */ |
| core_writel(priv, 0, CORE_CFP_MASK_PORT(4)); |
| |
| /* All other UDFs should be matched with the filter */ |
| core_writel(priv, 0xff, CORE_CFP_MASK_PORT(3)); |
| core_writel(priv, 0xffffffff, CORE_CFP_MASK_PORT(2)); |
| core_writel(priv, 0xffffffff, CORE_CFP_MASK_PORT(1)); |
| core_writel(priv, 0xffffff0f, CORE_CFP_MASK_PORT(0)); |
| |
| /* Locate the first rule available */ |
| if (fs->location == RX_CLS_LOC_ANY) |
| rule_index = find_first_zero_bit(priv->cfp.used, |
| bcm_sf2_cfp_rule_size(priv)); |
| else |
| rule_index = fs->location; |
| |
| /* Insert into TCAM now */ |
| bcm_sf2_cfp_rule_addr_set(priv, rule_index); |
| |
| 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; |
| } |
| |
| /* Replace ARL derived destination with DST_MAP derived, define |
| * which port and queue this should be forwarded to. |
| * |
| * We have a small oddity where Port 6 just does not have a |
| * valid bit here (so we subtract by one). |
| */ |
| queue_num = fs->ring_cookie % 8; |
| if (port_num >= 7) |
| port_num -= 1; |
| |
| reg = CHANGE_FWRD_MAP_IB_REP_ARL | BIT(port_num + DST_MAP_IB_SHIFT) | |
| CHANGE_TC | queue_num << NEW_TC_SHIFT; |
| |
| core_writel(priv, reg, CORE_ACT_POL_DATA0); |
| |
| /* Set classification ID that needs to be put in Broadcom tag */ |
| core_writel(priv, rule_index << CHAIN_ID_SHIFT, |
| CORE_ACT_POL_DATA1); |
| |
| core_writel(priv, 0, CORE_ACT_POL_DATA2); |
| |
| /* Configure policer RAM now */ |
| ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | ACT_POL_RAM); |
| if (ret) { |
| pr_err("Policer entry at %d failed\n", rule_index); |
| return ret; |
| } |
| |
| /* Disable the policer */ |
| core_writel(priv, POLICER_MODE_DISABLE, CORE_RATE_METER0); |
| |
| /* Now the rate meter */ |
| ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | RATE_METER_RAM); |
| if (ret) { |
| pr_err("Meter entry at %d failed\n", rule_index); |
| return ret; |
| } |
| |
| /* Turn on CFP for this rule now */ |
| reg = core_readl(priv, CORE_CFP_CTL_REG); |
| reg |= BIT(port); |
| core_writel(priv, reg, CORE_CFP_CTL_REG); |
| |
| /* Flag the rule as being used and return it */ |
| set_bit(rule_index, priv->cfp.used); |
| fs->location = rule_index; |
| |
| return 0; |
| } |
| |
| static int bcm_sf2_cfp_rule_del(struct bcm_sf2_priv *priv, int port, |
| u32 loc) |
| { |
| int ret; |
| u32 reg; |
| |
| /* Refuse deletion of unused rules, and the default reserved rule */ |
| if (!test_bit(loc, priv->cfp.used) || loc == 0) |
| return -EINVAL; |
| |
| /* Indicate which rule we want to read */ |
| bcm_sf2_cfp_rule_addr_set(priv, loc); |
| |
| ret = bcm_sf2_cfp_op(priv, OP_SEL_READ | TCAM_SEL); |
| if (ret) |
| return ret; |
| |
| /* Clear its valid bits */ |
| reg = core_readl(priv, CORE_CFP_DATA_PORT(0)); |
| reg &= ~SLICE_VALID; |
| core_writel(priv, reg, CORE_CFP_DATA_PORT(0)); |
| |
| /* Write back this entry into the TCAM now */ |
| ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | TCAM_SEL); |
| if (ret) |
| return ret; |
| |
| clear_bit(loc, priv->cfp.used); |
| |
| return 0; |
| } |
| |
| static void bcm_sf2_invert_masks(struct ethtool_rx_flow_spec *flow) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < sizeof(flow->m_u); i++) |
| flow->m_u.hdata[i] ^= 0xff; |
| |
| flow->m_ext.vlan_etype ^= cpu_to_be16(~0); |
| flow->m_ext.vlan_tci ^= cpu_to_be16(~0); |
| flow->m_ext.data[0] ^= cpu_to_be32(~0); |
| flow->m_ext.data[1] ^= cpu_to_be32(~0); |
| } |
| |
| static int bcm_sf2_cfp_rule_get(struct bcm_sf2_priv *priv, int port, |
| struct ethtool_rxnfc *nfc, bool search) |
| { |
| struct ethtool_tcpip4_spec *v4_spec; |
| unsigned int queue_num; |
| u16 src_dst_port; |
| u32 reg, ipv4; |
| int ret; |
| |
| if (!search) { |
| 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; |
| } else { |
| reg = core_readl(priv, CORE_ACT_POL_DATA0); |
| } |
| |
| /* 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 *= 8; |
| |
| /* Extract the destination queue */ |
| queue_num = (reg >> NEW_TC_SHIFT) & NEW_TC_MASK; |
| nfc->fs.ring_cookie += queue_num; |
| |
| /* Extract the IP protocol */ |
| reg = core_readl(priv, CORE_CFP_DATA_PORT(6)); |
| switch ((reg & IPPROTO_MASK) >> IPPROTO_SHIFT) { |
| case IPPROTO_TCP: |
| nfc->fs.flow_type = TCP_V4_FLOW; |
| v4_spec = &nfc->fs.h_u.tcp_ip4_spec; |
| break; |
| case IPPROTO_UDP: |
| nfc->fs.flow_type = UDP_V4_FLOW; |
| v4_spec = &nfc->fs.h_u.udp_ip4_spec; |
| break; |
| default: |
| /* Clear to exit the search process */ |
| if (search) |
| core_readl(priv, CORE_CFP_DATA_PORT(7)); |
| return -EINVAL; |
| } |
| |
| v4_spec->tos = (reg >> 16) & IPPROTO_MASK; |
| nfc->fs.m_ext.data[0] = cpu_to_be32((reg >> 7) & 1); |
| |
| reg = core_readl(priv, CORE_CFP_DATA_PORT(3)); |
| /* src port [15:8] */ |
| src_dst_port = reg << 8; |
| |
| reg = core_readl(priv, CORE_CFP_DATA_PORT(2)); |
| /* src port [7:0] */ |
| src_dst_port |= (reg >> 24); |
| |
| v4_spec->pdst = cpu_to_be16(src_dst_port); |
| nfc->fs.m_u.tcp_ip4_spec.pdst = cpu_to_be16(~0); |
| v4_spec->psrc = cpu_to_be16((u16)(reg >> 8)); |
| nfc->fs.m_u.tcp_ip4_spec.psrc = cpu_to_be16(~0); |
| |
| /* IPv4 dst [15:8] */ |
| ipv4 = (reg & 0xff) << 8; |
| reg = core_readl(priv, CORE_CFP_DATA_PORT(1)); |
| /* IPv4 dst [31:16] */ |
| ipv4 |= ((reg >> 8) & 0xffff) << 16; |
| /* IPv4 dst [7:0] */ |
| ipv4 |= (reg >> 24) & 0xff; |
| v4_spec->ip4dst = cpu_to_be32(ipv4); |
| nfc->fs.m_u.tcp_ip4_spec.ip4dst = cpu_to_be32(~0); |
| |
| /* IPv4 src [15:8] */ |
| ipv4 = (reg & 0xff) << 8; |
| reg = core_readl(priv, CORE_CFP_DATA_PORT(0)); |
| |
| if (!(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); |
| nfc->fs.m_u.tcp_ip4_spec.ip4src = cpu_to_be32(~0); |
| |
| /* 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)) |
| return -EINVAL; |
| |
| bcm_sf2_invert_masks(&nfc->fs); |
| |
| /* Put the TCAM size here */ |
| nfc->data = bcm_sf2_cfp_rule_size(priv); |
| |
| return 0; |
| } |
| |
| /* We implement the search doing a TCAM search operation */ |
| static int bcm_sf2_cfp_rule_get_all(struct bcm_sf2_priv *priv, |
| int port, struct ethtool_rxnfc *nfc, |
| u32 *rule_locs) |
| { |
| unsigned int index = 1, rules_cnt = 0; |
| int ret; |
| u32 reg; |
| |
| /* Do not poll on OP_STR_DONE to be self-clearing for search |
| * operations, we cannot use bcm_sf2_cfp_op here because it completes |
| * on clearing OP_STR_DONE which won't clear until the entire search |
| * operation is over. |
| */ |
| reg = core_readl(priv, CORE_CFP_ACC); |
| reg &= ~(XCESS_ADDR_MASK << XCESS_ADDR_SHIFT); |
| reg |= index << XCESS_ADDR_SHIFT; |
| reg &= ~(OP_SEL_MASK | RAM_SEL_MASK); |
| reg |= OP_SEL_SEARCH | TCAM_SEL | OP_STR_DONE; |
| core_writel(priv, reg, CORE_CFP_ACC); |
| |
| do { |
| /* Wait for results to be ready */ |
| reg = core_readl(priv, CORE_CFP_ACC); |
| |
| /* Extract the address we are searching */ |
| index = reg >> XCESS_ADDR_SHIFT; |
| index &= XCESS_ADDR_MASK; |
| |
| /* We have a valid search result, so flag it accordingly */ |
| if (reg & SEARCH_STS) { |
| ret = bcm_sf2_cfp_rule_get(priv, port, nfc, true); |
| if (ret) |
| continue; |
| |
| rule_locs[rules_cnt] = index; |
| rules_cnt++; |
| } |
| |
| /* Search is over break out */ |
| if (!(reg & OP_STR_DONE)) |
| break; |
| |
| } while (index < CFP_NUM_RULES); |
| |
| /* Put the TCAM size here */ |
| nfc->data = bcm_sf2_cfp_rule_size(priv); |
| nfc->rule_cnt = rules_cnt; |
| |
| return 0; |
| } |
| |
| int bcm_sf2_get_rxnfc(struct dsa_switch *ds, int port, |
| struct ethtool_rxnfc *nfc, u32 *rule_locs) |
| { |
| struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); |
| int ret = 0; |
| |
| mutex_lock(&priv->cfp.lock); |
| |
| switch (nfc->cmd) { |
| case ETHTOOL_GRXCLSRLCNT: |
| /* Subtract the default, unusable rule */ |
| nfc->rule_cnt = bitmap_weight(priv->cfp.used, |
| CFP_NUM_RULES) - 1; |
| /* We support specifying rule locations */ |
| nfc->data |= RX_CLS_LOC_SPECIAL; |
| break; |
| case ETHTOOL_GRXCLSRULE: |
| ret = bcm_sf2_cfp_rule_get(priv, port, nfc, false); |
| break; |
| case ETHTOOL_GRXCLSRLALL: |
| ret = bcm_sf2_cfp_rule_get_all(priv, port, nfc, rule_locs); |
| break; |
| default: |
| ret = -EOPNOTSUPP; |
| break; |
| } |
| |
| mutex_unlock(&priv->cfp.lock); |
| |
| return ret; |
| } |
| |
| int bcm_sf2_set_rxnfc(struct dsa_switch *ds, int port, |
| struct ethtool_rxnfc *nfc) |
| { |
| struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); |
| int ret = 0; |
| |
| mutex_lock(&priv->cfp.lock); |
| |
| switch (nfc->cmd) { |
| case ETHTOOL_SRXCLSRLINS: |
| ret = bcm_sf2_cfp_rule_set(ds, port, &nfc->fs); |
| break; |
| |
| case ETHTOOL_SRXCLSRLDEL: |
| ret = bcm_sf2_cfp_rule_del(priv, port, nfc->fs.location); |
| break; |
| default: |
| ret = -EOPNOTSUPP; |
| break; |
| } |
| |
| mutex_unlock(&priv->cfp.lock); |
| |
| return ret; |
| } |
| |
| int bcm_sf2_cfp_rst(struct bcm_sf2_priv *priv) |
| { |
| unsigned int timeout = 1000; |
| u32 reg; |
| |
| reg = core_readl(priv, CORE_CFP_ACC); |
| reg |= TCAM_RESET; |
| core_writel(priv, reg, CORE_CFP_ACC); |
| |
| do { |
| reg = core_readl(priv, CORE_CFP_ACC); |
| if (!(reg & TCAM_RESET)) |
| break; |
| |
| cpu_relax(); |
| } while (timeout--); |
| |
| if (!timeout) |
| return -ETIMEDOUT; |
| |
| return 0; |
| } |