staticint bcm_sf2_cfp_act_pol_set(struct bcm_sf2_priv *priv, unsignedint rule_index, int src_port, unsignedint port_num, unsignedint queue_num, bool fwd_map_change)
{ int ret;
u32 reg;
/* Replace ARL derived destination with DST_MAP derived, define * which port and queue this should be forwarded to.
*/ if (fwd_map_change)
reg = CHANGE_FWRD_MAP_IB_REP_ARL |
BIT(port_num + DST_MAP_IB_SHIFT) |
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);
/* 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;
}
/* Locate the first rule available */ if (fs->location == RX_CLS_LOC_ANY)
rule_index = find_first_zero_bit(priv->cfp.used,
priv->num_cfp_rules); else
rule_index = fs->location;
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);
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) {
ret = -EINVAL; goto out_err_flow_rule;
}
/* Apply the UDF layout for this filter */
bcm_sf2_cfp_udf_set(priv, layout, slice_num);
/* Apply to all packets received through this port */
core_writel(priv, BIT(port), CORE_CFP_DATA_PORT(7));
/* Source port map match */
core_writel(priv, 0xff, CORE_CFP_MASK_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, ip.key->tos << IPTOS_SHIFT |
ip_proto << IPPROTO_SHIFT | ip_frag << IP_FRAG_SHIFT |
udf_upper_bits(num_udf),
CORE_CFP_DATA_PORT(6));
/* Mask with the specific layout for IPv4 packets */
core_writel(priv, layout->udfs[slice_num].mask_value |
udf_upper_bits(num_udf), CORE_CFP_MASK_PORT(6));
/* Program the match and the mask */
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);
ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | TCAM_SEL); if (ret) {
pr_err("TCAM entry at addr %d failed\n", rule_index); goto out_err_flow_rule;
}
/* Insert into Action and policer RAMs now */
ret = bcm_sf2_cfp_act_pol_set(priv, rule_index, port, port_num,
queue_num, true); if (ret) goto out_err_flow_rule;
/* 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);
set_bit(rule_index, priv->cfp.unique);
fs->location = rule_index;
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;
}
/* Negotiate two indexes, one for the second half which we are chained * from, which is what we will return to user-space, and a second one * which is used to store its first half. That first half does not * allow any choice of placement, so it just needs to find the next * available bit. We return the second half as fs->location because * that helps with the rule lookup later on since the second half is * chained from its first half, we can easily identify IPv6 CFP rules * by looking whether they carry a CHAIN_ID. * * We also want the second half to have a lower rule_index than its * first half because the HW search is by incrementing addresses.
*/ if (fs->location == RX_CLS_LOC_ANY)
rule_index[1] = find_first_zero_bit(priv->cfp.used,
priv->num_cfp_rules); else
rule_index[1] = fs->location; if (rule_index[1] > bcm_sf2_cfp_rule_size(priv)) return -ENOSPC;
/* Flag it as used (cleared on error path) such that we can immediately * obtain a second one to chain from.
*/
set_bit(rule_index[1], priv->cfp.used);
rule_index[0] = find_first_zero_bit(priv->cfp.used,
priv->num_cfp_rules); if (rule_index[0] > bcm_sf2_cfp_rule_size(priv)) {
ret = -ENOSPC; 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);
/* Apply to all packets received through this port */
core_writel(priv, BIT(port), CORE_CFP_DATA_PORT(7));
/* Source port map match */
core_writel(priv, 0xff, CORE_CFP_MASK_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]
*/
reg = 1 << L3_FRAMING_SHIFT | ip_proto << IPPROTO_SHIFT |
ip_frag << IP_FRAG_SHIFT | udf_upper_bits(num_udf);
core_writel(priv, reg, CORE_CFP_DATA_PORT(6));
/* Mask with the specific layout for IPv6 packets including * UDF_Valid[8]
*/
reg = layout->udfs[slice_num].mask_value | udf_upper_bits(num_udf);
core_writel(priv, reg, CORE_CFP_MASK_PORT(6));
/* Slice the IPv6 source address and port */
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]);
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_flow_rule;
}
/* Insert into Action and policer RAMs now */
ret = bcm_sf2_cfp_act_pol_set(priv, rule_index[0], port, port_num,
queue_num, false); if (ret) 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_flow_rule;
}
/* Apply the UDF layout for this filter */
bcm_sf2_cfp_udf_set(priv, layout, slice_num);
/* Chained rule, source port match is coming from the rule we are * chained from.
*/
core_writel(priv, 0, CORE_CFP_DATA_PORT(7));
core_writel(priv, 0, CORE_CFP_MASK_PORT(7));
/* Insert into TCAM now */
bcm_sf2_cfp_rule_addr_set(priv, rule_index[1]);
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_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, port_num,
queue_num, true); if (ret) goto out_err_flow_rule;
/* 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 second half rule as being used now, return it as the * location, and flag it as unique while dumping rules
*/
set_bit(rule_index[0], priv->cfp.used);
set_bit(rule_index[1], priv->cfp.unique);
fs->location = rule_index[1];
/* This rule is a Wake-on-LAN filter and we must specifically * target the CPU port in order for it to be working.
*/ if (ring_cookie == RX_CLS_FLOW_WAKE)
ring_cookie = cpu_port * SF2_NUM_EGRESS_QUEUES;
/* 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 = ring_cookie / SF2_NUM_EGRESS_QUEUES;
/* 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 = vid; if (be32_to_cpu(fs->h_ext.data[1]) & 1)
vlan.flags = BRIDGE_VLAN_INFO_UNTAGGED; else
vlan.flags = 0;
ret = ds->ops->port_vlan_add(ds, port_num, &vlan, NULL); if (ret) return ret;
}
/* * We have a small oddity where Port 6 just does not have a * valid bit here (so we substract by one).
*/
queue_num = ring_cookie % SF2_NUM_EGRESS_QUEUES; if (port_num >= 7)
port_num -= 1;
switch (fs->flow_type & ~FLOW_EXT) { case TCP_V4_FLOW: case UDP_V4_FLOW:
ret = bcm_sf2_cfp_ipv4_rule_set(priv, port, port_num,
queue_num, fs); break; case TCP_V6_FLOW: case UDP_V6_FLOW:
ret = bcm_sf2_cfp_ipv6_rule_set(priv, port, port_num,
queue_num, fs); break; default:
ret = -EINVAL; break;
}
return ret;
}
staticint 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_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;
}
staticint bcm_sf2_cfp_rule_del_one(struct bcm_sf2_priv *priv, int port,
u32 loc, u32 *next_loc)
{ int ret;
u32 reg;
/* 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;
/* Check if this is possibly an IPv6 rule that would * indicate we need to delete its companion rule * as well
*/
reg = core_readl(priv, CORE_CFP_DATA_PORT(6)); if (next_loc)
*next_loc = (reg >> 24) & CHAIN_ID_MASK;
staticint 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;
}
staticint 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)) return -EINVAL;
/* Refuse deleting unused rules, and those that are not unique since * that could leave IPv6 rules with one of the chained rule in the * table.
*/ if (!test_bit(loc, priv->cfp.unique) || loc == 0) return -EINVAL;
rule = bcm_sf2_cfp_rule_find(priv, port, loc); if (!rule) return -EINVAL;
/* 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 net_device *p = dsa_port_to_conduit(dsa_to_port(ds, port)); 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.unique,
priv->num_cfp_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); 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);
if (ret) return ret;
/* Pass up the commands to the attached master network device */ if (p->ethtool_ops->get_rxnfc) {
ret = p->ethtool_ops->get_rxnfc(p, nfc, rule_locs); if (ret == -EOPNOTSUPP)
ret = 0;
}
return ret;
}
int bcm_sf2_set_rxnfc(struct dsa_switch *ds, int port, struct ethtool_rxnfc *nfc)
{ struct net_device *p = dsa_port_to_conduit(dsa_to_port(ds, port)); 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);
if (ret) return ret;
/* Pass up the commands to the attached master network device. * This can fail, so rollback the operation if we need to.
*/ if (p->ethtool_ops->set_rxnfc) {
ret = p->ethtool_ops->set_rxnfc(p, nfc); if (ret && ret != -EOPNOTSUPP) {
mutex_lock(&priv->cfp.lock);
bcm_sf2_cfp_rule_del(priv, port, nfc->fs.location);
mutex_unlock(&priv->cfp.lock);
} else {
ret = 0;
}
}
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); unsignedint i, j;
if (stringset != ETH_SS_STATS) return;
for (i = 1; i < priv->num_cfp_rules; i++) for (j = 0; j < ARRAY_SIZE(bcm_sf2_cfp_stats); j++)
ethtool_sprintf(data, "CFP%03d_%sCntr", i,
bcm_sf2_cfp_stats[j].name);
}
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); unsignedint s = ARRAY_SIZE(bcm_sf2_cfp_stats); conststruct bcm_sf2_cfp_stat *stat; unsignedint 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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.